style: 移除多余空行并统一代码格式

修复代码中的多余空行问题,统一字符串引号使用双引号,调整对象和数组的格式,优化代码缩进和换行,提升代码可读性和一致性
This commit is contained in:
zhangtao
2025-11-14 01:45:22 +08:00
parent 66c345d590
commit d588abb386
100 changed files with 9192 additions and 5181 deletions
@@ -39,12 +39,17 @@
<!-- 验证码 -->
<el-form-item v-if="captchaState.enable" prop="captcha">
<div flex>
<el-input v-model.trim="loginForm.captcha" :placeholder="t('login.captchaCode')" clearable @keyup.enter="handleLoginSubmit" >
<el-input
v-model.trim="loginForm.captcha"
:placeholder="t('login.captchaCode')"
clearable
@keyup.enter="handleLoginSubmit"
>
<template #prefix>
<div class="i-svg:captcha" />
</template>
</el-input>
<div cursor-pointer flex-center ml-10px >
<div cursor-pointer flex-center ml-10px>
<el-icon v-if="codeLoading" class="is-loading">
<Loading />
</el-icon>
@@ -104,7 +109,7 @@ import type { FormInstance } from "element-plus";
import { LocationQuery, RouteLocationRaw, useRoute, useRouter } from "vue-router";
import { useI18n } from "vue-i18n";
import { onActivated, onMounted, watch } from "vue";
import AuthAPI, {type LoginFormData, type CaptchaInfo } from "@/api/module_system/auth";
import AuthAPI, { type LoginFormData, type CaptchaInfo } from "@/api/module_system/auth";
import { useAppStore, useUserStore, useSettingsStore } from "@/store";
import CommonWrapper from "@/components/CommonWrapper/index.vue";
@@ -126,7 +131,7 @@ onMounted(() => getCaptcha());
onActivated(() => {
getCaptcha();
// 重置登录表单
loginForm.captcha = '';
loginForm.captcha = "";
});
// 监听路由变化,确保每次进入登录页面都有最新验证码
@@ -134,7 +139,7 @@ watch(
() => route.fullPath,
() => {
getCaptcha();
loginForm.captcha = '';
loginForm.captcha = "";
}
);
@@ -143,7 +148,6 @@ const loading = ref(false);
// 是否大写锁定
const isCapsLock = ref(false);
const loginForm = reactive<LoginFormData>({
username: "",
password: "",
@@ -157,10 +161,10 @@ const loginForm = reactive<LoginFormData>({
watch(
() => [props.presetUsername, props.presetPassword],
([presetUsername, presetPassword]) => {
if (typeof presetUsername === 'string') {
if (typeof presetUsername === "string") {
loginForm.username = presetUsername;
}
if (typeof presetPassword === 'string') {
if (typeof presetPassword === "string") {
loginForm.password = presetPassword;
}
},
@@ -170,7 +174,7 @@ watch(
const captchaState = reactive<CaptchaInfo>({
enable: true,
key: "",
img_base: ""
img_base: "",
});
const loginRules = computed(() => {
@@ -209,7 +213,7 @@ const codeLoading = ref(false);
async function getCaptcha() {
try {
codeLoading.value = true;
const response = await AuthAPI.getCaptcha()
const response = await AuthAPI.getCaptcha();
loginForm.captcha_key = response.data.data.key;
captchaState.img_base = response.data.data.img_base;
} finally {
@@ -241,17 +245,15 @@ async function handleLoginSubmit() {
// 4. 记住我功能已实现,根据用户选择决定token的存储方式:
// - 选中"记住我": token存储在localStorage中,浏览器关闭后仍然有效
// - 未选中"记住我": token存储在sessionStorage中,浏览器关闭后失效
// 登录成功后自动开启项目引导
if (settingsStore.showGuide) {
appStore.showGuide(true);
}
} catch (error: any) {
if (error) {
getCaptcha(); // 刷新验证码
}
} finally {
loading.value = false;
}
@@ -295,8 +297,6 @@ const emit = defineEmits(["update:modelValue"]);
function toOtherForm(type: "register" | "resetPwd") {
emit("update:modelValue", type);
}
</script>
<style lang="scss" scoped>
@@ -1,7 +1,7 @@
<template>
<div>
<h3 text-center m-0 mb-20px>{{ t("login.reg") }}</h3>
<el-form ref="formRef" :model="model" :rules="rules" size="large" label-suffix=":" >
<el-form ref="formRef" :model="model" :rules="rules" size="large" label-suffix=":">
<!-- 账号 -->
<el-form-item prop="username">
<el-input v-model.trim="model.username" :placeholder="t('login.username')" clearable>
@@ -49,11 +49,17 @@
</el-form-item>
</el-tooltip>
<el-form-item>
<div class="flex-y-center w-full gap-10px">
<el-checkbox v-model="isRead">{{ t("login.agree") }}</el-checkbox>
<el-link type="primary" underline="never" :href="configStore.configData.sys_web_clause.config_value" target="_blank">{{ t("login.userAgreement") }}</el-link>
<el-link
type="primary"
underline="never"
:href="configStore.configData.sys_web_clause.config_value"
target="_blank"
>
{{ t("login.userAgreement") }}
</el-link>
</div>
</el-form-item>
@@ -81,9 +87,11 @@ const { t } = useI18n();
// 使用 defineModel 简化具名 v-model 绑定
const modelValue = defineModel<string>();
const presetUsername = defineModel<string>('presetUsername');
const presetPassword = defineModel<string>('presetPassword');
const toLogin = () => { modelValue.value = 'login'; };
const presetUsername = defineModel<string>("presetUsername");
const presetPassword = defineModel<string>("presetPassword");
const toLogin = () => {
modelValue.value = "login";
};
const configStore = useConfigStore();
@@ -92,7 +100,6 @@ const loading = ref(false); // 按钮 loading 状态
const isCapsLock = ref(false); // 是否大写锁定
const isRead = ref(false);
const model = ref<RegisterForm>({
username: "",
password: "",
@@ -150,7 +157,6 @@ function checkCapsLock(event: KeyboardEvent) {
}
}
const submit = async () => {
try {
// 0. 检查是否已勾选协议
@@ -164,12 +170,11 @@ const submit = async () => {
loading.value = true;
await UserAPI.registerUser(model.value)
await UserAPI.registerUser(model.value);
// 注册成功后,双向绑定回写父容器的用户名和密码,并切回登录
presetUsername.value = model.value.username;
presetPassword.value = model.value.password;
toLogin();
} catch (error) {
console.error(error);
} finally {
@@ -177,6 +182,5 @@ const submit = async () => {
}
};
onMounted(() => {
});
onMounted(() => {});
</script>
@@ -1,7 +1,7 @@
<template>
<div>
<h3 text-center m-0 mb-20px>{{ t("login.resetPassword") }}</h3>
<el-form ref="formRef" :model="model" :rules="rules" size="large" label-suffix=":">
<el-form ref="formRef" :model="model" :rules="rules" size="large" label-suffix=":">
<!-- 用户名 -->
<el-form-item prop="username">
<el-input v-model.trim="model.username" :placeholder="t('login.username')" clearable>
@@ -75,9 +75,11 @@ const { t } = useI18n();
// 使用 defineModel 简化具名 v-model 绑定
const modelValue = defineModel<string>();
const presetUsername = defineModel<string>('presetUsername');
const presetPassword = defineModel<string>('presetPassword');
const toLogin = () => { modelValue.value = 'login'; };
const presetUsername = defineModel<string>("presetUsername");
const presetPassword = defineModel<string>("presetPassword");
const toLogin = () => {
modelValue.value = "login";
};
const model = ref<ForgetPasswordForm>({
username: "",
@@ -145,12 +147,11 @@ const submit = async () => {
loading.value = true;
await UserAPI.forgetPassword(model.value)
await UserAPI.forgetPassword(model.value);
// 重置成功后,双向绑定回写父容器的用户名和新密码,并切回登录
presetUsername.value = model.value.username;
presetPassword.value = model.value.new_password;
toLogin();
} catch (error) {
console.error(error);
} finally {
@@ -1,5 +1,12 @@
<template>
<div class="login-container" :style="{ 'background-image': configStore.configData?.sys_login_background?.config_value ? `url(${configStore.configData.sys_login_background.config_value})` : '/background.svg' }">
<div
class="login-container"
:style="{
'background-image': configStore.configData?.sys_login_background?.config_value
? `url(${configStore.configData.sys_login_background.config_value})`
: '/background.svg',
}"
>
<!-- 右侧切换主题、语言按钮 -->
<div class="action-bar">
<el-tooltip :content="t('login.themeToggle')" placement="bottom">
@@ -26,11 +33,17 @@
<!-- 标题 -->
<!-- 添加小图标用于显示提示信息 -->
<div class="flex items-center justify-center mb-4">
<el-tooltip :content="configStore.configData.sys_web_description.config_value" placement="bottom">
<el-tooltip
:content="configStore.configData.sys_web_description.config_value"
placement="bottom"
>
<el-icon class="cursor-help"><QuestionFilled /></el-icon>
</el-tooltip>
<div class="ml-2 text-xl font-bold">
<el-badge :value="`v ${configStore.configData.sys_web_version.config_value}`" type="success">
<el-badge
:value="`v ${configStore.configData.sys_web_version.config_value}`"
type="success"
>
{{ configStore.configData.sys_web_title.config_value }}
</el-badge>
</div>
@@ -50,7 +63,9 @@
</div>
<!-- 登录页底部版权 -->
<el-text size="small" class="py-2.5! fixed bottom-0 text-center">
<a :href="configStore.configData.sys_git_code.config_value" target="_blank">{{ configStore.configData.sys_web_copyright.config_value }} |</a>
<a :href="configStore.configData.sys_git_code.config_value" target="_blank">
{{ configStore.configData.sys_web_copyright.config_value }} |
</a>
<a :href="configStore.configData.sys_help_doc.config_value" target="_blank">帮助 |</a>
<a :href="configStore.configData.sys_web_privacy.config_value" target="_blank">隐私 |</a>
<a :href="configStore.configData.sys_web_clause.config_value" target="_blank">条款</a>
@@ -81,7 +96,10 @@ const formComponents = {
};
// 预填登录信息(通过具名 v-model 双向绑定传递)
const loginPreset = reactive<{ username: string; password: string }>({ username: "admin", password: "123456" });
const loginPreset = reactive<{ username: string; password: string }>({
username: "admin",
password: "123456",
});
onMounted(() => {
configStore.getConfig();
@@ -145,6 +163,4 @@ onMounted(() => {
opacity: 0;
transform: translateX(30px);
}
</style>
+311 -96
View File
@@ -3,27 +3,48 @@
<div class="app-container">
<!-- 搜索区域 -->
<div class="search-container">
<el-form ref="queryFormRef" :model="queryFormData" :inline="true" label-suffix=":" @submit.prevent="handleQuery" >
<el-form
ref="queryFormRef"
:model="queryFormData"
:inline="true"
label-suffix=":"
@submit.prevent="handleQuery"
>
<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>
<el-select
v-model="queryFormData.status"
placeholder="请选择状态"
style="width: 167.5px"
clearable
>
<el-option value="true" label="启用" />
<el-option value="false" label="停用" />
</el-select>
</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 class="search-buttons">
<el-button v-hasPerm="['module_system:dept:query']" type="primary" icon="search" native-type="submit">查询</el-button>
<el-button v-hasPerm="['module_system:dept:query']" icon="refresh" @click="handleResetQuery">重置</el-button>
<el-button
v-hasPerm="['module_system:dept:query']"
type="primary"
icon="search"
native-type="submit"
>
查询
</el-button>
<el-button
v-hasPerm="['module_system:dept:query']"
icon="refresh"
@click="handleResetQuery"
>
重置
</el-button>
<!-- 展开/收起 -->
<template v-if="isExpandable">
<el-link class="ml-3" type="primary" underline="never" @click="isExpand = !isExpand">
@@ -60,18 +81,39 @@
<div class="data-table__toolbar--left">
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPerm="['module_system:dept:create']" type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
<el-button
v-hasPerm="['module_system:dept:create']"
type="success"
icon="plus"
@click="handleOpenDialog('create')"
>
新增
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPerm="['module_system:dept:delete']" type="danger" icon="delete" :disabled="selectIds.length === 0" @click="handleDelete(selectIds)">批量删除</el-button>
<el-button
v-hasPerm="['module_system:dept:delete']"
type="danger"
icon="delete"
:disabled="selectIds.length === 0"
@click="handleDelete(selectIds)"
>
批量删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-dropdown v-hasPerm="['module_system:dept:patch']" trigger="click">
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">更多</el-button>
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">
更多
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">批量启用</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">批量停用</el-dropdown-item>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">
批量启用
</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">
批量停用
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -82,7 +124,7 @@
<el-row :gutter="10">
<el-col :span="1.5">
<el-tooltip content="刷新">
<el-button type="primary" icon="refresh" circle @click="handleRefresh"/>
<el-button type="primary" icon="refresh" circle @click="handleRefresh" />
</el-tooltip>
</el-col>
<el-col :span="1.5">
@@ -102,60 +144,195 @@
</div>
<!-- 表格区域:系统配置列表 -->
<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">
<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>
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection" min-width="55" align="center" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" type="index" fixed label="序号" min-width="60" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'name')?.show" key="name" label="部门名称" prop="name" min-width="120" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'code')?.show" key="code" label="部门编码" prop="code" min-width="120" />')
<el-table-column v-if="tableColumns.find(col => col.prop === 'status')?.show" key="status" label="状态" prop="status" min-width="60">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'selection')?.show"
type="selection"
min-width="55"
align="center"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'index')?.show"
type="index"
fixed
label="序号"
min-width="60"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'name')?.show"
key="name"
label="部门名称"
prop="name"
min-width="120"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'code')?.show"
key="code"
label="部门编码"
prop="code"
min-width="120"
/>
')
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'status')?.show"
key="status"
label="状态"
prop="status"
min-width="60"
>
<template #default="scope">
<el-tag :type="scope.row.status === true ? 'success' : 'danger'">
{{ scope.row.status ? "启用" : "停用" }}
</el-tag>
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'order')?.show" key="order" label="排序" prop="order" min-width="60" show-overflow-tooltip />
<el-table-column v-if="tableColumns.find(col => col.prop === 'description')?.show" key="description" label="描述" prop="description" min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" key="created_at" label="创建时间" prop="created_at" min-width="120" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'updated_at')?.show" key="updated_at" label="更新时间" prop="updated_at" min-width="120" sortable />
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'order')?.show"
key="order"
label="排序"
prop="order"
min-width="60"
show-overflow-tooltip
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'description')?.show"
key="description"
label="描述"
prop="description"
min-width="100"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'created_at')?.show"
key="created_at"
label="创建时间"
prop="created_at"
min-width="120"
sortable
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'updated_at')?.show"
key="updated_at"
label="更新时间"
prop="updated_at"
min-width="120"
sortable
/>
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right" label="操作" align="center" min-width="200">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'operation')?.show"
fixed="right"
label="操作"
align="center"
min-width="200"
>
<template #default="scope">
<el-button v-hasPerm="['module_system:dept:create']" type="success" size="small" link icon="plus" @click="handleOpenDialog('create', undefined, scope.row.id)">新增</el-button>
<el-button v-hasPerm="['module_system:dept:detail']" type="info" size="small" link icon="document" @click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
<el-button v-hasPerm="['module_system:dept:update']" type="primary" size="small" link icon="edit" @click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
<el-button v-hasPerm="['module_system:dept:delete']" type="danger" size="small" link icon="delete" @click="handleDelete([scope.row.id])">删除</el-button>
<el-button
v-hasPerm="['module_system:dept:create']"
type="success"
size="small"
link
icon="plus"
@click="handleOpenDialog('create', undefined, scope.row.id)"
>
新增
</el-button>
<el-button
v-hasPerm="['module_system:dept:detail']"
type="info"
size="small"
link
icon="document"
@click="handleOpenDialog('detail', scope.row.id)"
>
详情
</el-button>
<el-button
v-hasPerm="['module_system:dept:update']"
type="primary"
size="small"
link
icon="edit"
@click="handleOpenDialog('update', scope.row.id)"
>
编辑
</el-button>
<el-button
v-hasPerm="['module_system:dept:delete']"
type="danger"
size="small"
link
icon="delete"
@click="handleDelete([scope.row.id])"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 弹窗区域 -->
<el-dialog v-model="dialogVisible.visible" :title="dialogVisible.title" @close="handleCloseDialog">
<el-dialog
v-model="dialogVisible.visible"
:title="dialogVisible.title"
@close="handleCloseDialog"
>
<!-- 详情 -->
<template v-if="dialogVisible.type === 'detail'">
<el-descriptions :column="4" border>
<el-descriptions-item label="部门名称" :span="2">{{ detailFormData.name }}</el-descriptions-item>
<el-descriptions-item label="部门编码" :span="2">{{ detailFormData.code }}</el-descriptions-item>
<el-descriptions-item label="上级部门" :span="2">{{ detailFormData.parent_name }}</el-descriptions-item>
<el-descriptions-item label="部门名称" :span="2">
{{ detailFormData.name }}
</el-descriptions-item>
<el-descriptions-item label="部门编码" :span="2">
{{ detailFormData.code }}
</el-descriptions-item>
<el-descriptions-item label="上级部门" :span="2">
{{ detailFormData.parent_name }}
</el-descriptions-item>
<el-descriptions-item label="状态" :span="2">
<el-tag :type="detailFormData.status ? 'success' : 'danger'">
{{ detailFormData.status ? '启用' : '停用' }}
{{ detailFormData.status ? "启用" : "停用" }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="排序" :span="2">{{ detailFormData.order }}</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">{{ detailFormData.created_at }}</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">{{ detailFormData.updated_at }}</el-descriptions-item>
<el-descriptions-item label="描述" :span="4">{{ detailFormData.description }}</el-descriptions-item>
<el-descriptions-item label="排序" :span="2">
{{ detailFormData.order }}
</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">
{{ detailFormData.created_at }}
</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">
{{ detailFormData.updated_at }}
</el-descriptions-item>
<el-descriptions-item label="描述" :span="4">
{{ detailFormData.description }}
</el-descriptions-item>
</el-descriptions>
</template>
<!-- 新增、编辑表单 -->
<template v-else>
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-suffix=":" label-width="auto" label-position="right">
<el-form
ref="dataFormRef"
:model="formData"
:rules="rules"
label-suffix=":"
label-width="auto"
label-position="right"
>
<el-form-item label="部门名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入部门名称" :maxlength="50" />
</el-form-item>
@@ -163,10 +340,22 @@
<el-input v-model="formData.code" placeholder="请输入部门编码" :maxlength="50" />
</el-form-item>
<el-form-item label="上级部门" prop="parent_id">
<el-tree-select v-model="formData.parent_id" placeholder="请选择上级部门" :data="deptOptions" filterable check-strictly :render-after-expand="false" />
<el-tree-select
v-model="formData.parent_id"
placeholder="请选择上级部门"
:data="deptOptions"
filterable
check-strictly
:render-after-expand="false"
/>
</el-form-item>
<el-form-item label="排序" prop="order">
<el-input-number v-model="formData.order" controls-position="right" :min="1" :max="999" />
<el-input-number
v-model="formData.order"
controls-position="right"
:min="1"
:max="999"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
@@ -175,7 +364,14 @@
</el-radio-group>
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="formData.description" :rows="4" :maxlength="100" show-word-limit type="textarea" placeholder="请输入描述" />
<el-input
v-model="formData.description"
:rows="4"
:maxlength="100"
show-word-limit
type="textarea"
placeholder="请输入描述"
/>
</el-form-item>
</el-form>
</template>
@@ -183,13 +379,26 @@
<template #footer>
<div class="dialog-footer">
<!-- 详情弹窗不需要确定按钮的提交逻辑 -->
<el-button v-if="dialogVisible.type !== 'detail'" v-hasPerm="['module_system:dept:submit']" type="primary" @click="handleSubmit">确定</el-button>
<el-button v-else v-hasPerm="['module_system:dept:detail']" type="primary" @click="handleCloseDialog">确定</el-button>
<el-button
v-if="dialogVisible.type !== 'detail'"
v-hasPerm="['module_system:dept:submit']"
type="primary"
@click="handleSubmit"
>
确定
</el-button>
<el-button
v-else
v-hasPerm="['module_system:dept:detail']"
type="primary"
@click="handleCloseDialog"
>
确定
</el-button>
<el-button @click="handleCloseDialog">取消</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
@@ -220,17 +429,17 @@ const deptOptions = ref<OptionType[]>([]);
// 表格列配置
const tableColumns = ref([
{ prop: 'selection', label: '选择框', show: true },
{ prop: 'index', label: '序号', show: true },
{ prop: 'name', label: '部门名称', show: true },
{ prop: 'code', label: '部门编码', show: true },
{ prop: 'order', label: '排序', show: true },
{ prop: 'status', label: '状态', show: true },
{ prop: 'description', label: '描述', show: true },
{ prop: 'created_at', label: '创建时间', show: true },
{ prop: 'updated_at', label: '更新时间', show: true },
{ prop: 'operation', label: '操作', show: true }
])
{ prop: "selection", label: "选择框", show: true },
{ prop: "index", label: "序号", show: true },
{ prop: "name", label: "部门名称", show: true },
{ prop: "code", label: "部门编码", show: true },
{ prop: "order", label: "排序", show: true },
{ prop: "status", label: "状态", show: true },
{ prop: "description", label: "描述", show: true },
{ prop: "created_at", label: "创建时间", show: true },
{ prop: "updated_at", label: "更新时间", show: true },
{ prop: "operation", label: "操作", show: true },
]);
// 详情表单
const detailFormData = ref<DeptTable>({});
@@ -252,13 +461,13 @@ const formData = reactive<DeptForm>({
parent_id: undefined,
status: true,
description: undefined,
})
});
// 弹窗状态
const dialogVisible = reactive({
title: "",
visible: false,
type: 'create' as 'create' | 'update' | 'detail',
type: "create" as "create" | "update" | "detail",
});
// 表单验证规则
@@ -269,7 +478,7 @@ const rules = reactive({
});
// 列表刷新
async function handleRefresh () {
async function handleRefresh() {
loading.value = true;
try {
const response = await DeptAPI.getDeptList(queryFormData);
@@ -279,7 +488,7 @@ async function handleRefresh () {
} finally {
loading.value = false;
}
};
}
// 加载表格数据
async function loadingData() {
@@ -289,11 +498,9 @@ async function loadingData() {
pageTableData.value = response.data.data;
// 加载部门选项
deptOptions.value = formatTree(response.data.data);
}
catch (error: any) {
} catch (error: any) {
console.error(error);
}
finally {
} finally {
loading.value = false;
}
}
@@ -321,7 +528,7 @@ const initialFormData: DeptForm = {
parent_id: undefined,
status: true,
description: undefined,
}
};
// 日期范围临时变量
const dateRange = ref<[Date, Date] | []>([]);
@@ -368,14 +575,18 @@ async function handleCloseDialog() {
}
// 打开系统配置弹窗
async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: number, parentId?: number) {
async function handleOpenDialog(
type: "create" | "update" | "detail",
id?: number,
parentId?: number
) {
dialogVisible.type = type;
if (id) {
const response = await DeptAPI.getDeptDetail(id);
if (type === 'detail') {
if (type === "detail") {
dialogVisible.title = "部门详情";
Object.assign(detailFormData.value, response.data.data);
} else if (type === 'update') {
} else if (type === "update") {
dialogVisible.title = "修改部门";
Object.assign(formData, response.data.data);
}
@@ -400,9 +611,9 @@ async function handleSubmit() {
const id = formData.id;
try {
if (id) {
await DeptAPI.updateDept(id, { id, ...formData })
await DeptAPI.updateDept(id, { id, ...formData });
} else {
await DeptAPI.createDept(formData)
await DeptAPI.createDept(formData);
}
dialogVisible.visible = false;
resetForm();
@@ -425,41 +636,45 @@ async function handleDelete(ids: number[]) {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await DeptAPI.deleteDept(ids);
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
}
// 批量启用/停用
async function handleMoreClick(status: boolean) {
if (selectIds.value.length) {
ElMessageBox.confirm(`确认${status ? '启用' : '停用'}该项数据?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
})
.then(async () => {
try {
loading.value = true;
await DeptAPI.batchAvailableDept({ ids: selectIds.value, status });
await DeptAPI.deleteDept(ids);
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}).catch(() => {
})
.catch(() => {
ElMessageBox.close();
});
}
// 批量启用/停用
async function handleMoreClick(status: boolean) {
if (selectIds.value.length) {
ElMessageBox.confirm(`确认${status ? "启用" : "停用"}该项数据?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
try {
loading.value = true;
await DeptAPI.batchAvailableDept({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
})
.catch(() => {
ElMessageBox.close();
});
}
}
@@ -1,31 +1,42 @@
<!-- 字典数据 -->
<template>
<el-drawer v-model="drawerVisible" :title="'【' + props.dictLabel + '】字典数据'" :size="drawerSize">
<el-drawer
v-model="drawerVisible"
:title="'【' + props.dictLabel + '】字典数据'"
:size="drawerSize"
>
<!-- 搜索区域 -->
<div class="search-container">
<el-form ref="queryFormRef" :model="queryFormData" :inline="true" label-suffix=":" @submit.prevent="handleQuery">
<el-form
ref="queryFormRef"
:model="queryFormData"
:inline="true"
label-suffix=":"
@submit.prevent="handleQuery"
>
<el-form-item prop="dict_label" label="字典数据标签">
<el-input v-model="queryFormData.dict_label" placeholder="请输入字典标签" clearable />
</el-form-item>
<el-form-item prop="status" label="状态">
<el-select v-model="queryFormData.status" placeholder="请选择状态" style="width: 167.5px" clearable>
<el-select
v-model="queryFormData.status"
placeholder="请选择状态"
style="width: 167.5px"
clearable
>
<el-option value="true" label="启用" />
<el-option value="false" label="停用" />
</el-select>
</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 v-if="isExpand" prop="creator" label="创建人">
<UserTableSelect
v-model="queryFormData.creator"
@confirm-click="handleConfirm"
@clear-click="handleQuery"
v-model="queryFormData.creator"
@confirm-click="handleConfirm"
@clear-click="handleQuery"
/>
</el-form-item>
<!-- 查询、重置、展开/收起按钮 -->
@@ -68,18 +79,39 @@
<div class="data-table__toolbar--left">
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPerm="['module_system:dict_data:create']" type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
<el-button
v-hasPerm="['module_system:dict_data:create']"
type="success"
icon="plus"
@click="handleOpenDialog('create')"
>
新增
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPerm="['module_system:dict_data:delete']" type="danger" icon="delete" :disabled="selectIds.length === 0" @click="handleDelete(selectIds)">批量删除</el-button>
<el-button
v-hasPerm="['module_system:dict_data:delete']"
type="danger"
icon="delete"
:disabled="selectIds.length === 0"
@click="handleDelete(selectIds)"
>
批量删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-dropdown v-hasPerm="['module_system:dict_data:patch']" trigger="click">
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">更多</el-button>
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">
更多
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">批量启用</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">批量停用</el-dropdown-item>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">
批量启用
</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">
批量停用
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -90,7 +122,13 @@
<el-row :gutter="10">
<el-col :span="1.5">
<el-tooltip content="导出">
<el-button v-hasPerm="['module_system:dict_data:export']" type="warning" icon="download" circle @click="handleOpenExportsModal"/>
<el-button
v-hasPerm="['module_system:dict_data:export']"
type="warning"
icon="download"
circle
@click="handleOpenExportsModal"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
@@ -103,7 +141,17 @@
</div>
<!-- 表格区域:系统配置列表 -->
<el-table ref="dataTableRef" v-loading="loading" :data="pageTableData" highlight-current-row class="data-table__content" height="460" border stripe @selection-change="handleSelectionChange">
<el-table
ref="dataTableRef"
v-loading="loading"
:data="pageTableData"
highlight-current-row
class="data-table__content"
height="460"
border
stripe
@selection-change="handleSelectionChange"
>
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
@@ -128,7 +176,12 @@
</el-table-column>
<el-table-column label="值" prop="dict_value" min-width="100" show-overflow-tooltip />
<el-table-column label="样式属性" prop="css_class" min-width="100" show-overflow-tooltip />
<el-table-column label="列表类样式" prop="list_class" min-width="100" show-overflow-tooltip />
<el-table-column
label="列表类样式"
prop="list_class"
min-width="100"
show-overflow-tooltip
/>
<el-table-column label="排序" prop="dict_sort" min-width="60" />
<el-table-column label="是否默认" prop="is_default" min-width="100">
<template #default="scope">
@@ -136,7 +189,7 @@
<el-tag v-else type="danger">否</el-tag>
</template>
</el-table-column>
<el-table-column label="描述" prop="description" min-width="100" show-overflow-tooltip/>
<el-table-column label="描述" prop="description" min-width="100" show-overflow-tooltip />
<el-table-column label="创建时间" prop="created_at" min-width="200" sortable />
<el-table-column label="更新时间" prop="updated_at" min-width="200" sortable />
<el-table-column label="创建人" prop="creator" min-width="100">
@@ -146,29 +199,74 @@
</el-table-column>
<el-table-column fixed="right" label="操作" align="center" min-width="200">
<template #default="scope">
<el-button type="info" size="small" link icon="document" @click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
<el-button v-hasPerm="['module_system:dict_data:update']" type="primary" size="small" link icon="edit" @click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
<el-button v-hasPerm="['module_system:dict_data:delete']" type="danger" size="small" link icon="delete" @click="handleDelete([scope.row.id])">删除</el-button>
<el-button
type="info"
size="small"
link
icon="document"
@click="handleOpenDialog('detail', scope.row.id)"
>
详情
</el-button>
<el-button
v-hasPerm="['module_system:dict_data:update']"
type="primary"
size="small"
link
icon="edit"
@click="handleOpenDialog('update', scope.row.id)"
>
编辑
</el-button>
<el-button
v-hasPerm="['module_system:dict_data:delete']"
type="danger"
size="small"
link
icon="delete"
@click="handleDelete([scope.row.id])"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<template #footer>
<pagination v-model:total="total" v-model:page="queryFormData.page_no" v-model:limit="queryFormData.page_size" @pagination="loadingData" />
<pagination
v-model:total="total"
v-model:page="queryFormData.page_no"
v-model:limit="queryFormData.page_size"
@pagination="loadingData"
/>
</template>
</el-card>
<!-- 弹窗区域 -->
<el-dialog v-model="dialogVisible.visible" :title="dialogVisible.title" @close="handleCloseDialog">
<el-dialog
v-model="dialogVisible.visible"
:title="dialogVisible.title"
@close="handleCloseDialog"
>
<!-- 详情 -->
<template v-if="dialogVisible.type === 'detail'">
<el-descriptions :column="4" border>
<el-descriptions-item label="数据标签" :span="2">{{ detailFormData.dict_label }}</el-descriptions-item>
<el-descriptions-item label="数据类型" :span="2">{{ detailFormData.dict_type }}</el-descriptions-item>
<el-descriptions-item label="数据值" :span="2">{{ detailFormData.dict_value }}</el-descriptions-item>
<el-descriptions-item label="样式属性" :span="2">{{ detailFormData.css_class }}</el-descriptions-item>
<el-descriptions-item label="列表样式属性" :span="2">{{ detailFormData.list_class }}</el-descriptions-item>
<el-descriptions-item label="数据标签" :span="2">
{{ detailFormData.dict_label }}
</el-descriptions-item>
<el-descriptions-item label="数据类型" :span="2">
{{ detailFormData.dict_type }}
</el-descriptions-item>
<el-descriptions-item label="数据值" :span="2">
{{ detailFormData.dict_value }}
</el-descriptions-item>
<el-descriptions-item label="样式属性" :span="2">
{{ detailFormData.css_class }}
</el-descriptions-item>
<el-descriptions-item label="列表样式属性" :span="2">
{{ detailFormData.list_class }}
</el-descriptions-item>
<el-descriptions-item label="是否默认" :span="2">
<el-tag v-if="detailFormData.is_default" type="success">是</el-tag>
<el-tag v-else type="danger">否</el-tag>
@@ -177,18 +275,40 @@
<el-tag v-if="detailFormData.status" type="success">启用</el-tag>
<el-tag v-else type="danger">停用</el-tag>
</el-descriptions-item>
<el-descriptions-item label="排序" :span="2">{{ detailFormData.dict_sort }}</el-descriptions-item>
<el-descriptions-item label="描述" :span="2">{{ detailFormData.description }}</el-descriptions-item>
<el-descriptions-item label="创建人" :span="2">{{ detailFormData.creator?.name }}</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">{{ detailFormData.created_at }}</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">{{ detailFormData.updated_at }}</el-descriptions-item>
<el-descriptions-item label="排序" :span="2">
{{ detailFormData.dict_sort }}
</el-descriptions-item>
<el-descriptions-item label="描述" :span="2">
{{ detailFormData.description }}
</el-descriptions-item>
<el-descriptions-item label="创建人" :span="2">
{{ detailFormData.creator?.name }}
</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">
{{ detailFormData.created_at }}
</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">
{{ detailFormData.updated_at }}
</el-descriptions-item>
</el-descriptions>
</template>
<!-- 新增、编辑表单 -->
<template v-else>
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-suffix=":" label-width="auto" label-position="right">
<el-form
ref="dataFormRef"
:model="formData"
:rules="rules"
label-suffix=":"
label-width="auto"
label-position="right"
>
<el-form-item label="数据类型" prop="dict_type">
<el-input v-model="formData.dict_type" placeholder="请输入数据类型" :maxlength="50" :disabled="true" />
<el-input
v-model="formData.dict_type"
placeholder="请输入数据类型"
:maxlength="50"
:disabled="true"
/>
</el-form-item>
<el-form-item label="数据标签" prop="dict_label">
<el-input v-model="formData.dict_label" placeholder="请输入数据标签" :maxlength="50" />
@@ -197,20 +317,27 @@
<el-input v-model="formData.dict_value" placeholder="请输入数据值" :maxlength="50" />
</el-form-item>
<el-form-item label="样式属性" prop="css_class">
<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
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">
@@ -233,11 +360,23 @@
<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">
<el-input v-model="formData.description" :rows="4" :maxlength="100" show-word-limit type="textarea" placeholder="请输入描述" />
<el-input
v-model="formData.description"
:rows="4"
:maxlength="100"
show-word-limit
type="textarea"
placeholder="请输入描述"
/>
</el-form-item>
</el-form>
</template>
@@ -246,12 +385,13 @@
<div class="dialog-footer">
<!-- 详情弹窗不需要确定按钮的提交逻辑 -->
<el-button @click="handleCloseDialog">取消</el-button>
<el-button v-if="dialogVisible.type !== 'detail'" type="primary" @click="handleSubmit">确定</el-button>
<el-button v-if="dialogVisible.type !== 'detail'" type="primary" @click="handleSubmit">
确定
</el-button>
<el-button v-else type="primary" @click="handleCloseDialog">确定</el-button>
</div>
</template>
</el-dialog>
</el-drawer>
<ExportModal
v-model="exportsDialogVisible"
@@ -267,15 +407,15 @@
const props = defineProps({
dictType: {
type: String,
required: true
required: true,
},
dictLabel: {
type: String,
required: true
}
})
required: true,
},
});
const drawerVisible = defineModel<boolean>()
const drawerVisible = defineModel<boolean>();
import DictAPI, { DictDataTable, DictDataForm, DictDataPageQuery } from "@/api/module_system/dict";
import { useAppStore } from "@/store/modules/app.store";
import { DeviceEnum } from "@/enums/settings/device.enum";
@@ -323,21 +463,21 @@ const queryFormData = reactive<DictDataPageQuery>({
const formData = reactive<DictDataForm>({
id: undefined,
dict_sort: 1,
dict_label: '',
dict_value: '',
dict_type: '',
css_class: '',
dict_label: "",
dict_value: "",
dict_type: "",
css_class: "",
list_class: undefined,
is_default: false,
status: true,
description: ''
})
description: "",
});
// 弹窗状态
const dialogVisible = reactive({
title: "",
visible: false,
type: 'create' as 'create' | 'update' | 'detail',
type: "create" as "create" | "update" | "detail",
});
// 表单验证规则
@@ -368,9 +508,9 @@ function handleDateRangeChange(range: [Date, Date]) {
}
// 列表刷新
async function handleRefresh () {
async function handleRefresh() {
await loadingData();
};
}
// 加载表格数据
async function loadingData() {
@@ -380,11 +520,9 @@ async function loadingData() {
const response = await DictAPI.getDictDataList(queryFormData);
pageTableData.value = response.data.data.items;
total.value = response.data.data.total;
}
catch (error: any) {
} catch (error: any) {
console.error(error);
}
finally {
} finally {
loading.value = false;
}
}
@@ -415,15 +553,15 @@ async function handleResetQuery() {
const initialFormData: DictDataForm = {
id: undefined,
dict_sort: 1,
dict_label: '',
dict_value: '',
dict_type: '',
css_class: '',
dict_label: "",
dict_value: "",
dict_type: "",
css_class: "",
list_class: undefined,
is_default: false,
status: true,
description: ''
}
description: "",
};
// 重置表单
async function resetForm() {
@@ -448,14 +586,14 @@ async function handleCloseDialog() {
}
// 打开系统配置弹窗
async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: number) {
async function handleOpenDialog(type: "create" | "update" | "detail", id?: number) {
dialogVisible.type = type;
if (id) {
const response = await DictAPI.getDictDataDetail(id);
if (type === 'detail') {
if (type === "detail") {
dialogVisible.title = "字典数据详情";
Object.assign(detailFormData.value, response.data.data);
} else if (type === 'update') {
} else if (type === "update") {
dialogVisible.title = "修改字典数据";
Object.assign(formData, response.data.data);
}
@@ -480,7 +618,7 @@ async function handleSubmit() {
const id = formData.id;
if (id) {
try {
await DictAPI.updateDictData(id, { id, ...formData })
await DictAPI.updateDictData(id, { id, ...formData });
dialogVisible.visible = false;
resetForm();
handleResetQuery();
@@ -491,7 +629,7 @@ async function handleSubmit() {
}
} else {
try {
await DictAPI.createDictData(formData)
await DictAPI.createDictData(formData);
dialogVisible.visible = false;
resetForm();
handleResetQuery();
@@ -511,19 +649,21 @@ async function handleDelete(ids: number[]) {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await DictAPI.deleteDictData(ids);
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
})
.then(async () => {
try {
loading.value = true;
await DictAPI.deleteDictData(ids);
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
})
.catch(() => {
ElMessageBox.close();
});
}
// 打开导出弹窗
@@ -534,48 +674,50 @@ function handleOpenExportsModal() {
// 批量启用/停用
async function handleMoreClick(status: boolean) {
if (selectIds.value.length) {
ElMessageBox.confirm(`确认${status ? '启用' : '停用'}该项数据?`, "警告", {
ElMessageBox.confirm(`确认${status ? "启用" : "停用"}该项数据?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await DictAPI.batchAvailableDictData({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
})
.then(async () => {
try {
loading.value = true;
await DictAPI.batchAvailableDictData({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
})
.catch(() => {
ElMessageBox.close();
});
}
}
// 导出字段
const exportColumns = [
{ prop: 'dict_label', label: '数据标签' },
{ prop: 'dict_type', label: '数据类型' },
{ prop: 'dict_value', label: '数据值' },
{ prop: 'css_class', label: '样式属性' },
{ prop: 'list_class', label: '列表类样式' },
{ prop: 'dict_sort', label: '排序' },
{ prop: 'is_default', label: '是否默认' },
{ prop: 'status', label: '状态' },
{ prop: 'description', label: '描述' },
{ prop: 'created_at', label: '创建时间' },
{ prop: 'updated_at', label: '更新时间' },
{ prop: "dict_label", label: "数据标签" },
{ prop: "dict_type", label: "数据类型" },
{ prop: "dict_value", label: "数据值" },
{ prop: "css_class", label: "样式属性" },
{ prop: "list_class", label: "列表类样式" },
{ prop: "dict_sort", label: "排序" },
{ prop: "is_default", label: "是否默认" },
{ prop: "status", label: "状态" },
{ prop: "description", label: "描述" },
{ prop: "created_at", label: "创建时间" },
{ prop: "updated_at", label: "更新时间" },
];
// 导出配置(用于导出弹窗)
const curdContentConfig = {
permPrefix: 'module_system:dict_data',
permPrefix: "module_system:dict_data",
cols: exportColumns as any,
exportsAction: async (params: any) => {
const query: any = { ...params };
if (typeof query.status === 'string') query.status = query.status === 'true';
if (typeof query.status === "string") query.status = query.status === "true";
// dict_type 已在查询表单中设置为 props.dictType
query.page_no = 1;
query.page_size = 1000;
+325 -114
View File
@@ -3,7 +3,13 @@
<div class="app-container">
<!-- 搜索区域 -->
<div class="search-container">
<el-form ref="queryFormRef" :model="queryFormData" :inline="true" label-suffix=":" @submit.prevent="handleQuery" >
<el-form
ref="queryFormRef"
:model="queryFormData"
:inline="true"
label-suffix=":"
@submit.prevent="handleQuery"
>
<el-form-item prop="dict_name" label="字典名称">
<el-input v-model="queryFormData.dict_name" placeholder="请输入字典名称" clearable />
</el-form-item>
@@ -11,29 +17,44 @@
<el-input v-model="queryFormData.dict_type" placeholder="请输入字典类型" clearable />
</el-form-item>
<el-form-item prop="status" label="状态">
<el-select v-model="queryFormData.status" placeholder="请选择状态" style="width: 167.5px" clearable>
<el-select
v-model="queryFormData.status"
placeholder="请选择状态"
style="width: 167.5px"
clearable
>
<el-option value="true" label="启用" />
<el-option value="false" label="停用" />
</el-select>
</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 v-if="isExpand" prop="creator" label="创建人">
<UserTableSelect
v-model="queryFormData.creator"
@confirm-click="handleConfirm"
@clear-click="handleQuery"
v-model="queryFormData.creator"
@confirm-click="handleConfirm"
@clear-click="handleQuery"
/>
</el-form-item>
<!-- 查询、重置、展开/收起按钮 -->
<el-form-item class="search-buttons">
<el-button v-hasPerm="['module_system:dict_type:query']" type="primary" icon="search" native-type="submit">查询</el-button>
<el-button v-hasPerm="['module_system:dict_type:query']" icon="refresh" @click="handleResetQuery">重置</el-button>
<el-button
v-hasPerm="['module_system:dict_type:query']"
type="primary"
icon="search"
native-type="submit"
>
查询
</el-button>
<el-button
v-hasPerm="['module_system:dict_type:query']"
icon="refresh"
@click="handleResetQuery"
>
重置
</el-button>
<!-- 展开/收起 -->
<template v-if="isExpandable">
<el-link class="ml-3" type="primary" underline="never" @click="isExpand = !isExpand">
@@ -70,10 +91,25 @@
<div class="data-table__toolbar--left">
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPerm="['module_system:dict_type:create']" type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
<el-button
v-hasPerm="['module_system:dict_type:create']"
type="success"
icon="plus"
@click="handleOpenDialog('create')"
>
新增
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPerm="['module_system:dict_type:delete']" type="danger" icon="delete" :disabled="selectIds.length === 0" @click="handleDelete(selectIds)">批量删除</el-button>
<el-button
v-hasPerm="['module_system:dict_type:delete']"
type="danger"
icon="delete"
:disabled="selectIds.length === 0"
@click="handleDelete(selectIds)"
>
批量删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-dropdown v-hasPerm="['module_system:dict_type:patch']" trigger="click">
@@ -82,8 +118,12 @@
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">批量启用</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">批量停用</el-dropdown-item>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">
批量启用
</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">
批量停用
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -94,12 +134,24 @@
<el-row :gutter="10">
<el-col :span="1.5">
<el-tooltip content="导出">
<el-button v-hasPerm="['module_system:dict_type:export']" type="warning" icon="download" circle @click="handleOpenExportsModal"/>
<el-button
v-hasPerm="['module_system:dict_type:export']"
type="warning"
icon="download"
circle
@click="handleOpenExportsModal"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-tooltip content="刷新">
<el-button v-hasPerm="['module_system:dict_type:refresh']" type="primary" icon="refresh" circle @click="handleRefresh"/>
<el-button
v-hasPerm="['module_system:dict_type:refresh']"
type="primary"
icon="refresh"
circle
@click="handleRefresh"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
@@ -119,59 +171,178 @@
</div>
<!-- 表格区域:系统配置列表 -->
<el-table ref="dataTableRef" v-loading="loading" :data="pageTableData" highlight-current-row class="data-table__content" :height="450" border stripe @selection-change="handleSelectionChange">
<el-table
ref="dataTableRef"
v-loading="loading"
:data="pageTableData"
highlight-current-row
class="data-table__content"
:height="450"
border
stripe
@selection-change="handleSelectionChange"
>
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection" min-width="55" align="center" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" type="index" fixed label="序号" min-width="60">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'selection')?.show"
type="selection"
min-width="55"
align="center"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'index')?.show"
type="index"
fixed
label="序号"
min-width="60"
>
<template #default="scope">
{{ (queryFormData.page_no - 1) * queryFormData.page_size + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'dict_name')?.show" key="dict_name" label="字典名称" prop="dict_name" min-width="140" show-overflow-tooltip/>
<el-table-column v-if="tableColumns.find(col => col.prop === 'dict_type')?.show" key="dict_type" label="字典类型" prop="dict_type" min-width="180">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'dict_name')?.show"
key="dict_name"
label="字典名称"
prop="dict_name"
min-width="140"
show-overflow-tooltip
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'dict_type')?.show"
key="dict_type"
label="字典类型"
prop="dict_type"
min-width="180"
>
<template #default="scope">
<el-tag type="primary">{{ scope.row.dict_type }}</el-tag>
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'status')?.show" key="status" label="状态" prop="status" min-width="80">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'status')?.show"
key="status"
label="状态"
prop="status"
min-width="80"
>
<template #default="scope">
<el-tag :type="scope.row.status === true ? 'success' : 'danger'">
{{ scope.row.status ? "启用" : "停用" }}
</el-tag>
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'description')?.show" key="description" label="描述" prop="description" min-width="140" show-overflow-tooltip/>
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" key="created_at" label="创建时间" prop="created_at" min-width="200" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'updated_at')?.show" key="updated_at" label="更新时间" prop="updated_at" min-width="200" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'creator')?.show" label="创建人" prop="creator" min-width="120">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'description')?.show"
key="description"
label="描述"
prop="description"
min-width="140"
show-overflow-tooltip
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'created_at')?.show"
key="created_at"
label="创建时间"
prop="created_at"
min-width="200"
sortable
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'updated_at')?.show"
key="updated_at"
label="更新时间"
prop="updated_at"
min-width="200"
sortable
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'creator')?.show"
label="创建人"
prop="creator"
min-width="120"
>
<template #default="scope">
{{ scope.row.creator?.name }}
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right" label="操作" align="center" min-width="260">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'operation')?.show"
fixed="right"
label="操作"
align="center"
min-width="260"
>
<template #default="scope">
<el-button v-hasPerm="['module_system:dict_data:query']" type="warning" size="small" link icon="document" @click="handleDictDataDrawer(scope.row.dict_type, scope.row.dict_name)">字典</el-button>
<el-button v-hasPerm="['module_system:dict_type:detail']" type="info" size="small" link icon="document" @click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
<el-button v-hasPerm="['module_system:dict_type:update']" type="primary" size="small" link icon="edit" @click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
<el-button v-hasPerm="['module_system:dict_type:delete']" type="danger" size="small" link icon="delete" @click="handleDelete([scope.row.id])">删除</el-button>
<el-button
v-hasPerm="['module_system:dict_data:query']"
type="warning"
size="small"
link
icon="document"
@click="handleDictDataDrawer(scope.row.dict_type, scope.row.dict_name)"
>
字典
</el-button>
<el-button
v-hasPerm="['module_system:dict_type:detail']"
type="info"
size="small"
link
icon="document"
@click="handleOpenDialog('detail', scope.row.id)"
>
详情
</el-button>
<el-button
v-hasPerm="['module_system:dict_type:update']"
type="primary"
size="small"
link
icon="edit"
@click="handleOpenDialog('update', scope.row.id)"
>
编辑
</el-button>
<el-button
v-hasPerm="['module_system:dict_type:delete']"
type="danger"
size="small"
link
icon="delete"
@click="handleDelete([scope.row.id])"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<template #footer>
<pagination v-model:total="total" v-model:page="queryFormData.page_no" v-model:limit="queryFormData.page_size" @pagination="loadingData" />
<pagination
v-model:total="total"
v-model:page="queryFormData.page_no"
v-model:limit="queryFormData.page_size"
@pagination="loadingData"
/>
</template>
</el-card>
<!-- 弹窗区域 -->
<el-dialog v-model="dialogVisible.visible" :title="dialogVisible.title" @close="handleCloseDialog">
<el-dialog
v-model="dialogVisible.visible"
:title="dialogVisible.title"
@close="handleCloseDialog"
>
<!-- 详情 -->
<template v-if="dialogVisible.type === 'detail'">
<el-descriptions :column="4" border>
<el-descriptions-item label="字典名称" :span="2">{{ detailFormData.dict_name }}</el-descriptions-item>
<el-descriptions-item label="字典名称" :span="2">
{{ detailFormData.dict_name }}
</el-descriptions-item>
<el-descriptions-item label="字典类型" :span="2">
<el-tag type="primary">{{ detailFormData.dict_type }}</el-tag>
</el-descriptions-item>
@@ -179,15 +350,30 @@
<el-tag v-if="detailFormData.status" type="success">启用</el-tag>
<el-tag v-else type="danger">停用</el-tag>
</el-descriptions-item>
<el-descriptions-item label="描述" :span="2">{{ detailFormData.description }}</el-descriptions-item>
<el-descriptions-item label="创建人" :span="2">{{ detailFormData.creator?.name }}</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">{{ detailFormData.created_at }}</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">{{ detailFormData.updated_at }}</el-descriptions-item>
<el-descriptions-item label="描述" :span="2">
{{ detailFormData.description }}
</el-descriptions-item>
<el-descriptions-item label="创建人" :span="2">
{{ detailFormData.creator?.name }}
</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">
{{ detailFormData.created_at }}
</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">
{{ detailFormData.updated_at }}
</el-descriptions-item>
</el-descriptions>
</template>
<!-- 新增、编辑表单 -->
<template v-else>
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-suffix=":" label-width="auto" label-position="right">
<el-form
ref="dataFormRef"
:model="formData"
:rules="rules"
label-suffix=":"
label-width="auto"
label-position="right"
>
<el-form-item label="字典名称" prop="dict_name">
<el-input v-model="formData.dict_name" placeholder="请输入字典名称" :maxlength="50" />
</el-form-item>
@@ -201,7 +387,14 @@
</el-radio-group>
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="formData.description" :rows="4" :maxlength="100" show-word-limit type="textarea" placeholder="请输入描述" />
<el-input
v-model="formData.description"
:rows="4"
:maxlength="100"
show-word-limit
type="textarea"
placeholder="请输入描述"
/>
</el-form-item>
</el-form>
</template>
@@ -210,13 +403,32 @@
<div class="dialog-footer">
<!-- 详情弹窗不需要确定按钮的提交逻辑 -->
<el-button @click="handleCloseDialog">取消</el-button>
<el-button v-if="dialogVisible.type !== 'detail'" v-hasPerm="['module_system:dict_type:submit']" type="primary" @click="handleSubmit">确定</el-button>
<el-button v-else v-hasPerm="['module_system:dict_type:detail']" type="primary" @click="handleCloseDialog">确定</el-button>
<el-button
v-if="dialogVisible.type !== 'detail'"
v-hasPerm="['module_system:dict_type:submit']"
type="primary"
@click="handleSubmit"
>
确定
</el-button>
<el-button
v-else
v-hasPerm="['module_system:dict_type:detail']"
type="primary"
@click="handleCloseDialog"
>
确定
</el-button>
</div>
</template>
</el-dialog>
<DataDrawer v-if="drawerVisible" v-model="drawerVisible" :dict-type="currentDictType" :dict-label="currentDictLabel" />
<DataDrawer
v-if="drawerVisible"
v-model="drawerVisible"
:dict-type="currentDictType"
:dict-label="currentDictLabel"
/>
<ExportModal
v-model="exportsDialogVisible"
:content-config="curdContentConfig"
@@ -224,7 +436,6 @@
:page-data="pageTableData"
:selection-data="selectionRows"
/>
</div>
</template>
@@ -235,7 +446,7 @@ defineOptions({
});
import DictAPI, { DictTable, DictForm, DictPageQuery } from "@/api/module_system/dict";
import DataDrawer from "@/views/module_system/dict/components/DataDrawer.vue"
import DataDrawer from "@/views/module_system/dict/components/DataDrawer.vue";
import UserTableSelect from "@/views/module_system/user/components/UserTableSelect.vue";
import ExportModal from "@/components/CURD/ExportModal.vue";
import type { IContentConfig } from "@/components/CURD/types";
@@ -257,20 +468,19 @@ const pageTableData = ref<DictTable[]>([]);
const exportsDialogVisible = ref(false);
const selectionRows = ref<DictTable[]>([]);
// 表格列配置
const tableColumns = ref([
{ prop: 'selection', label: '选择框', show: true },
{ prop: 'index', label: '序号', show: true },
{ prop: 'dict_name', label: '字典名称', show: true },
{ prop: 'dict_type', label: '字典类型', show: true },
{ prop: 'status', label: '状态', show: true },
{ prop: 'description', label: '描述', show: true },
{ prop: 'creator', label: '创建人', show: true },
{ prop: 'created_at', label: '创建时间', show: true },
{ prop: 'updated_at', label: '更新时间', show: true },
{ prop: 'operation', label: '操作', show: true }
])
{ prop: "selection", label: "选择框", show: true },
{ prop: "index", label: "序号", show: true },
{ prop: "dict_name", label: "字典名称", show: true },
{ prop: "dict_type", label: "字典类型", show: true },
{ prop: "status", label: "状态", show: true },
{ prop: "description", label: "描述", show: true },
{ prop: "creator", label: "创建人", show: true },
{ prop: "created_at", label: "创建时间", show: true },
{ prop: "updated_at", label: "更新时间", show: true },
{ prop: "operation", label: "操作", show: true },
]);
// 详情表单
const detailFormData = ref<DictTable>({});
@@ -291,17 +501,17 @@ const queryFormData = reactive<DictPageQuery>({
// 编辑表单
const formData = reactive<DictForm>({
id: undefined,
dict_name: '',
dict_type: '',
dict_name: "",
dict_type: "",
status: true,
description: undefined,
})
});
// 弹窗状态
const dialogVisible = reactive({
title: "",
visible: false,
type: 'create' as 'create' | 'update' | 'detail',
type: "create" as "create" | "update" | "detail",
});
// 表单验证规则
@@ -327,19 +537,18 @@ function handleDateRangeChange(range: [Date, Date]) {
}
// 列表刷新
async function handleRefresh () {
async function handleRefresh() {
await loadingData();
};
}
// 抽屉显隐
const drawerVisible = ref(false);
// 添加字典类型变量
const currentDictType = ref('');
const currentDictType = ref("");
// 添加字典名称变量
const currentDictLabel = ref('');
const currentDictLabel = ref("");
// 加载表格数据
async function loadingData() {
@@ -348,11 +557,9 @@ async function loadingData() {
const response = await DictAPI.getDictTypeList(queryFormData);
pageTableData.value = response.data.data.items;
total.value = response.data.data.total;
}
catch (error: any) {
} catch (error: any) {
console.error(error);
}
finally {
} finally {
loading.value = false;
}
}
@@ -382,11 +589,11 @@ async function handleResetQuery() {
// 定义初始表单数据常量
const initialFormData: DictForm = {
id: undefined,
dict_name: '',
dict_type: '',
dict_name: "",
dict_type: "",
status: true,
description: undefined,
}
};
// 重置表单
async function resetForm() {
@@ -411,14 +618,14 @@ async function handleCloseDialog() {
}
// 打开系统配置弹窗
async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: number) {
async function handleOpenDialog(type: "create" | "update" | "detail", id?: number) {
dialogVisible.type = type;
if (id) {
const response = await DictAPI.getDictTypeDetail(id);
if (type === 'detail') {
if (type === "detail") {
dialogVisible.title = "字典详情";
Object.assign(detailFormData.value, response.data.data);
} else if (type === 'update') {
} else if (type === "update") {
dialogVisible.title = "修改字典";
Object.assign(formData, response.data.data);
}
@@ -439,7 +646,7 @@ async function handleSubmit() {
const id = formData.id;
if (id) {
try {
await DictAPI.updateDictType(id, { id, ...formData })
await DictAPI.updateDictType(id, { id, ...formData });
dialogVisible.visible = false;
resetForm();
handleResetQuery();
@@ -450,7 +657,7 @@ async function handleSubmit() {
}
} else {
try {
await DictAPI.createDictType(formData)
await DictAPI.createDictType(formData);
dialogVisible.visible = false;
resetForm();
handleResetQuery();
@@ -470,19 +677,21 @@ async function handleDelete(ids: number[]) {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await DictAPI.deleteDictType(ids);
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
})
.then(async () => {
try {
loading.value = true;
await DictAPI.deleteDictType(ids);
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
})
.catch(() => {
ElMessageBox.close();
});
}
// 打开导出弹窗
@@ -493,23 +702,25 @@ function handleOpenExportsModal() {
// 批量启用/停用
async function handleMoreClick(status: boolean) {
if (selectIds.value.length) {
ElMessageBox.confirm(`确认${status ? '启用' : '停用'}该项数据?`, "警告", {
ElMessageBox.confirm(`确认${status ? "启用" : "停用"}该项数据?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await DictAPI.batchAvailableDict({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
})
.then(async () => {
try {
loading.value = true;
await DictAPI.batchAvailableDict({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
})
.catch(() => {
ElMessageBox.close();
});
}
}
@@ -521,21 +732,21 @@ function handleDictDataDrawer(dictType: string, dictLabel: string) {
// 导出字段
const exportColumns = [
{ prop: 'dict_name', label: '字典名称' },
{ prop: 'dict_type', label: '字典类型' },
{ prop: 'status', label: '状态' },
{ prop: 'description', label: '描述' },
{ prop: 'created_at', label: '创建时间' },
{ prop: 'updated_at', label: '更新时间' },
{ prop: "dict_name", label: "字典名称" },
{ prop: "dict_type", label: "字典类型" },
{ prop: "status", label: "状态" },
{ prop: "description", label: "描述" },
{ prop: "created_at", label: "创建时间" },
{ prop: "updated_at", label: "更新时间" },
];
// 导出配置(用于导出弹窗)
const curdContentConfig = {
permPrefix: 'module_system:dict_type',
permPrefix: "module_system:dict_type",
cols: exportColumns as any,
exportsAction: async (params: any) => {
const query: any = { ...params };
if (typeof query.status === 'string') query.status = query.status === 'true';
if (typeof query.status === "string") query.status = query.status === "true";
query.page_no = 1;
query.page_size = 1000;
const all: any[] = [];
+209 -93
View File
@@ -3,34 +3,55 @@
<div class="app-container">
<!-- 搜索区域 -->
<div class="search-container">
<el-form ref="queryFormRef" :model="queryFormData" :inline="true" label-suffix=":" @submit.prevent="handleQuery" >
<el-form
ref="queryFormRef"
:model="queryFormData"
:inline="true"
label-suffix=":"
@submit.prevent="handleQuery"
>
<el-form-item prop="request_path" label="请求路径">
<el-input v-model="queryFormData.request_path" 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 />
<el-option label="操作日志" value=2 />
<el-select
v-model="queryFormData.type"
placeholder="请选择日志类型"
style="width: 167.5px"
clearable
>
<el-option label="登录日志" value="1" />
<el-option label="操作日志" value="2" />
</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"
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 class="search-buttons">
<el-button v-hasPerm="['module_system:log:query']" type="primary" icon="search" native-type="submit">查询</el-button>
<el-button v-hasPerm="['module_system:log:query']" icon="refresh" @click="handleResetQuery">重置</el-button>
<el-button
v-hasPerm="['module_system:log:query']"
type="primary"
icon="search"
native-type="submit"
>
查询
</el-button>
<el-button
v-hasPerm="['module_system:log:query']"
icon="refresh"
@click="handleResetQuery"
>
重置
</el-button>
<!-- 展开/收起 -->
<template v-if="isExpandable">
<el-link class="ml-3" type="primary" underline="never" @click="isExpand = !isExpand">
@@ -67,7 +88,15 @@
<div class="data-table__toolbar--left">
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPerm="['module_system:log:delete']" type="danger" icon="delete" :disabled="selectIds.length === 0" @click="handleDelete(selectIds)">批量删除</el-button>
<el-button
v-hasPerm="['module_system:log:delete']"
type="danger"
icon="delete"
:disabled="selectIds.length === 0"
@click="handleDelete(selectIds)"
>
批量删除
</el-button>
</el-col>
</el-row>
</div>
@@ -75,12 +104,24 @@
<el-row :gutter="10">
<el-col :span="1.5">
<el-tooltip content="导出">
<el-button v-hasPerm="['module_system:log:export']" type="warning" icon="download" circle @click="handleOpenExportsModal"/>
<el-button
v-hasPerm="['module_system:log:export']"
type="warning"
icon="download"
circle
@click="handleOpenExportsModal"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-tooltip content="刷新">
<el-button v-hasPerm="['module_system:log:refresh']" type="default" icon="refresh" circle @click="handleRefresh"/>
<el-button
v-hasPerm="['module_system:log:refresh']"
type="default"
icon="refresh"
circle
@click="handleRefresh"
/>
</el-tooltip>
</el-col>
</el-row>
@@ -88,12 +129,22 @@
</div>
<!-- 表格区域:系统配置列表 -->
<el-table ref="dataTableRef" v-loading="loading" :data="pageTableData" highlight-current-row class="data-table__content" ::height="450" border stripe @selection-change="handleSelectionChange">
<el-table
ref="dataTableRef"
v-loading="loading"
:data="pageTableData"
highlight-current-row
class="data-table__content"
::height="450"
border
stripe
@selection-change="handleSelectionChange"
>
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column prop='selection' type="selection" min-width="55" align="center" />
<el-table-column type="index" fixed label="序号" min-width="60" >
<el-table-column prop="selection" type="selection" min-width="55" align="center" />
<el-table-column type="index" fixed label="序号" min-width="60">
<template #default="scope">
{{ (queryFormData.page_no - 1) * queryFormData.page_size + scope.$index + 1 }}
</template>
@@ -101,11 +152,16 @@
<el-table-column label="日志类型" prop="type" min-width="100">
<template #default="scope">
<el-tag :type="scope.row.type === 1 ? 'success' : 'primary'">
{{ scope.row.type === 1 ? '登录日志' : '操作日志' }}
{{ scope.row.type === 1 ? "登录日志" : "操作日志" }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="请求路径" prop="request_path" min-width="200" show-overflow-tooltip />
<el-table-column
label="请求路径"
prop="request_path"
min-width="200"
show-overflow-tooltip
/>
<el-table-column label="请求方法" prop="request_method" min-width="100">
<template #default="scope">
<el-tag :type="getMethodType(scope.row.request_method)">
@@ -123,11 +179,20 @@
<el-table-column label="请求IP" prop="request_ip" min-width="180" show-overflow-tooltip>
<template #default="scope">
<el-text>{{ scope.row.request_ip }}</el-text>
<CopyButton v-if="scope.row.request_ip" :text="scope.row.request_ip" style="margin-left: 2px" />
<CopyButton
v-if="scope.row.request_ip"
:text="scope.row.request_ip"
style="margin-left: 2px"
/>
</template>
</el-table-column>
<el-table-column label="处理时间" prop="process_time" min-width="120" />
<el-table-column label="浏览器" prop="request_browser" min-width="220" show-overflow-tooltip/>
<el-table-column
label="浏览器"
prop="request_browser"
min-width="220"
show-overflow-tooltip
/>
<el-table-column label="系统" prop="request_os" min-width="100" />
<el-table-column label="描述" prop="description" min-width="120" show-overflow-tooltip />
<el-table-column label="创建时间" prop="created_at" min-width="200" sortable />
@@ -138,29 +203,58 @@
</el-table-column>
<el-table-column label="操作" fixed="right" align="center" min-width="150">
<template #default="scope">
<el-button v-hasPerm="['module_system:log:detail']" type="info" size="small" link icon="document" @click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
<el-button v-hasPerm="['module_system:log:delete']" type="danger" size="small" link icon="delete" @click="handleDelete([scope.row.id])">删除</el-button>
<el-button
v-hasPerm="['module_system:log:detail']"
type="info"
size="small"
link
icon="document"
@click="handleOpenDialog('detail', scope.row.id)"
>
详情
</el-button>
<el-button
v-hasPerm="['module_system:log:delete']"
type="danger"
size="small"
link
icon="delete"
@click="handleDelete([scope.row.id])"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<template #footer>
<pagination v-model:total="total" v-model:page="queryFormData.page_no" v-model:limit="queryFormData.page_size" @pagination="loadingData" />
<pagination
v-model:total="total"
v-model:page="queryFormData.page_no"
v-model:limit="queryFormData.page_size"
@pagination="loadingData"
/>
</template>
</el-card>
<!-- 弹窗区域 -->
<el-dialog v-model="dialogVisible.visible" :title="dialogVisible.title" @close="handleCloseDialog">
<el-dialog
v-model="dialogVisible.visible"
:title="dialogVisible.title"
@close="handleCloseDialog"
>
<!-- 详情 -->
<template v-if="dialogVisible.type === 'detail'">
<el-descriptions :column="8" border label-width="200px">
<el-descriptions-item label="日志类型" :span="2">
<el-tag :type="formData.type === 1 ? 'success' : 'primary'">
{{ formData.type === 1 ? '登录日志' : '操作日志' }}
{{ formData.type === 1 ? "登录日志" : "操作日志" }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="请求路径" :span="2">{{ formData.request_path }}</el-descriptions-item>
<el-descriptions-item label="请求路径" :span="2">
{{ formData.request_path }}
</el-descriptions-item>
<el-descriptions-item label="请求方法" :span="2">
<el-tag :type="getMethodType(formData.request_method)">
{{ formData.request_method }}
@@ -171,21 +265,39 @@
{{ formData.response_code }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="请求IP" :span="2">{{ formData.request_ip }}</el-descriptions-item>
<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="请求IP" :span="2">
{{ formData.request_ip }}
</el-descriptions-item>
<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="8">
<JsonPretty :value="formData.request_payload" height="80px" />
</el-descriptions-item>
<el-descriptions-item label="响应数据" :span="8">
<JsonPretty :value="formData.response_json" height="140px" />
</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-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>
@@ -193,7 +305,13 @@
<div class="dialog-footer">
<!-- 详情弹窗不需要确定按钮的提交逻辑 -->
<el-button @click="handleCloseDialog">取消</el-button>
<el-button v-hasPerm="['module_system:log:detail']" type="primary" @click="handleCloseDialog">确定</el-button>
<el-button
v-hasPerm="['module_system:log:detail']"
type="primary"
@click="handleCloseDialog"
>
确定
</el-button>
</div>
</template>
</el-dialog>
@@ -206,7 +324,6 @@
:page-data="pageTableData"
:selection-data="selectionRows"
/>
</div>
</template>
@@ -258,7 +375,7 @@ const queryFormData = reactive<LogPageQuery>({
const dialogVisible = reactive({
title: "",
visible: false,
type: 'create' as 'create' | 'update' | 'detail',
type: "create" as "create" | "update" | "detail",
});
// 日期范围临时变量
@@ -277,41 +394,41 @@ function handleDateRangeChange(range: [Date, Date]) {
}
// 列表刷新
async function handleRefresh () {
async function handleRefresh() {
await loadingData();
};
}
const getStatusCodeType = (code?: number) => {
if (code === undefined) {
return 'info';
return "info";
}
if (code >= 200 && code < 300) {
return 'success';
return "success";
} else if (code >= 300 && code < 400) {
return 'warning';
return "warning";
} else if (code >= 400 && code < 500) {
return 'danger';
return "danger";
} else {
return 'danger';
return "danger";
}
}
};
const getMethodType = (method?: string) => {
if (method === undefined) {
return 'info';
return "info";
}
if (method === 'GET') {
return 'info';
} else if (method === 'POST') {
return 'success';
} else if (method === 'PUT' || method === 'PATCH') {
return 'warning';
} else if (method === 'DELETE') {
return 'danger';
if (method === "GET") {
return "info";
} else if (method === "POST") {
return "success";
} else if (method === "PUT" || method === "PATCH") {
return "warning";
} else if (method === "DELETE") {
return "danger";
} else {
return 'info';
return "info";
}
}
};
// 加载表格数据
async function loadingData() {
@@ -320,11 +437,9 @@ async function loadingData() {
const response = await LogAPI.getLogList(queryFormData);
pageTableData.value = response.data.data.items;
total.value = response.data.data.total;
}
catch (error: any) {
} catch (error: any) {
console.error(error);
}
finally {
} finally {
loading.value = false;
}
}
@@ -373,11 +488,11 @@ async function handleCloseDialog() {
}
// 打开日志详情弹窗
async function handleOpenDialog(type: 'create' | 'update' | 'detail', id: number) {
async function handleOpenDialog(type: "create" | "update" | "detail", id: number) {
dialogVisible.type = type;
if (id) {
const response = await LogAPI.getLogDetail(id);
if (type === 'detail') {
if (type === "detail") {
dialogVisible.title = "日志详情";
Object.assign(formData.value, response.data.data);
}
@@ -391,22 +506,23 @@ async function handleDelete(ids: number[]) {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await LogAPI.deleteLog(ids);
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
})
.then(async () => {
try {
loading.value = true;
await LogAPI.deleteLog(ids);
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
})
.catch(() => {
ElMessageBox.close();
});
}
// 打开导出弹窗
function handleOpenExportsModal() {
exportsDialogVisible.value = true;
@@ -414,23 +530,23 @@ function handleOpenExportsModal() {
// 导出字段
const exportColumns = [
{ prop: 'type', label: '日志类型' },
{ prop: 'request_path', label: '请求路径' },
{ prop: 'request_method', label: '请求方法' },
{ prop: 'response_code', label: '状态码' },
{ prop: 'request_ip', label: '请求IP' },
{ prop: 'login_location', label: '登录地点' },
{ prop: 'process_time', label: '处理时间' },
{ prop: 'request_browser', label: '浏览器' },
{ prop: 'request_os', label: '系统' },
{ prop: 'description', label: '描述' },
{ prop: 'created_at', label: '创建时间' },
{ prop: 'updated_at', label: '更新时间' },
{ prop: "type", label: "日志类型" },
{ prop: "request_path", label: "请求路径" },
{ prop: "request_method", label: "请求方法" },
{ prop: "response_code", label: "状态码" },
{ prop: "request_ip", label: "请求IP" },
{ prop: "login_location", label: "登录地点" },
{ prop: "process_time", label: "处理时间" },
{ prop: "request_browser", label: "浏览器" },
{ prop: "request_os", label: "系统" },
{ prop: "description", label: "描述" },
{ prop: "created_at", label: "创建时间" },
{ prop: "updated_at", label: "更新时间" },
];
// 导入/导出配置(用于导出弹窗)
const curdContentConfig = {
permPrefix: 'module_system:log',
permPrefix: "module_system:log",
cols: exportColumns as any,
exportsAction: async (params: any) => {
const query: any = { ...params };
+339 -130
View File
@@ -3,27 +3,48 @@
<div class="app-container">
<!-- 搜索区域 -->
<div class="search-container">
<el-form ref="queryFormRef" :model="queryFormData" :inline="true" label-suffix=":" @submit.prevent="handleQuery" >
<el-form
ref="queryFormRef"
:model="queryFormData"
:inline="true"
label-suffix=":"
@submit.prevent="handleQuery"
>
<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>
<el-select
v-model="queryFormData.status"
placeholder="请选择状态"
style="width: 167.5px"
clearable
>
<el-option value="true" label="启用" />
<el-option value="false" label="停用" />
</el-select>
</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 class="search-buttons">
<el-button v-hasPerm="['module_system:menu:query']" type="primary" icon="search" native-type="submit">查询</el-button>
<el-button v-hasPerm="['module_system:menu:query']" icon="refresh" @click="handleResetQuery">重置</el-button>
<el-button
v-hasPerm="['module_system:menu:query']"
type="primary"
icon="search"
native-type="submit"
>
查询
</el-button>
<el-button
v-hasPerm="['module_system:menu:query']"
icon="refresh"
@click="handleResetQuery"
>
重置
</el-button>
<!-- 展开/收起 -->
<template v-if="isExpandable">
<el-link class="ml-3" type="primary" underline="never" @click="isExpand = !isExpand">
@@ -60,18 +81,39 @@
<div class="data-table__toolbar--left">
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPerm="['module_system:menu:create']" type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
<el-button
v-hasPerm="['module_system:menu:create']"
type="success"
icon="plus"
@click="handleOpenDialog('create')"
>
新增
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPerm="['module_system:menu:delete']" type="danger" icon="delete" :disabled="selectIds.length === 0" @click="handleDelete(selectIds)">批量删除</el-button>
<el-button
v-hasPerm="['module_system:menu:delete']"
type="danger"
icon="delete"
:disabled="selectIds.length === 0"
@click="handleDelete(selectIds)"
>
批量删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-dropdown v-hasPerm="['module_system:menu:patch']" trigger="click">
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">更多</el-button>
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">
更多
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">批量启用</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">批量停用</el-dropdown-item>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">
批量启用
</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">
批量停用
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -82,7 +124,13 @@
<el-row :gutter="10">
<el-col :span="1.5">
<el-tooltip content="刷新">
<el-button v-hasPerm="['module_system:menu:refresh']" type="primary" icon="refresh" circle @click="handleRefresh"/>
<el-button
v-hasPerm="['module_system:menu:refresh']"
type="primary"
icon="refresh"
circle
@click="handleRefresh"
/>
</el-tooltip>
</el-col>
</el-row>
@@ -90,7 +138,19 @@
</div>
<!-- 表格区域 -->
<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">
<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>
@@ -129,36 +189,36 @@
<el-table-column label="是否缓存" prop="keep_alive" min-width="100">
<template #default="scope">
<el-tag :type="scope.row.keep_alive ? 'success' : 'danger'">
{{ scope.row.keep_alive ? '是' : '否' }}
{{ scope.row.keep_alive ? "是" : "否" }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="是否隐藏" prop="hidden" min-width="100">
<template #default="scope">
<el-tag :type="scope.row.hidden ? 'success' : 'danger'">
{{ scope.row.hidden ? '是' : '否' }}
{{ scope.row.hidden ? "是" : "否" }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="显示根路由" prop="always_show" min-width="120">
<template #default="scope">
<el-tag :type="scope.row.always_show ? 'success' : 'danger'">
{{ scope.row.always_show ? '是' : '否' }}
{{ scope.row.always_show ? "是" : "否" }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="固定路由" prop="affix" min-width="100">
<template #default="scope">
<el-tag :type="scope.row.affix ? 'success' : 'danger'">
{{ scope.row.affix ? '是' : '否' }}
{{ scope.row.affix ? "是" : "否" }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="菜单标题" prop="title" min-width="200"/>
<el-table-column label="权限标识" prop="permission" min-width="220" />
<el-table-column label="菜单标题" prop="title" min-width="200" />
<el-table-column label="权限标识" prop="permission" min-width="220" />
<el-table-column label="路由名称" prop="route_name" min-width="200" />
<el-table-column label="路由路径" prop="route_path" min-width="200" />
<el-table-column label="组件路径" prop="component_path" min-width="200" />
<el-table-column label="组件路径" prop="component_path" min-width="200" />
<el-table-column label="路由参数" prop="params" min-width="100" />
<el-table-column label="描述" prop="description" min-width="200" />
<el-table-column label="创建时间" prop="created_at" min-width="200" sortable />
@@ -166,23 +226,68 @@
<el-table-column fixed="right" label="操作" align="center" min-width="260">
<template #default="scope">
<el-button v-if="scope.row.type == MenuTypeEnum.CATALOG || scope.row.type == MenuTypeEnum.MENU" v-hasPerm="['module_system:menu:create']" type="success" link size="small" icon="plus" @click.stop="handleOpenDialog('create', undefined, scope.row.id)">新增</el-button>
<el-button v-hasPerm="['module_system:menu:detail']" type="info" size="small" link icon="document" @click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
<el-button v-hasPerm="['module_system:menu:update']" type="primary" size="small" link icon="edit" @click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
<el-button v-hasPerm="['module_system:menu:delete']" type="danger" size="small" link icon="delete" @click="handleDelete([scope.row.id])">删除</el-button>
<el-button
v-if="scope.row.type == MenuTypeEnum.CATALOG || scope.row.type == MenuTypeEnum.MENU"
v-hasPerm="['module_system:menu:create']"
type="success"
link
size="small"
icon="plus"
@click.stop="handleOpenDialog('create', undefined, scope.row.id)"
>
新增
</el-button>
<el-button
v-hasPerm="['module_system:menu:detail']"
type="info"
size="small"
link
icon="document"
@click="handleOpenDialog('detail', scope.row.id)"
>
详情
</el-button>
<el-button
v-hasPerm="['module_system:menu:update']"
type="primary"
size="small"
link
icon="edit"
@click="handleOpenDialog('update', scope.row.id)"
>
编辑
</el-button>
<el-button
v-hasPerm="['module_system:menu:delete']"
type="danger"
size="small"
link
icon="delete"
@click="handleDelete([scope.row.id])"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 弹窗区域 -->
<el-drawer v-model="dialogVisible.visible" :title="dialogVisible.title" :size="drawerSize" @close="handleCloseDialog">
<el-drawer
v-model="dialogVisible.visible"
:title="dialogVisible.title"
:size="drawerSize"
@close="handleCloseDialog"
>
<!-- 详情 -->
<template v-if="dialogVisible.type === 'detail'">
<el-descriptions :column="4" border>
<el-descriptions-item label="编号" :span="2">{{ detailFormData.id }}</el-descriptions-item>
<el-descriptions-item label="菜单名称" :span="2">{{ detailFormData.name }}</el-descriptions-item>
<el-descriptions-item label="编号" :span="2">
{{ detailFormData.id }}
</el-descriptions-item>
<el-descriptions-item label="菜单名称" :span="2">
{{ detailFormData.name }}
</el-descriptions-item>
<el-descriptions-item label="菜单类型" :span="2">
<el-tag v-if="detailFormData.type === MenuTypeEnum.CATALOG" type="warning">目录</el-tag>
<el-tag v-if="detailFormData.type === MenuTypeEnum.MENU" type="success">菜单</el-tag>
@@ -191,63 +296,110 @@
</el-descriptions-item>
<el-descriptions-item label="图标" :span="2">
<template #default>
<template v-if=" detailFormData.icon && detailFormData.icon.startsWith('el-icon')">
<template v-if="detailFormData.icon && detailFormData.icon.startsWith('el-icon')">
<el-icon style="vertical-align: -0.15em">
<component :is="detailFormData.icon.replace('el-icon-', '')" />
</el-icon>
</template>
<template v-else-if=" detailFormData.icon">
<template v-else-if="detailFormData.icon">
<div :class="`i-svg:${detailFormData.icon}`" />
</template>
</template>
</el-descriptions-item>
<el-descriptions-item label="排序" :span="2">{{ detailFormData.order }}</el-descriptions-item>
<el-descriptions-item label="权限标识" :span="2">{{ detailFormData.permission }}</el-descriptions-item>
<el-descriptions-item label="路由名称" :span="2">{{ detailFormData.route_name }}</el-descriptions-item>
<el-descriptions-item label="路由路径" :span="2">{{ detailFormData.route_path }}</el-descriptions-item>
<el-descriptions-item label="组件路径" :span="2">{{ detailFormData.component_path }}</el-descriptions-item>
<el-descriptions-item label="重定向" :span="2">{{ detailFormData.redirect }}</el-descriptions-item>
<el-descriptions-item label="父级编号" :span="2">{{ detailFormData.parent_id }}</el-descriptions-item>
<el-descriptions-item label="父级菜单" :span="2">{{ detailFormData.parent_name }}</el-descriptions-item>
<el-descriptions-item label="排序" :span="2">
{{ detailFormData.order }}
</el-descriptions-item>
<el-descriptions-item label="权限标识" :span="2">
{{ detailFormData.permission }}
</el-descriptions-item>
<el-descriptions-item label="路由名称" :span="2">
{{ detailFormData.route_name }}
</el-descriptions-item>
<el-descriptions-item label="路由路径" :span="2">
{{ detailFormData.route_path }}
</el-descriptions-item>
<el-descriptions-item label="组件路径" :span="2">
{{ detailFormData.component_path }}
</el-descriptions-item>
<el-descriptions-item label="重定向" :span="2">
{{ detailFormData.redirect }}
</el-descriptions-item>
<el-descriptions-item label="父级编号" :span="2">
{{ detailFormData.parent_id }}
</el-descriptions-item>
<el-descriptions-item label="父级菜单" :span="2">
{{ detailFormData.parent_name }}
</el-descriptions-item>
<el-descriptions-item label="是否缓存" :span="2">
<el-tag :type="detailFormData.keep_alive ? 'success' : 'danger'">
{{ detailFormData.keep_alive ? '是' : '否' }}
{{ detailFormData.keep_alive ? "是" : "否" }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="是否显示" :span="2">
<el-tag :type="detailFormData.hidden ? 'success' : 'danger'">
{{ detailFormData.hidden ? '是' : '否' }}
{{ detailFormData.hidden ? "是" : "否" }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="是否显示根路由" :span="2">
<el-tag :type="detailFormData.always_show ? 'success' : 'danger'">
{{ detailFormData.always_show ? '是' : '否' }}
{{ detailFormData.always_show ? "是" : "否" }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="菜单标题" :span="2">{{ detailFormData.title }}</el-descriptions-item>
<el-descriptions-item label="路由参数" :span="2">{{ detailFormData.params }}</el-descriptions-item>
<el-descriptions-item label="菜单标题" :span="2">
{{ detailFormData.title }}
</el-descriptions-item>
<el-descriptions-item label="路由参数" :span="2">
{{ detailFormData.params }}
</el-descriptions-item>
<el-descriptions-item label="是否固定路由" :span="2">
<el-tag :type="detailFormData.affix ? 'success' : 'danger'">
{{ detailFormData.affix ? '是' : '否' }}
{{ detailFormData.affix ? "是" : "否" }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="状态" :span="2">
<el-tag :type="detailFormData.status ? 'success' : 'danger'">
{{ detailFormData.status ? '启用' : '停用' }}
{{ detailFormData.status ? "启用" : "停用" }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="排序" :span="2">{{ detailFormData.order }}</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">{{ detailFormData.created_at }}</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">{{ detailFormData.updated_at }}</el-descriptions-item>
<el-descriptions-item label="描述" :span="4">{{ detailFormData.description }}</el-descriptions-item>
<el-descriptions-item label="排序" :span="2">
{{ detailFormData.order }}
</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">
{{ detailFormData.created_at }}
</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">
{{ detailFormData.updated_at }}
</el-descriptions-item>
<el-descriptions-item label="描述" :span="4">
{{ detailFormData.description }}
</el-descriptions-item>
</el-descriptions>
</template>
<!-- 新增、编辑表单 -->
<template v-else>
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-suffix=":" label-width="auto" label-position="right">
<el-form-item v-if="formData.type !== MenuTypeEnum.CATALOG" label="父级菜单" prop="parent_id" if >
<el-tree-select v-model="formData.parent_id" placeholder="选择上级菜单" :data="menuOptions" filterable check-strictly :render-after-expand="false" />
<el-form
ref="dataFormRef"
:model="formData"
:rules="rules"
label-suffix=":"
label-width="auto"
label-position="right"
>
<el-form-item
v-if="formData.type !== MenuTypeEnum.CATALOG"
label="父级菜单"
prop="parent_id"
if
>
<el-tree-select
v-model="formData.parent_id"
placeholder="选择上级菜单"
:data="menuOptions"
filterable
check-strictly
:render-after-expand="false"
/>
</el-form-item>
<el-form-item label="菜单名称" prop="name">
@@ -271,7 +423,7 @@
<el-input v-model="formData.route_path" placeholder="请输入外链完整路径" />
</el-form-item>
<el-form-item prop="route_name">
<el-form-item prop="route_name">
<template #label>
<div class="flex-y-center">
路由名称
@@ -288,14 +440,17 @@
<el-input v-model="formData.route_name" placeholder="请输入路由名称" />
</el-form-item>
<el-form-item v-if="formData.type == MenuTypeEnum.CATALOG || formData.type == MenuTypeEnum.MENU" prop="route_path">
<el-form-item
v-if="formData.type == MenuTypeEnum.CATALOG || formData.type == MenuTypeEnum.MENU"
prop="route_path"
>
<template #label>
<div class="flex-y-center">
路由路径
<el-tooltip placement="bottom" effect="light">
<template #content>
定义应用中不同页面对应的 URL 路径,目录需以 / 开头,菜单项不用。例如:系统管理目录
/system,系统管理下的用户管理菜单 user。
定义应用中不同页面对应的 URL 路径,目录需以 /
开头,菜单项不用。例如:系统管理目录 /system,系统管理下的用户管理菜单 user。
</template>
<el-icon class="ml-1 cursor-pointer">
<QuestionFilled />
@@ -303,7 +458,7 @@
</el-tooltip>
</div>
</template>
<el-input v-model="formData.route_path" placeholder="请输入路由路径,如:/system" />
<el-input v-model="formData.route_path" placeholder="请输入路由路径,如:/system" />
</el-form-item>
<el-form-item v-if="formData.type == MenuTypeEnum.MENU" prop="component">
@@ -321,7 +476,11 @@
</div>
</template>
<el-input v-model="formData.component_path" placeholder="请输入组件路径,如system/user/index" style="width: 95%">
<el-input
v-model="formData.component_path"
placeholder="请输入组件路径,如system/user/index"
style="width: 95%"
>
<template v-if="formData.type == MenuTypeEnum.MENU" #prepend>src/views/</template>
<template v-if="formData.type == MenuTypeEnum.MENU" #append>.vue</template>
</el-input>
@@ -342,7 +501,11 @@
</div>
</template>
<div v-if="!formData.params || (Array.isArray(formData.params) && formData.params.length === 0)">
<div
v-if="
!formData.params || (Array.isArray(formData.params) && formData.params.length === 0)
"
>
<el-button type="success" plain @click="formData.params = [{ key: '', value: '' }]">
添加路由参数
</el-button>
@@ -356,10 +519,19 @@
<el-input v-model="item.value" placeholder="参数值" style="width: 100px" />
<el-icon v-if="formData.params.indexOf(item) === formData.params.length - 1" class="ml-2 cursor-pointer color-[var(--el-color-success)]" style="vertical-align: -0.15em" @click="formData.params.push({ key: '', value: '' })">
<el-icon
v-if="formData.params.indexOf(item) === formData.params.length - 1"
class="ml-2 cursor-pointer color-[var(--el-color-success)]"
style="vertical-align: -0.15em"
@click="formData.params.push({ key: '', value: '' })"
>
<CirclePlusFilled />
</el-icon>
<el-icon class="ml-2 cursor-pointer color-[var(--el-color-danger)]" style="vertical-align: -0.15em" @click="formData.params.splice(formData.params.indexOf(item), 1)">
<el-icon
class="ml-2 cursor-pointer color-[var(--el-color-danger)]"
style="vertical-align: -0.15em"
@click="formData.params.splice(formData.params.indexOf(item), 1)"
>
<DeleteFilled />
</el-icon>
</div>
@@ -390,7 +562,9 @@
</el-radio-group>
</el-form-item>
<el-form-item v-if="formData.type === MenuTypeEnum.CATALOG || formData.type === MenuTypeEnum.MENU">
<el-form-item
v-if="formData.type === MenuTypeEnum.CATALOG || formData.type === MenuTypeEnum.MENU"
>
<template #label>
<div class="flex-y-center">
始终显示
@@ -423,11 +597,15 @@
</el-form-item>
<el-form-item label="排序" prop="order">
<el-input-number v-model="formData.order" controls-position="right" :min="1" />
<el-input-number v-model="formData.order" controls-position="right" :min="1" />
</el-form-item>
<!-- 权限标识 -->
<el-form-item v-if="formData.type == MenuTypeEnum.BUTTON || formData.type === MenuTypeEnum.MENU" label="权限标识" prop="perm">
<el-form-item
v-if="formData.type == MenuTypeEnum.BUTTON || formData.type === MenuTypeEnum.MENU"
label="权限标识"
prop="perm"
>
<el-input v-model="formData.permission" placeholder="请输入权限标识,如sys:user:add" />
</el-form-item>
@@ -436,7 +614,11 @@
<icon-select v-model="formData.icon" />
</el-form-item>
<el-form-item v-if="formData.type == MenuTypeEnum.CATALOG || formData.type === MenuTypeEnum.MENU" label="重定向" prop="redirect">
<el-form-item
v-if="formData.type == MenuTypeEnum.CATALOG || formData.type === MenuTypeEnum.MENU"
label="重定向"
prop="redirect"
>
<el-input v-model="formData.redirect" placeholder="请输入重定向路由" />
</el-form-item>
@@ -455,7 +637,14 @@
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="formData.description" :rows="4" :maxlength="100" show-word-limit type="textarea" placeholder="请输入描述" />
<el-input
v-model="formData.description"
:rows="4"
:maxlength="100"
show-word-limit
type="textarea"
placeholder="请输入描述"
/>
</el-form-item>
</el-form>
</template>
@@ -463,8 +652,22 @@
<template #footer>
<div class="dialog-footer">
<!-- 详情弹窗不需要确定按钮的提交逻辑 -->
<el-button v-if="dialogVisible.type !== 'detail'" v-hasPerm="['module_system:menu:submit']" type="primary" @click="handleSubmit">确定</el-button>
<el-button v-else v-hasPerm="['module_system:menu:detail']" type="primary" @click="handleCloseDialog">确定</el-button>
<el-button
v-if="dialogVisible.type !== 'detail'"
v-hasPerm="['module_system:menu:submit']"
type="primary"
@click="handleSubmit"
>
确定
</el-button>
<el-button
v-else
v-hasPerm="['module_system:menu:detail']"
type="primary"
@click="handleCloseDialog"
>
确定
</el-button>
<el-button @click="handleCloseDialog">取消</el-button>
</div>
</template>
@@ -519,27 +722,27 @@ const formData = reactive<MenuForm>({
type: MenuTypeEnum.CATALOG,
icon: undefined,
order: 999,
permission: '',
route_name: '',
route_path: '',
permission: "",
route_name: "",
route_path: "",
component_path: undefined,
redirect: undefined,
parent_id: undefined,
keep_alive: false,
hidden: false,
always_show: false,
title: '',
title: "",
params: undefined,
affix: false,
status: true,
description: undefined,
})
});
// 弹窗状态
const dialogVisible = reactive({
title: "",
visible: false,
type: 'create' as 'create' | 'update' | 'detail',
type: "create" as "create" | "update" | "detail",
});
const drawerSize = computed(() => (appStore.device === DeviceEnum.DESKTOP ? "600px" : "90%"));
@@ -562,14 +765,14 @@ const rules = reactive({
{ required: true, message: "请输入路由路径", trigger: "blur" },
{
validator: (rule: any, value: string, callback: any) => {
if (value && !value.startsWith('/')) {
callback(new Error('目录和菜单路由必须以/开头'));
if (value && !value.startsWith("/")) {
callback(new Error("目录和菜单路由必须以/开头"));
} else {
callback();
}
},
trigger: 'blur'
}
trigger: "blur",
},
],
component_path: [{ required: true, message: "请输入组件路径", trigger: "blur" }],
title: [
@@ -601,25 +804,25 @@ function handleDateRangeChange(range: [Date, Date]) {
}
// 列表刷新
async function handleRefresh () {
async function handleRefresh() {
loading.value = true;
try {
const response = await MenuAPI.getMenuList(queryFormData);
pageTableData.value = response.data.data
pageTableData.value = response.data.data;
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
};
}
// 修改菜单选项过滤逻辑,添加递归过滤函数
const filterMenuTypes = (nodes: MenuTable[]) => {
return nodes
.filter(node => node.type === MenuTypeEnum.CATALOG || node.type === MenuTypeEnum.MENU)
.filter((node) => node.type === MenuTypeEnum.CATALOG || node.type === MenuTypeEnum.MENU)
.map((node: any): any => ({
...node,
children: node.children ? filterMenuTypes(node.children) : []
children: node.children ? filterMenuTypes(node.children) : [],
}));
};
@@ -628,14 +831,12 @@ async function loadingData() {
loading.value = true;
try {
const response = await MenuAPI.getMenuList(queryFormData);
pageTableData.value = response.data.data
pageTableData.value = response.data.data;
// 加载菜单选项,只显示目录、菜单
menuOptions.value = formatTree(filterMenuTypes(response.data.data));
}
catch (error: any) {
} catch (error: any) {
console.error(error);
}
finally {
} finally {
loading.value = false;
}
}
@@ -662,21 +863,21 @@ const initialFormData: MenuForm = {
type: MenuTypeEnum.MENU,
icon: undefined,
order: 1,
permission: '',
route_name: '',
route_path: '',
component_path: '',
redirect: '',
permission: "",
route_name: "",
route_path: "",
component_path: "",
redirect: "",
parent_id: undefined,
keep_alive: false,
hidden: false,
always_show: false,
title: '',
title: "",
params: [] as { key: string; value: string }[],
affix: false,
status: true,
description: undefined,
}
};
// 重置表单
async function resetForm() {
@@ -705,14 +906,18 @@ async function handleCloseDialog() {
}
//打开弹窗
async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: number, parentId?: number) {
async function handleOpenDialog(
type: "create" | "update" | "detail",
id?: number,
parentId?: number
) {
dialogVisible.type = type;
if (id) {
const response = await MenuAPI.getMenuDetail(id);
if (type === 'detail') {
if (type === "detail") {
dialogVisible.title = "菜单详情";
Object.assign(detailFormData.value, response.data.data);
} else if (type === 'update') {
} else if (type === "update") {
dialogVisible.title = "修改菜单";
Object.assign(formData, response.data.data);
}
@@ -748,9 +953,9 @@ async function handleSubmit() {
const id = formData.id;
try {
if (id) {
await MenuAPI.updateMenu(id, formData)
await MenuAPI.updateMenu(id, formData);
} else {
await MenuAPI.createMenu(formData)
await MenuAPI.createMenu(formData);
}
await userStore.getUserInfo();
dialogVisible.visible = false;
@@ -771,41 +976,45 @@ async function handleDelete(ids: number[]) {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await MenuAPI.deleteMenu(ids);
await userStore.getUserInfo();
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
})
.then(async () => {
try {
loading.value = true;
await MenuAPI.deleteMenu(ids);
await userStore.getUserInfo();
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
})
.catch(() => {
ElMessageBox.close();
});
}
// 批量启用/停用
async function handleMoreClick(status: boolean) {
ElMessageBox.confirm(`确认${status ? '启用' : '停用'}该项数据?`, "警告", {
ElMessageBox.confirm(`确认${status ? "启用" : "停用"}该项数据?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await MenuAPI.batchAvailableMenu({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
})
.then(async () => {
try {
loading.value = true;
await MenuAPI.batchAvailableMenu({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
})
.catch(() => {
ElMessageBox.close();
});
}
onMounted(() => {
+328 -128
View File
@@ -3,12 +3,23 @@
<div class="app-container">
<!-- 搜索区域 -->
<div class="search-container">
<el-form ref="queryFormRef" :model="queryFormData" :inline="true" label-suffix=":" @submit.prevent="handleQuery">
<el-form
ref="queryFormRef"
:model="queryFormData"
:inline="true"
label-suffix=":"
@submit.prevent="handleQuery"
>
<el-form-item prop="notice_title" label="标题">
<el-input v-model="queryFormData.notice_title" placeholder="请输入标题" clearable />
</el-form-item>
<el-form-item prop="notice_type" label="类型">
<el-select v-model="queryFormData.notice_type" placeholder="请选择类型" style="width: 167.5px" clearable>
<el-select
v-model="queryFormData.notice_type"
placeholder="请选择类型"
style="width: 167.5px"
clearable
>
<el-option
v-for="item in dictStore.getDictArray('sys_notice_type')"
:key="item.dict_value"
@@ -18,33 +29,44 @@
</el-select>
</el-form-item>
<el-form-item prop="status" label="状态">
<el-select v-model="queryFormData.status" placeholder="请选择状态" style="width: 167.5px" clearable>
<el-select
v-model="queryFormData.status"
placeholder="请选择状态"
style="width: 167.5px"
clearable
>
<el-option value="true" label="启用" />
<el-option value="false" label="停用" />
</el-select>
</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 v-if="isExpand" prop="creator" label="创建人">
<UserTableSelect
v-model="queryFormData.creator"
@confirm-click="handleConfirm"
@clear-click="handleQuery"
v-model="queryFormData.creator"
@confirm-click="handleConfirm"
@clear-click="handleQuery"
/>
</el-form-item>
<!-- 查询、重置、展开/收起按钮 -->
<el-form-item class="search-buttons">
<el-button v-hasPerm="['module_system:notice:query']" type="primary" icon="search" native-type="submit">
查询
</el-button>
<el-button v-hasPerm="['module_system:notice:query']" icon="refresh" @click="handleResetQuery">
重置
</el-button>
<el-button
v-hasPerm="['module_system:notice:query']"
type="primary"
icon="search"
native-type="submit"
>
查询
</el-button>
<el-button
v-hasPerm="['module_system:notice:query']"
icon="refresh"
@click="handleResetQuery"
>
重置
</el-button>
<!-- 展开/收起 -->
<template v-if="isExpandable">
<el-link class="ml-3" type="primary" underline="never" @click="isExpand = !isExpand">
@@ -81,18 +103,39 @@
<div class="data-table__toolbar--left">
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPerm="['module_system:notice:create']" type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
<el-button
v-hasPerm="['module_system:notice:create']"
type="success"
icon="plus"
@click="handleOpenDialog('create')"
>
新增
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPerm="['module_system:notice:delete']" type="danger" icon="delete" :disabled="selectIds.length === 0" @click="handleDelete(selectIds)">批量删除</el-button>
<el-button
v-hasPerm="['module_system:notice:delete']"
type="danger"
icon="delete"
:disabled="selectIds.length === 0"
@click="handleDelete(selectIds)"
>
批量删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-dropdown v-hasPerm="['module_system:notice:patch']" trigger="click">
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">更多</el-button>
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">
更多
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">批量启用</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">批量停用</el-dropdown-item>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">
批量启用
</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">
批量停用
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -103,12 +146,24 @@
<el-row :gutter="10">
<el-col :span="1.5">
<el-tooltip content="导出">
<el-button v-hasPerm="['module_system:notice:export']" type="warning" icon="download" circle @click="handleOpenExportsModal" />
<el-button
v-hasPerm="['module_system:notice:export']"
type="warning"
icon="download"
circle
@click="handleOpenExportsModal"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-tooltip content="刷新">
<el-button v-hasPerm="['module_system:notice:refresh']" type="primary" icon="refresh" circle @click="handleRefresh" />
<el-button
v-hasPerm="['module_system:notice:refresh']"
type="primary"
icon="refresh"
circle
@click="handleRefresh"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
@@ -128,58 +183,170 @@
</div>
<!-- 表格区域:系统配置列表 -->
<el-table ref="dataTableRef" v-loading="loading" :data="pageTableData" highlight-current-row class="data-table__content" :height="450" border stripe @selection-change="handleSelectionChange">
<el-table
ref="dataTableRef"
v-loading="loading"
:data="pageTableData"
highlight-current-row
class="data-table__content"
:height="450"
border
stripe
@selection-change="handleSelectionChange"
>
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection" min-width="55" align="center" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" fixed label="序号" min-width="60" >
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'selection')?.show"
type="selection"
min-width="55"
align="center"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'index')?.show"
fixed
label="序号"
min-width="60"
>
<template #default="scope">
{{ (queryFormData.page_no - 1) * queryFormData.page_size + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'notice_title')?.show" label="通知标题" prop="notice_title" min-width="140" show-overflow-tooltip/>
<el-table-column v-if="tableColumns.find(col => col.prop === 'status')?.show" label="状态" prop="status" min-width="80">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'notice_title')?.show"
label="通知标题"
prop="notice_title"
min-width="140"
show-overflow-tooltip
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'status')?.show"
label="状态"
prop="status"
min-width="80"
>
<template #default="scope">
<el-tag :type="scope.row.status === true ? 'success' : 'danger'">
{{ scope.row.status === true ? "启用" : "停用" }}
</el-tag>
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'notice_type')?.show" label="类型" prop="notice_type" min-width="80">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'notice_type')?.show"
label="类型"
prop="notice_type"
min-width="80"
>
<template #default="scope">
<el-tag :type="scope.row.notice_type === '1' ? 'primary' : 'warning'">
{{ (scope.row.notice_type ? dictStore.getDictLabel('sys_notice_type', scope.row.notice_type) as any : undefined)?.dict_label || scope.row.notice_type }}
{{
(scope.row.notice_type
? (dictStore.getDictLabel("sys_notice_type", scope.row.notice_type) as any)
: undefined
)?.dict_label || scope.row.notice_type
}}
</el-tag>
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'notice_content')?.show" label="内容" prop="notice_content" min-width="200" show-overflow-tooltip />
<el-table-column v-if="tableColumns.find(col => col.prop === 'description')?.show" label="描述" prop="description" min-width="140" show-overflow-tooltip/>
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" label="创建时间" prop="created_at" min-width="180" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'updated_at')?.show" label="更新时间" prop="updated_at" min-width="180" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'creator')?.show" key="creator" label="创建人" min-width="100">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'notice_content')?.show"
label="内容"
prop="notice_content"
min-width="200"
show-overflow-tooltip
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'description')?.show"
label="描述"
prop="description"
min-width="140"
show-overflow-tooltip
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'created_at')?.show"
label="创建时间"
prop="created_at"
min-width="180"
sortable
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'updated_at')?.show"
label="更新时间"
prop="updated_at"
min-width="180"
sortable
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'creator')?.show"
key="creator"
label="创建人"
min-width="100"
>
<template #default="scope">
{{ scope.row.creator?.name }}
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right" label="操作" align="center" min-width="200">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'operation')?.show"
fixed="right"
label="操作"
align="center"
min-width="200"
>
<template #default="scope">
<el-button v-hasPerm="['module_system:notice:detail']" type="info" size="small" link icon="document" @click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
<el-button v-hasPerm="['module_system:notice:update']" type="primary" size="small" link icon="edit" @click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
<el-button v-hasPerm="['module_system:notice:delete']" type="danger" size="small" link icon="delete" @click="handleDelete([scope.row.id])">删除</el-button>
<el-button
v-hasPerm="['module_system:notice:detail']"
type="info"
size="small"
link
icon="document"
@click="handleOpenDialog('detail', scope.row.id)"
>
详情
</el-button>
<el-button
v-hasPerm="['module_system:notice:update']"
type="primary"
size="small"
link
icon="edit"
@click="handleOpenDialog('update', scope.row.id)"
>
编辑
</el-button>
<el-button
v-hasPerm="['module_system:notice:delete']"
type="danger"
size="small"
link
icon="delete"
@click="handleDelete([scope.row.id])"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<template #footer>
<pagination v-model:total="total" v-model:page="queryFormData.page_no" v-model:limit="queryFormData.page_size" @pagination="loadingData" />
<pagination
v-model:total="total"
v-model:page="queryFormData.page_no"
v-model:limit="queryFormData.page_size"
@pagination="loadingData"
/>
</template>
</el-card>
<!-- 弹窗区域 -->
<el-dialog v-model="dialogVisible.visible" :title="dialogVisible.title" @close="handleCloseDialog">
<el-dialog
v-model="dialogVisible.visible"
:title="dialogVisible.title"
@close="handleCloseDialog"
>
<!-- 详情 -->
<template v-if="dialogVisible.type === 'detail'">
<el-descriptions :column="4" border>
@@ -188,12 +355,17 @@
</el-descriptions-item>
<el-descriptions-item label="类型" :span="2">
<el-tag :type="detailFormData.notice_type === '1' ? 'primary' : 'warning'">
{{ (detailFormData.notice_type ? dictStore.getDictLabel('sys_notice_type', detailFormData.notice_type) as any : undefined)?.dict_label || detailFormData.notice_type }}
{{
(detailFormData.notice_type
? (dictStore.getDictLabel("sys_notice_type", detailFormData.notice_type) as any)
: undefined
)?.dict_label || detailFormData.notice_type
}}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="状态" :span="2">
<el-tag :type="detailFormData.status ? 'success' : 'danger'">
{{ detailFormData.status ? '启用' : '停用' }}
{{ detailFormData.status ? "启用" : "停用" }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="内容" :span="2">
@@ -215,12 +387,27 @@
</template>
<!-- 新增、编辑表单 -->
<template v-else>
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-suffix=":" label-width="auto" label-position="right" :inline="true">
<el-form
ref="dataFormRef"
:model="formData"
:rules="rules"
label-suffix=":"
label-width="auto"
label-position="right"
:inline="true"
>
<el-form-item label="标题" prop="notice_title">
<el-input v-model="formData.notice_title" placeholder="请输入标题" :maxlength="50" />
</el-form-item>
<el-form-item label="描述" prop="description" >
<el-input v-model="formData.description" :rows="1" :maxlength="100" show-word-limit type="textarea" placeholder="请输入描述" />
<el-form-item label="描述" prop="description">
<el-input
v-model="formData.description"
:rows="1"
:maxlength="100"
show-word-limit
type="textarea"
placeholder="请输入描述"
/>
</el-form-item>
<el-form-item label="类型" prop="notice_type" class="w-50">
<el-select v-model="formData.notice_type" placeholder="请选择类型" clearable>
@@ -230,17 +417,12 @@
:value="item.dict_value"
:label="item.dict_label"
/>
</el-select>
</el-form-item>
<el-form-item label="状态" prop="status" >
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
<el-radio :value="true">
启用
</el-radio>
<el-radio :value="false">
停用
</el-radio>
<el-radio :value="true">启用</el-radio>
<el-radio :value="false">停用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="内容" prop="notice_content">
@@ -254,14 +436,31 @@
<div class="dialog-footer">
<!-- 详情弹窗不需要确定按钮的提交逻辑 -->
<el-button @click="handleCloseDialog">取消</el-button>
<el-button v-if="dialogVisible.type !== 'detail'" v-hasPerm="['module_system:notice:submit']" type="primary" @click="handleSubmit">确定</el-button>
<el-button v-else v-hasPerm="['module_system:notice:detail']" type="primary" @click="handleCloseDialog">确定</el-button>
<el-button
v-if="dialogVisible.type !== 'detail'"
v-hasPerm="['module_system:notice:submit']"
type="primary"
@click="handleSubmit"
>
确定
</el-button>
<el-button
v-else
v-hasPerm="['module_system:notice:detail']"
type="primary"
@click="handleCloseDialog"
>
确定
</el-button>
</div>
</template>
</el-dialog>
<ExportModal v-model="exportsDialogVisible" :content-config="curdContentConfig" :selection-data="selectionRows" />
<ExportModal
v-model="exportsDialogVisible"
:content-config="curdContentConfig"
:selection-data="selectionRows"
/>
</div>
</template>
@@ -297,18 +496,18 @@ const selectionRows = ref<NoticeTable[]>([]);
// 表格列配置
const tableColumns = ref([
{ prop: 'selection', label: '选择框', show: true },
{ prop: 'index', label: '序号', show: true },
{ prop: 'notice_title', label: '标题', show: true },
{ prop: 'notice_type', label: '类型', show: true },
{ prop: 'notice_content', label: '内容', show: true },
{ prop: 'status', label: '状态', show: true },
{ prop: 'description', label: '描述', show: true },
{ prop: 'created_at', label: '创建时间', show: true },
{ prop: 'updated_at', label: '更新时间', show: true },
{ prop: 'creator', label: '创建人', show: true },
{ prop: 'operation', label: '操作', show: true }
])
{ prop: "selection", label: "选择框", show: true },
{ prop: "index", label: "序号", show: true },
{ prop: "notice_title", label: "标题", show: true },
{ prop: "notice_type", label: "类型", show: true },
{ prop: "notice_content", label: "内容", show: true },
{ prop: "status", label: "状态", show: true },
{ prop: "description", label: "描述", show: true },
{ prop: "created_at", label: "创建时间", show: true },
{ prop: "updated_at", label: "更新时间", show: true },
{ prop: "creator", label: "创建人", show: true },
{ prop: "operation", label: "操作", show: true },
]);
// 详情表单
const detailFormData = ref<NoticeTable>({});
@@ -329,18 +528,18 @@ const queryFormData = reactive<NoticePageQuery>({
// 编辑表单
const formData = reactive<NoticeForm>({
id: undefined,
notice_title: '',
notice_type: '',
notice_content: '',
notice_title: "",
notice_type: "",
notice_content: "",
status: true,
description: undefined,
})
});
// 弹窗状态
const dialogVisible = reactive({
title: "",
visible: false,
type: 'create' as 'create' | 'update' | 'detail',
type: "create" as "create" | "update" | "detail",
});
// 表单验证规则
@@ -367,9 +566,9 @@ function handleDateRangeChange(range: [Date, Date]) {
}
// 列表刷新
async function handleRefresh () {
async function handleRefresh() {
await loadingData();
};
}
// 加载表格数据
async function loadingData() {
@@ -378,11 +577,9 @@ async function loadingData() {
const response = await NoticeAPI.getNoticeList(queryFormData);
pageTableData.value = response.data.data.items;
total.value = response.data.data.total;
}
catch (error: any) {
} catch (error: any) {
console.error(error);
}
finally {
} finally {
loading.value = false;
}
}
@@ -412,12 +609,12 @@ async function handleResetQuery() {
// 定义初始表单数据常量
const initialFormData: NoticeForm = {
id: undefined,
notice_title: '',
notice_type: '',
notice_content: '',
notice_title: "",
notice_type: "",
notice_content: "",
status: true,
description: undefined,
}
};
// 重置表单
async function resetForm() {
@@ -442,14 +639,14 @@ async function handleCloseDialog() {
}
// 打开弹窗
async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: number) {
async function handleOpenDialog(type: "create" | "update" | "detail", id?: number) {
dialogVisible.type = type;
if (id) {
const response = await NoticeAPI.getNoticeDetail(id);
if (type === 'detail') {
if (type === "detail") {
dialogVisible.title = "公告通知详情";
Object.assign(detailFormData.value, response.data.data);
} else if (type === 'update') {
} else if (type === "update") {
dialogVisible.title = "修改公告通知";
Object.assign(formData, response.data.data);
}
@@ -470,7 +667,7 @@ async function handleSubmit() {
const id = formData.id;
if (id) {
try {
await NoticeAPI.updateNotice(id, { id, ...formData })
await NoticeAPI.updateNotice(id, { id, ...formData });
dialogVisible.visible = false;
resetForm();
handleCloseDialog();
@@ -482,7 +679,7 @@ async function handleSubmit() {
}
} else {
try {
await NoticeAPI.createNotice(formData)
await NoticeAPI.createNotice(formData);
dialogVisible.visible = false;
resetForm();
handleCloseDialog();
@@ -503,19 +700,21 @@ async function handleDelete(ids: number[]) {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await NoticeAPI.deleteNotice(ids);
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
})
.then(async () => {
try {
loading.value = true;
await NoticeAPI.deleteNotice(ids);
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
})
.catch(() => {
ElMessageBox.close();
});
}
// 导出弹窗
@@ -526,45 +725,46 @@ async function handleOpenExportsModal() {
// 批量启用/停用
async function handleMoreClick(status: boolean) {
if (selectIds.value.length) {
ElMessageBox.confirm(`确认${status ? '启用' : '停用'}该项数据?`, "警告", {
ElMessageBox.confirm(`确认${status ? "启用" : "停用"}该项数据?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await NoticeAPI.batchAvailableNotice({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
})
.then(async () => {
try {
loading.value = true;
await NoticeAPI.batchAvailableNotice({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
})
.catch(() => {
ElMessageBox.close();
});
}
}
// 导出字段
const exportColumns = [
{ prop: 'notice_title', label: '标题' },
{ prop: 'status', label: '状态' },
{ prop: 'notice_type', label: '类型' },
{ prop: 'notice_content', label: '内容' },
{ prop: 'description', label: '描述' },
{ prop: 'created_at', label: '创建时间' },
{ prop: 'updated_at', label: '更新时间' },
{ prop: "notice_title", label: "标题" },
{ prop: "status", label: "状态" },
{ prop: "notice_type", label: "类型" },
{ prop: "notice_content", label: "内容" },
{ prop: "description", label: "描述" },
{ prop: "created_at", label: "创建时间" },
{ prop: "updated_at", label: "更新时间" },
];
// 导入/导出配置(用于导出弹窗)
const curdContentConfig = {
permPrefix: 'module_system:notice',
permPrefix: "module_system:notice",
cols: exportColumns as any,
exportsAction: async (params: any) => {
const query: any = { ...params };
if (typeof query.status === 'string') query.status = query.status === 'true';
if (typeof query.status === "string") query.status = query.status === "true";
// notice_type 为字符串,无需转换;如果后端需要数字,可在此转换
query.page_no = 1;
query.page_size = 1000;
@@ -583,7 +783,7 @@ const curdContentConfig = {
onMounted(async () => {
// 加载字典数据
await dictStore.getDict(['sys_notice_type']);
await dictStore.getDict(["sys_notice_type"]);
// 加载表格数据
loadingData();
});
@@ -1,6 +1,12 @@
<template>
<el-drawer v-model="drawerVisible" title="配置中心" :size="drawerSize" destroy-on-close @closed="onDrawerClosed">
<el-tabs v-model="activeTabRef" type="border-card" >
<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">
<el-form :model="configState" label-suffix=":" label-width="auto" label-position="right">
@@ -37,9 +43,8 @@
/>
</div>
</el-form-item>
</div>
</div>
</el-form>
</el-tab-pane>
<el-tab-pane label="安全隐私" name="securityPrivacy">
<el-form :model="configState" label-suffix=":" label-width="auto" label-position="right">
@@ -86,17 +91,23 @@
<div v-for="(item, key) in apiWhitelistConfigs" :key="key">
<el-form-item :label="item.config_name">
<div class="space-y-2">
<div v-for="listItem in apiWhitelistItems" :key="listItem.id" class="flex items-center gap-2">
<div
v-for="listItem in apiWhitelistItems"
:key="listItem.id"
class="flex items-center gap-2"
>
<el-input
v-model="listItem.value"
:placeholder="'/api/v1/users/get'"
clearable
@input="markModified(key)"
@blur="{
if (!isValidApiPath(listItem.value) && listItem.value.trim()) {
ElMessage.warning('请输入有效的接口路径格式(以/开头)');
@blur="
{
if (!isValidApiPath(listItem.value) && listItem.value.trim()) {
ElMessage.warning('请输入有效的接口路径格式(以/开头)');
}
}
}"
"
/>
<el-button
type="danger"
@@ -130,18 +141,24 @@
<div v-for="(item, key) in ipBlacklistConfigs" :key="key">
<el-form-item :label="item.config_name">
<div class="space-y-2">
<div v-for="listItem in ipBlacklistItems" :key="listItem.id" class="flex items-center gap-2">
<div
v-for="listItem in ipBlacklistItems"
:key="listItem.id"
class="flex items-center gap-2"
>
<el-input
v-model="listItem.value"
:placeholder="'192.168.1.1'"
clearable
style="flex: 1"
@input="markModified(key)"
@blur="{
if (!isValidIp(listItem.value) && listItem.value.trim()) {
ElMessage.warning('请输入有效的IP地址格式');
@blur="
{
if (!isValidIp(listItem.value) && listItem.value.trim()) {
ElMessage.warning('请输入有效的IP地址格式');
}
}
}"
"
/>
<el-button
type="danger"
@@ -176,17 +193,18 @@
<el-form-item :label="item.config_name">
<!-- 演示模式开关 -->
<template v-if="key === 'demo_enable'">
<el-switch
inline-prompt
active-text="启用"
inactive-text="禁用"
:model-value="item.config_value === 'true'"
@update:model-value="(value) => {
<el-switch
inline-prompt
active-text="启用"
inactive-text="禁用"
:model-value="item.config_value === 'true'"
@update:model-value="
(value) => {
item.config_value = value ? 'true' : 'false';
markModified(key);
}"
/>
}
"
/>
<div class="text-xs text-gray-500 mt-1">
配置说明:启用后系统将进入演示模式,部分功能可能受限。
</div>
@@ -194,18 +212,24 @@
<!-- IP白名单 -->
<template v-else-if="key === 'ip_white_list'">
<div class="space-y-2">
<div v-for="listItem in demoIpWhitelistItems" :key="listItem.id" class="flex items-center gap-2">
<div
v-for="listItem in demoIpWhitelistItems"
:key="listItem.id"
class="flex items-center gap-2"
>
<el-input
v-model="listItem.value"
:placeholder="'192.168.1.1'"
clearable
style="flex: 1"
@input="markModified(key)"
@blur="{
if (!isValidIp(listItem.value) && listItem.value.trim()) {
ElMessage.warning('请输入有效的IP地址格式');
@blur="
{
if (!isValidIp(listItem.value) && listItem.value.trim()) {
ElMessage.warning('请输入有效的IP地址格式');
}
}
}"
"
/>
<el-button
type="danger"
@@ -228,7 +252,6 @@
配置说明:演示模式下,只有白名单中的IP地址可以访问系统,支持单个IP配置。
</div>
</div>
</template>
<!-- 其他配置项 -->
<template v-else>
@@ -245,20 +268,27 @@
</el-form>
</el-tab-pane>
</el-tabs>
<template #footer>
<template #footer>
<el-button @click="handleCloseDialog">取消</el-button>
<el-button v-hasPerm="['module_system:config:update']" type="primary" :disabled="!hasChanges" @click="submitChanges">保存</el-button>
<el-button
v-hasPerm="['module_system:config:update']"
type="primary"
:disabled="!hasChanges"
@click="submitChanges"
>
保存
</el-button>
</template>
</el-drawer>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted, computed } from 'vue';
import ParamsAPI, { type ConfigTable } from '@/api/module_system/params';
import { ref, reactive, onMounted, computed } from "vue";
import ParamsAPI, { type ConfigTable } from "@/api/module_system/params";
import { useConfigStore } from "@/store";
import { useI18n } from 'vue-i18n';
import { ElMessage, ElMessageBox } from 'element-plus';
import SingleImageUpload from '@/components/Upload/SingleImageUpload.vue';
import { useI18n } from "vue-i18n";
import { ElMessage, ElMessageBox } from "element-plus";
import SingleImageUpload from "@/components/Upload/SingleImageUpload.vue";
import { useAppStore } from "@/store/modules/app.store";
import { DeviceEnum } from "@/enums/settings/device.enum";
@@ -275,7 +305,8 @@ const generateId = () => {
// IP地址验证函数
const isValidIp = (ip: string): boolean => {
const ipRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
const ipRegex =
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
return ipRegex.test(ip);
};
@@ -283,31 +314,31 @@ const isValidIp = (ip: string): boolean => {
const isValidApiPath = (path: string): boolean => {
const pathRegex = /^\/[\w\-/]+$/;
return pathRegex.test(path);
}
};
const appStore = useAppStore();
const drawerSize = computed(() => (appStore.device === DeviceEnum.DESKTOP ? "60%" : "60%"));
const t = useI18n().t;
const configStore = useConfigStore();
const activeTabRef = ref('website')
const activeTabRef = ref("website");
// 与父组件的 v-model 同步
const props = defineProps<{ modelValue: boolean }>();
const emit = defineEmits(['update:modelValue']);
const emit = defineEmits(["update:modelValue"]);
const drawerVisible = computed({
get: () => props.modelValue,
set: (val: boolean) => emit('update:modelValue', val)
set: (val: boolean) => emit("update:modelValue", val),
});
// 配置状态管理
const configState = reactive<ConfigTable>({
id: undefined,
config_name: '',
config_key: '',
config_value: '',
config_name: "",
config_key: "",
config_value: "",
config_type: undefined,
description: ''
description: "",
});
// 记录修改过的字段
@@ -329,68 +360,72 @@ const submitChanges = async () => {
try {
// 准备提交数据
// 1. 处理接口白名单
if ('white_api_list_path' in modifiedFields && apiWhitelistConfigs.value.white_api_list_path?.id) {
if (
"white_api_list_path" in modifiedFields &&
apiWhitelistConfigs.value.white_api_list_path?.id
) {
const apiWhitelistArray = apiWhitelistItems.value
.map(item => item.value.trim())
.map((item) => item.value.trim())
.filter(Boolean);
// 转换为JSON字符串格式保存
const apiWhitelistJson = JSON.stringify(apiWhitelistArray);
await ParamsAPI.updateConfig(apiWhitelistConfigs.value.white_api_list_path.id, {
...apiWhitelistConfigs.value.white_api_list_path,
config_value: apiWhitelistJson
config_value: apiWhitelistJson,
});
}
// 2. 处理IP黑名单
if ('ip_black_list' in modifiedFields && ipBlacklistConfigs.value.ip_black_list?.id) {
if ("ip_black_list" in modifiedFields && ipBlacklistConfigs.value.ip_black_list?.id) {
const ipBlacklistArray = ipBlacklistItems.value
.map(item => item.value.trim())
.map((item) => item.value.trim())
.filter(Boolean);
// 转换为JSON字符串格式保存
const ipBlacklistJson = JSON.stringify(ipBlacklistArray);
await ParamsAPI.updateConfig(ipBlacklistConfigs.value.ip_black_list.id, {
...ipBlacklistConfigs.value.ip_black_list,
config_value: ipBlacklistJson
config_value: ipBlacklistJson,
});
}
// 3. 处理演示环境IP白名单
if ('ip_white_list' in modifiedFields && demoConfigs.value.ip_white_list?.id) {
if ("ip_white_list" in modifiedFields && demoConfigs.value.ip_white_list?.id) {
const demoIpWhitelistArray = demoIpWhitelistItems.value
.map(item => item.value.trim())
.map((item) => item.value.trim())
.filter(Boolean);
// 转换为JSON字符串格式保存
const demoIpWhitelistJson = JSON.stringify(demoIpWhitelistArray);
await ParamsAPI.updateConfig(demoConfigs.value.ip_white_list.id, {
...demoConfigs.value.ip_white_list,
config_value: demoIpWhitelistJson
config_value: demoIpWhitelistJson,
});
}
// 4. 处理其他配置项
const otherKeys = keysToSubmit.filter(key =>
!['white_api_list_path', 'ip_black_list', 'ip_white_list'].includes(key)
const otherKeys = keysToSubmit.filter(
(key) => !["white_api_list_path", "ip_black_list", "ip_white_list"].includes(key)
);
const otherUpdatePromises = otherKeys.map(key => {
const item = systemConfigs.value[key as keyof typeof systemConfigs.value] ||
logoConfigs.value[key as keyof typeof logoConfigs.value] ||
securityPrivacyConfigs.value[key as keyof typeof securityPrivacyConfigs.value] ||
userAgreementConfigs.value[key as keyof typeof userAgreementConfigs.value] ||
demoConfigs.value[key as keyof typeof demoConfigs.value];
const otherUpdatePromises = otherKeys.map((key) => {
const item =
systemConfigs.value[key as keyof typeof systemConfigs.value] ||
logoConfigs.value[key as keyof typeof logoConfigs.value] ||
securityPrivacyConfigs.value[key as keyof typeof securityPrivacyConfigs.value] ||
userAgreementConfigs.value[key as keyof typeof userAgreementConfigs.value] ||
demoConfigs.value[key as keyof typeof demoConfigs.value];
return item && item.id ? ParamsAPI.updateConfig(item.id, { ...item }) : Promise.resolve();
});
await Promise.all(otherUpdatePromises);
// 清除已提交的修改标记
keysToSubmit.forEach(key => {
keysToSubmit.forEach((key) => {
delete modifiedFields[key];
});
// 重新加载配置数据
await configStore.getConfig();
initializeLists();
} catch (error) {
console.error('保存失败:', error);
console.error("保存失败:", error);
}
};
@@ -398,22 +433,27 @@ const submitChanges = async () => {
const resetForm = () => {
// 重置动态列表
initializeLists();
// 重置其他配置项
const keysToReset = Object.keys(modifiedFields);
for (const key of keysToReset) {
if (systemConfigs.value[key as keyof typeof systemConfigs.value]) {
systemConfigs.value[key as keyof typeof systemConfigs.value].config_value = configStore.configData[key as keyof typeof configStore.configData]?.config_value || '';
systemConfigs.value[key as keyof typeof systemConfigs.value].config_value =
configStore.configData[key as keyof typeof configStore.configData]?.config_value || "";
} else if (logoConfigs.value[key as keyof typeof logoConfigs.value]) {
logoConfigs.value[key as keyof typeof logoConfigs.value].config_value = configStore.configData[key as keyof typeof configStore.configData]?.config_value || '';
logoConfigs.value[key as keyof typeof logoConfigs.value].config_value =
configStore.configData[key as keyof typeof configStore.configData]?.config_value || "";
} else if (securityPrivacyConfigs.value[key as keyof typeof securityPrivacyConfigs.value]) {
securityPrivacyConfigs.value[key as keyof typeof securityPrivacyConfigs.value].config_value = configStore.configData[key as keyof typeof configStore.configData]?.config_value || '';
securityPrivacyConfigs.value[key as keyof typeof securityPrivacyConfigs.value].config_value =
configStore.configData[key as keyof typeof configStore.configData]?.config_value || "";
} else if (userAgreementConfigs.value[key as keyof typeof userAgreementConfigs.value]) {
userAgreementConfigs.value[key as keyof typeof userAgreementConfigs.value].config_value = configStore.configData[key as keyof typeof configStore.configData]?.config_value || '';
userAgreementConfigs.value[key as keyof typeof userAgreementConfigs.value].config_value =
configStore.configData[key as keyof typeof configStore.configData]?.config_value || "";
} else if (demoConfigs.value[key as keyof typeof demoConfigs.value]) {
// 除了IP白名单外的演示配置项
if (key !== 'ip_white_list') {
demoConfigs.value[key as keyof typeof demoConfigs.value].config_value = configStore.configData[key as keyof typeof configStore.configData]?.config_value || '';
if (key !== "ip_white_list") {
demoConfigs.value[key as keyof typeof demoConfigs.value].config_value =
configStore.configData[key as keyof typeof configStore.configData]?.config_value || "";
}
}
delete modifiedFields[key];
@@ -462,130 +502,136 @@ const demoIpWhitelistItems = ref<ListItem[]>([]);
// 从配置数据初始化列表
const initializeLists = () => {
// 初始化接口白名单
const apiWhitelistStr = configStore.configData.white_api_list_path?.config_value || '';
const apiWhitelistStr = configStore.configData.white_api_list_path?.config_value || "";
try {
// 尝试解析为JSON数组
const apiWhitelistArray = JSON.parse(apiWhitelistStr);
if (Array.isArray(apiWhitelistArray)) {
apiWhitelistItems.value = apiWhitelistArray
.filter(item => typeof item === 'string' && item.trim())
.map(item => ({ id: generateId(), value: item.trim() }));
.filter((item) => typeof item === "string" && item.trim())
.map((item) => ({ id: generateId(), value: item.trim() }));
} else {
// 如果不是数组,回退到按换行符分割
apiWhitelistItems.value = apiWhitelistStr
? apiWhitelistStr.split('\n')
.filter(item => item.trim())
.map(item => ({ id: generateId(), value: item.trim() }))
: [{ id: generateId(), value: '' }];
? apiWhitelistStr
.split("\n")
.filter((item) => item.trim())
.map((item) => ({ id: generateId(), value: item.trim() }))
: [{ id: generateId(), value: "" }];
}
} catch {
// 解析失败,回退到按换行符分割
apiWhitelistItems.value = apiWhitelistStr
? apiWhitelistStr.split('\n')
.filter(item => item.trim())
.map(item => ({ id: generateId(), value: item.trim() }))
: [{ id: generateId(), value: '' }];
? apiWhitelistStr
.split("\n")
.filter((item) => item.trim())
.map((item) => ({ id: generateId(), value: item.trim() }))
: [{ id: generateId(), value: "" }];
}
// 初始化IP黑名单
const ipBlacklistStr = configStore.configData.ip_black_list?.config_value || '';
const ipBlacklistStr = configStore.configData.ip_black_list?.config_value || "";
try {
// 尝试解析为JSON数组
const ipBlacklistArray = JSON.parse(ipBlacklistStr);
if (Array.isArray(ipBlacklistArray)) {
ipBlacklistItems.value = ipBlacklistArray
.filter(item => typeof item === 'string' && item.trim())
.map(item => ({ id: generateId(), value: item.trim() }));
.filter((item) => typeof item === "string" && item.trim())
.map((item) => ({ id: generateId(), value: item.trim() }));
} else {
// 如果不是数组,回退到按换行符分割
ipBlacklistItems.value = ipBlacklistStr
? ipBlacklistStr.split('\n')
.filter(item => item.trim())
.map(item => ({ id: generateId(), value: item.trim() }))
: [{ id: generateId(), value: '' }];
? ipBlacklistStr
.split("\n")
.filter((item) => item.trim())
.map((item) => ({ id: generateId(), value: item.trim() }))
: [{ id: generateId(), value: "" }];
}
} catch {
// 解析失败,回退到按换行符分割
ipBlacklistItems.value = ipBlacklistStr
? ipBlacklistStr.split('\n')
.filter(item => item.trim())
.map(item => ({ id: generateId(), value: item.trim() }))
: [{ id: generateId(), value: '' }];
? ipBlacklistStr
.split("\n")
.filter((item) => item.trim())
.map((item) => ({ id: generateId(), value: item.trim() }))
: [{ id: generateId(), value: "" }];
}
// 初始化演示环境IP白名单
const demoIpWhitelistStr = configStore.configData.ip_white_list?.config_value || '';
const demoIpWhitelistStr = configStore.configData.ip_white_list?.config_value || "";
try {
// 尝试解析为JSON数组
const demoIpWhitelistArray = JSON.parse(demoIpWhitelistStr);
if (Array.isArray(demoIpWhitelistArray)) {
demoIpWhitelistItems.value = demoIpWhitelistArray
.filter(item => typeof item === 'string' && item.trim())
.map(item => ({ id: generateId(), value: item.trim() }));
.filter((item) => typeof item === "string" && item.trim())
.map((item) => ({ id: generateId(), value: item.trim() }));
} else {
// 如果不是数组,回退到按换行符分割
demoIpWhitelistItems.value = demoIpWhitelistStr
? demoIpWhitelistStr.split('\n')
.filter(item => item.trim())
.map(item => ({ id: generateId(), value: item.trim() }))
: [{ id: generateId(), value: '' }];
? demoIpWhitelistStr
.split("\n")
.filter((item) => item.trim())
.map((item) => ({ id: generateId(), value: item.trim() }))
: [{ id: generateId(), value: "" }];
}
} catch {
// 解析失败,回退到按换行符分割
demoIpWhitelistItems.value = demoIpWhitelistStr
? demoIpWhitelistStr.split('\n')
.filter(item => item.trim())
.map(item => ({ id: generateId(), value: item.trim() }))
: [{ id: generateId(), value: '' }];
? demoIpWhitelistStr
.split("\n")
.filter((item) => item.trim())
.map((item) => ({ id: generateId(), value: item.trim() }))
: [{ id: generateId(), value: "" }];
}
};
// 添加接口白名单项
const addApiWhitelistItem = () => {
apiWhitelistItems.value.push({ id: generateId(), value: '' });
markModified('white_api_list_path');
apiWhitelistItems.value.push({ id: generateId(), value: "" });
markModified("white_api_list_path");
};
// 移除接口白名单项
const removeApiWhitelistItem = (id: string) => {
if (apiWhitelistItems.value.length <= 1) {
ElMessage.warning('至少需要保留一个接口白名单配置');
ElMessage.warning("至少需要保留一个接口白名单配置");
return;
}
apiWhitelistItems.value = apiWhitelistItems.value.filter(item => item.id !== id);
markModified('white_api_list_path');
apiWhitelistItems.value = apiWhitelistItems.value.filter((item) => item.id !== id);
markModified("white_api_list_path");
};
// 添加IP黑名单项
const addIpBlacklistItem = () => {
ipBlacklistItems.value.push({ id: generateId(), value: '' });
markModified('ip_black_list');
ipBlacklistItems.value.push({ id: generateId(), value: "" });
markModified("ip_black_list");
};
// 移除IP黑名单项
const removeIpBlacklistItem = (id: string) => {
if (ipBlacklistItems.value.length <= 1) {
ElMessage.warning('至少需要保留一个IP黑名单配置');
ElMessage.warning("至少需要保留一个IP黑名单配置");
return;
}
ipBlacklistItems.value = ipBlacklistItems.value.filter(item => item.id !== id);
markModified('ip_black_list');
ipBlacklistItems.value = ipBlacklistItems.value.filter((item) => item.id !== id);
markModified("ip_black_list");
};
// 添加演示环境IP白名单项
const addDemoIpWhitelistItem = () => {
demoIpWhitelistItems.value.push({ id: generateId(), value: '' });
markModified('ip_white_list');
demoIpWhitelistItems.value.push({ id: generateId(), value: "" });
markModified("ip_white_list");
};
// 移除演示环境IP白名单项
const removeDemoIpWhitelistItem = (id: string) => {
if (demoIpWhitelistItems.value.length <= 1) {
ElMessage.warning('至少需要保留一个IP白名单配置');
ElMessage.warning("至少需要保留一个IP白名单配置");
return;
}
demoIpWhitelistItems.value = demoIpWhitelistItems.value.filter(item => item.id !== id);
markModified('ip_white_list');
demoIpWhitelistItems.value = demoIpWhitelistItems.value.filter((item) => item.id !== id);
markModified("ip_white_list");
};
// 接口白名单配置项
@@ -641,15 +687,14 @@ const handleUploadSuccess = (fileInfo: UploadFilePath, type: string) => {
// 图片上传失败的回调处理
const handleUploadError = (error: any) => {
console.error('上传失败:', error.message || '未知错误');
ElMessage.error(`上传失败:${error.message || '请稍后重试'}`);
console.error("上传失败:", error.message || "未知错误");
ElMessage.error(`上传失败:${error.message || "请稍后重试"}`);
};
// 生命周期钩子
onMounted(() => {
configStore.getConfig();
});
</script>
<style lang="scss" scoped>
+312 -103
View File
@@ -3,7 +3,13 @@
<div class="app-container">
<!-- 搜索区域 -->
<div class="search-container">
<el-form ref="queryFormRef" :model="queryFormData" :inline="true" label-suffix=":" @submit.prevent="handleQuery" >
<el-form
ref="queryFormRef"
:model="queryFormData"
:inline="true"
label-suffix=":"
@submit.prevent="handleQuery"
>
<el-form-item prop="config_name" label="配置名称">
<el-input v-model="queryFormData.config_name" placeholder="请输入配置名称" clearable />
</el-form-item>
@@ -11,28 +17,43 @@
<el-input v-model="queryFormData.config_key" placeholder="请输入配置键名" clearable />
</el-form-item>
<el-form-item prop="config_type" label="系统内置">
<el-select v-model="queryFormData.config_type" placeholder="请选择系统内置" style="width: 167.5px" clearable>
<el-select
v-model="queryFormData.config_type"
placeholder="请选择系统内置"
style="width: 167.5px"
clearable
>
<el-option value="true" label="是" />
<el-option value="false" label="否" />
</el-select>
</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 v-if="isExpand" prop="creator" label="创建人">
<UserTableSelect
v-model="queryFormData.creator"
@confirm-click="handleConfirm"
@clear-click="handleQuery"
v-model="queryFormData.creator"
@confirm-click="handleConfirm"
@clear-click="handleQuery"
/>
</el-form-item>
<el-form-item class="search-buttons">
<el-button v-hasPerm="['module_system:config:query']" type="primary" icon="search" native-type="submit">查询</el-button>
<el-button v-hasPerm="['module_system:config:query']" icon="refresh" @click="handleResetQuery">重置</el-button>
<el-button
v-hasPerm="['module_system:config:query']"
type="primary"
icon="search"
native-type="submit"
>
查询
</el-button>
<el-button
v-hasPerm="['module_system:config:query']"
icon="refresh"
@click="handleResetQuery"
>
重置
</el-button>
<!-- 展开/收起 -->
<template v-if="isExpandable">
<el-link class="ml-3" type="primary" underline="never" @click="isExpand = !isExpand">
@@ -69,10 +90,25 @@
<div class="data-table__toolbar--left">
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPerm="['module_system:config:create']" type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
<el-button
v-hasPerm="['module_system:config:create']"
type="success"
icon="plus"
@click="handleOpenDialog('create')"
>
新增
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPerm="['module_system:config:delete']" type="danger" icon="delete" :disabled="selectIds.length === 0" @click="handleDelete(selectIds)">批量删除</el-button>
<el-button
v-hasPerm="['module_system:config:delete']"
type="danger"
icon="delete"
:disabled="selectIds.length === 0"
@click="handleDelete(selectIds)"
>
批量删除
</el-button>
</el-col>
</el-row>
</div>
@@ -80,12 +116,24 @@
<el-row :gutter="10">
<el-col :span="1.5">
<el-tooltip content="导出">
<el-button v-hasPerm="['module_system:config:export']" type="warning" icon="download" circle @click="handleOpenExportsModal"/>
<el-button
v-hasPerm="['module_system:config:export']"
type="warning"
icon="download"
circle
@click="handleOpenExportsModal"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-tooltip content="刷新">
<el-button v-hasPerm="['module_system:config:refresh']" type="primary" icon="refresh" circle @click="handleRefresh"/>
<el-button
v-hasPerm="['module_system:config:refresh']"
type="primary"
icon="refresh"
circle
@click="handleRefresh"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
@@ -93,7 +141,11 @@
<el-button type="default" icon="operation" circle />
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item v-for="column in tableColumns" :key="column.prop" :command="column">
<el-dropdown-item
v-for="column in tableColumns"
:key="column.prop"
:command="column"
>
<el-checkbox v-model="column.show">{{ column.label }}</el-checkbox>
</el-dropdown-item>
</el-dropdown-menu>
@@ -105,69 +157,203 @@
</div>
<!-- 表格区域:系统配置列表 -->
<el-table ref="dataTableRef" v-loading="loading" :data="pageTableData" highlight-current-row class="data-table__content" :height="450" border stripe @selection-change="handleSelectionChange">
<el-table
ref="dataTableRef"
v-loading="loading"
:data="pageTableData"
highlight-current-row
class="data-table__content"
:height="450"
border
stripe
@selection-change="handleSelectionChange"
>
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection" min-width="55" align="center" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" type="index" fixed label="序号" min-width="60">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'selection')?.show"
type="selection"
min-width="55"
align="center"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'index')?.show"
type="index"
fixed
label="序号"
min-width="60"
>
<template #default="scope">
{{ (queryFormData.page_no - 1) * queryFormData.page_size + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'config_name')?.show" key="config_name" label="配置名称" prop="config_name" min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'config_key')?.show" key="config_key" label="配置键" prop="config_key" min-width="200" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'config_value')?.show" key="config_value" label="配置值" prop="config_value" min-width="200" show-overflow-tooltip />
<el-table-column v-if="tableColumns.find(col => col.prop === 'config_type')?.show" key="config_type" label="系统内置" prop="config_type" min-width="100">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'config_name')?.show"
key="config_name"
label="配置名称"
prop="config_name"
min-width="100"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'config_key')?.show"
key="config_key"
label="配置键"
prop="config_key"
min-width="200"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'config_value')?.show"
key="config_value"
label="配置值"
prop="config_value"
min-width="200"
show-overflow-tooltip
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'config_type')?.show"
key="config_type"
label="系统内置"
prop="config_type"
min-width="100"
>
<template #default="scope">
<el-tag v-if="scope.row.config_type" type="success">是</el-tag>
<el-tag v-else type="danger">否</el-tag>
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'description')?.show" key="description" label="描述" prop="description" min-width="120" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" key="created_at" label="创建时间" prop="created_at" min-width="200" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'updated_at')?.show" key="updated_at" label="更新时间" prop="updated_at" min-width="200" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'creator')?.show" key="creator" label="创建人" min-width="120">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'description')?.show"
key="description"
label="描述"
prop="description"
min-width="120"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'created_at')?.show"
key="created_at"
label="创建时间"
prop="created_at"
min-width="200"
sortable
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'updated_at')?.show"
key="updated_at"
label="更新时间"
prop="updated_at"
min-width="200"
sortable
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'creator')?.show"
key="creator"
label="创建人"
min-width="120"
>
<template #default="scope">
{{ scope.row.creator?.name }}
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right" label="操作" align="center" min-width="200">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'operation')?.show"
fixed="right"
label="操作"
align="center"
min-width="200"
>
<template #default="scope">
<el-button v-hasPerm="['module_system:config:detail']" type="info" size="small" link icon="document" @click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
<el-button v-hasPerm="['module_system:config:update']" type="primary" size="small" link icon="edit" @click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
<el-button v-hasPerm="['module_system:config:delete']" type="danger" size="small" link icon="delete" @click="handleDelete([scope.row.id])">删除</el-button>
<el-button
v-hasPerm="['module_system:config:detail']"
type="info"
size="small"
link
icon="document"
@click="handleOpenDialog('detail', scope.row.id)"
>
详情
</el-button>
<el-button
v-hasPerm="['module_system:config:update']"
type="primary"
size="small"
link
icon="edit"
@click="handleOpenDialog('update', scope.row.id)"
>
编辑
</el-button>
<el-button
v-hasPerm="['module_system:config:delete']"
type="danger"
size="small"
link
icon="delete"
@click="handleDelete([scope.row.id])"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<template #footer>
<pagination v-model:total="total" v-model:page="queryFormData.page_no" v-model:limit="queryFormData.page_size" @pagination="loadingData" />
<pagination
v-model:total="total"
v-model:page="queryFormData.page_no"
v-model:limit="queryFormData.page_size"
@pagination="loadingData"
/>
</template>
</el-card>
<!-- 弹窗区域 -->
<el-dialog v-model="dialogVisible.visible" :title="dialogVisible.title" @close="handleCloseDialog">
<el-dialog
v-model="dialogVisible.visible"
:title="dialogVisible.title"
@close="handleCloseDialog"
>
<!-- 详情 -->
<template v-if="dialogVisible.type === 'detail'">
<el-descriptions :column="4" border>
<el-descriptions-item label="配置名称" :span="2">{{ detailFormData.config_name }}</el-descriptions-item>
<el-descriptions-item label="配置名称" :span="2">
{{ detailFormData.config_name }}
</el-descriptions-item>
<el-descriptions-item label="系统内置" :span="2">
<el-tag v-if="detailFormData.config_type" type="success">是</el-tag>
<el-tag v-else type="danger">否</el-tag>
</el-descriptions-item>
<el-descriptions-item label="配置键" :span="2">{{ detailFormData.config_key }}</el-descriptions-item>
<el-descriptions-item label="配置值" :span="2">{{ detailFormData.config_value }}</el-descriptions-item>
<el-descriptions-item label="描述" :span="2">{{ detailFormData.description }}</el-descriptions-item>
<el-descriptions-item label="创建人" :span="2">{{ detailFormData.creator?.name }}</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">{{ detailFormData.created_at }}</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">{{ detailFormData.updated_at }}</el-descriptions-item>
<el-descriptions-item label="配置键" :span="2">
{{ detailFormData.config_key }}
</el-descriptions-item>
<el-descriptions-item label="配置值" :span="2">
{{ detailFormData.config_value }}
</el-descriptions-item>
<el-descriptions-item label="描述" :span="2">
{{ detailFormData.description }}
</el-descriptions-item>
<el-descriptions-item label="创建人" :span="2">
{{ detailFormData.creator?.name }}
</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">
{{ detailFormData.created_at }}
</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">
{{ detailFormData.updated_at }}
</el-descriptions-item>
</el-descriptions>
</template>
<!-- 新增、编辑表单 -->
<template v-else>
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-suffix=":" label-width="auto" label-position="right">
<el-form
ref="dataFormRef"
:model="formData"
:rules="rules"
label-suffix=":"
label-width="auto"
label-position="right"
>
<el-form-item label="配置名称" prop="config_name">
<el-input v-model="formData.config_name" placeholder="请输入配置名称" :maxlength="50" />
</el-form-item>
@@ -184,7 +370,14 @@
</el-radio-group>
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="formData.description" :rows="4" :maxlength="100" show-word-limit type="textarea" placeholder="请输入描述" />
<el-input
v-model="formData.description"
:rows="4"
:maxlength="100"
show-word-limit
type="textarea"
placeholder="请输入描述"
/>
</el-form-item>
</el-form>
</template>
@@ -193,14 +386,31 @@
<div class="dialog-footer">
<!-- 详情弹窗不需要确定按钮的提交逻辑 -->
<el-button @click="handleCloseDialog">取消</el-button>
<el-button v-if="dialogVisible.type !== 'detail'" v-hasPerm="['module_system:config:create']" type="primary" @click="handleSubmit">确定</el-button>
<el-button v-else v-hasPerm="['module_system:config:detail']" type="primary" @click="handleCloseDialog">确定</el-button>
<el-button
v-if="dialogVisible.type !== 'detail'"
v-hasPerm="['module_system:config:create']"
type="primary"
@click="handleSubmit"
>
确定
</el-button>
<el-button
v-else
v-hasPerm="['module_system:config:detail']"
type="primary"
@click="handleCloseDialog"
>
确定
</el-button>
</div>
</template>
</el-dialog>
<ExportModal v-model="exportsDialogVisible" :content-config="curdContentConfig" :selection-data="selectionRows" />
<ExportModal
v-model="exportsDialogVisible"
:content-config="curdContentConfig"
:selection-data="selectionRows"
/>
</div>
</template>
@@ -232,21 +442,20 @@ const pageTableData = ref<ConfigTable[]>([]);
const exportsDialogVisible = ref(false);
const selectionRows = ref<ConfigTable[]>([]);
// 表格列配置
const tableColumns = ref([
{ prop: 'selection', label: '选择框', show: true },
{ prop: 'index', label: '序号', show: true },
{ prop: 'config_name', label: '配置名称', show: true },
{ prop: 'config_key', label: '配置键', show: true },
{ prop: 'config_value', label: '配置值', show: true },
{ prop: 'config_type', label: '系统内置', show: true },
{ prop: 'description', label: '描述', show: true },
{ prop: 'created_at', label: '创建时间', show: true },
{ prop: 'updated_at', label: '更新时间', show: true },
{ prop: 'creator', label: '创建人', show: true },
{ prop: 'operation', label: '操作', show: true }
])
{ prop: "selection", label: "选择框", show: true },
{ prop: "index", label: "序号", show: true },
{ prop: "config_name", label: "配置名称", show: true },
{ prop: "config_key", label: "配置键", show: true },
{ prop: "config_value", label: "配置值", show: true },
{ prop: "config_type", label: "系统内置", show: true },
{ prop: "description", label: "描述", show: true },
{ prop: "created_at", label: "创建时间", show: true },
{ prop: "updated_at", label: "更新时间", show: true },
{ prop: "creator", label: "创建人", show: true },
{ prop: "operation", label: "操作", show: true },
]);
// 详情表单
const detailFormData = ref<ConfigTable>({});
@@ -267,18 +476,18 @@ const queryFormData = reactive<ConfigPageQuery>({
// 编辑表单
const formData = reactive<ConfigForm>({
id: undefined,
config_name: '',
config_key: '',
config_value: '',
config_name: "",
config_key: "",
config_value: "",
config_type: false,
description: ''
description: "",
});
// 弹窗状态
const dialogVisible = reactive({
title: "",
visible: false,
type: 'create' as 'create' | 'update' | 'detail',
type: "create" as "create" | "update" | "detail",
});
// 表单验证规则
@@ -305,9 +514,9 @@ function handleDateRangeChange(range: [Date, Date]) {
}
// 列表刷新
async function handleRefresh () {
async function handleRefresh() {
await loadingData();
};
}
// 加载表格数据
async function loadingData() {
@@ -316,11 +525,9 @@ async function loadingData() {
const response = await ParamsAPI.getConfigList(queryFormData);
pageTableData.value = response.data.data.items;
total.value = response.data.data.total;
}
catch (error: any) {
} catch (error: any) {
console.error(error);
}
finally {
} finally {
loading.value = false;
}
}
@@ -350,12 +557,12 @@ async function handleResetQuery() {
// 定义初始表单数据常量
const initialFormData: ConfigForm = {
id: undefined,
config_name: '',
config_key: '',
config_value: '',
config_name: "",
config_key: "",
config_value: "",
config_type: false,
description: ''
}
description: "",
};
// 重置表单
async function resetForm() {
@@ -380,14 +587,14 @@ async function handleCloseDialog() {
}
// 打开系统配置弹窗
async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: number) {
async function handleOpenDialog(type: "create" | "update" | "detail", id?: number) {
dialogVisible.type = type;
if (id) {
const response = await ParamsAPI.getConfigDetail(id);
if (type === 'detail') {
if (type === "detail") {
dialogVisible.title = "系统配置详情";
Object.assign(detailFormData.value, response.data.data);
} else if (type === 'update') {
} else if (type === "update") {
dialogVisible.title = "修改系统配置";
Object.assign(formData, response.data.data);
}
@@ -408,7 +615,7 @@ async function handleSubmit() {
const id = formData.id;
if (id) {
try {
await ParamsAPI.updateConfig(id, formData)
await ParamsAPI.updateConfig(id, formData);
dialogVisible.visible = false;
resetForm();
handleResetQuery();
@@ -419,7 +626,7 @@ async function handleSubmit() {
}
} else {
try {
await ParamsAPI.createConfig(formData)
await ParamsAPI.createConfig(formData);
dialogVisible.visible = false;
resetForm();
handleResetQuery();
@@ -439,19 +646,21 @@ async function handleDelete(ids: number[]) {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await ParamsAPI.deleteConfig(ids);
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
})
.then(async () => {
try {
loading.value = true;
await ParamsAPI.deleteConfig(ids);
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
})
.catch(() => {
ElMessageBox.close();
});
}
// 导出弹窗
@@ -461,23 +670,23 @@ async function handleOpenExportsModal() {
// 导出字段
const exportColumns = [
{ prop: 'config_name', label: '配置名称' },
{ prop: 'config_key', label: '配置键' },
{ prop: 'config_value', label: '配置值' },
{ prop: 'config_type', label: '系统内置' },
{ prop: 'description', label: '描述' },
{ prop: 'created_at', label: '创建时间' },
{ prop: 'updated_at', label: '更新时间' },
{ prop: "config_name", label: "配置名称" },
{ prop: "config_key", label: "配置键" },
{ prop: "config_value", label: "配置值" },
{ prop: "config_type", label: "系统内置" },
{ prop: "description", label: "描述" },
{ prop: "created_at", label: "创建时间" },
{ prop: "updated_at", label: "更新时间" },
];
// 导入/导出配置(用于导出弹窗)
const curdContentConfig = {
permPrefix: 'module_system:config',
permPrefix: "module_system:config",
cols: exportColumns as any,
exportsAction: async (params: any) => {
const query: any = { ...params };
if (typeof query.config_type === 'string') {
query.config_type = query.config_type === 'true';
if (typeof query.config_type === "string") {
query.config_type = query.config_type === "true";
}
query.page_no = 1;
query.page_size = 1000;
@@ -3,34 +3,55 @@
<div class="app-container">
<!-- 搜索区域 -->
<div class="search-container">
<el-form ref="queryFormRef" :model="queryFormData" :inline="true" label-suffix=":" @submit.prevent="handleQuery" >
<el-form
ref="queryFormRef"
:model="queryFormData"
:inline="true"
label-suffix=":"
@submit.prevent="handleQuery"
>
<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>
<el-select
v-model="queryFormData.status"
placeholder="请选择状态"
style="width: 167.5px"
clearable
>
<el-option value="true" label="启用" />
<el-option value="false" label="停用" />
</el-select>
</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 v-if="isExpand" prop="creator" label="创建人">
<UserTableSelect
v-model="queryFormData.creator"
@confirm-click="handleConfirm"
@clear-click="handleQuery"
v-model="queryFormData.creator"
@confirm-click="handleConfirm"
@clear-click="handleQuery"
/>
</el-form-item>
<!-- 查询、重置、展开/收起按钮 -->
<el-form-item class="search-buttons">
<el-button v-hasPerm="['module_system:position:query']" type="primary" icon="search" native-type="submit">查询</el-button>
<el-button v-hasPerm="['module_system:position:query']" icon="refresh" @click="handleResetQuery">重置</el-button>
<el-button
v-hasPerm="['module_system:position:query']"
type="primary"
icon="search"
native-type="submit"
>
查询
</el-button>
<el-button
v-hasPerm="['module_system:position:query']"
icon="refresh"
@click="handleResetQuery"
>
重置
</el-button>
<!-- 展开/收起 -->
<template v-if="isExpandable">
<el-link class="ml-3" type="primary" underline="never" @click="isExpand = !isExpand">
@@ -67,18 +88,39 @@
<div class="data-table__toolbar--left">
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPerm="['module_system:position:create']" type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
<el-button
v-hasPerm="['module_system:position:create']"
type="success"
icon="plus"
@click="handleOpenDialog('create')"
>
新增
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPerm="['module_system:position:delete']" type="danger" icon="delete" :disabled="selectIds.length === 0" @click="handleDelete(selectIds)">批量删除</el-button>
<el-button
v-hasPerm="['module_system:position:delete']"
type="danger"
icon="delete"
:disabled="selectIds.length === 0"
@click="handleDelete(selectIds)"
>
批量删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-dropdown v-hasPerm="['module_system:position:patch']" trigger="click">
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">更多</el-button>
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">
更多
</el-button>
<template #dropdown>
<el-dropdown-menu v-hasPerm="['module_system:position:filter']">
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">批量启用</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">批量停用</el-dropdown-item>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">
批量启用
</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">
批量停用
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -89,12 +131,24 @@
<el-row :gutter="10">
<el-col :span="1.5">
<el-tooltip content="导出">
<el-button v-hasPerm="['module_system:position:export']" type="warning" icon="download" circle @click="handleOpenExportsModal" />
<el-button
v-hasPerm="['module_system:position:export']"
type="warning"
icon="download"
circle
@click="handleOpenExportsModal"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-tooltip content="刷新">
<el-button v-hasPerm="['module_system:position:refresh']" type="primary" icon="refresh" circle @click="handleRefresh" />
<el-button
v-hasPerm="['module_system:position:refresh']"
type="primary"
icon="refresh"
circle
@click="handleRefresh"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
@@ -114,55 +168,164 @@
</div>
<!-- 表格区域:系统配置列表 -->
<el-table ref="dataTableRef" v-loading="loading" :data="pageTableData" highlight-current-row class="data-table__content" :height="450" border stripe @selection-change="handleSelectionChange">
<el-table
ref="dataTableRef"
v-loading="loading"
:data="pageTableData"
highlight-current-row
class="data-table__content"
:height="450"
border
stripe
@selection-change="handleSelectionChange"
>
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection" min-width="55" align="center" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" type="index" fixed label="序号" min-width="60">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'selection')?.show"
type="selection"
min-width="55"
align="center"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'index')?.show"
type="index"
fixed
label="序号"
min-width="60"
>
<template #default="scope">
{{ (queryFormData.page_no - 1) * queryFormData.page_size + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'name')?.show" key="name" label="岗位名称" prop="name" min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'status')?.show" key="status" label="状态" prop="status" min-width="80">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'name')?.show"
key="name"
label="岗位名称"
prop="name"
min-width="100"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'status')?.show"
key="status"
label="状态"
prop="status"
min-width="80"
>
<template #default="scope">
<el-tag :type="scope.row.status === true ? 'success' : 'danger'">
{{ scope.row.status === true ? "启用" : "停用" }}
</el-tag>
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'order')?.show" key="order" label="岗位排序" prop="order" min-width="80" show-overflow-tooltip />
<el-table-column v-if="tableColumns.find(col => col.prop === 'description')?.show" key="description" label="描述" prop="description" min-width="120" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" key="created_at" label="创建时间" prop="created_at" min-width="200" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'updated_at')?.show" key="updated_at" label="更新时间" prop="updated_at" min-width="200" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'creator')?.show" key="creator" label="创建人" min-width="100">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'order')?.show"
key="order"
label="岗位排序"
prop="order"
min-width="80"
show-overflow-tooltip
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'description')?.show"
key="description"
label="描述"
prop="description"
min-width="120"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'created_at')?.show"
key="created_at"
label="创建时间"
prop="created_at"
min-width="200"
sortable
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'updated_at')?.show"
key="updated_at"
label="更新时间"
prop="updated_at"
min-width="200"
sortable
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'creator')?.show"
key="creator"
label="创建人"
min-width="100"
>
<template #default="scope">
{{ scope.row.creator?.name }}
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right" label="操作" align="center" min-width="200">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'operation')?.show"
fixed="right"
label="操作"
align="center"
min-width="200"
>
<template #default="scope">
<el-button v-hasPerm="['module_system:position:detail']" type="info" size="small" link icon="document" @click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
<el-button v-hasPerm="['module_system:position:update']" type="primary" size="small" link icon="edit" @click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
<el-button v-hasPerm="['module_system:position:delete']" type="danger" size="small" link icon="delete" @click="handleDelete([scope.row.id])">删除</el-button>
<el-button
v-hasPerm="['module_system:position:detail']"
type="info"
size="small"
link
icon="document"
@click="handleOpenDialog('detail', scope.row.id)"
>
详情
</el-button>
<el-button
v-hasPerm="['module_system:position:update']"
type="primary"
size="small"
link
icon="edit"
@click="handleOpenDialog('update', scope.row.id)"
>
编辑
</el-button>
<el-button
v-hasPerm="['module_system:position:delete']"
type="danger"
size="small"
link
icon="delete"
@click="handleDelete([scope.row.id])"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<template #footer>
<pagination v-model:total="total" v-model:page="queryFormData.page_no" v-model:limit="queryFormData.page_size" @pagination="loadingData" />
<pagination
v-model:total="total"
v-model:page="queryFormData.page_no"
v-model:limit="queryFormData.page_size"
@pagination="loadingData"
/>
</template>
</el-card>
<!-- 弹窗区域 -->
<el-dialog v-model="dialogVisible.visible" :title="dialogVisible.title" @close="handleCloseDialog">
<el-dialog
v-model="dialogVisible.visible"
:title="dialogVisible.title"
@close="handleCloseDialog"
>
<!-- 详情 -->
<template v-if="dialogVisible.type === 'detail'">
<el-descriptions :column="4" border>
<el-descriptions-item label="岗位名称" :span="2">{{ detailFormData.name }}</el-descriptions-item>
<el-descriptions-item label="岗位名称" :span="2">
{{ detailFormData.name }}
</el-descriptions-item>
<el-descriptions-item label="排序" :span="2">
{{ detailFormData.order }}
</el-descriptions-item>
@@ -170,15 +333,30 @@
<el-tag v-if="detailFormData.status" type="success">启用</el-tag>
<el-tag v-else type="danger">停用</el-tag>
</el-descriptions-item>
<el-descriptions-item label="创建人" :span="2">{{ detailFormData.creator?.name }}</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">{{ detailFormData.created_at }}</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">{{ detailFormData.updated_at }}</el-descriptions-item>
<el-descriptions-item label="描述" :span="4">{{ detailFormData.description }}</el-descriptions-item>
<el-descriptions-item label="创建人" :span="2">
{{ detailFormData.creator?.name }}
</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">
{{ detailFormData.created_at }}
</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">
{{ detailFormData.updated_at }}
</el-descriptions-item>
<el-descriptions-item label="描述" :span="4">
{{ detailFormData.description }}
</el-descriptions-item>
</el-descriptions>
</template>
<!-- 新增、编辑表单 -->
<template v-else>
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-suffix=":" label-width="auto" label-position="right">
<el-form
ref="dataFormRef"
:model="formData"
:rules="rules"
label-suffix=":"
label-width="auto"
label-position="right"
>
<el-form-item label="岗位名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入岗位名称" :maxlength="50" />
</el-form-item>
@@ -192,7 +370,14 @@
</el-radio-group>
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="formData.description" :rows="4" :maxlength="100" show-word-limit type="textarea" placeholder="请输入描述" />
<el-input
v-model="formData.description"
:rows="4"
:maxlength="100"
show-word-limit
type="textarea"
placeholder="请输入描述"
/>
</el-form-item>
</el-form>
</template>
@@ -201,14 +386,31 @@
<div class="dialog-footer">
<!-- 详情弹窗不需要确定按钮的提交逻辑 -->
<el-button @click="handleCloseDialog">取消</el-button>
<el-button v-if="dialogVisible.type !== 'detail'" v-hasPerm="['module_system:position:submit']" type="primary" @click="handleSubmit">确定</el-button>
<el-button v-else v-hasPerm="['module_system:position:detail']" type="primary" @click="handleCloseDialog">确定</el-button>
<el-button
v-if="dialogVisible.type !== 'detail'"
v-hasPerm="['module_system:position:submit']"
type="primary"
@click="handleSubmit"
>
确定
</el-button>
<el-button
v-else
v-hasPerm="['module_system:position:detail']"
type="primary"
@click="handleCloseDialog"
>
确定
</el-button>
</div>
</template>
</el-dialog>
<ExportModal v-model="exportsDialogVisible" :content-config="curdContentConfig" :selection-data="selectionRows" />
<ExportModal
v-model="exportsDialogVisible"
:content-config="curdContentConfig"
:selection-data="selectionRows"
/>
</div>
</template>
@@ -218,7 +420,11 @@ defineOptions({
inheritAttrs: false,
});
import PositionAPI, { PositionTable, PositionForm, PositionPageQuery } from "@/api/module_system/position";
import PositionAPI, {
PositionTable,
PositionForm,
PositionPageQuery,
} from "@/api/module_system/position";
import { useUserStore } from "@/store";
import UserTableSelect from "@/views/module_system/user/components/UserTableSelect.vue";
import ExportModal from "@/components/CURD/ExportModal.vue";
@@ -241,20 +447,19 @@ const pageTableData = ref<PositionTable[]>([]);
const exportsDialogVisible = ref(false);
const selectionRows = ref<PositionTable[]>([]);
// 表格列配置
const tableColumns = ref([
{ prop: 'selection', label: '选择框', show: true },
{ prop: 'index', label: '序号', show: true },
{ prop: 'name', label: '岗位名称', show: true },
{ prop: 'order', label: '岗位排序', show: true },
{ prop: 'status', label: '状态', show: true },
{ prop: 'description', label: '描述', show: true },
{ prop: 'created_at', label: '创建时间', show: true },
{ prop: 'updated_at', label: '更新时间', show: true },
{ prop: 'creator', label: '创建人', show: true },
{ prop: 'operation', label: '操作', show: true }
])
{ prop: "selection", label: "选择框", show: true },
{ prop: "index", label: "序号", show: true },
{ prop: "name", label: "岗位名称", show: true },
{ prop: "order", label: "岗位排序", show: true },
{ prop: "status", label: "状态", show: true },
{ prop: "description", label: "描述", show: true },
{ prop: "created_at", label: "创建时间", show: true },
{ prop: "updated_at", label: "更新时间", show: true },
{ prop: "creator", label: "创建人", show: true },
{ prop: "operation", label: "操作", show: true },
]);
// 详情表单
const detailFormData = ref<PositionTable>({});
@@ -263,7 +468,7 @@ const detailFormData = ref<PositionTable>({});
const queryFormData = reactive<PositionPageQuery>({
page_no: 1,
page_size: 10,
name: '',
name: "",
status: undefined,
start_time: undefined,
end_time: undefined,
@@ -278,13 +483,13 @@ const formData = reactive<PositionForm>({
order: 1,
status: true,
description: undefined,
})
});
// 弹窗状态
const dialogVisible = reactive({
title: "",
visible: false,
type: 'create' as 'create' | 'update' | 'detail',
type: "create" as "create" | "update" | "detail",
});
// 表单验证规则
@@ -310,10 +515,9 @@ function handleDateRangeChange(range: [Date, Date]) {
}
// 列表刷新
async function handleRefresh () {
async function handleRefresh() {
await loadingData();
};
}
// 加载表格数据
async function loadingData() {
@@ -322,11 +526,9 @@ async function loadingData() {
const response = await PositionAPI.getPositionList(queryFormData);
pageTableData.value = response.data.data.items;
total.value = response.data.data.total;
}
catch (error: any) {
} catch (error: any) {
console.error(error);
}
finally {
} finally {
loading.value = false;
}
}
@@ -360,7 +562,7 @@ const initialFormData: PositionForm = {
order: 1,
status: true,
description: undefined,
}
};
// 重置表单
async function resetForm() {
@@ -385,14 +587,14 @@ async function handleCloseDialog() {
}
// 打开系统配置弹窗
async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: number) {
async function handleOpenDialog(type: "create" | "update" | "detail", id?: number) {
dialogVisible.type = type;
if (id) {
const response = await PositionAPI.getPositionDetail(id);
if (type === 'detail') {
if (type === "detail") {
dialogVisible.title = "岗位详情";
Object.assign(detailFormData.value, response.data.data);
} else if (type === 'update') {
} else if (type === "update") {
dialogVisible.title = "修改岗位";
Object.assign(formData, response.data.data);
}
@@ -413,9 +615,9 @@ async function handleSubmit() {
const id = formData.id;
try {
if (id) {
await PositionAPI.updatePosition(id, { id, ...formData })
await PositionAPI.updatePosition(id, { id, ...formData });
} else {
await PositionAPI.createPosition(formData)
await PositionAPI.createPosition(formData);
}
dialogVisible.visible = false;
resetForm();
@@ -438,19 +640,21 @@ async function handleDelete(ids: number[]) {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await PositionAPI.deletePosition(ids);
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
})
.then(async () => {
try {
loading.value = true;
await PositionAPI.deletePosition(ids);
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
})
.catch(() => {
ElMessageBox.close();
});
}
// 导出
@@ -460,44 +664,46 @@ async function handleOpenExportsModal() {
// 批量启用/停用
async function handleMoreClick(status: boolean) {
if (selectIds.value.length) {
ElMessageBox.confirm(`确认${status ? '启用' : '停用'}该项数据?`, "警告", {
ElMessageBox.confirm(`确认${status ? "启用" : "停用"}该项数据?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await PositionAPI.batchAvailablePosition({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
})
.then(async () => {
try {
loading.value = true;
await PositionAPI.batchAvailablePosition({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
})
.catch(() => {
ElMessageBox.close();
});
}
}
// 导出字段
const exportColumns = [
{ prop: 'name', label: '岗位名称' },
{ prop: 'order', label: '岗位排序' },
{ prop: 'status', label: '状态' },
{ prop: 'description', label: '描述' },
{ prop: 'created_at', label: '创建时间' },
{ prop: 'updated_at', label: '更新时间' },
{ prop: "name", label: "岗位名称" },
{ prop: "order", label: "岗位排序" },
{ prop: "status", label: "状态" },
{ prop: "description", label: "描述" },
{ prop: "created_at", label: "创建时间" },
{ prop: "updated_at", label: "更新时间" },
];
// 导入/导出配置(用于导出)
const curdContentConfig = {
permPrefix: 'module_system:position',
permPrefix: "module_system:position",
cols: exportColumns as any,
exportsAction: async (params: any) => {
const query: any = { ...params };
if (typeof query.status === 'string') {
query.status = query.status === 'true';
if (typeof query.status === "string") {
query.status = query.status === "true";
}
query.page_no = 1;
query.page_size = 1000;
@@ -521,36 +727,13 @@ onMounted(() => {
<style lang="scss" scoped></style>
// 导出字段
const exportColumns = [
{ prop: 'name', label: '岗位名称' },
{ prop: 'order', label: '岗位排序' },
{ prop: 'status', label: '状态' },
{ prop: 'description', label: '描述' },
{ prop: 'created_at', label: '创建时间' },
{ prop: 'updated_at', label: '更新时间' },
];
// 导入/导出配置(仅用于导出)
const curdContentConfig = {
permPrefix: 'module_system:position',
cols: exportColumns as any,
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 = 1000;
const all: any[] = [];
while (true) {
const res = await PositionAPI.getPositionList(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 exportColumns = [ { prop: 'name', label: '岗位名称' }, { prop: 'order', label:
'岗位排序' }, { prop: 'status', label: '状态' }, { prop: 'description', label: '描述' }, { prop:
'created_at', label: '创建时间' }, { prop: 'updated_at', label: '更新时间' }, ]; //
导入/导出配置(仅用于导出) const curdContentConfig = { permPrefix: 'module_system:position', cols:
exportColumns as any, 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 = 1000; const all: any[] = []; while (true) { const res = await
PositionAPI.getPositionList(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;
@@ -1,16 +1,20 @@
<!-- 角色授权 -->
<template>
<!-- 分配权限弹窗 -->
<el-drawer v-model="drawerVisible" :title="'【' + props.roleName + '】权限分配'" :size="drawerSize">
<el-drawer
v-model="drawerVisible"
:title="'【' + props.roleName + '】权限分配'"
:size="drawerSize"
>
<el-container>
<!-- 数据权限 -->
<el-aside >
<el-aside>
<div class="border-r-1 border-r-[#f0f0f0] b-r-solid h-[100%] p-[20px] box-border">
<div class="flex items-center">
<div style="display: flex; gap: 10px; ">
<div style="width: 10px; background-color: #409eff;"></div>
<div style="display: flex; gap: 10px">
<div style="width: 10px; background-color: #409eff"></div>
<div>
<span style="font-size: 16px;">数据授权</span>
<span style="font-size: 16px">数据授权</span>
<el-tooltip placement="right">
<template #content>
<span>授权用户可操作的数据范围</span>
@@ -32,38 +36,41 @@
<el-option :key="4" label="全部数据权限" :value="4" />
<el-option :key="5" label="自定义数据权限" :value="5" />
</el-select>
</el-form-item>
</el-form>
<div v-if="permissionState.data_scope === 5 && deptTreeData.length" class="mt-5 max-h-[60vh] b-1 b-solid b-[#e5e7eb] p-10px overflow-auto box-border">
<el-input v-model="deptFilterText" placeholder="部门名称" />
<el-tree
ref="deptTreeRef"
node-key="value"
show-checkbox
:data="deptTreeData"
:filter-node-method="handleFilter"
default-expand-all
:highlight-current="true"
:check-strictly="!parentChildLinked"
style="height: calc(100% - 60px); overflow-y: auto; margin-top: 10px;"
@check="deptTreeCheck"
>
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
</el-tree>
</div>
</el-form-item>
</el-form>
<div
v-if="permissionState.data_scope === 5 && deptTreeData.length"
class="mt-5 max-h-[60vh] b-1 b-solid b-[#e5e7eb] p-10px overflow-auto box-border"
>
<el-input v-model="deptFilterText" placeholder="部门名称" />
<el-tree
ref="deptTreeRef"
node-key="value"
show-checkbox
:data="deptTreeData"
:filter-node-method="handleFilter"
default-expand-all
:highlight-current="true"
:check-strictly="!parentChildLinked"
style="height: calc(100% - 60px); overflow-y: auto; margin-top: 10px"
@check="deptTreeCheck"
>
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
</el-tree>
</div>
</div>
</div>
</el-aside>
<!-- 菜单权限 -->
<el-main>
<div style="display: flex; gap: 10px; ">
<div style="width: 10px; background-color: #409eff;"></div>
<div style="display: flex; gap: 10px">
<div style="width: 10px; background-color: #409eff"></div>
<div>
<span style="font-size: 16px;">菜单授权</span>
<span style="font-size: 16px">菜单授权</span>
<el-tooltip placement="right">
<template #content>
<span>授权用户可操作的菜单权限</span>
@@ -83,7 +90,11 @@
</template>
{{ isExpanded ? "收缩" : "展开" }}
</el-button>
<el-checkbox v-model="parentChildLinked" class="ml-5" @change="handleParentChildLinkedChange">
<el-checkbox
v-model="parentChildLinked"
class="ml-5"
@change="handleParentChildLinkedChange"
>
父子联动
</el-checkbox>
@@ -99,16 +110,16 @@
</div>
<div class="mt-5 max-h-[65vh] b-1 b-solid b-[#e5e7eb] p-10px overflow-auto box-border">
<el-tree
ref="permTreeRef"
node-key="value"
show-checkbox
<el-tree
ref="permTreeRef"
node-key="value"
show-checkbox
:data="menuTreeData"
:filter-node-method="handleFilter"
:filter-node-method="handleFilter"
default-expand-all
:highlight-current="true"
:check-strictly="!parentChildLinked"
style="height: calc(100% - 60px); overflow: auto; margin-top: 10px;"
style="height: calc(100% - 60px); overflow: auto; margin-top: 10px"
@check="menuTreeCheck"
>
<template #empty>
@@ -133,46 +144,50 @@
const props = defineProps({
roleName: {
type: String,
required: true
required: true,
},
roleId: {
type: Number,
required: true
required: true,
},
modelValue: {
type: Boolean,
required: true
}
})
required: true,
},
});
import { listToTree, formatTree } from "@/utils/common";
import RoleAPI, { permissionDataType, permissionDeptType, permissionMenuType } from "@/api/module_system/role";
import RoleAPI, {
permissionDataType,
permissionDeptType,
permissionMenuType,
} from "@/api/module_system/role";
import DeptAPI from "@/api/module_system/dept";
import MenuAPI from "@/api/module_system/menu";
import type { TreeInstance } from 'element-plus'
import type { TreeInstance } from "element-plus";
import { useAppStore } from "@/store/modules/app.store";
import { DeviceEnum } from "@/enums/settings/device.enum";
import { useUserStore } from "@/store";
const appStore = useAppStore();
const drawerSize = computed(() => (appStore.device === DeviceEnum.DESKTOP ? "800px" : "90%"));
const emit = defineEmits(['update:modelValue', 'saved'])
const emit = defineEmits(["update:modelValue", "saved"]);
const permTreeRef = ref<TreeInstance>();
const deptTreeRef = ref<TreeInstance>()
const deptFilterText = ref("")
const deptTreeRef = ref<TreeInstance>();
const deptFilterText = ref("");
const permFilterText = ref("");
const dataFormRef = ref();
const drawerVisible = computed({
get() {
return props.modelValue
return props.modelValue;
},
set(value) {
emit('update:modelValue', value)
}
})
emit("update:modelValue", value);
},
});
const isExpanded = ref(true);
const parentChildLinked = ref(false)
const parentChildLinked = ref(false);
const loading = ref<boolean>(false);
const deptTreeData = ref<permissionDeptType[]>([]);
const menuTreeData = ref<permissionMenuType[]>([]);
@@ -180,7 +195,7 @@ const permissionState = ref<permissionDataType>({
role_ids: [],
menu_ids: [],
data_scope: 1,
dept_ids: []
dept_ids: [],
});
// 初始化方法,用于打开抽屉并加载数据
@@ -202,13 +217,16 @@ const init = async () => {
// 更新权限状态
permissionState.value = {
role_ids: [props.roleId],
menu_ids: roleResponse.data.data.menus?.map(menu => menu.id) || [],
menu_ids: roleResponse.data.data.menus?.map((menu) => menu.id) || [],
data_scope: roleResponse.data.data.data_scope || 1,
dept_ids: roleResponse.data.data.depts?.map(dept => dept.id) || []
dept_ids: roleResponse.data.data.depts?.map((dept) => dept.id) || [],
};
// 根据保存的权限数据判断是否应该开启父子联动
parentChildLinked.value = checkParentChildLinked(permissionState.value.menu_ids, menuTreeData.value);
parentChildLinked.value = checkParentChildLinked(
permissionState.value.menu_ids,
menuTreeData.value
);
// 回显菜单树选中项
if (permTreeRef.value) {
@@ -220,14 +238,13 @@ const init = async () => {
// await 一定不能丢,否则到导致初始化时候deptTreeRef.value 为 undefined
await deptTreeRef.value.setCheckedKeys(permissionState.value.dept_ids);
}
} catch (error: any) {
ElMessage.error('获取权限数据失败: ' + error.message);
ElMessage.error("获取权限数据失败: " + error.message);
} finally {
loading.value = false;
// handleCancel()
}
}
};
// 取消按钮处理函数
function handleCancel() {
@@ -235,30 +252,30 @@ function handleCancel() {
}
// 保存权限分配
async function handleDrawerSave () {
async function handleDrawerSave() {
try {
if (props.roleId === 1) {
ElMessage.warning('系统默认角色,不可操作');
ElMessage.warning("系统默认角色,不可操作");
return;
}
loading.value = true;
// 构造提交数据
const submitData : permissionDataType = {
const submitData: permissionDataType = {
role_ids: [props.roleId],
menu_ids: (permTreeRef.value?.getCheckedKeys() || []).map(key => Number(key)),
menu_ids: (permTreeRef.value?.getCheckedKeys() || []).map((key) => Number(key)),
data_scope: permissionState.value.data_scope,
dept_ids: (deptTreeRef.value?.getCheckedKeys() || []).map(key => Number(key))
dept_ids: (deptTreeRef.value?.getCheckedKeys() || []).map((key) => Number(key)),
};
await RoleAPI.setPermission(submitData)
await RoleAPI.setPermission(submitData);
// 更新全局用户状态,刷新权限信息
const userStore = useUserStore();
await userStore.getUserInfo();
drawerVisible.value = false;
emit('saved');
emit("saved");
} catch (error: any) {
console.error(error);
} finally {
@@ -269,12 +286,12 @@ async function handleDrawerSave () {
// 部门树选择回调
const deptTreeCheck = (checkedIds: number[]) => {
permissionState.value.dept_ids = checkedIds;
}
};
// 菜单选择变更回调
const menuTreeCheck = (checkedIds: number[]) => {
permissionState.value.menu_ids = checkedIds;
}
};
// 展开/收缩 菜单权限树
function togglePermTree() {
@@ -293,7 +310,7 @@ function togglePermTree() {
// 部门筛选
watch(deptFilterText, (val) => {
deptTreeRef.value!.filter(val);
})
});
// 菜单筛选
watch(permFilterText, (val) => {
@@ -301,10 +318,7 @@ watch(permFilterText, (val) => {
});
// 树节点过滤
function handleFilter(
value: string,
data: {[key: string]: any;}
) {
function handleFilter(value: string, data: { [key: string]: any }) {
if (!value) return true;
return data.label.includes(value);
}
@@ -312,11 +326,11 @@ function handleFilter(
// 检查权限数据是否遵循父子联动模式
function checkParentChildLinked(menuIds: number[], menuTreeData: permissionMenuType[]): boolean {
if (!menuIds.length || !menuTreeData.length) return false;
// 创建一个映射来快速查找菜单项
const menuMap = new Map<number, permissionMenuType>();
const buildMenuMap = (menus: permissionMenuType[]) => {
menus.forEach(menu => {
menus.forEach((menu) => {
menuMap.set(menu.id, menu);
if (menu.children) {
buildMenuMap(menu.children);
@@ -324,23 +338,23 @@ function checkParentChildLinked(menuIds: number[], menuTreeData: permissionMenuT
});
};
buildMenuMap(menuTreeData);
let hasParentChildConflict = false;
// 检查每个选中的菜单项
for (const menuId of menuIds) {
const menu = menuMap.get(menuId);
if (!menu) continue;
// 如果选中了父菜单,检查是否有子菜单未被选中
if (menu.children && menu.children.length > 0) {
const hasUnselectedChildren = menu.children.some(child => !menuIds.includes(child.id));
const hasUnselectedChildren = menu.children.some((child) => !menuIds.includes(child.id));
if (hasUnselectedChildren) {
hasParentChildConflict = true;
break; // 发现冲突,直接返回false
}
}
// 如果选中了子菜单,检查父菜单是否也被选中
const parentMenu = findParentMenu(menuId, menuTreeData);
if (parentMenu && !menuIds.includes(parentMenu.id)) {
@@ -348,13 +362,16 @@ function checkParentChildLinked(menuIds: number[], menuTreeData: permissionMenuT
break; // 发现冲突,直接返回false
}
}
// 如果没有发现父子冲突,说明是父子联动模式
return !hasParentChildConflict;
}
// 查找父菜单
function findParentMenu(menuId: number, menuTreeData: permissionMenuType[]): permissionMenuType | null {
function findParentMenu(
menuId: number,
menuTreeData: permissionMenuType[]
): permissionMenuType | null {
for (const menu of menuTreeData) {
if (menu.children) {
for (const child of menu.children) {
@@ -377,7 +394,7 @@ function handleParentChildLinkedChange(val: any) {
onMounted(async () => {
await init();
})
});
</script>
<style lang="scss" scoped></style>
+353 -146
View File
@@ -3,34 +3,55 @@
<div class="app-container">
<!-- 搜索区域 -->
<div class="search-container">
<el-form ref="queryFormRef" :model="queryFormData" :inline="true" label-suffix=":" @submit.prevent="handleQuery" >
<el-form
ref="queryFormRef"
:model="queryFormData"
:inline="true"
label-suffix=":"
@submit.prevent="handleQuery"
>
<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>
<el-select
v-model="queryFormData.status"
placeholder="请选择状态"
style="width: 167.5px"
clearable
>
<el-option value="true" label="启用" />
<el-option value="false" label="停用" />
</el-select>
</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 v-if="isExpand" prop="creator" label="创建人">
<UserTableSelect
v-model="queryFormData.creator"
@confirm-click="handleConfirm"
@clear-click="handleQuery"
v-model="queryFormData.creator"
@confirm-click="handleConfirm"
@clear-click="handleQuery"
/>
</el-form-item>
<!-- 查询、重置、展开/收起按钮 -->
<el-form-item class="search-buttons">
<el-button v-hasPerm="['module_system:role:query']" type="primary" icon="search" native-type="submit">查询</el-button>
<el-button v-hasPerm="['module_system:role:query']" icon="refresh" @click="handleResetQuery">重置</el-button>
<el-button
v-hasPerm="['module_system:role:query']"
type="primary"
icon="search"
native-type="submit"
>
查询
</el-button>
<el-button
v-hasPerm="['module_system:role:query']"
icon="refresh"
@click="handleResetQuery"
>
重置
</el-button>
<!-- 展开/收起 -->
<template v-if="isExpandable">
<el-link class="ml-3" type="primary" underline="never" @click="isExpand = !isExpand">
@@ -67,10 +88,25 @@
<div class="data-table__toolbar--left">
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPerm="['module_system:role:create']" type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
<el-button
v-hasPerm="['module_system:role:create']"
type="success"
icon="plus"
@click="handleOpenDialog('create')"
>
新增
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPerm="['module_system:role:delete']" type="danger" icon="delete" :disabled="selectIds.length === 0" @click="handleDelete(selectIds)">批量删除</el-button>
<el-button
v-hasPerm="['module_system:role:delete']"
type="danger"
icon="delete"
:disabled="selectIds.length === 0"
@click="handleDelete(selectIds)"
>
批量删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-dropdown v-hasPerm="['module_system:role:patch']" trigger="click">
@@ -79,8 +115,12 @@
</el-button>
<template #dropdown>
<el-dropdown-menu v-hasPerm="['module_system:role:filter']">
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">批量启用</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">批量停用</el-dropdown-item>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">
批量启用
</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">
批量停用
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -91,12 +131,24 @@
<el-row :gutter="10">
<el-col :span="1.5">
<el-tooltip content="导出">
<el-button v-hasPerm="['module_system:role:export']" type="warning" icon="download" circle @click="handleOpenExportsModal" />
<el-button
v-hasPerm="['module_system:role:export']"
type="warning"
icon="download"
circle
@click="handleOpenExportsModal"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-tooltip content="刷新">
<el-button v-hasPerm="['module_system:role:refresh']" type="primary" icon="refresh" circle @click="handleRefresh" />
<el-button
v-hasPerm="['module_system:role:refresh']"
type="primary"
icon="refresh"
circle
@click="handleRefresh"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
@@ -115,128 +167,268 @@
</div>
</div>
<el-table ref="dataTableRef" v-loading="loading" :data="pageTableData" highlight-current-row class="data-table__content" :height="450" border stripe @selection-change="handleSelectionChange">
<el-table
ref="dataTableRef"
v-loading="loading"
:data="pageTableData"
highlight-current-row
class="data-table__content"
:height="450"
border
stripe
@selection-change="handleSelectionChange"
>
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection" width="55" align="center" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" type="index" fixed label="序号" width="60" >
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'selection')?.show"
type="selection"
width="55"
align="center"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'index')?.show"
type="index"
fixed
label="序号"
width="60"
>
<template #default="scope">
{{ (queryFormData.page_no - 1) * queryFormData.page_size + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'name')?.show" key="name" label="角色名称" prop="name" min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'code')?.show" key="code" label="角色编码" prop="code" min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'data_scope')?.show" key="data_scope" label="数据权限" prop="data_scope" min-width="200">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'name')?.show"
key="name"
label="角色名称"
prop="name"
min-width="100"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'code')?.show"
key="code"
label="角色编码"
prop="code"
min-width="100"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'data_scope')?.show"
key="data_scope"
label="数据权限"
prop="data_scope"
min-width="200"
>
<template #default="scope">
<el-tag v-if="scope.row.data_scope === 1" type="primary">仅本人数据权限</el-tag>
<el-tag v-else-if="scope.row.data_scope === 2" type="info">本部门数据权限</el-tag>
<el-tag v-else-if="scope.row.data_scope === 3" type="warning">本部门及以下数据权限</el-tag>
<el-tag v-else-if="scope.row.data_scope === 3" type="warning">
本部门及以下数据权限
</el-tag>
<el-tag v-else-if="scope.row.data_scope === 4" type="success">全部数据权限</el-tag>
<el-tag v-else type="danger" >自定义数据权限</el-tag>
<el-tag v-else type="danger">自定义数据权限</el-tag>
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'order')?.show" key="order" label="排序" prop="order" min-width="80" show-overflow-tooltip />
<el-table-column v-if="tableColumns.find(col => col.prop === 'status')?.show" key="status" label="状态" prop="status" min-width="80">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'order')?.show"
key="order"
label="排序"
prop="order"
min-width="80"
show-overflow-tooltip
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'status')?.show"
key="status"
label="状态"
prop="status"
min-width="80"
>
<template #default="scope">
<el-tag :type="scope.row.status === true ? 'success' : 'danger'">
{{ scope.row.status === true ? "启用" : "停用" }}
</el-tag>
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'description')?.show" key="description" label="描述" prop="description" min-width="100" />
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" key="created_at" label="创建时间" prop="created_at" min-width="200" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'updated_at')?.show" key="updated_at" label="更新时间" prop="updated_at" min-width="200" sortable />
<el-table-column v-if="tableColumns.find(col => col.prop === 'creator')?.show" key="creator" label="创建人" min-width="120">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'description')?.show"
key="description"
label="描述"
prop="description"
min-width="100"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'created_at')?.show"
key="created_at"
label="创建时间"
prop="created_at"
min-width="200"
sortable
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'updated_at')?.show"
key="updated_at"
label="更新时间"
prop="updated_at"
min-width="200"
sortable
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'creator')?.show"
key="creator"
label="创建人"
min-width="120"
>
<template #default="scope">
{{ scope.row.creator?.name }}
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right" label="操作" align="center" min-width="280">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'operation')?.show"
fixed="right"
label="操作"
align="center"
min-width="280"
>
<template #default="scope">
<el-button
v-hasPerm="['module_system:role:permission']"
type="warning"
size="small"
link
icon="position"
<el-button
v-hasPerm="['module_system:role:permission']"
type="warning"
size="small"
link
icon="position"
:disabled="scope.row.id === 1"
@click="scope.row.id === 1 ? ElMessage.warning('系统默认角色,不可操作') : handleOpenAssignPermDialog(scope.row.id, scope.row.name)"
>分配权限</el-button>
<el-button
@click="
scope.row.id === 1
? ElMessage.warning('系统默认角色,不可操作')
: handleOpenAssignPermDialog(scope.row.id, scope.row.name)
"
>
分配权限
</el-button>
<el-button
v-hasPerm="['module_system:role:detail']"
type="info"
size="small"
link
icon="document"
type="info"
size="small"
link
icon="document"
@click="handleOpenDialog('detail', scope.row.id)"
>详情</el-button>
<el-button
v-hasPerm="['module_system:role:update']"
type="primary"
size="small"
link
icon="edit"
>
详情
</el-button>
<el-button
v-hasPerm="['module_system:role:update']"
type="primary"
size="small"
link
icon="edit"
:disabled="scope.row.id === 1"
@click="scope.row.id === 1 ? ElMessage.warning('系统默认角色,不可操作') : handleOpenDialog('update', scope.row.id)"
>编辑</el-button>
<el-button
v-hasPerm="['module_system:role:delete']"
type="danger"
size="small"
link
icon="delete"
@click="
scope.row.id === 1
? ElMessage.warning('系统默认角色,不可操作')
: handleOpenDialog('update', scope.row.id)
"
>
编辑
</el-button>
<el-button
v-hasPerm="['module_system:role:delete']"
type="danger"
size="small"
link
icon="delete"
:disabled="scope.row.id === 1"
@click="scope.row.id === 1 ? ElMessage.warning('系统默认角色,不可操作') : handleDelete([scope.row.id])"
>删除</el-button>
@click="
scope.row.id === 1
? ElMessage.warning('系统默认角色,不可操作')
: handleDelete([scope.row.id])
"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<template #footer>
<pagination v-model:total="total" v-model:page="queryFormData.page_no" v-model:limit="queryFormData.page_size" @pagination="loadingData" />
<pagination
v-model:total="total"
v-model:page="queryFormData.page_no"
v-model:limit="queryFormData.page_size"
@pagination="loadingData"
/>
</template>
</el-card>
<!-- 角色表单弹窗 -->
<el-dialog v-model="dialogVisible.visible" :title="dialogVisible.title" @close="handleCloseDialog">
<el-dialog
v-model="dialogVisible.visible"
:title="dialogVisible.title"
@close="handleCloseDialog"
>
<!-- 详情 -->
<template v-if="dialogVisible.type === 'detail'">
<el-descriptions :column="4" border>
<el-descriptions-item label="角色名称" :span="2">{{ detailFormData.name }}</el-descriptions-item>
<el-descriptions-item label="角色名称" :span="2">
{{ detailFormData.name }}
</el-descriptions-item>
<el-descriptions-item label="排序" :span="2">
{{ detailFormData.order }}
</el-descriptions-item>
<el-descriptions-item label="角色编码" :span="2">{{ detailFormData.code }}</el-descriptions-item>
<el-descriptions-item label="角色编码" :span="2">
{{ detailFormData.code }}
</el-descriptions-item>
<el-descriptions-item label="数据权限" :span="2">
<el-tag v-if="detailFormData.data_scope === 1" type="primary">仅本人数据权限</el-tag>
<el-tag v-else-if="detailFormData.data_scope === 2" type="info">本部门数据权限</el-tag>
<el-tag v-else-if="detailFormData.data_scope === 3" type="warning">本部门及以下数据权限</el-tag>
<el-tag v-else-if="detailFormData.data_scope === 3" type="warning">
本部门及以下数据权限
</el-tag>
<el-tag v-else-if="detailFormData.data_scope === 4" type="success">全部数据权限</el-tag>
<el-tag v-else type="danger" >自定义数据权限</el-tag>
<el-tag v-else type="danger">自定义数据权限</el-tag>
</el-descriptions-item>
<el-descriptions-item label="状态" :span="2">
<el-tag :type="detailFormData.status ? 'success' : 'danger'">
{{ detailFormData.status ? '启用' : '停用' }}
{{ detailFormData.status ? "启用" : "停用" }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="创建人" :span="2">{{ detailFormData.creator?.name }}</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">{{ detailFormData.created_at }}</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">{{ detailFormData.updated_at }}</el-descriptions-item>
<el-descriptions-item label="描述" :span="4">{{ detailFormData.description }}</el-descriptions-item>
<el-descriptions-item label="创建人" :span="2">
{{ detailFormData.creator?.name }}
</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">
{{ detailFormData.created_at }}
</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">
{{ detailFormData.updated_at }}
</el-descriptions-item>
<el-descriptions-item label="描述" :span="4">
{{ detailFormData.description }}
</el-descriptions-item>
</el-descriptions>
</template>
<!-- 新增、编辑表单 -->
<template v-else>
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-suffix=":" label-width="auto" label-position="right">
<el-form
ref="dataFormRef"
:model="formData"
:rules="rules"
label-suffix=":"
label-width="auto"
label-position="right"
>
<el-form-item label="角色名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入角色名称" />
</el-form-item>
<el-form-item label="排序" prop="order">
<el-input-number v-model="formData.order" controls-position="right" :min="0" style="width: 100px" />
<el-input-number
v-model="formData.order"
controls-position="right"
:min="0"
style="width: 100px"
/>
</el-form-item>
<el-form-item label="角色编码" prop="code">
@@ -251,24 +443,38 @@
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="formData.description" :rows="4" :maxlength="100" show-word-limit type="textarea" placeholder="请输入描述" />
<el-input
v-model="formData.description"
:rows="4"
:maxlength="100"
show-word-limit
type="textarea"
placeholder="请输入描述"
/>
</el-form-item>
</el-form>
</template>
<template #footer>
<div class="dialog-footer">
<el-button @click="handleCloseDialog">取 消</el-button>
<el-button v-hasPerm="['module_system:role:submit']" type="primary" @click="handleSubmit">确 定</el-button>
<el-button v-hasPerm="['module_system:role:submit']" type="primary" @click="handleSubmit">
确 定
</el-button>
</div>
</template>
</el-dialog>
<PermissonDrawer v-if="drawerVisible" v-model="drawerVisible" :role-name="checkedRole.name" :role-id="checkedRole.id" @saved="handleRefresh" />
<PermissonDrawer
v-if="drawerVisible"
v-model="drawerVisible"
:role-name="checkedRole.name"
:role-id="checkedRole.id"
@saved="handleRefresh"
/>
<!-- 导出弹窗 -->
<ExportModal
<ExportModal
v-model="exportsDialogVisible"
:content-config="curdContentConfig"
:query-params="queryFormData"
@@ -309,19 +515,19 @@ const drawerVisible = ref(false);
// 表格列配置
const tableColumns = ref([
{ prop: 'selection', label: '选择框', show: true },
{ prop: 'index', label: '序号', show: true },
{ prop: 'name', label: '角色名称', show: true },
{ prop: 'data_scope', label: '数据权限', show: true },
{ prop: 'order', label: '排序', show: true },
{ prop: 'code', label: '角色编码', show: true },
{ prop: 'status', label: '状态', show: true },
{ prop: 'description', label: '描述', show: true },
{ prop: 'created_at', label: '创建时间', show: true },
{ prop: 'updated_at', label: '更新时间', show: true },
{ prop: 'creator', label: '创建人', show: true },
{ prop: 'operation', label: '操作', show: true }
])
{ prop: "selection", label: "选择框", show: true },
{ prop: "index", label: "序号", show: true },
{ prop: "name", label: "角色名称", show: true },
{ prop: "data_scope", label: "数据权限", show: true },
{ prop: "order", label: "排序", show: true },
{ prop: "code", label: "角色编码", show: true },
{ prop: "status", label: "状态", show: true },
{ prop: "description", label: "描述", show: true },
{ prop: "created_at", label: "创建时间", show: true },
{ prop: "updated_at", label: "更新时间", show: true },
{ prop: "creator", label: "创建人", show: true },
{ prop: "operation", label: "操作", show: true },
]);
// 详情表单
const detailFormData = ref<RoleTable>({} as RoleTable);
@@ -332,7 +538,7 @@ const checkedRole = ref<RoleTable>({} as RoleTable);
const queryFormData = reactive<TablePageQuery>({
page_no: 1,
page_size: 10,
name: '',
name: "",
status: undefined,
start_time: undefined,
end_time: undefined,
@@ -354,7 +560,7 @@ const formData = reactive<RoleForm>({
const dialogVisible = reactive({
title: "",
visible: false,
type: 'create' as 'create' | 'update' | 'detail',
type: "create" as "create" | "update" | "detail",
});
// 表单验证规则
@@ -364,7 +570,6 @@ const rules = reactive({
status: [{ required: true, message: "请选择状态", trigger: "blur" }],
});
// 加载表格数据
async function loadingData() {
loading.value = true;
@@ -372,19 +577,17 @@ async function loadingData() {
const response = await RoleAPI.getRoleList(queryFormData);
pageTableData.value = response.data.data.items;
total.value = response.data.data.total;
}
catch (error: any) {
} catch (error: any) {
console.error(error);
}
finally {
} finally {
loading.value = false;
}
}
// 列表刷新
async function handleRefresh () {
async function handleRefresh() {
await loadingData();
};
}
// 查询(重置页码后获取数据)
async function handleQuery() {
@@ -422,7 +625,7 @@ const initialFormData: RoleForm = {
code: undefined,
status: true,
description: undefined,
}
};
// 日期范围临时变量
const dateRange = ref<[Date, Date] | []>([]);
@@ -456,14 +659,14 @@ async function handleCloseDialog() {
}
// 打开系统配置弹窗
async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: number) {
async function handleOpenDialog(type: "create" | "update" | "detail", id?: number) {
dialogVisible.type = type;
if (id) {
const response = await RoleAPI.getRoleDetail(id);
if (type === 'detail') {
if (type === "detail") {
dialogVisible.title = "角色详情";
Object.assign(detailFormData.value, response.data.data);
} else if (type === 'update') {
} else if (type === "update") {
dialogVisible.title = "修改角色";
Object.assign(formData, response.data.data);
}
@@ -484,9 +687,9 @@ async function handleSubmit() {
const id = formData.id;
try {
if (id) {
await RoleAPI.updateRole(id, { id, ...formData })
await RoleAPI.updateRole(id, { id, ...formData });
} else {
await RoleAPI.createRole(formData)
await RoleAPI.createRole(formData);
}
dialogVisible.visible = false;
resetForm();
@@ -509,19 +712,21 @@ async function handleDelete(ids: number[]) {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await RoleAPI.deleteRole(ids);
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
})
.then(async () => {
try {
loading.value = true;
await RoleAPI.deleteRole(ids);
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
})
.catch(() => {
ElMessageBox.close();
});
}
// 导出已改为通过导出弹窗 ExportModal 统一处理
@@ -532,23 +737,25 @@ function handleOpenExportsModal() {
// 批量启用/停用
async function handleMoreClick(status: boolean) {
if (selectIds.value.length) {
ElMessageBox.confirm(`确认${status ? '启用' : '停用'}该项数据?`, "警告", {
ElMessageBox.confirm(`确认${status ? "启用" : "停用"}该项数据?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await RoleAPI.batchAvailableRole({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
})
.then(async () => {
try {
loading.value = true;
await RoleAPI.batchAvailableRole({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
})
.catch(() => {
ElMessageBox.close();
});
}
}
@@ -566,24 +773,24 @@ const exportsDialogVisible = ref(false);
const selectionRows = ref<RoleTable[]>([]);
// 导出字段
const exportColumns = [
{ prop: 'name', label: '角色名称' },
{ prop: 'code', label: '角色编码' },
{ prop: 'data_scope', label: '数据权限' },
{ prop: 'order', label: '排序' },
{ prop: 'status', label: '状态' },
{ prop: 'description', label: '描述' },
{ prop: 'created_at', label: '创建时间' },
{ prop: 'updated_at', label: '更新时间' },
{ prop: "name", label: "角色名称" },
{ prop: "code", label: "角色编码" },
{ prop: "data_scope", label: "数据权限" },
{ prop: "order", label: "排序" },
{ prop: "status", label: "状态" },
{ prop: "description", label: "描述" },
{ prop: "created_at", label: "创建时间" },
{ prop: "updated_at", label: "更新时间" },
];
// 导入/导出配置
const curdContentConfig = {
permPrefix: 'module_system:role',
permPrefix: "module_system:role",
cols: exportColumns as any,
exportsAction: async (params: any) => {
const query: any = { ...params };
if (typeof query.status === 'string') {
query.status = query.status === 'true';
if (typeof query.status === "string") {
query.status = query.status === "true";
}
query.page_no = 1;
query.page_size = 1000;
+304 -136
View File
@@ -13,28 +13,30 @@
<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: 170px;" clearable>
<el-form-item prop="status" label="状态">
<el-select
v-model="queryFormData.status"
placeholder="请选择状态"
style="width: 170px"
clearable
>
<el-option value="true" label="启用" />
<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"
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 >
<el-form-item>
<el-button
v-hasPerm="['module_generator:tenant:query']"
type="primary"
@@ -67,7 +69,7 @@
</el-form-item>
</el-form>
</div>
<!-- 内容区域 -->
<el-card class="data-table">
<template #header>
@@ -86,18 +88,39 @@
<div class="data-table__toolbar--left">
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPerm="['module_generator:tenant:create']" type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
<el-button
v-hasPerm="['module_generator:tenant:create']"
type="success"
icon="plus"
@click="handleOpenDialog('create')"
>
新增
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPerm="['module_generator:tenant:delete']" type="danger" icon="delete" :disabled="selectIds.length === 0" @click="handleDelete(selectIds)">批量删除</el-button>
<el-button
v-hasPerm="['module_generator:tenant:delete']"
type="danger"
icon="delete"
:disabled="selectIds.length === 0"
@click="handleDelete(selectIds)"
>
批量删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-dropdown v-hasPerm="['module_generator:tenant:batch']" trigger="click">
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">更多</el-button>
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">
更多
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">批量启用</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">批量停用</el-dropdown-item>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">
批量启用
</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">
批量停用
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -108,22 +131,46 @@
<el-row :gutter="10">
<el-col :span="1.5">
<el-tooltip content="导入">
<el-button v-hasPerm="['module_generator:tenant:import']" type="success" icon="upload" circle @click="handleOpenImportDialog" />
<el-button
v-hasPerm="['module_generator:tenant:import']"
type="success"
icon="upload"
circle
@click="handleOpenImportDialog"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-tooltip content="导出">
<el-button v-hasPerm="['module_generator:tenant:export']" type="warning" icon="download" circle @click="handleOpenExportsModal" />
<el-button
v-hasPerm="['module_generator:tenant:export']"
type="warning"
icon="download"
circle
@click="handleOpenExportsModal"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-tooltip content="搜索显示/隐藏">
<el-button v-hasPerm="['*:*:*']" type="info" icon="search" circle @click="visible = !visible" />
<el-button
v-hasPerm="['*:*:*']"
type="info"
icon="search"
circle
@click="visible = !visible"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-tooltip content="刷新">
<el-button v-hasPerm="['module_generator:tenant:refresh']" type="primary" icon="refresh" circle @click="handleRefresh" />
<el-button
v-hasPerm="['module_generator:tenant:refresh']"
type="primary"
icon="refresh"
circle
@click="handleRefresh"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
@@ -143,50 +190,142 @@
</div>
<!-- 表格区域:系统配置列表 -->
<el-table ref="tableRef" v-loading="loading" :data="pageTableData" highlight-current-row class="data-table__content" :height="450" border stripe @selection-change="handleSelectionChange">
<el-table
ref="tableRef"
v-loading="loading"
:data="pageTableData"
highlight-current-row
class="data-table__content"
:height="450"
border
stripe
@selection-change="handleSelectionChange"
>
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column v-if="tableColumns.find((col) => col.prop === 'selection')?.show" type="selection" min-width="55" align="center"/>
<el-table-column v-if="tableColumns.find((col) => col.prop === 'index')?.show" fixed label="序号" min-width="60">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'selection')?.show"
type="selection"
min-width="55"
align="center"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'index')?.show"
fixed
label="序号"
min-width="60"
>
<template #default="scope">
{{ (queryFormData.page_no - 1) * queryFormData.page_size + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find((col) => col.prop === 'name')?.show" label="名称" prop="name" min-width="140" />
<el-table-column v-if="tableColumns.find((col) => col.prop === 'code')?.show" label="编码" prop="code" min-width="140" />
<el-table-column v-if="tableColumns.find((col) => col.prop === 'status')?.show" label="状态" prop="status" min-width="120">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'name')?.show"
label="名称"
prop="name"
min-width="140"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'code')?.show"
label="编码"
prop="code"
min-width="140"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'status')?.show"
label="状态"
prop="status"
min-width="120"
>
<template #default="scope">
<el-tag :type="scope.row.status ? 'success' : 'info'">
{{ scope.row.status ? '启用' : '停用' }}
{{ scope.row.status ? "启用" : "停用" }}
</el-tag>
</template>
</el-table-column>
<el-table-column v-if="tableColumns.find((col) => col.prop === 'description')?.show" label="描述" prop="description" min-width="140" />
<el-table-column v-if="tableColumns.find((col) => col.prop === 'created_at')?.show" label="创建时间" prop="created_at" min-width="180" />
<el-table-column v-if="tableColumns.find((col) => col.prop === 'updated_at')?.show" label="更新时间" prop="updated_at" min-width="180" />
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'description')?.show"
label="描述"
prop="description"
min-width="140"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'created_at')?.show"
label="创建时间"
prop="created_at"
min-width="180"
/>
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'updated_at')?.show"
label="更新时间"
prop="updated_at"
min-width="180"
/>
<!-- <el-table-column v-if="tableColumns.find((col) => col.prop === 'creator')?.show" label="创建人" prop="creator" min-width="120">
<template #default="scope">
<el-tag>{{ scope.row.creator?.name }}</el-tag>
</template>
</el-table-column> -->
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right" label="操作" align="center" min-width="180">
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'operation')?.show"
fixed="right"
label="操作"
align="center"
min-width="180"
>
<template #default="scope">
<el-button v-hasPerm="['module_generator:tenant:detail']" type="info" size="small" link icon="document" @click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
<el-button v-hasPerm="['module_generator:tenant:update']" type="primary" size="small" link icon="edit" @click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
<el-button v-hasPerm="['module_generator:tenant:delete']" type="danger" size="small" link icon="delete" @click="handleDelete([scope.row.id])">删除</el-button>
<el-button
v-hasPerm="['module_generator:tenant:detail']"
type="info"
size="small"
link
icon="document"
@click="handleOpenDialog('detail', scope.row.id)"
>
详情
</el-button>
<el-button
v-hasPerm="['module_generator:tenant:update']"
type="primary"
size="small"
link
icon="edit"
@click="handleOpenDialog('update', scope.row.id)"
>
编辑
</el-button>
<el-button
v-hasPerm="['module_generator:tenant:delete']"
type="danger"
size="small"
link
icon="delete"
@click="handleDelete([scope.row.id])"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<template #footer>
<pagination v-model:total="total" v-model:page="queryFormData.page_no" v-model:limit="queryFormData.page_size" @pagination="loadingData" />
<pagination
v-model:total="total"
v-model:page="queryFormData.page_no"
v-model:limit="queryFormData.page_size"
@pagination="loadingData"
/>
</template>
</el-card>
<!-- 弹窗区域 -->
<el-dialog v-model="dialogVisible.visible" :title="dialogVisible.title" @close="handleCloseDialog">
<el-dialog
v-model="dialogVisible.visible"
:title="dialogVisible.title"
@close="handleCloseDialog"
>
<!-- 详情 -->
<template v-if="dialogVisible.type === 'detail'">
<el-descriptions :column="4" border>
@@ -198,7 +337,7 @@
</el-descriptions-item>
<el-descriptions-item label="状态" :span="2">
<el-tag :type="detailFormData.status ? 'success' : 'danger'">
{{ detailFormData.status ? '启用' : '停用' }}
{{ detailFormData.status ? "启用" : "停用" }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="描述" :span="2">
@@ -217,7 +356,14 @@
</template>
<!-- 新增、编辑表单 -->
<template v-else>
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-suffix=":" label-width="auto" label-position="right">
<el-form
ref="dataFormRef"
:model="formData"
:rules="rules"
label-suffix=":"
label-width="auto"
label-position="right"
>
<el-form-item label="名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入名称" :maxlength="64" />
</el-form-item>
@@ -231,7 +377,14 @@
</el-radio-group>
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="formData.description" :rows="4" :maxlength="100" show-word-limit type="textarea" placeholder="请输入描述" />
<el-input
v-model="formData.description"
:rows="4"
:maxlength="100"
show-word-limit
type="textarea"
placeholder="请输入描述"
/>
</el-form-item>
</el-form>
</template>
@@ -240,21 +393,35 @@
<div class="dialog-footer">
<!-- 详情弹窗不需要确定按钮的提交逻辑 -->
<el-button @click="handleCloseDialog">取消</el-button>
<el-button v-if="dialogVisible.type !== 'detail'" v-hasPerm="['module_generator:tenant:submit']" type="primary" @click="handleSubmit">确定</el-button>
<el-button v-else v-hasPerm="['smodule_generator:tenant:detail']" type="primary" @click="handleCloseDialog">确定</el-button>
<el-button
v-if="dialogVisible.type !== 'detail'"
v-hasPerm="['module_generator:tenant:submit']"
type="primary"
@click="handleSubmit"
>
确定
</el-button>
<el-button
v-else
v-hasPerm="['smodule_generator:tenant:detail']"
type="primary"
@click="handleCloseDialog"
>
确定
</el-button>
</div>
</template>
</el-dialog>
<!-- 导入弹窗 -->
<ImportModal
v-model="importDialogVisible"
<ImportModal
v-model="importDialogVisible"
:content-config="curdContentConfig"
@upload="handleUpload"
@upload="handleUpload"
/>
<!-- 导出弹窗 -->
<ExportModal
<ExportModal
v-model="exportsDialogVisible"
:content-config="curdContentConfig"
:query-params="queryFormData"
@@ -281,7 +448,6 @@ import { QuestionFilled, ArrowUp, ArrowDown } from "@element-plus/icons-vue";
import { formatToDateTime } from "@/utils/dateUtil";
import TenantAPI, { ObjTable, ObjForm, ObjPageQuery } from "@/api/module_system/tenant";
const visible = ref(true);
const queryFormRef = ref();
const dataFormRef = ref();
@@ -297,37 +463,37 @@ const pageTableData = ref<ObjTable[]>([]);
// 表格列配置
const tableColumns = ref([
{ prop: 'selection', label: '选择框', show: true },
{ prop: 'index', label: '序号', show: true },
{ prop: 'name', label: '名称', show: true },
{ prop: 'code', label: '编码', show: true },
{ prop: 'status', label: '状态', show: true },
{ prop: 'description', label: '描述', show: true },
{ prop: 'created_at', label: '创建时间', show: true },
{ prop: 'updated_at', label: '更新时间', show: true },
{ prop: 'creator', label: '创建人', show: true },
{ prop: 'operation', label: '操作', show: true }
])
{ prop: "selection", label: "选择框", show: true },
{ prop: "index", label: "序号", show: true },
{ prop: "name", label: "名称", show: true },
{ prop: "code", label: "编码", show: true },
{ prop: "status", label: "状态", show: true },
{ prop: "description", label: "描述", show: true },
{ prop: "created_at", label: "创建时间", show: true },
{ prop: "updated_at", label: "更新时间", show: true },
{ prop: "creator", label: "创建人", show: true },
{ prop: "operation", label: "操作", show: true },
]);
// 仅用于导出字段的列(排除非数据列及嵌套对象列)
const exportColumns = [
{ prop: 'name', label: '名称' },
{ prop: 'code', label: '编码' },
{ prop: 'status', label: '状态' },
{ prop: 'description', label: '描述' },
{ prop: 'created_at', label: '创建时间' },
{ prop: 'updated_at', label: '更新时间' },
{ prop: "name", label: "名称" },
{ prop: "code", label: "编码" },
{ prop: "status", label: "状态" },
{ prop: "description", label: "描述" },
{ prop: "created_at", label: "创建时间" },
{ prop: "updated_at", label: "更新时间" },
];
// 导入/导出配置
const curdContentConfig = {
permPrefix: 'module_generator:tenant',
permPrefix: "module_generator:tenant",
cols: exportColumns as any,
importTemplate: () => TenantAPI.download(),
exportsAction: async (params: any) => {
const query: any = { ...params };
if (typeof query.status === 'string') {
query.status = query.status === 'true';
if (typeof query.status === "string") {
query.status = query.status === "true";
}
query.page_no = 1;
query.page_size = 9999;
@@ -348,7 +514,7 @@ const detailFormData = ref<ObjTable>({});
// 日期范围临时变量
const dateRange = ref<[Date, Date] | []>([]);
// 处理日期范围变化
// 处理日期范围变化
function handleDateRangeChange(range: [Date, Date]) {
dateRange.value = range;
if (range && range.length === 2) {
@@ -374,17 +540,17 @@ const queryFormData = reactive<ObjPageQuery>({
// 编辑表单
const formData = reactive<ObjForm>({
id: undefined,
name: '',
code: '',
name: "",
code: "",
status: true,
description: undefined,
})
});
// 弹窗状态
const dialogVisible = reactive({
title: "",
visible: false,
type: 'create' as 'create' | 'update' | 'detail',
type: "create" as "create" | "update" | "detail",
});
// 表单验证规则
@@ -413,7 +579,7 @@ function handleOpenExportsModal() {
// 列表刷新
async function handleRefresh() {
await loadingData();
};
}
// 加载表格数据
async function loadingData() {
@@ -422,11 +588,9 @@ async function loadingData() {
const response = await TenantAPI.list(queryFormData);
pageTableData.value = response.data.data.items;
total.value = response.data.data.total;
}
catch (error: any) {
} catch (error: any) {
console.error(error);
}
finally {
} finally {
loading.value = false;
}
}
@@ -456,11 +620,11 @@ async function handleResetQuery() {
// 定义初始表单数据常量
const initialFormData: ObjForm = {
id: undefined,
name: '',
code: '',
name: "",
code: "",
status: true,
description: '',
}
description: "",
};
// 重置表单
async function resetForm() {
@@ -485,14 +649,14 @@ async function handleCloseDialog() {
}
// 打开弹窗
async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: number) {
async function handleOpenDialog(type: "create" | "update" | "detail", id?: number) {
dialogVisible.type = type;
if (id) {
const response = await TenantAPI.detail(id);
if (type === 'detail') {
if (type === "detail") {
dialogVisible.title = "详情";
Object.assign(detailFormData.value, response.data.data);
} else if (type === 'update') {
} else if (type === "update") {
dialogVisible.title = "修改";
Object.assign(formData, response.data.data);
}
@@ -508,35 +672,35 @@ async function handleSubmit() {
// 表单校验
dataFormRef.value.validate(async (valid: any) => {
if (valid) {
loading.value = true;
// 根据弹窗传入的参数(deatil\create\update)判断走什么逻辑
const id = formData.id;
if (id) {
try {
await TenantAPI.update(id, { id, ...formData })
dialogVisible.visible = false;
resetForm();
handleCloseDialog();
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
} else {
try {
await TenantAPI.create(formData)
dialogVisible.visible = false;
resetForm();
handleCloseDialog();
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
loading.value = true;
// 根据弹窗传入的参数(deatil\create\update)判断走什么逻辑
const id = formData.id;
if (id) {
try {
await TenantAPI.update(id, { id, ...formData });
dialogVisible.visible = false;
resetForm();
handleCloseDialog();
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
} else {
try {
await TenantAPI.create(formData);
dialogVisible.visible = false;
resetForm();
handleCloseDialog();
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}
}
});
}
@@ -546,41 +710,45 @@ async function handleDelete(ids: number[]) {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await TenantAPI.delete(ids);
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
}
// 批量启用/停用
async function handleMoreClick(status: boolean) {
if (selectIds.value.length) {
ElMessageBox.confirm(`确认${status ? '启用' : '停用'}该项数据?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
})
.then(async () => {
try {
loading.value = true;
await TenantAPI.batch({ ids: selectIds.value, status });
await TenantAPI.delete(ids);
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}).catch(() => {
})
.catch(() => {
ElMessageBox.close();
});
}
// 批量启用/停用
async function handleMoreClick(status: boolean) {
if (selectIds.value.length) {
ElMessageBox.confirm(`确认${status ? "启用" : "停用"}该项数据?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
try {
loading.value = true;
await TenantAPI.batch({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
})
.catch(() => {
ElMessageBox.close();
});
}
}
@@ -594,7 +762,7 @@ const handleUpload = async (formData: FormData) => {
await handleQuery();
}
} catch (error: any) {
console.error(error);
console.error(error);
}
};
@@ -9,15 +9,16 @@
</template>
</el-input>
<el-tree
<el-tree
ref="deptTreeRef"
class="mt-2"
class="mt-2"
:data="deptOptions"
:props="{ children: 'children', label: 'label', disabled: 'disabled' }"
:props="{ children: 'children', label: 'label', disabled: 'disabled' }"
:expand-on-click-node="false"
:filter-node-method="handleFilter"
default-expand-all
@node-click="handleNodeClick" >
:filter-node-method="handleFilter"
default-expand-all
@node-click="handleNodeClick"
>
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
@@ -28,7 +29,7 @@
<script setup lang="ts">
import DeptAPI, { DeptPageQuery } from "@/api/module_system/dept";
import { formatTree } from "@/utils/common";
import type { FilterNodeMethodFunction, TreeInstance } from 'element-plus'
import type { FilterNodeMethodFunction, TreeInstance } from "element-plus";
const props = defineProps({
modelValue: {
@@ -46,11 +47,11 @@ const emits = defineEmits(["node-click", "update:modelValue"]);
const deptId = useVModel(props, "modelValue", emits);
watch(deptName, (val) => {
deptTreeRef.value!.filter(val)
})
deptTreeRef.value!.filter(val);
});
interface Tree {
[key: string]: any
[key: string]: any;
}
/**
@@ -58,8 +59,8 @@ interface Tree {
*/
const handleFilter: FilterNodeMethodFunction = (value: string, data: Tree) => {
if (!value) return true;
return data.label.includes(value)
}
return data.label.includes(value);
};
/** 部门树节点 Click */
function handleNodeClick(data: { [key: string]: any }) {
@@ -78,10 +79,12 @@ const loading = ref(true);
onBeforeMount(() => {
loading.value = true;
DeptAPI.getDeptList(queryFormData).then((response) => {
deptOptions.value = formatTree(response.data.data);
}).finally(() => {
loading.value = false;
});
DeptAPI.getDeptList(queryFormData)
.then((response) => {
deptOptions.value = formatTree(response.data.data);
})
.finally(() => {
loading.value = false;
});
});
</script>
@@ -1,6 +1,11 @@
<!-- 列表选择器 -->
<template>
<table-select :text="text" :select-config="selectConfig" @confirm-click="handleConfirm" @clear-click="handleClearSelection">
<table-select
:text="text"
:select-config="selectConfig"
@confirm-click="handleConfirm"
@clear-click="handleClearSelection"
>
<template #status="scope">
<el-tag :type="scope.row[scope.prop] === true ? 'success' : 'danger'">
{{ scope.row[scope.prop] === true ? "启用" : "停用" }}
+358 -162
View File
@@ -4,14 +4,20 @@
<el-row :gutter="12">
<!-- 部门树 -->
<el-col :lg="4" :xs="24">
<DeptTree v-model="queryFormData.dept_id" class="h-full" @node-click="handleQuery" />
<DeptTree v-model="queryFormData.dept_id" class="h-full" @node-click="handleQuery" />
</el-col>
<!-- 用户列表 -->
<el-col :lg="20" :xs="24">
<!-- 搜索区域 -->
<div class="search-container">
<el-form ref="queryFormRef" :model="queryFormData" :inline="true" label-suffix=":" @submit.prevent="handleQuery" >
<el-form
ref="queryFormRef"
:model="queryFormData"
:inline="true"
label-suffix=":"
@submit.prevent="handleQuery"
>
<el-form-item prop="username" label="账号">
<el-input v-model="queryFormData.username" placeholder="请输入账号" clearable />
</el-form-item>
@@ -19,32 +25,52 @@
<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>
<el-select
v-model="queryFormData.status"
placeholder="请选择状态"
style="width: 167.5px"
clearable
>
<el-option value="true" label="启用" />
<el-option value="false" label="停用" />
</el-select>
</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 v-if="isExpand" prop="creator" label="创建人">
<UserTableSelect
v-model="queryFormData.creator"
@confirm-click="handleConfirm"
@clear-click="handleQuery"
v-model="queryFormData.creator"
@confirm-click="handleConfirm"
@clear-click="handleQuery"
/>
</el-form-item>
<!-- 查询、重置、展开/收起按钮 -->
<el-form-item class="search-buttons">
<el-button v-hasPerm="['module_system:user:query']" type="primary" icon="search" native-type="submit">查询</el-button>
<el-button v-hasPerm="['module_system:user:query']" icon="refresh" @click="handleResetQuery">重置</el-button>
<el-button
v-hasPerm="['module_system:user:query']"
type="primary"
icon="search"
native-type="submit"
>
查询
</el-button>
<el-button
v-hasPerm="['module_system:user:query']"
icon="refresh"
@click="handleResetQuery"
>
重置
</el-button>
<!-- 展开/收起 -->
<template v-if="isExpandable">
<el-link class="ml-3" type="primary" underline="never" @click="isExpand = !isExpand">
<el-link
class="ml-3"
type="primary"
underline="never"
@click="isExpand = !isExpand"
>
{{ isExpand ? "收起" : "展开" }}
<el-icon>
<template v-if="isExpand">
@@ -77,10 +103,25 @@
<div class="data-table__toolbar--left">
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPerm="['module_system:user:create']" type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
<el-button
v-hasPerm="['module_system:user:create']"
type="success"
icon="plus"
@click="handleOpenDialog('create')"
>
新增
</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPerm="['module_system:user:delete']" type="danger" icon="delete" :disabled="selectIds.length === 0" @click="handleDelete(selectIds)">批量删除</el-button>
<el-button
v-hasPerm="['module_system:user:delete']"
type="danger"
icon="delete"
:disabled="selectIds.length === 0"
@click="handleDelete(selectIds)"
>
批量删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-dropdown v-hasPerm="['module_system:user:patch']" trigger="click">
@@ -89,8 +130,12 @@
</el-button>
<template #dropdown>
<el-dropdown-menu v-hasPerm="['module_system:user:filter']">
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">批量启用</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">批量停用</el-dropdown-item>
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">
批量启用
</el-dropdown-item>
<el-dropdown-item icon="CircleClose" @click="handleMoreClick(false)">
批量停用
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -102,17 +147,35 @@
<el-row :gutter="10">
<el-col :span="1.5">
<el-tooltip content="导入">
<el-button v-hasPerm="['module_system:user:import']" type="info" icon="upload" circle @click="handleOpenImportDialog" />
<el-button
v-hasPerm="['module_system:user:import']"
type="info"
icon="upload"
circle
@click="handleOpenImportDialog"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-tooltip content="导出">
<el-button v-hasPerm="['module_system:user:export']" type="warning" icon="download" circle @click="handleOpenExportsModal" />
<el-button
v-hasPerm="['module_system:user:export']"
type="warning"
icon="download"
circle
@click="handleOpenExportsModal"
/>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-tooltip content="刷新">
<el-button v-hasPerm="['module_system:user:refresh']" type="default" icon="refresh" circle @click="handleRefresh"/>
<el-button
v-hasPerm="['module_system:user:refresh']"
type="default"
icon="refresh"
circle
@click="handleRefresh"
/>
</el-tooltip>
</el-col>
</el-row>
@@ -120,12 +183,22 @@
</div>
<!-- 表格区域 -->
<el-table ref="dataTableRef" v-loading="loading" :data="pageTableData" highlight-current-row class="data-table__content" :height="450" border stripe @selection-change="handleSelectionChange">
<el-table
ref="dataTableRef"
v-loading="loading"
:data="pageTableData"
highlight-current-row
class="data-table__content"
:height="450"
border
stripe
@selection-change="handleSelectionChange"
>
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column type="selection" min-width="55" align="center" />
<el-table-column type="index" fixed label="序号" align="center" min-width="60" >
<el-table-column type="index" fixed label="序号" align="center" min-width="60">
<template #default="scope">
{{ (queryFormData.page_no - 1) * queryFormData.page_size + scope.$index + 1 }}
</template>
@@ -152,14 +225,14 @@
<el-table-column label="是否超管" prop="is_superuser" min-width="100">
<template #default="scope">
<el-tag :type="scope.row.is_superuser ? 'success' : 'info'">
{{ scope.row.is_superuser ? '是' : '否' }}
{{ scope.row.is_superuser ? "是" : "否" }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="性别" prop="gender" min-width="100">
<template #default="scope">
<el-tag v-if="scope.row.gender ==='0'" type="success">男</el-tag>
<el-tag v-else-if="scope.row.gender ==='1'" type="warning">女</el-tag>
<el-tag v-if="scope.row.gender === '0'" type="success">男</el-tag>
<el-tag v-else-if="scope.row.gender === '1'" type="warning">女</el-tag>
<el-tag v-else type="info">未知</el-tag>
</template>
</el-table-column>
@@ -175,59 +248,92 @@
</el-table-column>
<el-table-column fixed="right" label="操作" align="center" min-width="280">
<template #default="scope">
<el-button
v-hasPerm="['module_system:user:update']"
type="warning"
icon="RefreshLeft"
size="small"
<el-button
v-hasPerm="['module_system:user:update']"
type="warning"
icon="RefreshLeft"
size="small"
link
:disabled="scope.row.is_superuser === true"
@click="scope.row.is_superuser === true ? ElMessage.warning('系统超管角色,不可操作') : hancleResetPassword(scope.row)"
>重置密码</el-button>
<el-button
:disabled="scope.row.is_superuser === true"
@click="
scope.row.is_superuser === true
? ElMessage.warning('系统超管角色,不可操作')
: hancleResetPassword(scope.row)
"
>
重置密码
</el-button>
<el-button
v-hasPerm="['module_system:user:detail']"
type="info"
size="small"
link
icon="document"
type="info"
size="small"
link
icon="document"
@click="handleOpenDialog('detail', scope.row.id)"
>详情</el-button>
<el-button
v-hasPerm="['module_system:user:update']"
type="primary"
size="small"
link
icon="edit"
:disabled="scope.row.is_superuser === true"
@click="scope.row.is_superuser === true ? ElMessage.warning('系统超管角色,不可操作') : handleOpenDialog('update', scope.row.id)"
>编辑</el-button>
<el-button
v-hasPerm="['module_system:user:delete']"
type="danger"
size="small"
link
icon="delete"
:disabled="scope.row.is_superuser === true"
@click="scope.row.is_superuser === true ? ElMessage.warning('系统超管角色,不可操作') : handleDelete([scope.row.id])"
>删除</el-button>
>
详情
</el-button>
<el-button
v-hasPerm="['module_system:user:update']"
type="primary"
size="small"
link
icon="edit"
:disabled="scope.row.is_superuser === true"
@click="
scope.row.is_superuser === true
? ElMessage.warning('系统超管角色,不可操作')
: handleOpenDialog('update', scope.row.id)
"
>
编辑
</el-button>
<el-button
v-hasPerm="['module_system:user:delete']"
type="danger"
size="small"
link
icon="delete"
:disabled="scope.row.is_superuser === true"
@click="
scope.row.is_superuser === true
? ElMessage.warning('系统超管角色,不可操作')
: handleDelete([scope.row.id])
"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页区域 -->
<template #footer>
<pagination v-model:total="total" v-model:page="queryFormData.page_no" v-model:limit="queryFormData.page_size" @pagination="loadingData" />
<pagination
v-model:total="total"
v-model:page="queryFormData.page_no"
v-model:limit="queryFormData.page_size"
@pagination="loadingData"
/>
</template>
</el-card>
</el-col>
</el-row>
<!-- 弹窗区域 -->
<el-drawer v-model="dialogVisible.visible" :title="dialogVisible.title" append-to-body :size="drawerSize" @close="handleCloseDialog">
<el-drawer
v-model="dialogVisible.visible"
:title="dialogVisible.title"
append-to-body
:size="drawerSize"
@close="handleCloseDialog"
>
<!-- 详情 -->
<template v-if="dialogVisible.type === 'detail'">
<el-descriptions :column="2" border>
<el-descriptions-item label="编号" :span="2">{{ detailFormData.id }}</el-descriptions-item>
<el-descriptions-item label="编号" :span="2">
{{ detailFormData.id }}
</el-descriptions-item>
<el-descriptions-item label="头像" :span="2">
<template v-if="detailFormData.avatar">
<el-avatar :src="detailFormData.avatar" size="small"></el-avatar>
@@ -236,40 +342,81 @@
<el-avatar icon="UserFilled" size="small"></el-avatar>
</template>
</el-descriptions-item>
<el-descriptions-item label="账号" :span="2">{{ detailFormData.username }}</el-descriptions-item>
<el-descriptions-item label="用户名" :span="2">{{ detailFormData.name }}</el-descriptions-item>
<el-descriptions-item label="账号" :span="2">
{{ detailFormData.username }}
</el-descriptions-item>
<el-descriptions-item label="用户名" :span="2">
{{ detailFormData.name }}
</el-descriptions-item>
<el-descriptions-item label="性别" :span="2">
<el-tag v-if="detailFormData.gender ==='0'" type="success">男</el-tag>
<el-tag v-else-if="detailFormData.gender ==='1'" type="warning">女</el-tag>
<el-tag v-if="detailFormData.gender === '0'" type="success">男</el-tag>
<el-tag v-else-if="detailFormData.gender === '1'" type="warning">女</el-tag>
<el-tag v-else type="info">未知</el-tag>
</el-descriptions-item>
<el-descriptions-item label="部门" :span="2">{{ detailFormData.dept ? detailFormData.dept.name : '' }}</el-descriptions-item>
<el-descriptions-item label="角色" :span="2">{{ detailFormData.roles ? detailFormData.roles.map(item => item.name).join('、') : '' }}</el-descriptions-item>
<el-descriptions-item label="岗位" :span="2">{{ detailFormData.positions ? detailFormData.positions.map(item => item.name).join('、') : '' }}</el-descriptions-item>
<el-descriptions-item label="邮箱" :span="2">{{ detailFormData.email }}</el-descriptions-item>
<el-descriptions-item label="手机号" :span="2">{{ detailFormData.mobile }}</el-descriptions-item>
<el-descriptions-item label="部门" :span="2">
{{ detailFormData.dept ? detailFormData.dept.name : "" }}
</el-descriptions-item>
<el-descriptions-item label="角色" :span="2">
{{
detailFormData.roles ? detailFormData.roles.map((item) => item.name).join("、") : ""
}}
</el-descriptions-item>
<el-descriptions-item label="岗位" :span="2">
{{
detailFormData.positions
? detailFormData.positions.map((item) => item.name).join("、")
: ""
}}
</el-descriptions-item>
<el-descriptions-item label="邮箱" :span="2">
{{ detailFormData.email }}
</el-descriptions-item>
<el-descriptions-item label="手机号" :span="2">
{{ detailFormData.mobile }}
</el-descriptions-item>
<el-descriptions-item label="是否超管" :span="2">
<el-tag :type="detailFormData.is_superuser ? 'success' : 'info'">
{{ detailFormData.is_superuser ? '是' : '否' }}
{{ detailFormData.is_superuser ? "是" : "否" }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="状态" :span="2">
<el-tag :type="detailFormData.status ? 'success' : 'danger'">
{{ detailFormData.status ? '启用' : '停用' }}
{{ detailFormData.status ? "启用" : "停用" }}
</el-tag>
</el-descriptions-item>
<el-descriptions-item label="上次登录时间" :span="2">{{ detailFormData.last_login }}</el-descriptions-item>
<el-descriptions-item label="创建人" :span="2">{{ detailFormData.creator?.name }}</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">{{ detailFormData.created_at }}</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">{{ detailFormData.updated_at }}</el-descriptions-item>
<el-descriptions-item label="描述" :span="4">{{ detailFormData.description }}</el-descriptions-item>
<el-descriptions-item label="上次登录时间" :span="2">
{{ detailFormData.last_login }}
</el-descriptions-item>
<el-descriptions-item label="创建人" :span="2">
{{ detailFormData.creator?.name }}
</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">
{{ detailFormData.created_at }}
</el-descriptions-item>
<el-descriptions-item label="更新时间" :span="2">
{{ detailFormData.updated_at }}
</el-descriptions-item>
<el-descriptions-item label="描述" :span="4">
{{ detailFormData.description }}
</el-descriptions-item>
</el-descriptions>
</template>
<!-- 新增、编辑表单 -->
<template v-else>
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-suffix=":" label-width="auto" label-position="right">
<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" :disabled="!!formData.id" placeholder="请输入账号" />
<el-input
v-model="formData.username"
:disabled="!!formData.id"
placeholder="请输入账号"
/>
</el-form-item>
<el-form-item label="用户名" prop="name">
@@ -293,23 +440,50 @@
</el-form-item>
<el-form-item label="部门" prop="dept_id">
<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-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" :disabled="item.disabled" />
<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" :disabled="item.disabled" />
<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 label="密码" prop="password">
<el-input v-model="formData.password" :readonly="!!formData.id" placeholder="请输入密码" type="password" show-password clearable />
<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">
@@ -324,38 +498,56 @@
</el-form-item>
<el-form-item label="描述" prop="description">
<el-input v-model="formData.description" :rows="4" :maxlength="100" show-word-limit type="textarea" placeholder="请输入描述" />
<el-input
v-model="formData.description"
:rows="4"
:maxlength="100"
show-word-limit
type="textarea"
placeholder="请输入描述"
/>
</el-form-item>
</el-form>
</template>
<template #footer>
<div class="dialog-footer">
<el-button v-if="dialogVisible.type === 'create' || dialogVisible.type === 'update'" v-hasPerm="['module_system:user:create']" type="primary" @click="handleSubmit">确定</el-button>
<el-button v-else v-hasPerm="['module_system:user:detail']" type="primary" @click="handleCloseDialog">确定</el-button>
<el-button
v-if="dialogVisible.type === 'create' || dialogVisible.type === 'update'"
v-hasPerm="['module_system:user:create']"
type="primary"
@click="handleSubmit"
>
确定
</el-button>
<el-button
v-else
v-hasPerm="['module_system:user:detail']"
type="primary"
@click="handleCloseDialog"
>
确定
</el-button>
<el-button @click="handleCloseDialog">取消</el-button>
</div>
</template>
</el-drawer>
<!-- 导入弹窗 -->
<ImportModal
v-model="importDialogVisible"
<ImportModal
v-model="importDialogVisible"
:content-config="curdContentConfig"
@upload="handleUpload"
@upload="handleUpload"
/>
<!-- 导出弹窗 -->
<ExportModal
<ExportModal
v-model="exportsDialogVisible"
:content-config="curdContentConfig"
:query-params="queryFormData"
:page-data="pageTableData"
:selection-data="selectionRows"
/>
</div>
</template>
@@ -369,7 +561,11 @@ import { useAppStore } from "@/store/modules/app.store";
import { DeviceEnum } from "@/enums/settings/device.enum";
import { ResultEnum } from "@/enums/api/result.enum";
import UserAPI, { type UserForm, type UserInfo, type UserPageQuery } from "@/api/module_system/user";
import UserAPI, {
type UserForm,
type UserInfo,
type UserPageQuery,
} from "@/api/module_system/user";
import { formatTree } from "@/utils/common";
import PositionAPI from "@/api/module_system/position";
import DeptAPI from "@/api/module_system/dept";
@@ -443,13 +639,13 @@ const formData = reactive<UserForm>({
is_superuser: false, //默认不是超管
status: true,
description: undefined,
})
});
// 弹窗状态
const dialogVisible = reactive({
title: "",
visible: false,
type: 'create' as 'create' | 'update' | 'detail',
type: "create" as "create" | "update" | "detail",
});
// 表单验证规则
@@ -481,27 +677,27 @@ const dateRange = ref<[Date, Date] | []>([]);
// 仅用于导出字段的列(排除非数据列及嵌套对象列)
const exportColumns = [
{ prop: 'username', label: '账号' },
{ prop: 'name', label: '名称' },
{ prop: 'status', label: '状态' },
{ prop: 'gender', label: '性别' },
{ prop: 'email', label: '邮箱' },
{ prop: 'mobile', label: '手机号' },
{ prop: 'is_superuser', label: '是否超管' },
{ prop: 'description', label: '描述' },
{ prop: 'created_at', label: '创建时间' },
{ prop: 'updated_at', label: '更新时间' },
{ prop: "username", label: "账号" },
{ prop: "name", label: "名称" },
{ prop: "status", label: "状态" },
{ prop: "gender", label: "性别" },
{ prop: "email", label: "邮箱" },
{ prop: "mobile", label: "手机号" },
{ prop: "is_superuser", label: "是否超管" },
{ prop: "description", label: "描述" },
{ prop: "created_at", label: "创建时间" },
{ prop: "updated_at", label: "更新时间" },
];
// 导入/导出配置
const curdContentConfig = {
permPrefix: 'module_system:user',
permPrefix: "module_system:user",
cols: exportColumns as any,
importTemplate: () => UserAPI.downloadTemplate(),
exportsAction: async (params: any) => {
const query: any = { ...params };
if (typeof query.status === 'string') {
query.status = query.status === 'true';
if (typeof query.status === "string") {
query.status = query.status === "true";
}
query.page_no = 1;
query.page_size = 9999;
@@ -518,7 +714,6 @@ const curdContentConfig = {
},
} as unknown as IContentConfig;
// 选择创建人后触发查询
function handleConfirm() {
handleQuery();
@@ -537,9 +732,9 @@ function handleDateRangeChange(range: [Date, Date]) {
}
// 列表刷新
async function handleRefresh () {
async function handleRefresh() {
await loadingData();
};
}
// 加载表格数据
async function loadingData() {
@@ -548,11 +743,9 @@ async function loadingData() {
const response = await UserAPI.getUserList(queryFormData);
pageTableData.value = response.data.data.items;
total.value = response.data.data.total;
}
catch (error: any) {
} catch (error: any) {
console.error(error);
}
finally {
} finally {
loading.value = false;
}
}
@@ -598,7 +791,7 @@ const initialFormData: UserForm = {
is_superuser: false, //默认不是超管
status: true,
description: undefined,
}
};
// 重置表单
async function resetForm() {
@@ -627,7 +820,7 @@ function hancleResetPassword(row: UserInfo) {
ElMessage.warning("密码至少需要6位字符,请重新输入");
return false;
}
await UserAPI.resetUserPassword({id: row.id!, password: value})
await UserAPI.resetUserPassword({ id: row.id!, password: value });
},
() => {
ElMessageBox.close();
@@ -642,19 +835,19 @@ async function handleCloseDialog() {
}
// 打开弹窗
async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: number) {
async function handleOpenDialog(type: "create" | "update" | "detail", id?: number) {
dialogVisible.type = type;
if (id) {
const response = await UserAPI.getUserDetail(id);
if (type === 'detail') {
if (type === "detail") {
dialogVisible.title = "用户详情";
Object.assign(detailFormData.value, response.data.data);
} else if (type === 'update') {
} else if (type === "update") {
dialogVisible.title = "修改用户";
Object.assign(formData, response.data.data);
// 确保角色和岗位ID正确设置
formData.role_ids = (response.data.data.roles || []).map(item => item.id as number);
formData.position_ids = (response.data.data.positions || []).map(item => item.id as number);
formData.role_ids = (response.data.data.roles || []).map((item) => item.id as number);
formData.position_ids = (response.data.data.positions || []).map((item) => item.id as number);
}
} else {
dialogVisible.title = "新增用户";
@@ -674,24 +867,24 @@ async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: numbe
// 获取角色列表
const roleResponse = await RoleAPI.getRoleList();
roleOptions.value = roleResponse.data.data.items
.filter(item => item.id !== undefined && item.name !== undefined)
.map(item => ({
.filter((item) => item.id !== undefined && item.name !== undefined)
.map((item) => ({
value: item.id as number,
label: item.name as string,
disabled: item.status === false || String(item.status) === 'false'
disabled: item.status === false || String(item.status) === "false",
}))
.filter(opt => !opt.disabled);
.filter((opt) => !opt.disabled);
// 获取岗位列表
const positionResponse = await PositionAPI.getPositionList();
positionOptions.value = positionResponse.data.data.items
.filter(item => item.id !== undefined && item.name !== undefined)
.map(item => ({
.filter((item) => item.id !== undefined && item.name !== undefined)
.map((item) => ({
value: item.id as number,
label: item.name as string,
disabled: item.status === false || String(item.status) === 'false'
disabled: item.status === false || String(item.status) === "false",
}))
.filter(opt => !opt.disabled);
.filter((opt) => !opt.disabled);
}
// 提交表单(防抖)
@@ -704,9 +897,9 @@ async function handleSubmit() {
const id = formData.id;
try {
if (id) {
await UserAPI.updateUser(id, { id, ...formData })
await UserAPI.updateUser(id, { id, ...formData });
} else {
await UserAPI.createUser(formData)
await UserAPI.createUser(formData);
}
dialogVisible.visible = false;
resetForm();
@@ -733,19 +926,21 @@ async function handleDelete(ids: number[]) {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await UserAPI.deleteUser(ids);
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
})
.then(async () => {
try {
loading.value = true;
await UserAPI.deleteUser(ids);
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
})
.catch(() => {
ElMessageBox.close();
});
}
// 批量启用/停用
@@ -755,19 +950,21 @@ async function handleMoreClick(status: boolean) {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
try {
loading.value = true;
await UserAPI.batchAvailableUser({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
}).catch(() => {
ElMessageBox.close();
});
})
.then(async () => {
try {
loading.value = true;
await UserAPI.batchAvailableUser({ ids: selectIds.value, status });
handleResetQuery();
} catch (error: any) {
console.error(error);
} finally {
loading.value = false;
}
})
.catch(() => {
ElMessageBox.close();
});
}
}
@@ -781,7 +978,7 @@ function handleOpenExportsModal() {
exportsDialogVisible.value = true;
}
const emit = defineEmits(['import-success']);
const emit = defineEmits(["import-success"]);
// 上传文件
const handleUpload = async (formData: FormData) => {
@@ -791,15 +988,14 @@ const handleUpload = async (formData: FormData) => {
ElMessage.success(`${response.data.msg},${response.data.data}`);
importDialogVisible.value = false;
await handleQuery();
emit('import-success');
emit("import-success");
}
} catch (error: any) {
console.error(error);
ElMessage.error('上传失败:' + error);
ElMessage.error("上传失败:" + error);
}
};
onMounted(() => {
handleQuery();
});