mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-10-02 08:29:21 +00:00
feat: 新增刷新缓存功能并优化系统初始化
refactor: 重构模型加载选项和CRUD预加载逻辑 fix: 修复日期范围查询格式问题 style: 优化前端组件样式和布局 perf: 提升字典数据更新性能 docs: 更新注释和文档说明 chore: 清理无用文件和代码 test: 更新测试用例 build: 添加vue-json-pretty依赖 ci: 优化初始化脚本和事务处理
This commit is contained in:
@@ -109,7 +109,7 @@
|
||||
<el-table-column label="关键字参数" prop="job_kwargs" min-width="150" show-overflow-tooltip />
|
||||
<el-table-column label="触发器" prop="job_trigger" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="创建时间" prop="create_time" min-width="180" sortable />
|
||||
<el-table-column fixed="right" label="操作" align="center" min-width="120">
|
||||
<el-table-column fixed="right" label="操作" align="center" min-width="150">
|
||||
<template #default="scope">
|
||||
<el-button type="info" size="small" link icon="document" @click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
|
||||
<el-button type="danger" size="small" link icon="delete" @click="handleDelete([scope.row.id])">删除</el-button>
|
||||
@@ -177,6 +177,7 @@ import { useAppStore } from "@/store/modules/app.store";
|
||||
import { DeviceEnum } from "@/enums/settings/device.enum";
|
||||
import ExportModal from "@/components/CURD/ExportModal.vue";
|
||||
import type { IContentConfig } from "@/components/CURD/types";
|
||||
import { formatToDateTime } from "@/utils/dateUtil";
|
||||
|
||||
const appStore = useAppStore();
|
||||
const drawerSize = computed(() => (appStore.device === DeviceEnum.DESKTOP ? "80%" : "60%"));
|
||||
@@ -208,6 +209,7 @@ const queryFormData = reactive<JobLogPageQuery>({
|
||||
status: undefined,
|
||||
start_time: undefined,
|
||||
end_time: undefined,
|
||||
job_id: props.jobId,
|
||||
});
|
||||
|
||||
// 弹窗状态
|
||||
@@ -224,8 +226,8 @@ const dateRange = ref<[Date, Date] | []>([]);
|
||||
function handleDateRangeChange(range: [Date, Date]) {
|
||||
dateRange.value = range;
|
||||
if (range && range.length === 2) {
|
||||
queryFormData.start_time = range[0].toISOString();
|
||||
queryFormData.end_time = range[1].toISOString();
|
||||
queryFormData.start_time = formatToDateTime(range[0]);
|
||||
queryFormData.end_time = formatToDateTime(range[1]);
|
||||
} else {
|
||||
queryFormData.start_time = undefined;
|
||||
queryFormData.end_time = undefined;
|
||||
@@ -273,7 +275,11 @@ async function handleResetQuery() {
|
||||
|
||||
// 行复选框选中项变化
|
||||
async function handleSelectionChange(selection: any) {
|
||||
selectIds.value = selection.map((item: any) => item.id);
|
||||
// 提取有效的数字ID,过滤掉 null/undefined 并转为 number
|
||||
selectIds.value = selection
|
||||
.map((item: any) => item?.id)
|
||||
.filter((id: any) => id !== null && id !== undefined)
|
||||
.map((id: any) => Number(id));
|
||||
// 记录选中行数据供导出弹窗使用
|
||||
selectionRows.value = selection;
|
||||
}
|
||||
@@ -296,6 +302,10 @@ async function handleOpenDialog(type: 'detail', id?: number) {
|
||||
|
||||
// 删除、批量删除
|
||||
async function handleDelete(ids: number[]) {
|
||||
// 如果没有有效ID,直接返回
|
||||
const validIds = ids.filter((id) => id !== null && id !== undefined) as number[];
|
||||
if (validIds.length === 0) return;
|
||||
|
||||
ElMessageBox.confirm("确认删除该任务日志?", "警告", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
@@ -303,8 +313,10 @@ async function handleDelete(ids: number[]) {
|
||||
}).then(async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
await JobAPI.deleteJobLog(ids);
|
||||
await JobAPI.deleteJobLog(validIds);
|
||||
// 删除后刷新并清空选择状态
|
||||
handleResetQuery();
|
||||
selectIds.value = [];
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
@@ -380,8 +392,6 @@ const curdContentConfig = {
|
||||
// 打开抽屉
|
||||
function openDrawer() {
|
||||
drawerVisible.value = true;
|
||||
// 重置查询条件并加载数据
|
||||
handleResetQuery();
|
||||
}
|
||||
|
||||
// 关闭抽屉
|
||||
@@ -397,6 +407,7 @@ defineExpose({
|
||||
|
||||
onMounted(() => {
|
||||
// 抽屉打开时会自动加载数据
|
||||
loadingData();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -34,6 +34,13 @@
|
||||
@update:model-value="handleDateRangeChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="isExpand" prop="creator" label="创建人">
|
||||
<UserTableSelect
|
||||
v-model="queryFormData.creator"
|
||||
@confirm-click="handleConfirm"
|
||||
@clear-click="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<!-- 查询、重置、展开/收起按钮 -->
|
||||
<el-form-item class="search-buttons">
|
||||
<el-button v-hasPerm="['app:job:query']" type="primary" icon="search" @click="handleQuery">查询</el-button>
|
||||
@@ -604,6 +611,7 @@ import JobLogDrawer from "@/views/application/job/components/JobLogDrawer.vue"
|
||||
import OperationColumn from "@/components/OperationColumn/index.vue";
|
||||
import ExportModal from "@/components/CURD/ExportModal.vue";
|
||||
import type { IContentConfig } from "@/components/CURD/types";
|
||||
import { formatToDateTime } from "@/utils/dateUtil";
|
||||
|
||||
const dictStore = useDictStore();
|
||||
|
||||
@@ -639,6 +647,8 @@ const queryFormData = reactive<JobPageQuery>({
|
||||
status: undefined,
|
||||
start_time: undefined,
|
||||
end_time: undefined,
|
||||
// 创建人
|
||||
creator: undefined,
|
||||
});
|
||||
|
||||
// 编辑表单
|
||||
@@ -649,7 +659,7 @@ const formData = reactive<JobForm>({
|
||||
trigger: undefined,
|
||||
args: undefined,
|
||||
kwargs: undefined,
|
||||
coalesce: undefined,
|
||||
coalesce: false,
|
||||
max_instances: 1,
|
||||
jobstore: undefined,
|
||||
executor: undefined,
|
||||
@@ -688,8 +698,8 @@ const dateRange = ref<[Date, Date] | []>([]);
|
||||
function handleDateRangeChange(range: [Date, Date]) {
|
||||
dateRange.value = range;
|
||||
if (range && range.length === 2) {
|
||||
queryFormData.start_time = range[0].toISOString();
|
||||
queryFormData.end_time = range[1].toISOString();
|
||||
queryFormData.start_time = formatToDateTime(range[0]);
|
||||
queryFormData.end_time = formatToDateTime(range[1]);
|
||||
} else {
|
||||
queryFormData.start_time = undefined;
|
||||
queryFormData.end_time = undefined;
|
||||
@@ -721,10 +731,19 @@ async function handleQuery() {
|
||||
loadingData();
|
||||
}
|
||||
|
||||
// 选择创建人后触发查询
|
||||
function handleConfirm() {
|
||||
handleQuery();
|
||||
}
|
||||
|
||||
// 重置查询
|
||||
async function handleResetQuery() {
|
||||
queryFormRef.value.resetFields();
|
||||
queryFormData.page_no = 1;
|
||||
// 额外清空日期范围与时间查询参数
|
||||
dateRange.value = [];
|
||||
queryFormData.start_time = undefined;
|
||||
queryFormData.end_time = undefined;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
|
||||
@@ -205,6 +205,7 @@ defineOptions({
|
||||
import { ref, reactive, onMounted } from "vue";
|
||||
import ExampleAPI, { ExampleTable, ExampleForm, ExamplePageQuery } from "@/api/generator/demo";
|
||||
import DatePicker from "@/components/DatePicker/index.vue";
|
||||
import { formatToDateTime } from "@/utils/dateUtil";
|
||||
|
||||
const queryFormRef = ref();
|
||||
const dataFormRef = ref();
|
||||
@@ -240,8 +241,8 @@ const dateRange = ref<[Date, Date] | []>([]);
|
||||
function handleDateRangeChange(range: [Date, Date]) {
|
||||
dateRange.value = range;
|
||||
if (range && range.length === 2) {
|
||||
queryFormData.start_time = range[0].toISOString();
|
||||
queryFormData.end_time = range[1].toISOString();
|
||||
queryFormData.start_time = formatToDateTime(range[0]);
|
||||
queryFormData.end_time = formatToDateTime(range[1]);
|
||||
} else {
|
||||
queryFormData.start_time = undefined;
|
||||
queryFormData.end_time = undefined;
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
</div>
|
||||
</template>
|
||||
<div class="user-info-header">
|
||||
<div class="avatar-alert mb-10px">
|
||||
<!-- 提示:头像上传成功后请点击“保存更改”按钮才会生效 -->
|
||||
<el-alert type="info" show-icon :closable="false" title="头像上传,点击“保存更改”按钮使其生效" />
|
||||
</div>
|
||||
|
||||
<div class="avatar-wrapper">
|
||||
<el-avatar
|
||||
v-if="infoFormState.avatar"
|
||||
@@ -49,62 +54,73 @@
|
||||
|
||||
<el-divider />
|
||||
|
||||
<el-descriptions :column="1" size="small" class="user-details">
|
||||
<el-descriptions :column="1" border>
|
||||
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<el-icon style="vertical-align: middle;">
|
||||
<User />
|
||||
</el-icon>
|
||||
<span style="vertical-align: middle;">账号</span>
|
||||
<div class="cell-item">
|
||||
<el-icon :style="iconStyle">
|
||||
<User />
|
||||
</el-icon>
|
||||
<span >账号</span>
|
||||
</div>
|
||||
</template>
|
||||
<span style="vertical-align: middle;">{{ infoFormState.username }}</span>
|
||||
</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<el-icon style="vertical-align: middle;">
|
||||
<Coordinate />
|
||||
</el-icon>
|
||||
<span style="vertical-align: middle;">部门</span>
|
||||
</template>
|
||||
<span style="vertical-align: middle;">{{ infoFormState.dept?.name }}</span>
|
||||
<span >{{ infoFormState.username }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<el-icon style="vertical-align: middle;">
|
||||
<OfficeBuilding />
|
||||
</el-icon>
|
||||
<span style="vertical-align: middle;">岗位</span>
|
||||
<div class="cell-item">
|
||||
<el-icon :style="iconStyle">
|
||||
<Coordinate />
|
||||
</el-icon>
|
||||
<span >部门</span>
|
||||
</div>
|
||||
</template>
|
||||
<span style="vertical-align: middle;">{{infoFormState.positions?.map(item => item.name).join('、')}}</span>
|
||||
<span >{{ infoFormState.dept?.name }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<el-icon style="vertical-align: middle;">
|
||||
<Phone />
|
||||
</el-icon>
|
||||
<span style="vertical-align: middle;">手机</span>
|
||||
<div class="cell-item">
|
||||
<el-icon :style="iconStyle">
|
||||
<OfficeBuilding />
|
||||
</el-icon>
|
||||
<span >岗位</span>
|
||||
</div>
|
||||
</template>
|
||||
<span style="vertical-align: middle;">{{ infoFormState.mobile }}</span>
|
||||
<span >{{infoFormState.positions?.map(item => item.name).join('、')}}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<el-icon style="vertical-align: middle;">
|
||||
<Message />
|
||||
</el-icon>
|
||||
<span style="vertical-align: middle;">邮箱</span>
|
||||
<div class="cell-item">
|
||||
<el-icon :style="iconStyle">
|
||||
<Phone />
|
||||
</el-icon>
|
||||
<span >手机</span>
|
||||
</div>
|
||||
</template>
|
||||
<span style="vertical-align: middle;">{{ infoFormState.email }}</span>
|
||||
<span >{{ infoFormState.mobile }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<el-icon style="vertical-align: middle;">
|
||||
<Clock />
|
||||
</el-icon>
|
||||
<span style="vertical-align: middle;">加入时间</span>
|
||||
<div class="cell-item">
|
||||
<el-icon :style="iconStyle">
|
||||
<Message />
|
||||
</el-icon>
|
||||
<span >邮箱</span>
|
||||
</div>
|
||||
</template>
|
||||
<span style="vertical-align: middle;">{{ infoFormState.created_at }}</span>
|
||||
<span >{{ infoFormState.email }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item>
|
||||
<template #label>
|
||||
<div class="cell-item">
|
||||
<el-icon :style="iconStyle">
|
||||
<Clock />
|
||||
</el-icon>
|
||||
<span >加入时间</span>
|
||||
</div>
|
||||
</template>
|
||||
<span>{{ infoFormState.created_at }}</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
@@ -122,21 +138,21 @@
|
||||
<span>基本设置</span>
|
||||
</template>
|
||||
<div>
|
||||
<el-form ref="ruleFormRef" :model="infoFormState" :rules="rules" :inline="true" label-width="80px" label-suffix=":">
|
||||
<el-form ref="infoFormRef" :model="infoFormState" :rules="rules" label-width="80px" label-suffix=":">
|
||||
|
||||
<el-form-item label="姓名" name="name">
|
||||
<el-input v-model="infoFormState.name" placeholder="请输入姓名" prefix-icon="User" clearable style="width: 240px;" />
|
||||
<el-form-item label="用户名" prop="name">
|
||||
<el-input v-model="infoFormState.name" placeholder="请输入用户名" prefix-icon="User" clearable style="width: 240px;" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="手机号" name="mobile">
|
||||
<el-form-item label="手机号" prop="mobile">
|
||||
<el-input v-model="infoFormState.mobile" placeholder="请输入手机号码" prefix-icon="Phone" clearable style="width: 240px;" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="邮箱" name="email">
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="infoFormState.email" placeholder="请输入邮箱" prefix-icon="Message" clearable style="width: 240px;" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="性别" name="gender">
|
||||
<el-form-item label="性别" prop="gender">
|
||||
<el-radio-group v-model="infoFormState.gender">
|
||||
<el-radio v-for="item in dictDataStore['sys_user_sex']" :key="item.dict_value" :value="item.dict_value" >
|
||||
{{ item.dict_label }}
|
||||
@@ -159,25 +175,25 @@
|
||||
<span>安全设置</span>
|
||||
</template>
|
||||
<div>
|
||||
<el-form ref="ruleFormRef" :model="passwordFormState" :rules="resetPasswordRules" label-width="80px" label-suffix=":">
|
||||
<el-form-item label="当前密码" name="old_password">
|
||||
<el-input v-model.trim="passwordFormState.old_password" :placeholder="t('login.password')" type="password" show-password clearable style="width: 240px;">
|
||||
<el-form ref="passwordFormRef" :model="passwordFormState" :rules="resetPasswordRules" label-width="120px" label-suffix=":">
|
||||
<el-form-item label="当前密码" prop="old_password">
|
||||
<el-input v-model.trim="passwordFormState.old_password" :placeholder="t('login.password')" type="password" prefix-icon="Unlock" show-password clearable style="width: 240px;">
|
||||
<template #prefix>
|
||||
<Lock />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="新密码" name="new_password">
|
||||
<el-input v-model.trim="passwordFormState.new_password" type="password" :placeholder="t('login.newPassword')" show-password clearable style="width: 240px;">
|
||||
<el-form-item label="新密码" prop="new_password">
|
||||
<el-input v-model.trim="passwordFormState.new_password" type="password" :placeholder="t('login.newPassword')" prefix-icon="Unlock" show-password clearable style="width: 240px;">
|
||||
<template #prefix>
|
||||
<Key />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="确认密码" name="confirm_password">
|
||||
<el-input v-model.trim="passwordFormState.confirm_password" type="password" :placeholder="t('login.message.password.confirm')" show-password clearable style="width: 240px;">
|
||||
<el-form-item label="确认新密码" prop="confirm_password">
|
||||
<el-input v-model.trim="passwordFormState.confirm_password" type="password" :placeholder="t('login.message.password.confirm')" prefix-icon="Lock" show-password clearable style="width: 240px;">
|
||||
<template #prefix>
|
||||
<Check />
|
||||
</template>
|
||||
@@ -198,7 +214,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance, UploadRequestOptions, UploadFile, ElUpload } from 'element-plus'
|
||||
import type { FormInstance, UploadRequestOptions, UploadFile, ElUpload, ComponentSize } from 'element-plus'
|
||||
import UserAPI, { type InfoFormState, type PasswordFormState } from '@/api/system/user';
|
||||
import { useUserStore, useDictStore } from "@/store";
|
||||
import { useUserStoreHook } from "@/store/modules/user.store";
|
||||
@@ -211,11 +227,26 @@ import router from "@/router";
|
||||
const { t } = useI18n();
|
||||
const userStore = useUserStore();
|
||||
const dictStore = useDictStore();
|
||||
const ruleFormRef = ref<FormInstance>();
|
||||
const infoFormRef = ref<FormInstance>();
|
||||
const passwordFormRef = ref<FormInstance>();
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
const dictDataStore = computed(() => dictStore.dictData);
|
||||
|
||||
|
||||
const size = ref<ComponentSize>('default')
|
||||
|
||||
const iconStyle = computed(() => {
|
||||
const marginMap = {
|
||||
large: '8px',
|
||||
default: '6px',
|
||||
small: '4px',
|
||||
}
|
||||
return {
|
||||
marginRight: marginMap[size.value || 'default'],
|
||||
}
|
||||
})
|
||||
|
||||
// 字典数据
|
||||
const getOptions = async () => {
|
||||
return await dictStore.getDict(['sys_user_sex']);;
|
||||
@@ -280,6 +311,11 @@ const handleUpload = async (options: UploadRequestOptions) => {
|
||||
const fileUrl = response.data.data.file_url;
|
||||
updateAvatar(fileUrl);
|
||||
options.onSuccess(response);
|
||||
// 重置上传组件状态,允许再次选择上传
|
||||
if (uploadRef.value) {
|
||||
uploadRef.value.clearFiles();
|
||||
}
|
||||
fileList.value = [];
|
||||
} else {
|
||||
const errorMsg = response.data.msg || '上传失败';
|
||||
ElMessage.error(errorMsg);
|
||||
@@ -321,8 +357,6 @@ const updateAvatar = (fileUrl: string) => {
|
||||
if (fileUrl) {
|
||||
// 更新头像状态
|
||||
infoFormState.avatar = fileUrl;
|
||||
// 更新文件列表
|
||||
fileList.value = [{ url: fileUrl }];
|
||||
// 确保DOM正确更新
|
||||
nextTick(() => {
|
||||
console.log('头像已更新:', infoFormState.avatar);
|
||||
@@ -337,21 +371,19 @@ const updateAvatar = (fileUrl: string) => {
|
||||
// 邮箱校验规则优化
|
||||
const rules = {
|
||||
name: [
|
||||
{ required: true, message: "请输入姓名", trigger: "blur" },
|
||||
{ required: true, message: "请输入用户名", trigger: "blur" },
|
||||
],
|
||||
mobile: [
|
||||
{ required: true, message: "请输入手机号", trigger: "blur" },
|
||||
{
|
||||
pattern: /^1[3-9]\d{9}$/,
|
||||
message: "手机号格式不正确,请输入有效的中国大陆手机号",
|
||||
message: "请输入有效的手机号格式",
|
||||
trigger: "blur",
|
||||
},
|
||||
],
|
||||
email: [
|
||||
{ required: true, message: "请输入邮箱", trigger: "blur" },
|
||||
{
|
||||
pattern: /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/,
|
||||
message: "邮箱格式不正确,请输入有效的邮箱地址",
|
||||
message: "请输入有效的邮箱格式",
|
||||
trigger: "blur",
|
||||
},
|
||||
],
|
||||
@@ -359,14 +391,14 @@ const rules = {
|
||||
|
||||
|
||||
const resetPasswordRules = {
|
||||
oldPassword: [
|
||||
old_password: [
|
||||
{
|
||||
required: true,
|
||||
trigger: "blur",
|
||||
message: t("login.password"),
|
||||
},
|
||||
],
|
||||
newPassword: [
|
||||
new_password: [
|
||||
{
|
||||
required: true,
|
||||
trigger: "blur",
|
||||
@@ -378,7 +410,7 @@ const resetPasswordRules = {
|
||||
trigger: "blur",
|
||||
},
|
||||
],
|
||||
confirmPassword: [
|
||||
confirm_password: [
|
||||
{
|
||||
required: true,
|
||||
trigger: "blur",
|
||||
@@ -408,9 +440,9 @@ const initInfoForm = () => {
|
||||
// 初始化密码表单
|
||||
const initPasswordForm = () => {
|
||||
Object.assign(passwordFormState, {
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
old_password: '',
|
||||
new_password: '',
|
||||
confirm_password: ''
|
||||
});
|
||||
};
|
||||
|
||||
@@ -418,9 +450,14 @@ const initPasswordForm = () => {
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
infoSubmitting.value = true;
|
||||
const valid = await infoFormRef.value?.validate().catch(() => false);
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
// 确保avatar字段被正确处理
|
||||
const response = await UserAPI.updateCurrentUserInfo({...infoFormState});
|
||||
await userStore.setUserInfo(response.data.data);
|
||||
ElMessage.success('个人资料已保存');
|
||||
} finally {
|
||||
infoSubmitting.value = false;
|
||||
}
|
||||
@@ -430,6 +467,10 @@ const handleSave = async () => {
|
||||
const handlePasswordChange = async () => {
|
||||
try {
|
||||
passwordChanging.value = true;
|
||||
const valid = await passwordFormRef.value?.validate().catch(() => false);
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
const response = await UserAPI.changeCurrentUserPassword(passwordFormState);
|
||||
initPasswordForm();
|
||||
await redirectToLogin(response.data.msg);
|
||||
@@ -513,4 +554,13 @@ onMounted(async () => {
|
||||
.el-input__wrapper {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
.iconStyle {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.cell-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -292,7 +292,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: "Dashboard",
|
||||
name: "Home",
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
@@ -119,7 +119,7 @@
|
||||
<template #header>
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<el-tooltip content="快速访问常用功能,支持内部路由跳转和外部链接打开。" placement="top">
|
||||
<el-tooltip content="快速访问常用功能,标签右键收藏可添加快捷栏菜单。" placement="top">
|
||||
<el-icon class="cursor-help" size="16">
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
@@ -178,6 +178,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: "Workplace",
|
||||
inheritAttrs: false,
|
||||
});
|
||||
|
||||
import { EChartsOption } from 'echarts'
|
||||
import { useUserStore } from "@/store/index";
|
||||
import { greetings } from '@/utils/common';
|
||||
@@ -339,10 +344,6 @@ onUnmounted(() => {
|
||||
quickStartManager.removeListener(updateQuickLinks);
|
||||
});
|
||||
|
||||
defineOptions({
|
||||
name: "DashBoard",
|
||||
});
|
||||
|
||||
const welcome = '祝你开心每一天!';
|
||||
|
||||
const currentUser = {
|
||||
|
||||
@@ -18,9 +18,19 @@
|
||||
<el-option value="false" label="停用" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="isExpand" prop="creator" label="创建人">
|
||||
<UserTableSelect
|
||||
v-model="queryFormData.creator"
|
||||
@confirm-click="handleConfirm"
|
||||
@clear-click="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<!-- 时间范围,收起状态下隐藏 -->
|
||||
<el-form-item v-if="isExpand" prop="start_time" label="创建时间">
|
||||
<DatePicker v-model="dateRange" @update:model-value="handleDateRangeChange" />
|
||||
<DatePicker
|
||||
v-model="dateRange"
|
||||
@update:model-value="handleDateRangeChange"
|
||||
/>
|
||||
</el-form-item>
|
||||
<!-- 查询、重置、展开/收起按钮 -->
|
||||
<el-form-item >
|
||||
@@ -261,6 +271,7 @@ import ExportModal from "@/components/CURD/ExportModal.vue";
|
||||
import DatePicker from "@/components/DatePicker/index.vue";
|
||||
import type { IContentConfig } from "@/components/CURD/types";
|
||||
import { QuestionFilled, ArrowUp, ArrowDown } from "@element-plus/icons-vue";
|
||||
import { formatToDateTime } from "@/utils/dateUtil";
|
||||
|
||||
|
||||
const visible = ref(true);
|
||||
@@ -303,6 +314,24 @@ const curdContentConfig = {
|
||||
permPrefix: 'generator:demo',
|
||||
cols: exportColumns as any,
|
||||
importTemplate: () => ExampleAPI.downloadTemplate(),
|
||||
exportsAction: async (params: any) => {
|
||||
const query: any = { ...params };
|
||||
if (typeof query.status === 'string') {
|
||||
query.status = query.status === 'true';
|
||||
}
|
||||
query.page_no = 1;
|
||||
query.page_size = 9999;
|
||||
const all: any[] = [];
|
||||
while (true) {
|
||||
const res = await ExampleAPI.getExampleList(query);
|
||||
const items = res.data?.data?.items || [];
|
||||
const total = res.data?.data?.total || 0;
|
||||
all.push(...items);
|
||||
if (all.length >= total || items.length === 0) break;
|
||||
query.page_no += 1;
|
||||
}
|
||||
return all;
|
||||
},
|
||||
} as unknown as IContentConfig;
|
||||
// 详情表单
|
||||
const detailFormData = ref<ExampleTable>({});
|
||||
@@ -313,8 +342,8 @@ const dateRange = ref<[Date, Date] | []>([]);
|
||||
function handleDateRangeChange(range: [Date, Date]) {
|
||||
dateRange.value = range;
|
||||
if (range && range.length === 2) {
|
||||
queryFormData.start_time = range[0].toISOString();
|
||||
queryFormData.end_time = range[1].toISOString();
|
||||
queryFormData.start_time = formatToDateTime(range[0]);
|
||||
queryFormData.end_time = formatToDateTime(range[1]);
|
||||
} else {
|
||||
queryFormData.start_time = undefined;
|
||||
queryFormData.end_time = undefined;
|
||||
@@ -329,6 +358,7 @@ const queryFormData = reactive<ExamplePageQuery>({
|
||||
status: undefined,
|
||||
start_time: undefined,
|
||||
end_time: undefined,
|
||||
creator: undefined,
|
||||
});
|
||||
|
||||
// 编辑表单
|
||||
@@ -395,6 +425,11 @@ async function handleQuery() {
|
||||
loadingData();
|
||||
}
|
||||
|
||||
// 选择创建人后触发查询
|
||||
function handleConfirm() {
|
||||
handleQuery();
|
||||
}
|
||||
|
||||
// 重置查询
|
||||
async function handleResetQuery() {
|
||||
queryFormRef.value.resetFields();
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'name')?.show" key="name" label="用户名" prop="name" min-width="80" />
|
||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'user_name')?.show" key="user_name" label="用户姓名" prop="user_name" min-width="80" />
|
||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'user_name')?.show" key="user_name" label="账号" prop="user_name" min-width="80" />
|
||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'login_location')?.show" key="login_location" label="登录位置" prop="login_location" min-width="280" show-overflow-tooltip/>
|
||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'os')?.show" key="os" label="操作系统" prop="os" min-width="120" />
|
||||
<el-table-column v-if="tableColumns.find(col => col.prop === 'login_time')?.show" key="login_time" label="登录时间" prop="login_time" min-width="180" />
|
||||
|
||||
@@ -113,6 +113,9 @@ const userStore = useUserStore();
|
||||
const appStore = useAppStore();
|
||||
const settingsStore = useSettingsStore();
|
||||
|
||||
// 来自父容器的预填用户名和密码
|
||||
const props = defineProps<{ presetUsername?: string; presetPassword?: string }>();
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
@@ -142,14 +145,28 @@ const isCapsLock = ref(false);
|
||||
|
||||
|
||||
const loginForm = reactive<LoginFormData>({
|
||||
username: "admin",
|
||||
password: "123456",
|
||||
username: "",
|
||||
password: "",
|
||||
captcha: "",
|
||||
captcha_key: "",
|
||||
remember: true,
|
||||
login_type: "PC端",
|
||||
});
|
||||
|
||||
// 监听父组件传入的预填信息,立即填充到登录表单
|
||||
watch(
|
||||
() => [props.presetUsername, props.presetPassword],
|
||||
([presetUsername, presetPassword]) => {
|
||||
if (typeof presetUsername === 'string') {
|
||||
loginForm.username = presetUsername;
|
||||
}
|
||||
if (typeof presetPassword === 'string') {
|
||||
loginForm.password = presetPassword;
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const captchaState = reactive<CaptchaInfo>({
|
||||
enable: true,
|
||||
key: "",
|
||||
|
||||
@@ -79,8 +79,11 @@ import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
const toLogin = () => emit("update:modelValue", "login");
|
||||
// 使用 defineModel 简化具名 v-model 绑定
|
||||
const modelValue = defineModel<string>();
|
||||
const presetUsername = defineModel<string>('presetUsername');
|
||||
const presetPassword = defineModel<string>('presetPassword');
|
||||
const toLogin = () => { modelValue.value = 'login'; };
|
||||
|
||||
const configStore = useConfigStore();
|
||||
|
||||
@@ -162,7 +165,9 @@ const submit = async () => {
|
||||
loading.value = true;
|
||||
|
||||
await UserAPI.registerUser(model.value)
|
||||
|
||||
// 注册成功后,双向绑定回写父容器的用户名和密码,并切回登录
|
||||
presetUsername.value = model.value.username;
|
||||
presetPassword.value = model.value.password;
|
||||
toLogin();
|
||||
|
||||
} catch (error) {
|
||||
|
||||
@@ -73,8 +73,11 @@ const isCapsLock = ref(false); // 是否大写锁定
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
const toLogin = () => emit("update:modelValue", "login");
|
||||
// 使用 defineModel 简化具名 v-model 绑定
|
||||
const modelValue = defineModel<string>();
|
||||
const presetUsername = defineModel<string>('presetUsername');
|
||||
const presetPassword = defineModel<string>('presetPassword');
|
||||
const toLogin = () => { modelValue.value = 'login'; };
|
||||
|
||||
const model = ref<ForgetPasswordForm>({
|
||||
username: "",
|
||||
@@ -143,7 +146,9 @@ const submit = async () => {
|
||||
loading.value = true;
|
||||
|
||||
await UserAPI.forgetPassword(model.value)
|
||||
|
||||
// 重置成功后,双向绑定回写父容器的用户名和新密码,并切回登录
|
||||
presetUsername.value = model.value.username;
|
||||
presetPassword.value = model.value.new_password;
|
||||
toLogin();
|
||||
|
||||
} catch (error) {
|
||||
|
||||
@@ -38,7 +38,13 @@
|
||||
|
||||
<!-- 组件切换 -->
|
||||
<transition name="fade-slide" mode="out-in">
|
||||
<component :is="formComponents[component]" v-model="component" class="w-90%" />
|
||||
<component
|
||||
:is="formComponents[component]"
|
||||
v-model="component"
|
||||
v-model:preset-username="loginPreset.username"
|
||||
v-model:preset-password="loginPreset.password"
|
||||
class="w-90%"
|
||||
/>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,10 +80,12 @@ const formComponents = {
|
||||
resetPwd: defineAsyncComponent(() => import("./components/ResetPwd.vue")),
|
||||
};
|
||||
|
||||
// 预填登录信息(通过具名 v-model 双向绑定传递)
|
||||
const loginPreset = reactive<{ username: string; password: string }>({ username: "admin", password: "123456" });
|
||||
|
||||
onMounted(() => {
|
||||
configStore.getConfig();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 表格区域:系统配置列表 -->
|
||||
<el-table ref="dataTableRef" v-loading="loading" row-key="id" :data="pageTableData" :tree-props="{children: 'children'}" highlight-current-row class="data-table__content" height="540" border stripe @selection-change="handleSelectionChange">
|
||||
<el-table ref="dataTableRef" v-loading="loading" row-key="id" :data="pageTableData" :tree-props="{children: 'children', hasChildren: 'hasChildren'}" class="data-table__content" height="540" border stripe @selection-change="handleSelectionChange" @row-click="handleRowClick">
|
||||
<template #empty>
|
||||
<el-empty :image-size="80" description="暂无数据" />
|
||||
</template>
|
||||
@@ -183,9 +183,9 @@
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<!-- 详情弹窗不需要确定按钮的提交逻辑 -->
|
||||
<el-button @click="handleCloseDialog">取消</el-button>
|
||||
<el-button v-if="dialogVisible.type !== 'detail'" v-hasPerm="['system:dept:submit']" type="primary" @click="handleSubmit">确定</el-button>
|
||||
<el-button v-else v-hasPerm="['system:dept:detail']" type="primary" @click="handleCloseDialog">确定</el-button>
|
||||
<el-button @click="handleCloseDialog">取消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@@ -202,6 +202,7 @@ defineOptions({
|
||||
import DeptAPI, { DeptTable, DeptForm, DeptPageQuery } from "@/api/system/dept";
|
||||
import { useUserStore } from "@/store";
|
||||
import { formatTree } from "@/utils/common";
|
||||
import { formatToDateTime } from "@/utils/dateUtil";
|
||||
|
||||
const queryFormRef = ref();
|
||||
const dataFormRef = ref();
|
||||
@@ -305,6 +306,10 @@ async function handleQuery() {
|
||||
// 重置查询
|
||||
async function handleResetQuery() {
|
||||
queryFormRef.value.resetFields();
|
||||
// 额外清空日期范围与时间查询参数
|
||||
dateRange.value = [];
|
||||
queryFormData.start_time = undefined;
|
||||
queryFormData.end_time = undefined;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
@@ -325,8 +330,8 @@ const dateRange = ref<[Date, Date] | []>([]);
|
||||
function handleDateRangeChange(range: [Date, Date]) {
|
||||
dateRange.value = range;
|
||||
if (range && range.length === 2) {
|
||||
queryFormData.start_time = range[0].toISOString();
|
||||
queryFormData.end_time = range[1].toISOString();
|
||||
queryFormData.start_time = formatToDateTime(range[0]);
|
||||
queryFormData.end_time = formatToDateTime(range[1]);
|
||||
} else {
|
||||
queryFormData.start_time = undefined;
|
||||
queryFormData.end_time = undefined;
|
||||
@@ -348,6 +353,14 @@ async function handleSelectionChange(selection: any) {
|
||||
selectIds.value = selection.map((item: any) => item.id);
|
||||
}
|
||||
|
||||
// 选择表格的行菜单ID
|
||||
const selectedDeptId = ref<number | undefined>();
|
||||
|
||||
// 行点击事件
|
||||
async function handleRowClick(row: DeptTable) {
|
||||
selectedDeptId.value = row.id;
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
async function handleCloseDialog() {
|
||||
dialogVisible.visible = false;
|
||||
|
||||
@@ -197,7 +197,21 @@
|
||||
<el-input v-model="formData.dict_value" placeholder="请输入数据值" :maxlength="50" />
|
||||
</el-form-item>
|
||||
<el-form-item label="样式属性" prop="css_class">
|
||||
<el-input v-model="formData.css_class" placeholder="请输入样式属性" :maxlength="50" />
|
||||
<el-select v-model="formData.css_class" placeholder="请选择常用颜色或输入自定义" clearable filterable allow-create default-first-option>
|
||||
<!-- Element Plus 常用主题色(十六进制) -->
|
||||
<el-option value="#409EFF" label="蓝色-Primary (#409EFF)" />
|
||||
<el-option value="#67C23A" label="绿色-Success (#67C23A)" />
|
||||
<el-option value="#E6A23C" label="橙色-Warning (#E6A23C)" />
|
||||
<el-option value="#F56C6C" label="红色-Danger (#F56C6C)" />
|
||||
<el-option value="#909399" label="灰色-Info (#909399)" />
|
||||
<!-- 文本常用颜色 -->
|
||||
<el-option value="#303133" label="深灰-文本主要 (#303133)" />
|
||||
<el-option value="#606266" label="灰色-文本常规 (#606266)" />
|
||||
<el-option value="#C0C4CC" label="浅灰-占位文本 (#C0C4CC)" />
|
||||
<!-- 基础色 -->
|
||||
<el-option value="#000000" label="黑色 (#000000)" />
|
||||
<el-option value="#FFFFFF" label="白色 (#FFFFFF)" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="列表类样式" prop="list_class">
|
||||
<el-select v-model="formData.list_class" placeholder="请选择列表类样式" clearable>
|
||||
@@ -219,7 +233,7 @@
|
||||
<el-input-number v-model="formData.dict_sort" controls-position="right" :min="1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-switch v-model="formData.status" inline-prompt active-value="true" inactive-value="false" />
|
||||
<el-switch v-model="formData.status" inline-prompt :active-value="true" :inactive-value="false" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="描述" prop="description">
|
||||
@@ -268,6 +282,7 @@ import { DeviceEnum } from "@/enums/settings/device.enum";
|
||||
import UserTableSelect from "@/views/system/user/components/UserTableSelect.vue";
|
||||
import ExportModal from "@/components/CURD/ExportModal.vue";
|
||||
import type { IContentConfig } from "@/components/CURD/types";
|
||||
import { formatToDateTime } from "@/utils/dateUtil";
|
||||
|
||||
const appStore = useAppStore();
|
||||
const drawerSize = computed(() => (appStore.device === DeviceEnum.DESKTOP ? "80%" : "60%"));
|
||||
@@ -332,8 +347,8 @@ const rules = reactive({
|
||||
dict_value: [{ required: true, message: "请输入字典键值", trigger: "blur" }],
|
||||
status: [{ required: true, message: "请选择状态", trigger: "blur" }],
|
||||
dict_sort: [{ required: true, message: "请输入排序", trigger: "blur" }],
|
||||
css_class: [{ required: true, message: "请输入样式类名", trigger: "blur" }],
|
||||
list_class: [{ required: true, message: "请输入列表类名", trigger: "blur" }],
|
||||
// css_class: [{ required: true, message: "请输入样式类名", trigger: "blur" }],
|
||||
// list_class: [{ required: true, message: "请输入列表类名", trigger: "blur" }],
|
||||
is_default: [{ required: true, message: "请选择是否默认", trigger: "blur" }],
|
||||
});
|
||||
|
||||
@@ -344,8 +359,8 @@ const dateRange = ref<[Date, Date] | []>([]);
|
||||
function handleDateRangeChange(range: [Date, Date]) {
|
||||
dateRange.value = range;
|
||||
if (range && range.length === 2) {
|
||||
queryFormData.start_time = range[0].toISOString();
|
||||
queryFormData.end_time = range[1].toISOString();
|
||||
queryFormData.start_time = formatToDateTime(range[0]);
|
||||
queryFormData.end_time = formatToDateTime(range[1]);
|
||||
} else {
|
||||
queryFormData.start_time = undefined;
|
||||
queryFormData.end_time = undefined;
|
||||
@@ -389,6 +404,10 @@ function handleConfirm() {
|
||||
async function handleResetQuery() {
|
||||
queryFormRef.value.resetFields();
|
||||
queryFormData.page_no = 1;
|
||||
// 额外清空日期范围与时间查询参数
|
||||
dateRange.value = [];
|
||||
queryFormData.start_time = undefined;
|
||||
queryFormData.end_time = undefined;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
@@ -442,8 +461,11 @@ async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: numbe
|
||||
}
|
||||
} else {
|
||||
dialogVisible.title = "新增字典数据";
|
||||
formData.id = undefined;
|
||||
// 重置为初始值并确保状态默认开启
|
||||
Object.assign(formData, initialFormData);
|
||||
formData.dict_type = props.dictType;
|
||||
formData.status = true;
|
||||
formData.id = undefined;
|
||||
}
|
||||
dialogVisible.visible = true;
|
||||
}
|
||||
|
||||
@@ -239,6 +239,7 @@ import DataDrawer from "@/views/system/dict/components/DataDrawer.vue"
|
||||
import UserTableSelect from "@/views/system/user/components/UserTableSelect.vue";
|
||||
import ExportModal from "@/components/CURD/ExportModal.vue";
|
||||
import type { IContentConfig } from "@/components/CURD/types";
|
||||
import { formatToDateTime } from "@/utils/dateUtil";
|
||||
|
||||
const queryFormRef = ref();
|
||||
const dataFormRef = ref();
|
||||
@@ -317,8 +318,8 @@ const dateRange = ref<[Date, Date] | []>([]);
|
||||
function handleDateRangeChange(range: [Date, Date]) {
|
||||
dateRange.value = range;
|
||||
if (range && range.length === 2) {
|
||||
queryFormData.start_time = range[0].toISOString();
|
||||
queryFormData.end_time = range[1].toISOString();
|
||||
queryFormData.start_time = formatToDateTime(range[0]);
|
||||
queryFormData.end_time = formatToDateTime(range[1]);
|
||||
} else {
|
||||
queryFormData.start_time = undefined;
|
||||
queryFormData.end_time = undefined;
|
||||
@@ -371,6 +372,10 @@ function handleConfirm() {
|
||||
async function handleResetQuery() {
|
||||
queryFormRef.value.resetFields();
|
||||
queryFormData.page_no = 1;
|
||||
// 额外清空日期范围与时间查询参数
|
||||
dateRange.value = [];
|
||||
queryFormData.start_time = undefined;
|
||||
queryFormData.end_time = undefined;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,6 @@
|
||||
<el-form-item prop="request_path" label="请求路径">
|
||||
<el-input v-model="queryFormData.request_path" placeholder="请输入请求路径" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item prop="creator_name" label="创建人">
|
||||
<el-input v-model="queryFormData.creator_name" placeholder="请输入创建人" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item prop="type" label="日志类型">
|
||||
<el-select v-model="queryFormData.type" placeholder="请选择日志类型" style="width: 167.5px" clearable>
|
||||
<el-option label="登录日志" value=1 />
|
||||
@@ -157,7 +154,7 @@
|
||||
<el-dialog v-model="dialogVisible.visible" :title="dialogVisible.title" @close="handleCloseDialog">
|
||||
<!-- 详情 -->
|
||||
<template v-if="dialogVisible.type === 'detail'">
|
||||
<el-descriptions :column="4" border>
|
||||
<el-descriptions :column="8" border label-width="140px">
|
||||
<el-descriptions-item label="日志类型" :span="2">
|
||||
<el-tag :type="formData.type === 1 ? 'success' : 'primary'">
|
||||
{{ formData.type === 1 ? '登录日志' : '操作日志' }}
|
||||
@@ -178,17 +175,17 @@
|
||||
<el-descriptions-item label="处理时间" :span="2">{{ formData.process_time }}</el-descriptions-item>
|
||||
<el-descriptions-item label="浏览器" :span="2">{{ formData.request_browser }}</el-descriptions-item>
|
||||
<el-descriptions-item label="操作系统" :span="2">{{ formData.request_os }}</el-descriptions-item>
|
||||
<el-descriptions-item label="请求参数" :span="4">
|
||||
<el-input v-model="formData.request_payload" type="textarea" :rows="3" readonly class="long-text-editor" />
|
||||
<el-descriptions-item label="请求参数" :span="8">
|
||||
<JsonPretty :value="formData.request_payload" height="80px" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="响应数据" :span="4">
|
||||
<el-input v-model="formData.response_json" type="textarea" :rows="5" readonly class="long-text-editor" />
|
||||
<el-descriptions-item label="响应数据" :span="8">
|
||||
<JsonPretty :value="formData.response_json" height="140px" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="登录地点" :span="2">{{ formData.login_location }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建人" :span="2">{{ formData.creator?.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间" :span="2">{{ formData.created_at }}</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间" :span="2">{{ formData.updated_at }}</el-descriptions-item>
|
||||
<el-descriptions-item label="描述" :span="4">{{ formData.description }}</el-descriptions-item>
|
||||
<el-descriptions-item label="登录地点" :span="4">{{ formData.login_location }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建人" :span="4">{{ formData.creator?.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间" :span="4">{{ formData.created_at }}</el-descriptions-item>
|
||||
<el-descriptions-item label="更新时间" :span="4">{{ formData.updated_at }}</el-descriptions-item>
|
||||
<el-descriptions-item label="描述" :span="8">{{ formData.description }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</template>
|
||||
|
||||
@@ -222,7 +219,9 @@ defineOptions({
|
||||
import LogAPI, { LogTable, LogPageQuery } from "@/api/system/log";
|
||||
import UserTableSelect from "@/views/system/user/components/UserTableSelect.vue";
|
||||
import ExportModal from "@/components/CURD/ExportModal.vue";
|
||||
import JsonPretty from "@/components/JsonPretty/index.vue";
|
||||
import type { IContentConfig } from "@/components/CURD/types";
|
||||
import { formatToDateTime } from "@/utils/dateUtil";
|
||||
|
||||
const queryFormRef = ref();
|
||||
const dataFormRef = ref();
|
||||
@@ -269,8 +268,8 @@ const dateRange = ref<[Date, Date] | []>([]);
|
||||
function handleDateRangeChange(range: [Date, Date]) {
|
||||
dateRange.value = range;
|
||||
if (range && range.length === 2) {
|
||||
queryFormData.start_time = range[0].toISOString();
|
||||
queryFormData.end_time = range[1].toISOString();
|
||||
queryFormData.start_time = formatToDateTime(range[0]);
|
||||
queryFormData.end_time = formatToDateTime(range[1]);
|
||||
} else {
|
||||
queryFormData.start_time = undefined;
|
||||
queryFormData.end_time = undefined;
|
||||
@@ -345,6 +344,10 @@ function handleConfirm() {
|
||||
async function handleResetQuery() {
|
||||
queryFormRef.value.resetFields();
|
||||
queryFormData.page_no = 1;
|
||||
// 额外清空日期范围与时间查询参数
|
||||
dateRange.value = [];
|
||||
queryFormData.start_time = undefined;
|
||||
queryFormData.end_time = undefined;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
|
||||
@@ -485,6 +485,7 @@ import { DeviceEnum } from "@/enums/settings/device.enum";
|
||||
import MenuAPI, { MenuPageQuery, MenuForm, MenuTable } from "@/api/system/menu";
|
||||
import { MenuTypeEnum } from "@/enums/system/menu.enum";
|
||||
import { formatTree } from "@/utils/common";
|
||||
import { formatToDateTime } from "@/utils/dateUtil";
|
||||
|
||||
const appStore = useAppStore();
|
||||
const userStore = useUserStore();
|
||||
@@ -548,7 +549,10 @@ const menuOptions = ref<OptionType[]>([]);
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
name: [{ required: true, message: "请输入菜单名称", trigger: "blur" }],
|
||||
name: [
|
||||
{ required: true, message: "请输入菜单名称", trigger: "blur" },
|
||||
{ min: 2, max: 50, message: "长度 2 到 50 个字符", trigger: "blur" },
|
||||
],
|
||||
parent_id: [{ required: true, message: "请选择父级菜单", trigger: "blur" }],
|
||||
type: [{ required: true, message: "请选择菜单类型", trigger: "blur" }],
|
||||
order: [{ required: true, message: "请输入排序", trigger: "blur" }],
|
||||
@@ -568,7 +572,10 @@ const rules = reactive({
|
||||
}
|
||||
],
|
||||
component_path: [{ required: true, message: "请输入组件路径", trigger: "blur" }],
|
||||
title: [{ required: true, message: "请输入菜单标题", trigger: "blur" }],
|
||||
title: [
|
||||
{ required: true, message: "请输入菜单标题", trigger: "blur" },
|
||||
{ min: 2, max: 50, message: "长度 2 到 50 个字符", trigger: "blur" },
|
||||
],
|
||||
keep_alive: [{ required: true, message: "请选择是否缓存", trigger: "change" }],
|
||||
hidden: [{ required: true, message: "请选择是否隐藏", trigger: "change" }],
|
||||
always_show: [{ required: true, message: "请选择始终显示", trigger: "change" }],
|
||||
@@ -585,8 +592,8 @@ const dateRange = ref<[Date, Date] | []>([]);
|
||||
function handleDateRangeChange(range: [Date, Date]) {
|
||||
dateRange.value = range;
|
||||
if (range && range.length === 2) {
|
||||
queryFormData.start_time = range[0].toISOString();
|
||||
queryFormData.end_time = range[1].toISOString();
|
||||
queryFormData.start_time = formatToDateTime(range[0]);
|
||||
queryFormData.end_time = formatToDateTime(range[1]);
|
||||
} else {
|
||||
queryFormData.start_time = undefined;
|
||||
queryFormData.end_time = undefined;
|
||||
@@ -641,6 +648,10 @@ async function handleQuery() {
|
||||
// 重置查询
|
||||
async function handleResetQuery() {
|
||||
queryFormRef.value.resetFields();
|
||||
// 额外清空日期范围与时间查询参数
|
||||
dateRange.value = [];
|
||||
queryFormData.start_time = undefined;
|
||||
queryFormData.end_time = undefined;
|
||||
handleQuery();
|
||||
}
|
||||
|
||||
|
||||
@@ -270,6 +270,7 @@ import { useDictStore } from "@/store/index";
|
||||
import UserTableSelect from "@/views/system/user/components/UserTableSelect.vue";
|
||||
import ExportModal from "@/components/CURD/ExportModal.vue";
|
||||
import type { IContentConfig } from "@/components/CURD/types";
|
||||
import { formatToDateTime } from "@/utils/dateUtil";
|
||||
|
||||
const dictStore = useDictStore();
|
||||
defineOptions({
|
||||
@@ -357,8 +358,8 @@ const dateRange = ref<[Date, Date] | []>([]);
|
||||
function handleDateRangeChange(range: [Date, Date]) {
|
||||
dateRange.value = range;
|
||||
if (range && range.length === 2) {
|
||||
queryFormData.start_time = range[0].toISOString();
|
||||
queryFormData.end_time = range[1].toISOString();
|
||||
queryFormData.start_time = formatToDateTime(range[0]);
|
||||
queryFormData.end_time = formatToDateTime(range[1]);
|
||||
} else {
|
||||
queryFormData.start_time = undefined;
|
||||
queryFormData.end_time = undefined;
|
||||
@@ -401,6 +402,10 @@ function handleConfirm() {
|
||||
async function handleResetQuery() {
|
||||
queryFormRef.value.resetFields();
|
||||
queryFormData.page_no = 1;
|
||||
// 额外清空日期范围与时间查询参数
|
||||
dateRange.value = [];
|
||||
queryFormData.start_time = undefined;
|
||||
queryFormData.end_time = undefined;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-drawer v-model="drawerVisible" title="配置中心" :size="drawerSize">
|
||||
<el-drawer v-model="drawerVisible" title="配置中心" :size="drawerSize" destroy-on-close @closed="onDrawerClosed">
|
||||
<el-tabs v-model="activeTabRef" type="border-card" >
|
||||
<!-- 网站配置 -->
|
||||
<el-tab-pane label="网站配置" name="website">
|
||||
@@ -30,9 +30,10 @@
|
||||
:data="{ type: key }"
|
||||
:name="'file'"
|
||||
:max-file-size="item.maxFileSize"
|
||||
@on-success="(fileInfo: UploadFilePath) => handleUploadSuccess(fileInfo, key)"
|
||||
@on-error="handleUploadError"
|
||||
@input="markModified(key)"
|
||||
:show-tip="true"
|
||||
:enable-preview="true"
|
||||
@success="(fileInfo: UploadFilePath) => handleUploadSuccess(fileInfo, key)"
|
||||
@error="handleUploadError"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
@@ -61,6 +62,7 @@
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="用户协议" name="userAgreement">
|
||||
<!-- 系统配置 -->
|
||||
<el-form :model="configState" label-suffix=":" label-width="auto" label-position="right">
|
||||
<el-divider>用户协议</el-divider>
|
||||
<div v-for="(item, key) in userAgreementConfigs" :key="key">
|
||||
<el-form-item :label="item.config_name">
|
||||
@@ -75,6 +77,7 @@
|
||||
</span>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="接口白名单" name="apiWhitelist">
|
||||
<el-form :model="configState" label-suffix=":" label-width="auto" label-position="right">
|
||||
@@ -243,15 +246,14 @@
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<template #footer>
|
||||
<!-- 操作按钮 -->
|
||||
<el-button @click="resetForm">取消</el-button>
|
||||
<el-button @click="handleCloseDialog">取消</el-button>
|
||||
<el-button v-hasPerm="['system:config:update']" type="primary" :disabled="!hasChanges" @click="submitChanges">保存</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, onMounted, computed, watch } from 'vue';
|
||||
import { ref, reactive, onMounted, computed } from 'vue';
|
||||
import ParamsAPI, { type ConfigTable } from '@/api/system/params';
|
||||
import { useConfigStore } from "@/store";
|
||||
import { useI18n } from 'vue-i18n';
|
||||
@@ -290,8 +292,15 @@ const t = useI18n().t;
|
||||
const configStore = useConfigStore();
|
||||
const activeTabRef = ref('website')
|
||||
|
||||
// 与父组件的 v-model 同步
|
||||
const props = defineProps<{ modelValue: boolean }>();
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
const drawerVisible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val: boolean) => emit('update:modelValue', val)
|
||||
});
|
||||
|
||||
// 配置状态管理
|
||||
const drawerVisible = ref<boolean>(false);
|
||||
const configState = reactive<ConfigTable>({
|
||||
id: undefined,
|
||||
config_name: '',
|
||||
@@ -412,6 +421,16 @@ const resetForm = () => {
|
||||
ElMessageBox.close();
|
||||
};
|
||||
|
||||
async function handleCloseDialog() {
|
||||
// 仅关闭抽屉,等待关闭动画结束后再重置
|
||||
drawerVisible.value = false;
|
||||
}
|
||||
|
||||
function onDrawerClosed() {
|
||||
// 抽屉关闭动画结束后再执行重置,避免打断动画
|
||||
resetForm();
|
||||
}
|
||||
|
||||
// 系统配置项
|
||||
const systemConfigs = computed(() => ({
|
||||
sys_web_title: configStore.configData.sys_web_title,
|
||||
@@ -569,11 +588,6 @@ const removeDemoIpWhitelistItem = (id: string) => {
|
||||
markModified('ip_white_list');
|
||||
};
|
||||
|
||||
// 监听列表变化并标记为已修改
|
||||
watch([apiWhitelistItems, ipBlacklistItems, demoIpWhitelistItems], () => {
|
||||
// 这里可以添加验证逻辑
|
||||
}, { deep: true });
|
||||
|
||||
// 接口白名单配置项
|
||||
const apiWhitelistConfigs = computed(() => ({
|
||||
white_api_list_path: configStore.configData.white_api_list_path,
|
||||
@@ -603,7 +617,7 @@ const logoConfigs = computed(() => ({
|
||||
},
|
||||
sys_web_favicon: {
|
||||
...configStore.configData.sys_web_favicon,
|
||||
maxFileSize: 2,
|
||||
maxFileSize: 5,
|
||||
},
|
||||
sys_login_background: {
|
||||
...configStore.configData.sys_login_background,
|
||||
@@ -613,22 +627,15 @@ const logoConfigs = computed(() => ({
|
||||
|
||||
// 图片上传成功的回调处理
|
||||
const handleUploadSuccess = (fileInfo: UploadFilePath, type: string) => {
|
||||
// 使用正确的file_url属性
|
||||
const fileUrl = fileInfo.file_url;
|
||||
|
||||
// 更新store中的数据
|
||||
if (type in configStore.configData) {
|
||||
configStore.configData[type as keyof typeof configStore.configData].config_value = fileUrl;
|
||||
(configStore.configData as any)[type].config_value = fileUrl;
|
||||
}
|
||||
|
||||
// 更新对应的item.config_value,确保v-model绑定生效
|
||||
if (type in systemConfigs.value) {
|
||||
systemConfigs.value[type as keyof typeof systemConfigs.value].config_value = fileUrl;
|
||||
(systemConfigs.value as any)[type].config_value = fileUrl;
|
||||
} else if (type in logoConfigs.value) {
|
||||
logoConfigs.value[type as keyof typeof logoConfigs.value].config_value = fileUrl;
|
||||
(logoConfigs.value as any)[type].config_value = fileUrl;
|
||||
}
|
||||
|
||||
// 标记为已修改
|
||||
markModified(type);
|
||||
};
|
||||
|
||||
@@ -642,6 +649,7 @@ const handleUploadError = (error: any) => {
|
||||
onMounted(() => {
|
||||
configStore.getConfig();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -214,6 +214,7 @@ import ParamsAPI, { ConfigTable, ConfigForm, ConfigPageQuery } from "@/api/syste
|
||||
import UserTableSelect from "@/views/system/user/components/UserTableSelect.vue";
|
||||
import ExportModal from "@/components/CURD/ExportModal.vue";
|
||||
import type { IContentConfig } from "@/components/CURD/types";
|
||||
import { formatToDateTime } from "@/utils/dateUtil";
|
||||
|
||||
const queryFormRef = ref();
|
||||
const dataFormRef = ref();
|
||||
@@ -295,8 +296,8 @@ const dateRange = ref<[Date, Date] | []>([]);
|
||||
function handleDateRangeChange(range: [Date, Date]) {
|
||||
dateRange.value = range;
|
||||
if (range && range.length === 2) {
|
||||
queryFormData.start_time = range[0].toISOString();
|
||||
queryFormData.end_time = range[1].toISOString();
|
||||
queryFormData.start_time = formatToDateTime(range[0]);
|
||||
queryFormData.end_time = formatToDateTime(range[1]);
|
||||
} else {
|
||||
queryFormData.start_time = undefined;
|
||||
queryFormData.end_time = undefined;
|
||||
@@ -339,6 +340,10 @@ function handleConfirm() {
|
||||
async function handleResetQuery() {
|
||||
queryFormRef.value.resetFields();
|
||||
queryFormData.page_no = 1;
|
||||
// 额外清空日期范围与时间查询参数
|
||||
dateRange.value = [];
|
||||
queryFormData.start_time = undefined;
|
||||
queryFormData.end_time = undefined;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
|
||||
@@ -223,6 +223,7 @@ import { useUserStore } from "@/store";
|
||||
import UserTableSelect from "@/views/system/user/components/UserTableSelect.vue";
|
||||
import ExportModal from "@/components/CURD/ExportModal.vue";
|
||||
import type { IContentConfig } from "@/components/CURD/types";
|
||||
import { formatToDateTime } from "@/utils/dateUtil";
|
||||
|
||||
const queryFormRef = ref();
|
||||
const dataFormRef = ref();
|
||||
@@ -300,8 +301,8 @@ const dateRange = ref<[Date, Date] | []>([]);
|
||||
function handleDateRangeChange(range: [Date, Date]) {
|
||||
dateRange.value = range;
|
||||
if (range && range.length === 2) {
|
||||
queryFormData.start_time = range[0].toISOString();
|
||||
queryFormData.end_time = range[1].toISOString();
|
||||
queryFormData.start_time = formatToDateTime(range[0]);
|
||||
queryFormData.end_time = formatToDateTime(range[1]);
|
||||
} else {
|
||||
queryFormData.start_time = undefined;
|
||||
queryFormData.end_time = undefined;
|
||||
@@ -345,6 +346,10 @@ function handleConfirm() {
|
||||
async function handleResetQuery() {
|
||||
queryFormRef.value.resetFields();
|
||||
queryFormData.page_no = 1;
|
||||
// 额外清空日期范围与时间查询参数
|
||||
dateRange.value = [];
|
||||
queryFormData.start_time = undefined;
|
||||
queryFormData.end_time = undefined;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ import { useUserStore } from "@/store";
|
||||
|
||||
const appStore = useAppStore();
|
||||
const drawerSize = computed(() => (appStore.device === DeviceEnum.DESKTOP ? "800px" : "90%"));
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const emit = defineEmits(['update:modelValue', 'saved'])
|
||||
|
||||
const permTreeRef = ref<TreeInstance>();
|
||||
const deptTreeRef = ref<TreeInstance>()
|
||||
@@ -237,6 +237,10 @@ function handleCancel() {
|
||||
// 保存权限分配
|
||||
async function handleDrawerSave () {
|
||||
try {
|
||||
if (props.roleId === 1) {
|
||||
ElMessage.warning('系统默认角色,不可操作');
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
|
||||
// 构造提交数据
|
||||
@@ -249,13 +253,12 @@ async function handleDrawerSave () {
|
||||
|
||||
await RoleAPI.setPermission(submitData)
|
||||
|
||||
ElMessage.success('权限分配成功');
|
||||
|
||||
// 更新全局用户状态,刷新权限信息
|
||||
const userStore = useUserStore();
|
||||
await userStore.getUserInfo();
|
||||
|
||||
drawerVisible.value = false;
|
||||
emit('saved');
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
|
||||
@@ -265,7 +265,7 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<PermissonDrawer v-if="drawerVisible" v-model="drawerVisible" :role-name="checkedRole.name" :role-id="checkedRole.id" />
|
||||
<PermissonDrawer v-if="drawerVisible" v-model="drawerVisible" :role-name="checkedRole.name" :role-id="checkedRole.id" @saved="handleRefresh" />
|
||||
|
||||
<!-- 导出弹窗 -->
|
||||
<ExportModal
|
||||
@@ -291,6 +291,7 @@ import UserTableSelect from "@/views/system/user/components/UserTableSelect.vue"
|
||||
import ExportModal from "@/components/CURD/ExportModal.vue";
|
||||
import type { IContentConfig } from "@/components/CURD/types";
|
||||
import { QuestionFilled, ArrowUp, ArrowDown } from "@element-plus/icons-vue";
|
||||
import { formatToDateTime } from "@/utils/dateUtil";
|
||||
|
||||
const queryFormRef = ref();
|
||||
const dataFormRef = ref();
|
||||
@@ -406,6 +407,10 @@ async function handleSelectionChange(selection: any) {
|
||||
async function handleResetQuery() {
|
||||
queryFormRef.value.resetFields();
|
||||
queryFormData.page_no = 1;
|
||||
// 额外清空日期范围与时间查询参数
|
||||
dateRange.value = [];
|
||||
queryFormData.start_time = undefined;
|
||||
queryFormData.end_time = undefined;
|
||||
loadingData();
|
||||
}
|
||||
|
||||
@@ -426,8 +431,8 @@ const dateRange = ref<[Date, Date] | []>([]);
|
||||
function handleDateRangeChange(range: [Date, Date]) {
|
||||
dateRange.value = range;
|
||||
if (range && range.length === 2) {
|
||||
queryFormData.start_time = range[0].toISOString();
|
||||
queryFormData.end_time = range[1].toISOString();
|
||||
queryFormData.start_time = formatToDateTime(range[0]);
|
||||
queryFormData.end_time = formatToDateTime(range[1]);
|
||||
} else {
|
||||
queryFormData.start_time = undefined;
|
||||
queryFormData.end_time = undefined;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
ref="deptTreeRef"
|
||||
class="mt-2"
|
||||
:data="deptOptions"
|
||||
:props="{ children: 'children', label: 'label', disabled: '' }"
|
||||
:props="{ children: 'children', label: 'label', disabled: 'disabled' }"
|
||||
:expand-on-click-node="false"
|
||||
:filter-node-method="handleFilter"
|
||||
default-expand-all
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
<el-form-item prop="username" label="账号">
|
||||
<el-input v-model="queryFormData.username" placeholder="请输入账号" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item prop="name" label="姓名">
|
||||
<el-input v-model="queryFormData.name" placeholder="请输入姓名" clearable />
|
||||
<el-form-item prop="name" label="用户名">
|
||||
<el-input v-model="queryFormData.name" placeholder="请输入用户名" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item prop="status" label="状态">
|
||||
<el-select v-model="queryFormData.status" placeholder="请选择状态" style="width: 167.5px" clearable>
|
||||
@@ -176,13 +176,13 @@
|
||||
<el-table-column fixed="right" label="操作" align="center" min-width="280">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
v-if="scope.row.username !== 'admin'"
|
||||
v-hasPerm="['system:user:update']"
|
||||
type="warning"
|
||||
icon="RefreshLeft"
|
||||
size="small"
|
||||
link
|
||||
@click="hancleResetPassword(scope.row)"
|
||||
link
|
||||
:disabled="scope.row.is_superuser === true"
|
||||
@click="scope.row.is_superuser === true ? ElMessage.warning('系统超管角色,不可操作') : hancleResetPassword(scope.row)"
|
||||
>重置密码</el-button>
|
||||
<el-button
|
||||
v-hasPerm="['system:user:detail']"
|
||||
@@ -193,22 +193,22 @@
|
||||
@click="handleOpenDialog('detail', scope.row.id)"
|
||||
>详情</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.username !== 'admin'"
|
||||
v-hasPerm="['system:user:update']"
|
||||
type="primary"
|
||||
size="small"
|
||||
link
|
||||
icon="edit"
|
||||
@click="handleOpenDialog('update', scope.row.id)"
|
||||
:disabled="scope.row.is_superuser === true"
|
||||
@click="scope.row.is_superuser === true ? ElMessage.warning('系统超管角色,不可操作') : handleOpenDialog('update', scope.row.id)"
|
||||
>编辑</el-button>
|
||||
<el-button
|
||||
v-if="scope.row.username !== 'admin'"
|
||||
v-hasPerm="['system:user:delete']"
|
||||
type="danger"
|
||||
size="small"
|
||||
link
|
||||
icon="delete"
|
||||
@click="handleDelete([scope.row.id])"
|
||||
:disabled="scope.row.is_superuser === true"
|
||||
@click="scope.row.is_superuser === true ? ElMessage.warning('系统超管角色,不可操作') : handleDelete([scope.row.id])"
|
||||
>删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -269,7 +269,7 @@
|
||||
<template v-else>
|
||||
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-suffix=":" label-width="auto" label-position="right">
|
||||
<el-form-item label="账号" prop="username">
|
||||
<el-input v-model="formData.username" :readonly="!!formData.id" placeholder="请输入账号" />
|
||||
<el-input v-model="formData.username" :disabled="!!formData.id" placeholder="请输入账号" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="用户名" prop="name">
|
||||
@@ -293,23 +293,23 @@
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="部门" prop="dept_id">
|
||||
<el-tree-select v-model="formData.dept_id" placeholder="请选择上级部门" :data="deptOptions" filterable check-strictly :render-after-expand="false" />
|
||||
<el-tree-select v-model="formData.dept_id" placeholder="请选择上级部门" :data="deptOptions" :props="{ children: 'children', label: 'label', disabled: 'disabled' }" filterable check-strictly :render-after-expand="false" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="角色" prop="role_ids">
|
||||
<el-select v-model="formData.role_ids" multiple placeholder="请选择角色">
|
||||
<el-option v-for="item in roleOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
<el-option v-for="item in roleOptions" :key="item.value" :label="item.label" :value="item.value" :disabled="item.disabled" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="岗位" prop="position_ids">
|
||||
<el-select v-model="formData.position_ids" multiple placeholder="请选择岗位">
|
||||
<el-option v-for="item in positionOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
<el-option v-for="item in positionOptions" :key="item.value" :label="item.label" :value="item.value" :disabled="item.disabled" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="dialogVisible.type === 'create'" label="密码" prop="password">
|
||||
<el-input v-model="formData.password" placeholder="请输入密码" type="password" show-password clearable />
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-input v-model="formData.password" :readonly="!!formData.id" placeholder="请输入密码" type="password" show-password clearable />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="是否超管" prop="is_superuser">
|
||||
@@ -327,40 +327,34 @@
|
||||
<el-input v-model="formData.description" :rows="4" :maxlength="100" show-word-limit type="textarea" placeholder="请输入描述" />
|
||||
</el-form-item>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<!-- 详情弹窗不需要确定按钮的提交逻辑 -->
|
||||
<el-button v-if="dialogVisible.type === 'create' || dialogVisible.type === 'update'" v-hasPerm="['system:user:create']" type="primary" @click="handleSubmit">确定</el-button>
|
||||
<el-button v-else v-hasPerm="['system:user:detail']" type="primary" @click="handleCloseDialog">确定</el-button>
|
||||
<el-button @click="handleCloseDialog">取消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button v-hasPerm="['system:user:submit']" type="primary" @click="handleSubmit">确 定</el-button>
|
||||
<el-button @click="handleCloseDialog">取 消</el-button>
|
||||
<el-button v-if="dialogVisible.type === 'create' || dialogVisible.type === 'update'" v-hasPerm="['system:user:create']" type="primary" @click="handleSubmit">确定</el-button>
|
||||
<el-button v-else v-hasPerm="['system:user:detail']" type="primary" @click="handleCloseDialog">确定</el-button>
|
||||
<el-button @click="handleCloseDialog">取消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
|
||||
<!-- 导入弹窗 -->
|
||||
<ImportModal
|
||||
v-model="importDialogVisible"
|
||||
:content-config="curdContentConfig"
|
||||
@upload="handleUpload"
|
||||
/>
|
||||
<ImportModal
|
||||
v-model="importDialogVisible"
|
||||
:content-config="curdContentConfig"
|
||||
@upload="handleUpload"
|
||||
/>
|
||||
|
||||
<!-- 导出弹窗 -->
|
||||
<ExportModal
|
||||
v-model="exportsDialogVisible"
|
||||
:content-config="curdContentConfig"
|
||||
:query-params="queryFormData"
|
||||
:page-data="pageTableData"
|
||||
:selection-data="selectionRows"
|
||||
/>
|
||||
<!-- 导出弹窗 -->
|
||||
<ExportModal
|
||||
v-model="exportsDialogVisible"
|
||||
:content-config="curdContentConfig"
|
||||
:query-params="queryFormData"
|
||||
:page-data="pageTableData"
|
||||
:selection-data="selectionRows"
|
||||
/>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
@@ -380,6 +374,7 @@ import { formatTree } from "@/utils/common";
|
||||
import PositionAPI from "@/api/system/position";
|
||||
import DeptAPI from "@/api/system/dept";
|
||||
import RoleAPI from "@/api/system/role";
|
||||
import { formatToDateTime } from "@/utils/dateUtil";
|
||||
|
||||
import DeptTree from "./components/DeptTree.vue";
|
||||
import UserTableSelect from "./components/UserTableSelect.vue";
|
||||
@@ -404,9 +399,9 @@ const selectIds = ref<number[]>([]);
|
||||
// 部门下拉数据源
|
||||
const deptOptions = ref<OptionType[]>();
|
||||
// 角色下拉数据源
|
||||
const roleOptions = ref<OptionType[]>();
|
||||
const roleOptions = ref<Array<{ value: number; label: string; disabled?: boolean }>>();
|
||||
// 岗位下拉数据源
|
||||
const positionOptions = ref<OptionType[]>();
|
||||
const positionOptions = ref<Array<{ value: number; label: string; disabled?: boolean }>>();
|
||||
// 导入弹窗显示状态
|
||||
const importDialogVisible = ref(false);
|
||||
// 导出弹窗显示状态
|
||||
@@ -464,7 +459,6 @@ const rules = reactive({
|
||||
password: [{ required: true, message: "请输入密码", trigger: "blur" }],
|
||||
gender: [{ required: true, message: "请选择性别", trigger: "blur" }],
|
||||
email: [
|
||||
{ required: true, message: "请输入邮箱", trigger: "blur" },
|
||||
{
|
||||
pattern: /\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/,
|
||||
message: "请输入正确的邮箱地址",
|
||||
@@ -472,7 +466,6 @@ const rules = reactive({
|
||||
},
|
||||
],
|
||||
mobile: [
|
||||
{ required: true, message: "请输入手机号", trigger: "blur" },
|
||||
{
|
||||
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
|
||||
message: "请输入正确的手机号码",
|
||||
@@ -511,7 +504,7 @@ const curdContentConfig = {
|
||||
query.status = query.status === 'true';
|
||||
}
|
||||
query.page_no = 1;
|
||||
query.page_size = 1000;
|
||||
query.page_size = 9999;
|
||||
const all: any[] = [];
|
||||
while (true) {
|
||||
const res = await UserAPI.getUserList(query);
|
||||
@@ -535,8 +528,8 @@ function handleConfirm() {
|
||||
function handleDateRangeChange(range: [Date, Date]) {
|
||||
dateRange.value = range;
|
||||
if (range && range.length === 2) {
|
||||
queryFormData.start_time = range[0].toISOString();
|
||||
queryFormData.end_time = range[1].toISOString();
|
||||
queryFormData.start_time = formatToDateTime(range[0]);
|
||||
queryFormData.end_time = formatToDateTime(range[1]);
|
||||
} else {
|
||||
queryFormData.start_time = undefined;
|
||||
queryFormData.end_time = undefined;
|
||||
@@ -651,8 +644,6 @@ async function handleCloseDialog() {
|
||||
// 打开弹窗
|
||||
async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: number) {
|
||||
dialogVisible.type = type;
|
||||
// 动态设置密码验证规则
|
||||
rules.password[0].required = type === 'create';
|
||||
if (id) {
|
||||
const response = await UserAPI.getUserDetail(id);
|
||||
if (type === 'detail') {
|
||||
@@ -670,6 +661,10 @@ async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: numbe
|
||||
formData.id = undefined;
|
||||
}
|
||||
dialogVisible.visible = true;
|
||||
await nextTick();
|
||||
if (dataFormRef.value) {
|
||||
dataFormRef.value.clearValidate();
|
||||
}
|
||||
|
||||
// 获取部门树
|
||||
const deptResponse = await DeptAPI.getDeptList(queryFormData);
|
||||
@@ -682,8 +677,10 @@ async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: numbe
|
||||
.filter(item => item.id !== undefined && item.name !== undefined)
|
||||
.map(item => ({
|
||||
value: item.id as number,
|
||||
label: item.name as string
|
||||
}));
|
||||
label: item.name as string,
|
||||
disabled: item.status === false || String(item.status) === 'false'
|
||||
}))
|
||||
.filter(opt => !opt.disabled);
|
||||
|
||||
// 获取岗位列表
|
||||
const positionResponse = await PositionAPI.getPositionList();
|
||||
@@ -691,8 +688,10 @@ async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: numbe
|
||||
.filter(item => item.id !== undefined && item.name !== undefined)
|
||||
.map(item => ({
|
||||
value: item.id as number,
|
||||
label: item.name as string
|
||||
}));
|
||||
label: item.name as string,
|
||||
disabled: item.status === false || String(item.status) === 'false'
|
||||
}))
|
||||
.filter(opt => !opt.disabled);
|
||||
}
|
||||
|
||||
// 提交表单(防抖)
|
||||
|
||||
Reference in New Issue
Block a user