feat(components): 增强 FaForm/FaDialog/FaDrawer 并重构业务页面

- FaForm 新增 scrollbar 和 maxHeight 属性,支持表单滚动条
- FaForm 暴露 resetFields、clearValidate、validateField 方法
- FaDialog 和 FaDrawer 新增 formMode、confirmLoading 等属性及 confirm/cancel 事件
- 使用 FaDescriptions 和 FaForm 重构 demo 和 memory 页面,减少模板冗余
- 修复布局组件路径引用,统一至 @/components/layouts
- 补充 Fa 前缀组件的 ESLint 全局变量声明
This commit is contained in:
zhangtao
2026-05-15 19:42:39 +08:00
parent 9176d477f9
commit 0344765e5a
35 changed files with 1864 additions and 1762 deletions
@@ -74,48 +74,24 @@
width="920px"
dialog-class="session-detail-dialog"
modal-class="session-detail-dialog"
@close="handleCloseDialog"
:form-mode="dialogVisible.type"
:confirm-loading="submitLoading"
@cancel="handleCloseDialog"
@confirm="dialogVisible.type === 'detail' ? handleCloseDialog() : handleSubmit()"
>
<template v-if="dialogVisible.type === 'detail'">
<ElScrollbar max-height="70vh" :view-style="{ overflowX: 'hidden' }">
<ElDescriptions :column="2" border>
<ElDescriptionsItem label="会话ID" :span="2">
{{ detailFormData.session_id }}
</ElDescriptionsItem>
<ElDescriptionsItem label="标题" :span="2">
{{ detailFormData.title }}
</ElDescriptionsItem>
<ElDescriptionsItem label="用户ID" :span="1">
{{ detailFormData.user_id }}
</ElDescriptionsItem>
<ElDescriptionsItem label="团队ID" :span="1">
{{ detailFormData.team_id }}
</ElDescriptionsItem>
<ElDescriptionsItem label="部门名称" :span="1">
{{ detailFormData.team_name || "未知部门" }}
</ElDescriptionsItem>
<ElDescriptionsItem label="Agent ID" :span="1">
{{ detailFormData.agent_id }}
</ElDescriptionsItem>
<ElDescriptionsItem label="创建时间" :span="1">
{{ detailFormData.created_time }}
</ElDescriptionsItem>
<ElDescriptionsItem label="更新时间" :span="1">
{{ detailFormData.updated_time }}
</ElDescriptionsItem>
<ElDescriptionsItem label="消息数量" :span="1">
{{ detailFormData.message_count }}
</ElDescriptionsItem>
<ElDescriptionsItem label="会话摘要" :span="2">
{{ detailFormData.summary || "无" }}
</ElDescriptionsItem>
<ElDescriptionsItem label="元数据" :span="2">
<pre v-if="detailFormData.metadata">{{
JSON.stringify(detailFormData.metadata, null, 2)
}}</pre>
<FaDescriptions
:column="2"
:data="detailFormData"
:items="memoryDetailItems"
:scrollbar="false"
>
<template #metadata="{ row }">
<pre v-if="row?.metadata">{{ JSON.stringify(row?.metadata, null, 2) }}</pre>
<span v-else>无</span>
</ElDescriptionsItem>
</ElDescriptions>
</template>
</FaDescriptions>
<ElDivider content-position="left">消息记录</ElDivider>
<ElTimeline v-if="detailFormData.messages && detailFormData.messages.length > 0">
@@ -158,16 +134,6 @@
class="crud-dialog-art-form"
/>
</template>
<template #footer>
<div class="dialog-footer">
<ElButton @click="handleCloseDialog">取消</ElButton>
<ElButton v-if="dialogVisible.type !== 'detail'" type="primary" @click="handleSubmit">
确定
</ElButton>
<ElButton v-else type="primary" @click="handleCloseDialog">确定</ElButton>
</div>
</template>
</FaDialog>
</div>
</template>
@@ -184,13 +150,7 @@ import { ElMessage, ElMessageBox } from "element-plus";
import AiChatAPI, { type ChatSession, type ChatSessionDetail } from "@/api/module_ai/chat";
import { formatToDateTime } from "@utils/common";
import { useTable } from "@/hooks/core/useTable";
import FaTable from "@/components/tables/fa-table/index.vue";
import FaTableHeader from "@/components/tables/fa-table-header/index.vue";
import FaTableHeaderLeft from "@/components/tables/fa-table-header-left/index.vue";
import FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
import FaDialog from "@/components/modal/fa-dialog/index.vue";
import FaForm from "@/components/forms/fa-form/index.vue";
import type { FormItem } from "@/components/forms/fa-form/index.vue";
import type { ColumnOption } from "@/types/component";
import { useAuth } from "@/hooks/core/useAuth";
@@ -264,6 +224,7 @@ const memorySearchItems = computed<SearchFormItem[]>(() => [
]);
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
const submitLoading = ref(false);
const memoryFormRenderKey = ref(0);
const memoryDialogFormItems = computed<FormItem[]>(() => [
@@ -439,6 +400,21 @@ const dialogVisible = reactive({
const detailFormData = ref<Partial<ChatSessionDetail>>({});
const memoryDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] =
[
{ label: "会话ID", prop: "session_id" },
{ label: "标题", prop: "title" },
{ label: "用户ID", prop: "user_id", span: 1 },
{ label: "团队ID", prop: "team_id", span: 1 },
{ label: "部门名称", prop: "team_name", span: 1 },
{ label: "Agent ID", prop: "agent_id", span: 1 },
{ label: "创建时间", prop: "created_time", span: 1 },
{ label: "更新时间", prop: "updated_time", span: 1 },
{ label: "消息数量", prop: "message_count", span: 1 },
{ label: "会话摘要", prop: "summary" },
{ label: "元数据", prop: "metadata", slot: "metadata" },
];
const rules = reactive({
title: [{ required: true, message: "请输入标题", trigger: "blur" }],
});
@@ -469,8 +445,8 @@ async function onResetSearch() {
}
async function resetForm() {
dataFormRef.value?.ref?.resetFields();
dataFormRef.value?.ref?.clearValidate();
dataFormRef.value?.resetFields();
dataFormRef.value?.clearValidate();
Object.assign(formData, initialFormData);
}
@@ -457,8 +457,8 @@ function resetForm() {
status: "0",
description: "",
});
formRef.value?.ref?.resetFields();
formRef.value?.ref?.clearValidate();
formRef.value?.resetFields();
formRef.value?.clearValidate();
}
function handleCloseDialog() {
@@ -38,8 +38,8 @@
:perm-patch="['module_example:demo:patch']"
:delete-loading="batchDeleting"
@add="openEditDialog('add')"
@import="openImportModal"
@export="openExportModal"
@import="openImport"
@export="openExport"
@delete="handleBatchDelete"
@more="runBatchStatus"
/>
@@ -64,205 +64,86 @@
width="920px"
dialog-class="crud-embed-dialog"
modal-class="crud-embed-dialog"
@close="handleCloseDialog"
:form-mode="dialogVisible.type"
:confirm-loading="submitLoading"
@cancel="handleCloseDialog"
@confirm="dialogVisible.type === 'detail' ? handleCloseDialog() : handleSubmit()"
>
<template v-if="dialogVisible.type === 'detail'">
<ElScrollbar max-height="70vh" :view-style="{ overflowX: 'hidden' }">
<ElDescriptions :column="4" border>
<ElDescriptionsItem label="名称" :span="2">
{{ detailFormData.name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="UUID" :span="2">
{{ detailFormData.uuid }}
</ElDescriptionsItem>
<ElDescriptionsItem label="状态" :span="2">
<ElTag :type="detailFormData.status === '0' ? 'success' : 'danger'">
{{ detailFormData.status === "0" ? "启用" : "停用" }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="整数" :span="2">
{{ detailFormData.a }}
</ElDescriptionsItem>
<ElDescriptionsItem label="大整数" :span="2">
{{ detailFormData.b }}
</ElDescriptionsItem>
<ElDescriptionsItem label="浮点数" :span="2">
{{ detailFormData.c }}
</ElDescriptionsItem>
<ElDescriptionsItem label="布尔值" :span="2">
<ElTag :type="detailFormData.d ? 'success' : 'danger'">
{{ detailFormData.d ? "是" : "否" }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="日期" :span="2">
{{ detailFormData.e }}
</ElDescriptionsItem>
<ElDescriptionsItem label="时间" :span="2">
{{ detailFormData.f }}
</ElDescriptionsItem>
<ElDescriptionsItem label="日期时间" :span="2">
{{ detailFormData.g }}
</ElDescriptionsItem>
<ElDescriptionsItem label="长文本" :span="2">
{{ detailFormData.h }}
</ElDescriptionsItem>
<ElDescriptionsItem label="元数据" :span="2">
<JsonPretty
v-if="detailFormData.i != null"
:value="detailFormData.i"
height="140px"
/>
</ElDescriptionsItem>
<ElDescriptionsItem label="描述" :span="2">
{{ detailFormData.description }}
</ElDescriptionsItem>
<ElDescriptionsItem label="创建人" :span="2">
{{ detailFormData.created_by?.name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="更新人" :span="2">
{{ detailFormData.updated_by?.name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="创建时间" :span="2">
{{ detailFormData.created_time }}
</ElDescriptionsItem>
<ElDescriptionsItem label="更新时间" :span="2">
{{ detailFormData.updated_time }}
</ElDescriptionsItem>
</ElDescriptions>
</ElScrollbar>
<FaDescriptions
:column="4"
:data="detailFormData"
:items="demoDetailItems"
max-height="70vh"
>
<template #i="{ row }">
<JsonPretty v-if="row?.i != null" :value="row?.i" height="140px" />
</template>
</FaDescriptions>
</template>
<template v-else>
<ElScrollbar max-height="70vh" :view-style="{ overflowX: 'hidden' }">
<ElForm
ref="dataFormRef"
:model="formData"
:rules="rules"
label-suffix=":"
label-width="100px"
label-position="right"
inline
>
<ElFormItem label="名称" prop="name">
<ElInput v-model="formData.name" placeholder="请输入名称" :maxlength="50" />
</ElFormItem>
<ElFormItem label="状态" prop="status">
<ElRadioGroup v-model="formData.status">
<ElRadio value="0">启用</ElRadio>
<ElRadio value="1">停用</ElRadio>
</ElRadioGroup>
</ElFormItem>
<ElFormItem label="整数" prop="a">
<ElInputNumber v-model="formData.a" placeholder="请输入整数" />
</ElFormItem>
<ElFormItem label="大整数" prop="b">
<ElInputNumber v-model="formData.b" placeholder="请输入大整数" />
</ElFormItem>
<ElFormItem label="浮点数" prop="c">
<ElInputNumber
v-model="formData.c"
placeholder="请输入浮点数"
:step="0.01"
:precision="2"
/>
</ElFormItem>
<ElFormItem label="布尔值" prop="d">
<ElSwitch v-model="formData.d" />
</ElFormItem>
<ElFormItem label="日期" prop="e">
<ElDatePicker
v-model="formData.e"
type="date"
placeholder="请选择日期"
:style="'width: 100%'"
value-format="YYYY-MM-DD"
/>
</ElFormItem>
<ElFormItem label="时间" prop="f">
<ElTimePicker
v-model="formData.f"
placeholder="请选择时间"
:style="'width: 100%'"
value-format="HH:mm:ss"
/>
</ElFormItem>
<ElFormItem label="日期时间" prop="g">
<ElDatePicker
v-model="formData.g"
type="datetime"
placeholder="请选择日期时间"
:style="'width: 100%'"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</ElFormItem>
<ElFormItem label="长文本" prop="h">
<ElInput v-model="formData.h" :rows="4" type="textarea" placeholder="请输入长文本" />
</ElFormItem>
<ElFormItem label="描述" prop="description">
<ElInput
v-model="formData.description"
:rows="4"
:maxlength="100"
show-word-limit
type="textarea"
placeholder="请输入描述"
/>
</ElFormItem>
<ElFormItem label="元数据" prop="i">
<div class="flex flex-col gap-2">
<div
v-for="(item, index) in metadataList"
:key="index"
class="flex items-center gap-2"
>
<ElInput v-model="item.key" placeholder="键" />
<ElInput v-model="item.value" placeholder="值" />
<ElButton
type="primary"
icon="Plus"
circle
@click="metadataList.push({ key: '', value: '' })"
/>
<ElButton
type="danger"
icon="Delete"
circle
@click="metadataList.splice(index, 1)"
/>
</div>
<FaForm
:key="demoFormRenderKey"
scrollbar
max-height="70vh"
ref="dataFormRef"
v-model="formData"
:items="demoDialogFormItems"
:rules="rules"
label-suffix=":"
:label-width="'auto'"
label-position="right"
:span="24"
:gutter="16"
:show-reset="false"
:show-submit="false"
class="crud-dialog-art-form"
>
<template #i>
<div class="flex flex-col gap-2">
<div
v-for="(item, index) in metadataList"
:key="index"
class="flex items-center gap-2"
>
<ElInput v-model="item.key" placeholder="键" />
<ElInput v-model="item.value" placeholder="值" />
<ElButton
v-if="metadataList.length === 0"
type="primary"
icon="Plus"
circle
@click="metadataList.push({ key: '', value: '' })"
>
添加元数据
</ElButton>
/>
<ElButton
type="danger"
icon="Delete"
circle
@click="metadataList.splice(index, 1)"
/>
</div>
</ElFormItem>
</ElForm>
</ElScrollbar>
</template>
<template #footer>
<div class="dialog-footer" :style="'padding-right: var(--el-dialog-padding-primary)'">
<ElButton @click="handleCloseDialog">取消</ElButton>
<ElButton v-if="dialogVisible.type !== 'detail'" type="primary" @click="handleSubmit">
确定
</ElButton>
<ElButton v-else type="primary" @click="handleCloseDialog">确定</ElButton>
</div>
<ElButton
v-if="metadataList.length === 0"
type="primary"
icon="Plus"
@click="metadataList.push({ key: '', value: '' })"
>
添加元数据
</ElButton>
</div>
</template>
</FaForm>
</template>
</FaDialog>
<FaImportDialog
v-model="importModalVisible"
v-model="importVisible"
:content-config="demoImportContentConfig"
default-template-file-name="demo_import_template.xlsx"
@upload="handleCrudImportUpload"
/>
<FaExportDialog
v-model="exportModalVisible"
v-model="exportVisible"
:content-config="demoExportContentConfig"
:query-params="exportQueryParams"
:page-data="data"
@@ -272,25 +153,19 @@
</template>
<script setup lang="ts">
import { h, computed, ref } from "vue";
import { useAuth } from "@/hooks/core/useAuth";
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
import { useTable } from "@/hooks/core/useTable";
import FaTableHeaderLeft from "@/components/tables/fa-table-header-left/index.vue";
import FaImportDialog from "@/components/modal/fa-import-dialog/index.vue";
import FaExportDialog from "@/components/modal/fa-export-dialog/index.vue";
import { useImportExport } from "@/hooks/core/useImportExport";
import type { IContentConfig, IObject } from "@/components/modal/types";
import FaSearchBarWithAudit from "@/components/forms/fa-search-bar/FaSearchBarWithAudit.vue";
import type { AuditSearchFormParams } from "@/components/forms/fa-search-bar/auditSearchFormItems";
import FaDialog from "@/components/modal/fa-dialog/index.vue";
import JsonPretty from "@/components/others/fa-json-pretty/index.vue";
import type { FormItem } from "@/components/forms/fa-form/index.vue";
import type { ColumnOption } from "@/types/component";
import DemoAPI, {
type DemoForm,
type DemoPageQuery,
type DemoTable,
} from "@/api/module_example/demo";
import { ElMessage, ElMessageBox, ElTag } from "element-plus";
import { ResultEnum } from "@/enums/api/result.enum";
defineOptions({
@@ -502,6 +377,110 @@ const dialogVisible = reactive({
const detailFormData = ref<DemoTable>({});
const demoDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] =
[
{ label: "名称", prop: "name" },
{ label: "UUID", prop: "uuid" },
{
label: "状态",
prop: "status",
tag: {
map: { "0": { type: "success", text: "启用" }, "1": { type: "danger", text: "停用" } },
},
},
{ label: "整数", prop: "a" },
{ label: "大整数", prop: "b" },
{ label: "浮点数", prop: "c" },
{
label: "布尔值",
prop: "d",
tag: {
map: { true: { type: "success", text: "是" }, false: { type: "danger", text: "否" } },
},
},
{ label: "日期", prop: "e" },
{ label: "时间", prop: "f" },
{ label: "日期时间", prop: "g" },
{ label: "长文本", prop: "h" },
{ label: "元数据", prop: "i", slot: "i" },
{ label: "描述", prop: "description" },
{ label: "创建人", prop: "created_by.name" },
{ label: "更新人", prop: "updated_by.name" },
{ label: "创建时间", prop: "created_time" },
{ label: "更新时间", prop: "updated_time" },
];
// 示例页新增/编辑表单 —— 元数据字段用具名插槽渲染
const demoDialogFormItems: FormItem[] = [
{
key: "name",
label: "名称",
type: "input",
props: { placeholder: "请输入名称", maxlength: 50 },
},
{
key: "status",
label: "状态",
type: "radiogroup",
props: {
options: [
{ label: "启用", value: "0" },
{ label: "停用", value: "1" },
],
},
},
{ key: "a", label: "整数", type: "number", props: { placeholder: "请输入整数" } },
{ key: "b", label: "大整数", type: "number", props: { placeholder: "请输入大整数" } },
{
key: "c",
label: "浮点数",
type: "number",
props: { placeholder: "请输入浮点数", step: 0.01, precision: 2 },
},
{ key: "d", label: "布尔值", type: "switch" },
{
key: "e",
label: "日期",
type: "date",
props: { placeholder: "请选择日期", valueFormat: "YYYY-MM-DD", style: "width: 100%" },
},
{
key: "f",
label: "时间",
type: "timepicker",
props: { placeholder: "请选择时间", valueFormat: "HH:mm:ss", style: "width: 100%" },
},
{
key: "g",
label: "日期时间",
type: "datetime",
props: {
placeholder: "请选择日期时间",
valueFormat: "YYYY-MM-DD HH:mm:ss",
style: "width: 100%",
},
},
{
key: "h",
label: "长文本",
type: "input",
props: { type: "textarea", rows: 4, placeholder: "请输入长文本" },
},
{
key: "description",
label: "描述",
type: "input",
props: {
type: "textarea",
rows: 4,
maxlength: 100,
showWordLimit: true,
placeholder: "请输入描述",
},
},
{ key: "i", label: "元数据", type: "input" /* 由 #i 插槽接管 */ },
];
const formData = ref<DemoForm>({
id: undefined,
name: "",
@@ -523,11 +502,12 @@ const rules = reactive({
status: [{ required: true, message: "请选择状态", trigger: "blur" }],
});
const dataFormRef = ref();
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
const submitLoading = ref(false);
const demoFormRenderKey = ref(0);
const metadataList = ref<{ key: string; value: string }[]>([]);
const importModalVisible = ref(false);
const exportModalVisible = ref(false);
const { importVisible, exportVisible, openImport, openExport } = useImportExport();
const initialFormData: DemoForm = {
id: undefined,
@@ -628,6 +608,7 @@ async function openEditDialog(type: "add" | "edit", row?: DemoTable) {
Object.assign(formData.value, initialFormData);
formData.value.id = undefined;
metadataList.value = [];
demoFormRenderKey.value += 1;
} else if (row?.id) {
dialogVisible.title = "修改";
const response = await DemoAPI.getDemoDetail(row.id);
@@ -659,7 +640,7 @@ async function handleCloseDialog() {
}
async function handleSubmit() {
dataFormRef.value.validate(async (valid: boolean) => {
dataFormRef.value?.validate(async (valid: boolean) => {
if (!valid) return;
const submitData = { ...formData.value };
if (metadataList.value.length > 0) {
@@ -757,10 +738,6 @@ async function runBatchStatus(status: string) {
}
}
function openImportModal() {
importModalVisible.value = true;
}
async function handleCrudImportUpload(formData: FormData) {
try {
const res = await DemoAPI.importDemo(formData);
@@ -769,15 +746,11 @@ async function handleCrudImportUpload(formData: FormData) {
return;
}
ElMessage.success(res.data.msg || "导入成功");
importModalVisible.value = false;
importVisible.value = false;
await refreshData();
} catch (error) {
console.error("[Import]", error);
ElMessage.error("导入失败");
}
}
function openExportModal() {
exportModalVisible.value = true;
}
</script>
@@ -38,8 +38,8 @@
:perm-patch="['module_example:demo01:patch']"
:delete-loading="batchDeleting"
@add="openEditDialog('add')"
@import="openImportModal"
@export="openExportModal"
@import="openImport"
@export="openExport"
@delete="handleBatchDelete"
@more="runBatchStatus"
/>
@@ -64,87 +64,56 @@
width="768px"
dialog-class="crud-embed-dialog"
modal-class="crud-embed-dialog"
@close="handleCloseDialog"
:form-mode="dialogVisible.type"
:confirm-loading="submitLoading"
@cancel="handleCloseDialog"
@confirm="dialogVisible.type === 'detail' ? handleCloseDialog() : handleSubmit()"
>
<template v-if="dialogVisible.type === 'detail'">
<ElScrollbar max-height="70vh" :view-style="{ overflowX: 'hidden' }">
<ElDescriptions :column="2" border>
<ElDescriptionsItem label="名称" :span="2">
{{ detailFormData.name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="UUID" :span="2">
{{ detailFormData.uuid }}
</ElDescriptionsItem>
<ElDescriptionsItem label="状态" :span="2">
<ElTag :type="detailFormData.status === '0' ? 'success' : 'danger'">
{{ detailFormData.status === "0" ? "正常" : "停用" }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="描述" :span="2">
{{ detailFormData.description }}
</ElDescriptionsItem>
<ElDescriptionsItem label="创建人" :span="2">
{{ detailFormData.created_by?.name ?? "—" }}
</ElDescriptionsItem>
<ElDescriptionsItem label="更新人" :span="2">
{{ detailFormData.updated_by?.name ?? "—" }}
</ElDescriptionsItem>
<ElDescriptionsItem label="创建时间" :span="2">
{{ detailFormData.created_time }}
</ElDescriptionsItem>
<ElDescriptionsItem label="更新时间" :span="2">
{{ detailFormData.updated_time }}
</ElDescriptionsItem>
</ElDescriptions>
</ElScrollbar>
<FaDescriptions
:column="2"
:data="detailFormData"
:items="demo01DetailItems"
max-height="70vh"
/>
</template>
<template v-else>
<ElScrollbar max-height="70vh" :view-style="{ overflowX: 'hidden' }">
<FaForm
:key="demo01FormRenderKey"
ref="dataFormRef"
v-model="formData"
:items="demo01DialogFormItems"
:rules="rules"
label-suffix=":"
:label-width="'auto'"
label-position="right"
:span="24"
:gutter="16"
:show-reset="false"
:show-submit="false"
class="crud-dialog-art-form"
>
<template #status>
<ElRadioGroup v-model="formData.status">
<ElRadio value="0">正常</ElRadio>
<ElRadio value="1">停用</ElRadio>
</ElRadioGroup>
</template>
</FaForm>
</ElScrollbar>
</template>
<template #footer>
<div class="dialog-footer" :style="'padding-right: var(--el-dialog-padding-primary)'">
<ElButton @click="handleCloseDialog">取消</ElButton>
<ElButton v-if="dialogVisible.type !== 'detail'" type="primary" @click="handleSubmit">
确定
</ElButton>
<ElButton v-else type="primary" @click="handleCloseDialog">确定</ElButton>
</div>
<FaForm
:key="demo01FormRenderKey"
scrollbar
max-height="70vh"
ref="dataFormRef"
v-model="formData"
:items="demo01DialogFormItems"
:rules="rules"
label-suffix=":"
:label-width="'auto'"
label-position="right"
:span="24"
:gutter="16"
:show-reset="false"
:show-submit="false"
class="crud-dialog-art-form"
>
<template #status>
<ElRadioGroup v-model="formData.status">
<ElRadio value="0">正常</ElRadio>
<ElRadio value="1">停用</ElRadio>
</ElRadioGroup>
</template>
</FaForm>
</template>
</FaDialog>
<FaImportDialog
v-model="importModalVisible"
v-model="importVisible"
:content-config="demo01ImportContentConfig"
default-template-file-name="demo01_import_template.xlsx"
@upload="handleCrudImportUpload"
/>
<FaExportDialog
v-model="exportModalVisible"
v-model="exportVisible"
:content-config="demo01ExportContentConfig"
:query-params="exportQueryParams"
:page-data="data"
@@ -154,18 +123,12 @@
</template>
<script setup lang="ts">
import { h, computed, ref, reactive } from "vue";
import { useAuth } from "@/hooks/core/useAuth";
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
import { useTable } from "@/hooks/core/useTable";
import FaTableHeaderLeft from "@/components/tables/fa-table-header-left/index.vue";
import FaImportDialog from "@/components/modal/fa-import-dialog/index.vue";
import FaExportDialog from "@/components/modal/fa-export-dialog/index.vue";
import { useImportExport } from "@/hooks/core/useImportExport";
import type { IContentConfig, IObject } from "@/components/modal/types";
import FaSearchBarWithAudit from "@/components/forms/fa-search-bar/FaSearchBarWithAudit.vue";
import type { AuditSearchFormParams } from "@/components/forms/fa-search-bar/auditSearchFormItems";
import FaDialog from "@/components/modal/fa-dialog/index.vue";
import FaForm from "@/components/forms/fa-form/index.vue";
import type { FormItem } from "@/components/forms/fa-form/index.vue";
import type { ColumnOption } from "@/types/component";
import Demo01API, {
@@ -373,6 +336,24 @@ const dialogVisible = reactive({
const detailFormData = ref<Demo01Table>({});
const demo01DetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] =
[
{ label: "名称", prop: "name" },
{ label: "UUID", prop: "uuid" },
{
label: "状态",
prop: "status",
tag: {
map: { "0": { type: "success", text: "正常" }, "1": { type: "danger", text: "停用" } },
},
},
{ label: "描述", prop: "description" },
{ label: "创建人", prop: "created_by.name" },
{ label: "更新人", prop: "updated_by.name" },
{ label: "创建时间", prop: "created_time" },
{ label: "更新时间", prop: "updated_time" },
];
const formData = ref<Demo01Form>({
id: undefined,
name: "",
@@ -386,6 +367,7 @@ const rules = reactive({
});
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
const submitLoading = ref(false);
const demo01FormRenderKey = ref(0);
const demo01DialogFormItems = computed<FormItem[]>(() => [
@@ -412,8 +394,7 @@ const demo01DialogFormItems = computed<FormItem[]>(() => [
},
]);
const importModalVisible = ref(false);
const exportModalVisible = ref(false);
const { importVisible, exportVisible, openImport, openExport } = useImportExport();
const initialFormData: Demo01Form = {
id: undefined,
@@ -516,8 +497,8 @@ async function openEditDialog(type: "add" | "edit", row?: Demo01Table) {
}
async function resetForm() {
dataFormRef.value?.ref?.resetFields();
dataFormRef.value?.ref?.clearValidate();
dataFormRef.value?.resetFields();
dataFormRef.value?.clearValidate();
Object.assign(formData.value, initialFormData);
}
@@ -605,10 +586,6 @@ async function runBatchStatus(status: string) {
}
}
function openImportModal() {
importModalVisible.value = true;
}
async function handleCrudImportUpload(formDataUpload: FormData) {
try {
const res = await Demo01API.importDemo01(formDataUpload);
@@ -617,17 +594,13 @@ async function handleCrudImportUpload(formDataUpload: FormData) {
return;
}
ElMessage.success(res.data.msg || "导入成功");
importModalVisible.value = false;
importVisible.value = false;
await refreshData();
} catch (error) {
console.error("[Import]", error);
ElMessage.error("导入失败");
}
}
function openExportModal() {
exportModalVisible.value = true;
}
</script>
<style scoped lang="scss">
@@ -65,7 +65,7 @@
<div v-show="editMode === 'visual'" class="visual-pane">
<ElScrollbar max-height="min(58vh, 520px)" class="visual-pane-scroll">
<div class="visual-structure max-w-3xl">
<ElDescriptions :column="1" border size="small" class="visual-desc">
<FaDescriptions :column="1" size="small" class="visual-desc" :scrollbar="false">
<template #title>
<span class="text-sm font-medium text-[var(--el-text-color-primary)]">选项</span>
</template>
@@ -81,9 +81,9 @@
<ElRadioButton value="masterSub">主表 + 子表明细</ElRadioButton>
</ElRadioGroup>
</ElDescriptionsItem>
</ElDescriptions>
</FaDescriptions>
<ElDescriptions :column="1" border size="small" class="visual-desc mt-3">
<FaDescriptions :column="1" size="small" class="visual-desc mt-3" :scrollbar="false">
<template #title>
<span class="text-sm font-medium text-[var(--el-text-color-primary)]">主表</span>
</template>
@@ -101,14 +101,14 @@
<ElDescriptionsItem label="说明" label-class-name="visual-desc-label">
<ElInput v-model="visual.mainComment" placeholder="中文说明,可选" clearable />
</ElDescriptionsItem>
</ElDescriptions>
</FaDescriptions>
<ElDescriptions
<FaDescriptions
v-if="visual.subEnabled"
:column="1"
border
size="small"
class="visual-desc mt-3"
:scrollbar="false"
>
<template #title>
<span class="text-sm font-medium text-[var(--el-text-color-primary)]">子表</span>
@@ -137,7 +137,7 @@
<ElDescriptionsItem label="对应主表列" label-class-name="visual-desc-label">
<ElInput v-model="visual.fkRefColumn" placeholder="一般是 id" clearable />
</ElDescriptionsItem>
</ElDescriptions>
</FaDescriptions>
<div class="mt-3">
<ElLink type="primary" underline="never" @click="syncVisualToSql">
@@ -179,6 +179,7 @@ import { ElMessage } from "element-plus";
import { ArrowDown, CopyDocument } from "@element-plus/icons-vue";
import { useClipboard } from "@vueuse/core";
import FaDialog from "@/components/modal/fa-dialog/index.vue";
import FaDescriptions from "@/components/others/fa-descriptions/index.vue";
import { useSettingsStore } from "@stores";
import { ThemeMode } from "@/enums/settings/theme.enum";
import { buildSqlFromVisual } from "../utils/buildCreateTableSql";
+3 -2
View File
@@ -13,7 +13,7 @@
<span class="font-medium">Redis监控信息</span>
</div>
</template>
<ElDescriptions :column="6" border>
<FaDescriptions :column="6" :scrollbar="false">
<ElDescriptionsItem label="Redis版本">
{{ cache.info?.redis_version || "-" }}
</ElDescriptionsItem>
@@ -55,7 +55,7 @@
{{ cache.info?.instantaneous_input_kbps || 0 }}kps/
{{ cache.info?.instantaneous_output_kbps || 0 }}kps
</ElDescriptionsItem>
</ElDescriptions>
</FaDescriptions>
</ElCard>
</ElCol>
</ElRow>
@@ -262,6 +262,7 @@
</template>
<script lang="ts" setup>
import FaDescriptions from "@/components/others/fa-descriptions/index.vue";
import { ElMessageBox } from "element-plus";
import CacheAPI, {
type CacheInfo,
@@ -66,7 +66,6 @@ import FaTable from "@/components/tables/fa-table/index.vue";
import FaTableHeader from "@/components/tables/fa-table-header/index.vue";
import FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
import ArtButtonTable from "@/components/forms/fa-button-table/index.vue";
import CopyButton from "@/components/others/fa-copy-button/index.vue";
import OnlineAPI, { type OnlineUserTable } from "@/api/module_monitor/online";
import type { ColumnOption } from "@/types/component";
@@ -235,7 +234,7 @@ const {
}
return h(ElTooltip, { content: "强退", placement: "top" }, () =>
h("span", { class: "inline-flex" }, [
h(ArtButtonTable, {
h(FaButtonTable, {
type: "delete",
onClick: () => kickSession(row.session_id),
}),
@@ -222,7 +222,6 @@ import FaTable from "@/components/tables/fa-table/index.vue";
import FaTableHeader from "@/components/tables/fa-table-header/index.vue";
import FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
import ArtButtonTable from "@/components/forms/fa-button-table/index.vue";
import FaDialog from "@/components/modal/fa-dialog/index.vue";
import { ResourceAPI, type ResourceItem } from "@/api/module_monitor/resource";
import type { ColumnOption } from "@/types/component";
@@ -406,7 +405,7 @@ const {
!row.is_dir && hasAuth("module_monitor:resource:download")
? h(ElTooltip, { content: "下载", placement: "top" }, () =>
h("span", { class: "inline-flex" }, [
h(ArtButtonTable, {
h(FaButtonTable, {
type: "view",
icon: "ri:download-line",
onClick: () => void handleDownload(row),
@@ -417,7 +416,7 @@ const {
hasAuth("module_monitor:resource:rename")
? h(ElTooltip, { content: "重命名", placement: "top" }, () =>
h("span", { class: "inline-flex" }, [
h(ArtButtonTable, {
h(FaButtonTable, {
type: "edit",
onClick: () => void handleRenameOpen(row),
}),
@@ -427,7 +426,7 @@ const {
hasAuth("module_monitor:resource:delete")
? h(ElTooltip, { content: "删除", placement: "top" }, () =>
h("span", { class: "inline-flex" }, [
h(ArtButtonTable, {
h(FaButtonTable, {
type: "delete",
onClick: () => void handleDelete(row),
}),
@@ -25,7 +25,7 @@
>
<span class="text-sm">{{ (server.cpu?.used || 0).toFixed(1) }}%</span>
</ElProgress>
<ElDescriptions :column="2" border size="small">
<FaDescriptions :column="2" size="small" :scrollbar="false">
<ElDescriptionsItem label="核心数">
{{ server.cpu?.cpu_num || 0 }}
</ElDescriptionsItem>
@@ -38,7 +38,7 @@
<ElDescriptionsItem label="系统使用率">
{{ (server.cpu?.sys || 0).toFixed(1) }}%
</ElDescriptionsItem>
</ElDescriptions>
</FaDescriptions>
</div>
</div>
</ElCard>
@@ -68,7 +68,7 @@
>
<span class="text-sm">{{ (server.mem?.usage || 0).toFixed(1) }}%</span>
</ElProgress>
<ElDescriptions :column="2" border size="small">
<FaDescriptions :column="2" size="small" :scrollbar="false">
<ElDescriptionsItem label="总内存">
{{ server.mem?.total }}
</ElDescriptionsItem>
@@ -81,7 +81,7 @@
<ElDescriptionsItem label="Python内存">
{{ server.py?.memory_usage ? server.py.memory_usage.toFixed(1) + "%" : "-" }}
</ElDescriptionsItem>
</ElDescriptions>
</FaDescriptions>
</div>
</div>
</ElCard>
@@ -98,7 +98,7 @@
<span class="font-medium">服务器基本信息</span>
</div>
</template>
<ElDescriptions :column="2" border size="small">
<FaDescriptions :column="2" size="small" :scrollbar="false">
<ElDescriptionsItem label="服务器名称">
{{ server.sys?.computer_name || "-" }}
</ElDescriptionsItem>
@@ -111,7 +111,7 @@
<ElDescriptionsItem label="系统架构">
{{ server.sys?.os_arch || "-" }}
</ElDescriptionsItem>
</ElDescriptions>
</FaDescriptions>
</ElCard>
</ElCol>
@@ -124,7 +124,7 @@
<span class="font-medium">Python运行环境</span>
</div>
</template>
<ElDescriptions :column="2" border size="small">
<FaDescriptions :column="2" size="small" :scrollbar="false">
<ElDescriptionsItem label="Python名称">
{{ server.py?.name || "-" }}
</ElDescriptionsItem>
@@ -143,7 +143,7 @@
<ElDescriptionsItem label="项目路径" :span="2">
{{ server.sys?.user_dir || "-" }}
</ElDescriptionsItem>
</ElDescriptions>
</FaDescriptions>
</ElCard>
</ElCol>
</ElRow>
@@ -186,6 +186,7 @@
</template>
<script lang="ts" setup>
import FaDescriptions from "@/components/others/fa-descriptions/index.vue";
import ServerAPI, { type ServerInfo } from "@/api/module_monitor/server";
defineOptions({ name: "ServerMonitor" });
@@ -1,4 +1,4 @@
<!-- 部门配置:Art + 树形表格(对齐 system/menu 模版) -->
<!-- 部门配置:FA + 树形表格(对齐 system/menu 模版) -->
<template>
<div class="fa-full-height">
<FaSearchBar
@@ -63,97 +63,58 @@
width="640px"
dialog-class="crud-embed-dialog"
modal-class="crud-embed-dialog"
@close="handleCloseDialog"
:form-mode="dialogVisible.type"
:confirm-loading="submitLoading"
@cancel="handleCloseDialog"
@confirm="dialogVisible.type === 'detail' ? handleCloseDialog() : handleSubmit()"
>
<template v-if="dialogVisible.type === 'detail'">
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
<ElDescriptions :column="4" border>
<ElDescriptionsItem label="部门名称" :span="2">
{{ detailFormData.name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="部门编码" :span="2">
{{ detailFormData.code }}
</ElDescriptionsItem>
<ElDescriptionsItem label="上级部门" :span="2">
{{ detailFormData.parent_name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="状态" :span="2">
<ElTag :type="detailFormData.status === '0' ? 'success' : 'danger'">
{{ detailFormData.status === "0" ? "启用" : "停用" }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="排序" :span="2">
{{ detailFormData.order }}
</ElDescriptionsItem>
<ElDescriptionsItem label="创建时间" :span="2">
{{ detailFormData.created_time }}
</ElDescriptionsItem>
<ElDescriptionsItem label="更新时间" :span="2">
{{ detailFormData.updated_time }}
</ElDescriptionsItem>
<ElDescriptionsItem label="描述" :span="4">
{{ detailFormData.description }}
</ElDescriptionsItem>
</ElDescriptions>
</ElScrollbar>
<FaDescriptions
:column="4"
:data="detailFormData"
:items="deptDetailItems"
max-height="75vh"
/>
</template>
<template v-else>
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
<FaForm
:key="deptFormRenderKey"
ref="dataFormRef"
v-model="formData"
:items="deptDialogFormItems"
:rules="rules"
label-suffix=":"
:label-width="'auto'"
label-position="right"
:span="24"
:gutter="16"
:show-reset="false"
:show-submit="false"
class="crud-dialog-art-form"
>
<template #status>
<ElRadioGroup v-model="formData.status">
<ElRadio value="0">启用</ElRadio>
<ElRadio value="1">停用</ElRadio>
</ElRadioGroup>
</template>
</FaForm>
</ElScrollbar>
</template>
<template #footer>
<div class="dialog-footer" :style="'padding-right: var(--el-dialog-padding-primary)'">
<ElButton @click="handleCloseDialog">取消</ElButton>
<ElButton v-if="dialogVisible.type !== 'detail'" type="primary" @click="handleSubmit">
确定
</ElButton>
<ElButton v-else type="primary" @click="handleCloseDialog">确定</ElButton>
</div>
<FaForm
:key="deptFormRenderKey"
scrollbar
max-height="75vh"
ref="dataFormRef"
v-model="formData"
:items="deptDialogFormItems"
:rules="rules"
label-suffix=":"
:label-width="'auto'"
label-position="right"
:span="24"
:gutter="16"
:show-reset="false"
:show-submit="false"
class="crud-dialog-art-form"
>
<template #status>
<ElRadioGroup v-model="formData.status">
<ElRadio value="0">启用</ElRadio>
<ElRadio value="1">停用</ElRadio>
</ElRadioGroup>
</template>
</FaForm>
</template>
</FaDialog>
</div>
</template>
<script setup lang="ts">
import { h, ref, reactive, computed, nextTick, onMounted } from "vue";
import { useTableColumns } from "@/hooks/core/useTableColumns";
import DeptAPI, {
type DeptForm,
type DeptPageQuery,
type DeptTable,
} from "@/api/module_system/dept";
import FaTable from "@/components/tables/fa-table/index.vue";
import FaTableHeader from "@/components/tables/fa-table-header/index.vue";
import FaTableHeaderLeft from "@/components/tables/fa-table-header-left/index.vue";
import FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
import FaDialog from "@/components/modal/fa-dialog/index.vue";
import FaForm from "@/components/forms/fa-form/index.vue";
import type { FormItem } from "@/components/forms/fa-form/index.vue";
import { ElMessage, ElMessageBox, ElTag } from "element-plus";
import { useAuth } from "@/hooks/core/useAuth";
import { useUserStore } from "@stores";
import { formatTree } from "@utils/common";
@@ -368,6 +329,24 @@ const { columnChecks, columns } = useTableColumns<DeptTable>(() => [
const detailFormData = ref<DeptTable>({ code: "" });
const deptDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] =
[
{ label: "部门名称", prop: "name" },
{ label: "部门编码", prop: "code" },
{ label: "上级部门", prop: "parent_name" },
{
label: "状态",
prop: "status",
tag: {
map: { "0": { type: "success", text: "启用" }, "1": { type: "danger", text: "停用" } },
},
},
{ label: "排序", prop: "order" },
{ label: "创建时间", prop: "created_time" },
{ label: "更新时间", prop: "updated_time" },
{ label: "描述", prop: "description", span: 4 },
];
const formData = ref<DeptForm>({
id: undefined,
name: undefined,
@@ -411,6 +390,7 @@ const initialFormData: DeptForm = {
};
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
const submitLoading = ref(false);
const deptFormRenderKey = ref(0);
const deptDialogFormItems = computed<FormItem[]>(() => [
@@ -494,8 +474,8 @@ function onResetSearch() {
}
async function resetForm() {
dataFormRef.value?.ref?.resetFields();
dataFormRef.value?.ref?.clearValidate();
dataFormRef.value?.resetFields();
dataFormRef.value?.clearValidate();
Object.assign(formData, initialFormData);
}
@@ -42,7 +42,7 @@
:perm-patch="['module_system:dict_data:patch']"
:delete-loading="batchDeleting"
@add="handleOpenDialog('create')"
@export="openExportModal"
@export="openExport"
@delete="handleBatchDelete"
@more="handleMoreClick"
/>
@@ -67,167 +67,125 @@
width="720px"
dialog-class="crud-embed-dialog"
modal-class="crud-embed-dialog"
@close="handleCloseDialog"
:form-mode="dialogVisible.type"
:confirm-loading="submitLoading"
@cancel="handleCloseDialog"
@confirm="dialogVisible.type === 'detail' ? handleCloseDialog() : handleSubmit()"
>
<template v-if="dialogVisible.type === 'detail'">
<ElScrollbar max-height="70vh" :view-style="{ overflowX: 'hidden' }">
<ElDescriptions :column="2" border>
<ElDescriptionsItem label="数据标签" :span="2">
{{ detailFormData.dict_label }}
</ElDescriptionsItem>
<ElDescriptionsItem label="数据类型" :span="2">
{{ detailFormData.dict_type }}
</ElDescriptionsItem>
<ElDescriptionsItem label="数据值" :span="2">
{{ detailFormData.dict_value }}
</ElDescriptionsItem>
<ElDescriptionsItem label="样式属性" :span="2">
{{ detailFormData.css_class }}
</ElDescriptionsItem>
<ElDescriptionsItem label="列表样式属性" :span="2">
{{ detailFormData.list_class }}
</ElDescriptionsItem>
<ElDescriptionsItem label="是否默认" :span="2">
<ElTag v-if="detailFormData.is_default" type="success">是</ElTag>
<ElTag v-else type="danger">否</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="状态" :span="2">
<ElTag v-if="detailFormData.status === '0'" type="success">启用</ElTag>
<ElTag v-else type="danger">停用</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="排序" :span="2">
{{ detailFormData.dict_sort }}
</ElDescriptionsItem>
<ElDescriptionsItem label="描述" :span="2">
{{ detailFormData.description }}
</ElDescriptionsItem>
<ElDescriptionsItem label="创建时间" :span="2">
{{ detailFormData.created_time }}
</ElDescriptionsItem>
<ElDescriptionsItem label="更新时间" :span="2">
{{ detailFormData.updated_time }}
</ElDescriptionsItem>
</ElDescriptions>
</ElScrollbar>
<FaDescriptions
:column="2"
:data="detailFormData"
:items="dictDataDetailItems"
max-height="70vh"
/>
</template>
<template v-else>
<ElScrollbar max-height="70vh" :view-style="{ overflowX: 'hidden' }">
<FaForm
:key="dictDataFormRenderKey"
ref="dataFormRef"
v-model="formData"
:items="dictDataDialogFormItems"
:rules="rules"
label-suffix=":"
:label-width="'auto'"
label-position="right"
:span="24"
:gutter="16"
:show-reset="false"
:show-submit="false"
class="crud-dialog-art-form"
>
<template #css_class>
<ElSelect
v-model="formData.css_class"
placeholder="请选择常用颜色或输入自定义"
clearable
filterable
allow-create
default-first-option
>
<ElOption value="primary" label="主要(primary)">
<span class="tag-option-preview" :style="getTagPreviewStyle('primary')">
主要(primary)
</span>
</ElOption>
<ElOption value="success" label="成功(success)">
<span class="tag-option-preview" :style="getTagPreviewStyle('success')">
成功(success)
</span>
</ElOption>
<ElOption value="warning" label="警告(warning)">
<span class="tag-option-preview" :style="getTagPreviewStyle('warning')">
警告(warning)
</span>
</ElOption>
<ElOption value="danger" label="危险(danger)">
<span class="tag-option-preview" :style="getTagPreviewStyle('danger')">
危险(danger)
</span>
</ElOption>
<ElOption value="info" label="信息(info)">
<span class="tag-option-preview" :style="getTagPreviewStyle('info')">
信息(info)
</span>
</ElOption>
</ElSelect>
</template>
<template #list_class>
<ElSelect v-model="formData.list_class" placeholder="请选择列表类样式" clearable>
<ElOption value="default" label="默认(default)">
<span class="tag-option-preview tag-option-preview--default">
默认(default)
</span>
</ElOption>
<ElOption value="primary" label="主要(primary)">
<span class="tag-option-preview" :style="getTagPreviewStyle('primary')">
主要(primary)
</span>
</ElOption>
<ElOption value="success" label="成功(success)">
<span class="tag-option-preview" :style="getTagPreviewStyle('success')">
成功(success)
</span>
</ElOption>
<ElOption value="warning" label="警告(warning)">
<span class="tag-option-preview" :style="getTagPreviewStyle('warning')">
警告(warning)
</span>
</ElOption>
<ElOption value="danger" label="危险(danger)">
<span class="tag-option-preview" :style="getTagPreviewStyle('danger')">
危险(danger)
</span>
</ElOption>
<ElOption value="info" label="信息(info)">
<span class="tag-option-preview" :style="getTagPreviewStyle('info')">
信息(info)
</span>
</ElOption>
</ElSelect>
</template>
<template #is_default>
<ElRadioGroup v-model="formData.is_default">
<ElRadio :value="true">是</ElRadio>
<ElRadio :value="false">否</ElRadio>
</ElRadioGroup>
</template>
<template #status>
<ElSwitch
v-model="formData.status"
inline-prompt
:active-value="'0'"
:inactive-value="'1'"
/>
</template>
</FaForm>
</ElScrollbar>
</template>
<template #footer>
<div class="dialog-footer" :style="'padding-right: var(--el-dialog-padding-primary)'">
<ElButton @click="handleCloseDialog">取消</ElButton>
<ElButton v-if="dialogVisible.type !== 'detail'" type="primary" @click="handleSubmit">
确定
</ElButton>
<ElButton v-else type="primary" @click="handleCloseDialog">确定</ElButton>
</div>
<FaForm
:key="dictDataFormRenderKey"
scrollbar
max-height="70vh"
ref="dataFormRef"
v-model="formData"
:items="dictDataDialogFormItems"
:rules="rules"
label-suffix=":"
:label-width="'auto'"
label-position="right"
:span="24"
:gutter="16"
:show-reset="false"
:show-submit="false"
class="crud-dialog-art-form"
>
<template #css_class>
<ElSelect
v-model="formData.css_class"
placeholder="请选择常用颜色或输入自定义"
clearable
filterable
allow-create
default-first-option
>
<ElOption value="primary" label="主要(primary)">
<span class="tag-option-preview" :style="getTagPreviewStyle('primary')">
主要(primary)
</span>
</ElOption>
<ElOption value="success" label="成功(success)">
<span class="tag-option-preview" :style="getTagPreviewStyle('success')">
成功(success)
</span>
</ElOption>
<ElOption value="warning" label="警告(warning)">
<span class="tag-option-preview" :style="getTagPreviewStyle('warning')">
警告(warning)
</span>
</ElOption>
<ElOption value="danger" label="危险(danger)">
<span class="tag-option-preview" :style="getTagPreviewStyle('danger')">
危险(danger)
</span>
</ElOption>
<ElOption value="info" label="信息(info)">
<span class="tag-option-preview" :style="getTagPreviewStyle('info')">
信息(info)
</span>
</ElOption>
</ElSelect>
</template>
<template #list_class>
<ElSelect v-model="formData.list_class" placeholder="请选择列表类样式" clearable>
<ElOption value="default" label="默认(default)">
<span class="tag-option-preview tag-option-preview--default">默认(default)</span>
</ElOption>
<ElOption value="primary" label="主要(primary)">
<span class="tag-option-preview" :style="getTagPreviewStyle('primary')">
主要(primary)
</span>
</ElOption>
<ElOption value="success" label="成功(success)">
<span class="tag-option-preview" :style="getTagPreviewStyle('success')">
成功(success)
</span>
</ElOption>
<ElOption value="warning" label="警告(warning)">
<span class="tag-option-preview" :style="getTagPreviewStyle('warning')">
警告(warning)
</span>
</ElOption>
<ElOption value="danger" label="危险(danger)">
<span class="tag-option-preview" :style="getTagPreviewStyle('danger')">
危险(danger)
</span>
</ElOption>
<ElOption value="info" label="信息(info)">
<span class="tag-option-preview" :style="getTagPreviewStyle('info')">
信息(info)
</span>
</ElOption>
</ElSelect>
</template>
<template #is_default>
<ElRadioGroup v-model="formData.is_default">
<ElRadio :value="true">是</ElRadio>
<ElRadio :value="false">否</ElRadio>
</ElRadioGroup>
</template>
<template #status>
<ElSwitch
v-model="formData.status"
inline-prompt
:active-value="'0'"
:inactive-value="'1'"
/>
</template>
</FaForm>
</template>
</FaDialog>
<FaExportDialog
v-model="exportModalVisible"
v-model="exportVisible"
:content-config="dictDataExportContentConfig"
:query-params="exportQueryParams"
:page-data="data"
@@ -238,16 +196,10 @@
</template>
<script setup lang="ts">
import { h, computed, ref, reactive } from "vue";
import { useTable } from "@/hooks/core/useTable";
import FaTableHeaderLeft from "@/components/tables/fa-table-header-left/index.vue";
import FaExportDialog from "@/components/modal/fa-export-dialog/index.vue";
import { useImportExport } from "@/hooks/core/useImportExport";
import type { IObject } from "@/components/modal/types";
import FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
import FaDialog from "@/components/modal/fa-dialog/index.vue";
import FaDrawer from "@/components/modal/fa-drawer/index.vue";
import FaForm from "@/components/forms/fa-form/index.vue";
import type { FormItem } from "@/components/forms/fa-form/index.vue";
import type { ColumnOption } from "@/types/component";
import DictAPI, {
@@ -255,7 +207,6 @@ import DictAPI, {
type DictDataPageQuery,
type DictDataTable,
} from "@/api/module_system/dict";
import { ElMessage, ElMessageBox, ElTag } from "element-plus";
import { useAuth } from "@/hooks/core/useAuth";
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
import { useDictStore } from "@stores";
@@ -542,6 +493,33 @@ const dialogVisible = reactive({
const detailFormData = ref<DictDataTable>({});
const dictDataDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] =
[
{ label: "数据标签", prop: "dict_label" },
{ label: "数据类型", prop: "dict_type" },
{ label: "数据值", prop: "dict_value" },
{ label: "样式属性", prop: "css_class" },
{ label: "列表样式属性", prop: "list_class" },
{
label: "是否默认",
prop: "is_default",
tag: {
map: { true: { type: "success", text: "是" }, false: { type: "danger", text: "否" } },
},
},
{
label: "状态",
prop: "status",
tag: {
map: { "0": { type: "success", text: "启用" }, "1": { type: "danger", text: "停用" } },
},
},
{ label: "排序", prop: "dict_sort" },
{ label: "描述", prop: "description" },
{ label: "创建时间", prop: "created_time" },
{ label: "更新时间", prop: "updated_time" },
];
const formData = ref<DictDataForm>({
id: undefined,
dict_sort: 1,
@@ -566,6 +544,7 @@ const rules = reactive({
});
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
const submitLoading = ref(false);
const dictDataFormRenderKey = ref(0);
const dictDataDialogFormItems = computed<FormItem[]>(() => [
@@ -662,7 +641,7 @@ const initialFormData: DictDataForm = {
dict_type_id: props.dictTypeId,
};
const exportModalVisible = ref(false);
const { exportVisible, openExport } = useImportExport();
async function handleSearchBarSearch(params: DictDataSearchForm) {
await searchBarRef.value?.validate?.();
@@ -689,8 +668,8 @@ function onResetSearch() {
}
async function resetForm() {
dataFormRef.value?.ref?.resetFields();
dataFormRef.value?.ref?.clearValidate();
dataFormRef.value?.resetFields();
dataFormRef.value?.clearValidate();
Object.assign(formData, {
...initialFormData,
dict_type_id: props.dictTypeId,
@@ -848,10 +827,6 @@ async function handleMoreClick(status: string) {
// 用户取消
}
}
function openExportModal() {
exportModalVisible.value = true;
}
</script>
<style lang="scss" scoped>
@@ -37,7 +37,7 @@
:perm-patch="['module_system:dict_type:patch']"
:delete-loading="batchDeleting"
@add="handleOpenDialog('create')"
@export="openExportModal"
@export="openExport"
@delete="handleBatchDelete"
@more="handleMoreClick"
/>
@@ -62,73 +62,53 @@
width="640px"
dialog-class="crud-embed-dialog"
modal-class="crud-embed-dialog"
@close="handleCloseDialog"
:form-mode="dialogVisible.type"
:confirm-loading="submitLoading"
@cancel="handleCloseDialog"
@confirm="dialogVisible.type === 'detail' ? handleCloseDialog() : handleSubmit()"
>
<template v-if="dialogVisible.type === 'detail'">
<ElScrollbar max-height="70vh" :view-style="{ overflowX: 'hidden' }">
<ElDescriptions :column="2" border>
<ElDescriptionsItem label="字典名称" :span="2">
{{ detailFormData.dict_name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="字典类型" :span="2">
<ElTag type="primary">{{ detailFormData.dict_type }}</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="状态" :span="2">
<ElTag v-if="detailFormData.status === '0'" type="success">启用</ElTag>
<ElTag v-else type="danger">停用</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="描述" :span="2">
{{ detailFormData.description }}
</ElDescriptionsItem>
<ElDescriptionsItem label="创建时间" :span="2">
{{ detailFormData.created_time }}
</ElDescriptionsItem>
<ElDescriptionsItem label="更新时间" :span="2">
{{ detailFormData.updated_time }}
</ElDescriptionsItem>
</ElDescriptions>
</ElScrollbar>
<FaDescriptions
:column="2"
:data="detailFormData"
:items="dictDetailItems"
max-height="70vh"
>
<template #dict_type="{ row }">
<ElTag type="primary">{{ row?.dict_type }}</ElTag>
</template>
</FaDescriptions>
</template>
<template v-else>
<ElScrollbar max-height="70vh" :view-style="{ overflowX: 'hidden' }">
<FaForm
:key="dictFormRenderKey"
ref="dataFormRef"
v-model="formData"
:items="dictDialogFormItems"
:rules="rules"
label-suffix=":"
:label-width="'auto'"
label-position="right"
:span="24"
:gutter="16"
:show-reset="false"
:show-submit="false"
class="crud-dialog-art-form"
>
<template #status>
<ElRadioGroup v-model="formData.status">
<ElRadio value="0">启用</ElRadio>
<ElRadio value="1">停用</ElRadio>
</ElRadioGroup>
</template>
</FaForm>
</ElScrollbar>
</template>
<template #footer>
<div class="dialog-footer" :style="'padding-right: var(--el-dialog-padding-primary)'">
<ElButton @click="handleCloseDialog">取消</ElButton>
<ElButton v-if="dialogVisible.type !== 'detail'" type="primary" @click="handleSubmit">
确定
</ElButton>
<ElButton v-else type="primary" @click="handleCloseDialog">确定</ElButton>
</div>
<FaForm
:key="dictFormRenderKey"
scrollbar
max-height="70vh"
ref="dataFormRef"
v-model="formData"
:items="dictDialogFormItems"
:rules="rules"
label-suffix=":"
:label-width="'auto'"
label-position="right"
:span="24"
:gutter="16"
:show-reset="false"
:show-submit="false"
class="crud-dialog-art-form"
>
<template #status>
<ElRadioGroup v-model="formData.status">
<ElRadio value="0">启用</ElRadio>
<ElRadio value="1">停用</ElRadio>
</ElRadioGroup>
</template>
</FaForm>
</template>
</FaDialog>
<FaExportDialog
v-model="exportModalVisible"
v-model="exportVisible"
:content-config="dictTypeExportContentConfig"
:query-params="exportQueryParams"
:page-data="data"
@@ -146,15 +126,10 @@
</template>
<script setup lang="ts">
import { h, computed, ref, reactive } from "vue";
import { useTable } from "@/hooks/core/useTable";
import FaTableHeaderLeft from "@/components/tables/fa-table-header-left/index.vue";
import FaExportDialog from "@/components/modal/fa-export-dialog/index.vue";
import { useImportExport } from "@/hooks/core/useImportExport";
import type { IObject } from "@/components/modal/types";
import FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
import FaDialog from "@/components/modal/fa-dialog/index.vue";
import FaForm from "@/components/forms/fa-form/index.vue";
import type { FormItem } from "@/components/forms/fa-form/index.vue";
import type { ColumnOption } from "@/types/component";
import DictAPI, {
@@ -162,7 +137,6 @@ import DictAPI, {
type DictPageQuery,
type DictTable,
} from "@/api/module_system/dict";
import { ElMessage, ElMessageBox, ElTag } from "element-plus";
import { useDictStore } from "@stores";
import DataDrawer from "@views/module_system/dict/components/DataDrawer.vue";
import { useAuth } from "@/hooks/core/useAuth";
@@ -364,6 +338,22 @@ const dialogVisible = reactive({
const detailFormData = ref<DictTable>({});
const dictDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] =
[
{ label: "字典名称", prop: "dict_name" },
{ label: "字典类型", prop: "dict_type", slot: "dict_type" },
{
label: "状态",
prop: "status",
tag: {
map: { "0": { type: "success", text: "启用" }, "1": { type: "danger", text: "停用" } },
},
},
{ label: "描述", prop: "description" },
{ label: "创建时间", prop: "created_time" },
{ label: "更新时间", prop: "updated_time" },
];
const formData = ref<DictForm>({
id: undefined,
dict_name: "",
@@ -379,6 +369,7 @@ const rules = reactive({
});
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
const submitLoading = ref(false);
const dictFormRenderKey = ref(0);
const dictDialogFormItems = computed<FormItem[]>(() => [
@@ -426,7 +417,7 @@ const initialFormData: DictForm = {
description: undefined,
};
const exportModalVisible = ref(false);
const { exportVisible, openExport } = useImportExport();
const drawerVisible = ref(false);
const currentDictType = ref("");
@@ -511,8 +502,8 @@ function handleDictDataDrawer(dictTypeRow: DictTable) {
}
async function resetForm() {
dataFormRef.value?.ref?.resetFields();
dataFormRef.value?.ref?.clearValidate();
dataFormRef.value?.resetFields();
dataFormRef.value?.clearValidate();
Object.assign(formData, initialFormData);
}
@@ -629,10 +620,6 @@ async function handleMoreClick(status: string) {
// 用户取消
}
}
function openExportModal() {
exportModalVisible.value = true;
}
</script>
<style scoped lang="scss">
@@ -43,7 +43,7 @@
:perm-export="['module_system:log:export']"
:perm-delete="['module_system:log:delete']"
:delete-loading="batchDeleting"
@export="openExportModal"
@export="openExport"
@delete="handleBatchDelete"
/>
</template>
@@ -67,77 +67,42 @@
width="960px"
dialog-class="crud-embed-dialog"
modal-class="crud-embed-dialog"
@close="handleCloseDialog"
form-mode="detail"
@confirm="handleCloseDialog"
>
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
<ElDescriptions :column="8" border label-width="200px">
<ElDescriptionsItem label="日志类型" :span="2">
<ElTag :type="formData.type === 1 ? 'success' : 'primary'">
{{ formData.type === 1 ? "登录日志" : "操作日志" }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="请求路径" :span="2">
{{ formData.request_path }}
</ElDescriptionsItem>
<ElDescriptionsItem label="请求方法" :span="2">
<ElTag :type="getMethodType(formData.request_method)">
{{ formData.request_method }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="响应状态码" :span="2">
<ElTag :type="getStatusCodeType(formData.response_code)">
{{ formData.response_code }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="请求IP" :span="2">
{{ formData.request_ip }}
</ElDescriptionsItem>
<ElDescriptionsItem label="处理时间" :span="2">
{{ formData.process_time }}
</ElDescriptionsItem>
<ElDescriptionsItem label="浏览器" :span="2">
{{ formData.request_browser }}
</ElDescriptionsItem>
<ElDescriptionsItem label="操作系统" :span="2">
{{ formData.request_os }}
</ElDescriptionsItem>
<ElDescriptionsItem label="请求参数" :span="4">
<JsonPretty :value="formData.request_payload" height="80px" />
</ElDescriptionsItem>
<ElDescriptionsItem label="响应数据" :span="4">
<JsonPretty :value="formData.response_json" height="140px" />
</ElDescriptionsItem>
<ElDescriptionsItem label="登录地点" :span="2">
{{ formData.login_location }}
</ElDescriptionsItem>
<ElDescriptionsItem label="描述" :span="4">
{{ formData.description }}
</ElDescriptionsItem>
<ElDescriptionsItem label="创建人" :span="2">
{{ formData.created_by?.name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="更新人" :span="2">
{{ formData.updated_by?.name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="创建时间" :span="2">
{{ formData.created_time }}
</ElDescriptionsItem>
<ElDescriptionsItem label="更新时间" :span="2">
{{ formData.updated_time }}
</ElDescriptionsItem>
</ElDescriptions>
</ElScrollbar>
<template #footer>
<div class="dialog-footer" :style="'padding-right: var(--el-dialog-padding-primary)'">
<ElButton @click="handleCloseDialog">取消</ElButton>
<ElButton type="primary" @click="handleCloseDialog">确定</ElButton>
</div>
</template>
<FaDescriptions
:column="8"
:data="formData"
:items="logDetailItems"
label-width="200px"
max-height="75vh"
>
<template #type="{ row }">
<ElTag :type="row?.type === 1 ? 'success' : 'primary'">
{{ row?.type === 1 ? "登录日志" : "操作日志" }}
</ElTag>
</template>
<template #request_method="{ row }">
<ElTag :type="getMethodType(row?.request_method as string)">
{{ row?.request_method }}
</ElTag>
</template>
<template #response_code="{ row }">
<ElTag :type="getStatusCodeType(row?.response_code as number)">
{{ row?.response_code }}
</ElTag>
</template>
<template #request_payload="{ row }">
<JsonPretty :value="row?.request_payload as any" height="80px" />
</template>
<template #response_json="{ row }">
<JsonPretty :value="row?.response_json as any" height="140px" />
</template>
</FaDescriptions>
</FaDialog>
<FaExportDialog
v-model="exportModalVisible"
v-model="exportVisible"
:content-config="logExportContentConfig"
:query-params="exportQueryParams"
:page-data="data"
@@ -147,21 +112,12 @@
</template>
<script setup lang="ts">
import { h, computed, ref, nextTick } from "vue";
import { useTable } from "@/hooks/core/useTable";
import FaTable from "@/components/tables/fa-table/index.vue";
import FaTableHeader from "@/components/tables/fa-table-header/index.vue";
import FaTableHeaderLeft from "@/components/tables/fa-table-header-left/index.vue";
import FaExportDialog from "@/components/modal/fa-export-dialog/index.vue";
import { useImportExport } from "@/hooks/core/useImportExport";
import type { IObject } from "@/components/modal/types";
import FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
import FaDialog from "@/components/modal/fa-dialog/index.vue";
import JsonPretty from "@/components/others/fa-json-pretty/index.vue";
import CopyButton from "@/components/others/fa-copy-button/index.vue";
import type { ColumnOption } from "@/types/component";
import LogAPI, { type LogPageQuery, type LogTable } from "@/api/module_system/log";
import { ElMessage, ElMessageBox, ElTag } from "element-plus";
import { useAuth } from "@/hooks/core/useAuth";
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
import UserTableSelect from "@views/module_system/user/components/UserTableSelect.vue";
@@ -400,6 +356,25 @@ const logExportContentConfig = computed(() => ({
const formData = ref<LogTable>({});
const logDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] = [
{ label: "日志类型", prop: "type", slot: "type" },
{ label: "请求路径", prop: "request_path" },
{ label: "请求方法", prop: "request_method", slot: "request_method" },
{ label: "响应状态码", prop: "response_code", slot: "response_code" },
{ label: "请求IP", prop: "request_ip" },
{ label: "处理时间", prop: "process_time" },
{ label: "浏览器", prop: "request_browser" },
{ label: "操作系统", prop: "request_os" },
{ label: "请求参数", prop: "request_payload", slot: "request_payload", span: 4 },
{ label: "响应数据", prop: "response_json", slot: "response_json", span: 4 },
{ label: "登录地点", prop: "login_location" },
{ label: "描述", prop: "description", span: 4 },
{ label: "创建人", prop: "created_by.name" },
{ label: "更新人", prop: "updated_by.name" },
{ label: "创建时间", prop: "created_time" },
{ label: "更新时间", prop: "updated_time" },
];
const dialogVisible = ref({
title: "",
visible: false,
@@ -421,7 +396,7 @@ function getMethodType(method?: string) {
return "info";
}
const exportModalVisible = ref(false);
const { exportVisible, openExport } = useImportExport();
async function handleSearchBarSearch(params: LogSearchForm) {
await searchBarRef.value?.validate?.();
@@ -533,8 +508,4 @@ async function handleBatchDelete() {
batchDeleting.value = false;
}
}
function openExportModal() {
exportModalVisible.value = true;
}
</script>
@@ -71,122 +71,57 @@
v-model="dialogVisible.visible"
:title="dialogVisible.title"
:size="drawerSize"
@close="handleCloseDialog"
:form-mode="dialogVisible.type"
:confirm-loading="submitLoading"
@cancel="handleCloseDialog"
@confirm="dialogVisible.type === 'detail' ? handleCloseDialog() : handleSubmit()"
>
<!-- 详情 -->
<template v-if="dialogVisible.type === 'detail'">
<ElDescriptions :column="4" border>
<ElDescriptionsItem label="编号" :span="2">
{{ detailFormData.id }}
</ElDescriptionsItem>
<ElDescriptionsItem label="菜单名称" :span="2">
{{ detailFormData.name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="菜单类型" :span="2">
<ElTag v-if="detailFormData.type === MenuTypeEnum.CATALOG" type="warning">目录</ElTag>
<ElTag v-if="detailFormData.type === MenuTypeEnum.MENU" type="success">菜单</ElTag>
<ElTag v-if="detailFormData.type === MenuTypeEnum.BUTTON" type="danger">按钮</ElTag>
<ElTag v-if="detailFormData.type === MenuTypeEnum.EXTLINK" type="info">外链</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="终端" :span="2">
<ElTag v-if="detailFormData.client === MenuClientEnum.PC" type="primary">PC</ElTag>
<ElTag v-else-if="detailFormData.client === MenuClientEnum.APP" type="success">
APP
</ElTag>
<ElTag v-else type="info">{{ detailFormData.client || "—" }}</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="图标" :span="2">
<template #default>
<template v-if="detailFormData.icon">
<MenuRouteIcon :icon="detailFormData.icon" :style="'vertical-align: -0.15em'" />
</template>
<FaDescriptions
:column="4"
:data="detailFormData"
:items="menuDetailItems"
:scrollbar="false"
>
<template #type="{ row }">
<ElTag v-if="row?.type === MenuTypeEnum.CATALOG" type="warning">目录</ElTag>
<ElTag v-if="row?.type === MenuTypeEnum.MENU" type="success">菜单</ElTag>
<ElTag v-if="row?.type === MenuTypeEnum.BUTTON" type="danger">按钮</ElTag>
<ElTag v-if="row?.type === MenuTypeEnum.EXTLINK" type="info">外链</ElTag>
</template>
<template #client="{ row }">
<ElTag v-if="row?.client === MenuClientEnum.PC" type="primary">PC</ElTag>
<ElTag v-else-if="row?.client === MenuClientEnum.APP" type="success">APP</ElTag>
<ElTag v-else type="info">{{ row?.client || "—" }}</ElTag>
</template>
<template #icon="{ row }">
<template v-if="row?.icon">
<MenuRouteIcon :icon="row?.icon as string" :style="'vertical-align: -0.15em'" />
</template>
</ElDescriptionsItem>
<ElDescriptionsItem label="排序" :span="2">
{{ detailFormData.order }}
</ElDescriptionsItem>
<ElDescriptionsItem label="权限标识" :span="2">
{{ detailFormData.permission }}
</ElDescriptionsItem>
<ElDescriptionsItem label="路由名称" :span="2">
{{ detailFormData.route_name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="路由路径" :span="2">
{{ detailFormData.route_path }}
</ElDescriptionsItem>
<ElDescriptionsItem label="组件路径" :span="2">
{{ detailFormData.component_path }}
</ElDescriptionsItem>
<ElDescriptionsItem label="重定向" :span="2">
{{ detailFormData.redirect }}
</ElDescriptionsItem>
<ElDescriptionsItem label="父级编号" :span="2">
{{ detailFormData.parent_id }}
</ElDescriptionsItem>
<ElDescriptionsItem label="父级菜单" :span="2">
{{ detailFormData.parent_name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="是否缓存" :span="2">
<ElTag :type="detailFormData.keep_alive ? 'success' : 'danger'">
{{ detailFormData.keep_alive ? "是" : "否" }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="是否显示" :span="2">
<ElTag :type="detailFormData.hidden ? 'success' : 'danger'">
{{ detailFormData.hidden ? "是" : "否" }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="是否显示根路由" :span="2">
<ElTag :type="detailFormData.always_show ? 'success' : 'danger'">
{{ detailFormData.always_show ? "是" : "否" }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="菜单标题" :span="2">
{{ detailFormData.title }}
</ElDescriptionsItem>
<ElDescriptionsItem label="路由参数" :span="2">
{{ detailFormData.params }}
</ElDescriptionsItem>
<ElDescriptionsItem label="是否固定路由" :span="2">
<ElTag :type="detailFormData.affix ? 'success' : 'danger'">
{{ detailFormData.affix ? "是" : "否" }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="状态" :span="2">
<ElTag :type="detailFormData.status === '0' ? 'success' : 'danger'">
{{ detailFormData.status === "0" ? "启用" : "停用" }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="排序" :span="2">
{{ detailFormData.order }}
</ElDescriptionsItem>
<ElDescriptionsItem label="创建时间" :span="2">
{{ detailFormData.created_time }}
</ElDescriptionsItem>
<ElDescriptionsItem label="更新时间" :span="2">
{{ detailFormData.updated_time }}
</ElDescriptionsItem>
<ElDescriptionsItem label="描述" :span="4">
{{ detailFormData.description }}
</ElDescriptionsItem>
</ElDescriptions>
</template>
</FaDescriptions>
</template>
<!-- 新增、编辑表单 -->
<template v-else>
<ElForm
<FaForm
:key="menuFormRenderKey"
ref="dataFormRef"
:model="formData"
v-model="formData"
:items="menuDialogFormItems"
:rules="rules"
label-suffix=":"
label-width="100px"
:label-width="'auto'"
label-position="right"
:span="24"
:gutter="16"
:show-reset="false"
:show-submit="false"
class="crud-dialog-art-form"
>
<ElFormItem
v-if="formData.type !== MenuTypeEnum.CATALOG"
label="父级菜单"
prop="parent_id"
>
<!-- 父级菜单(条件显示) -->
<template v-if="formData.type !== MenuTypeEnum.CATALOG" #parent_id>
<ElTreeSelect
v-model="formData.parent_id"
placeholder="选择上级菜单"
@@ -199,17 +134,10 @@
<ElText v-if="createParentLocked" type="info" size="small" class="block mt-1">
在菜单下仅可新增按钮,父级已固定
</ElText>
</ElFormItem>
</template>
<ElFormItem label="菜单名称" prop="name">
<ElInput v-model="formData.name" placeholder="请输入菜单名称" />
</ElFormItem>
<ElFormItem label="菜单标题" prop="title">
<ElInput v-model="formData.title" placeholder="请输入菜单标题" />
</ElFormItem>
<ElFormItem label="菜单类型" prop="type">
<!-- 菜单类型(动态枚举按钮组) -->
<template #type>
<ElRadioGroup v-model="formData.type" @change="handleMenuTypeChange">
<ElRadio
v-if="allowedMenuTypeValues.includes(MenuTypeEnum.CATALOG)"
@@ -236,9 +164,10 @@
外链
</ElRadio>
</ElRadioGroup>
</ElFormItem>
</template>
<ElFormItem label="终端" prop="client">
<!-- 终端(禁用态+提示) -->
<template #client>
<ElRadioGroup v-model="formData.client" :disabled="createParentLocked">
<ElRadio :value="MenuClientEnum.PC">PC 桌面</ElRadio>
<ElRadio :value="MenuClientEnum.APP">APP 移动</ElRadio>
@@ -246,91 +175,41 @@
<ElText v-if="createParentLocked" type="info" size="small" class="block mt-1">
子级终端与父菜单一致
</ElText>
</ElFormItem>
</template>
<ElFormItem v-if="formData.type == MenuTypeEnum.EXTLINK" label="外链地址" prop="path">
<!-- 外链地址(条件显示) -->
<template v-if="formData.type == MenuTypeEnum.EXTLINK" #extlink_path>
<ElInput v-model="formData.route_path" placeholder="请输入外链完整路径" />
</ElFormItem>
</template>
<ElFormItem v-if="formData.type !== MenuTypeEnum.BUTTON" prop="route_name">
<template #label>
<div class="flex-y-center">
路由名称
<ElTooltip placement="bottom" effect="light">
<template #content>
如果需要开启缓存,需保证页面 defineOptions 中的 name 与此处一致,建议使用驼峰。
</template>
<ElIcon class="ml-1 cursor-pointer">
<QuestionFilled />
</ElIcon>
</ElTooltip>
</div>
</template>
<!-- 路由名称(Tooltip标签) -->
<template v-if="formData.type !== MenuTypeEnum.BUTTON" #route_name>
<ElInput v-model="formData.route_name" placeholder="请输入路由名称" />
</ElFormItem>
</template>
<ElFormItem
<!-- 路由路径(Tooltip标签) -->
<template
v-if="formData.type == MenuTypeEnum.CATALOG || formData.type == MenuTypeEnum.MENU"
prop="route_path"
#route_path
>
<template #label>
<div class="flex-y-center">
路由路径
<ElTooltip placement="bottom" effect="light">
<template #content>
定义应用中不同页面对应的 URL 路径,目录需以 /
开头,菜单项不用。例如:系统管理目录 /system,系统管理下的用户管理菜单 user。
</template>
<ElIcon class="ml-1 cursor-pointer">
<QuestionFilled />
</ElIcon>
</ElTooltip>
</div>
</template>
<ElInput v-model="formData.route_path" placeholder="请输入路由路径,如:/system" />
</ElFormItem>
<ElFormItem v-if="formData.type == MenuTypeEnum.MENU" prop="component">
<template #label>
<div class="flex-y-center">
组件路径
<ElTooltip placement="bottom" effect="light">
<template #content>
组件页面完整路径,相对于 src/views/,如 system/user/index,缺省后缀 .vue
</template>
<ElIcon class="ml-1 cursor-pointer">
<QuestionFilled />
</ElIcon>
</ElTooltip>
</div>
</template>
</template>
<!-- 组件路径(Tooltip+prepend/append) -->
<template v-if="formData.type == MenuTypeEnum.MENU" #component_path>
<ElInput
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>
<template #prepend>src/views/</template>
<template #append>.vue</template>
</ElInput>
</ElFormItem>
</template>
<ElFormItem v-if="formData.type == MenuTypeEnum.MENU">
<template #label>
<div class="flex-y-center">
路由参数
<ElTooltip placement="bottom" effect="light">
<template #content>
组件页面使用 `useRoute().query.参数名` 获取路由参数值。
</template>
<ElIcon class="ml-1 cursor-pointer">
<QuestionFilled />
</ElIcon>
</ElTooltip>
</div>
</template>
<div
<!-- 路由参数(动态键值编辑器) -->
<template v-if="formData.type == MenuTypeEnum.MENU" #params>
<template
v-if="
!formData.params || (Array.isArray(formData.params) && formData.params.length === 0)
"
@@ -338,16 +217,12 @@
<ElButton type="success" plain @click="formData.params = [{ key: '', value: '' }]">
添加路由参数
</ElButton>
</div>
<div v-else>
</template>
<template v-else>
<div v-for="(item, index) in formData.params" :key="index">
<ElInput v-model="item.key" placeholder="参数名" :style="'width: 100px'" />
<span class="mx-1">=</span>
<ElInput v-model="item.value" placeholder="参数值" :style="'width: 100px'" />
<ElIcon
v-if="formData.params.indexOf(item) === formData.params.length - 1"
class="ml-2 cursor-pointer color-[var(--el-color-success)]"
@@ -364,90 +239,52 @@
<DeleteFilled />
</ElIcon>
</div>
</div>
</ElFormItem>
<ElFormItem v-if="formData.type !== MenuTypeEnum.BUTTON">
<template #label>
<div class="flex-y-center">
是否隐藏
<ElTooltip placement="bottom" effect="light">
<template #content>
选择"是", 菜单中隐藏
<br />
选择"否",菜单中显示。
<br />
</template>
<ElIcon class="ml-1 cursor-pointer">
<QuestionFilled />
</ElIcon>
</ElTooltip>
</div>
</template>
</template>
<!-- 是否隐藏(Tooltip标签) -->
<template v-if="formData.type !== MenuTypeEnum.BUTTON" #hidden>
<ElRadioGroup v-model="formData.hidden">
<ElRadio :value="true">是</ElRadio>
<ElRadio :value="false">否</ElRadio>
</ElRadioGroup>
</ElFormItem>
</template>
<ElFormItem
<!-- 始终显示(Tooltip标签) -->
<template
v-if="formData.type === MenuTypeEnum.CATALOG || formData.type === MenuTypeEnum.MENU"
#always_show
>
<template #label>
<div class="flex-y-center">
始终显示
<ElTooltip placement="bottom" effect="light">
<template #content>
选择"是",即使目录或菜单下只有一个子节点,也会显示父节点。
<br />
选择"否",如果目录或菜单下只有一个子节点,则只显示该子节点,隐藏父节点。
<br />
如果是叶子节点,请选择"否"。
</template>
<ElIcon class="ml-1 cursor-pointer">
<QuestionFilled />
</ElIcon>
</ElTooltip>
</div>
</template>
<ElRadioGroup v-model="formData.always_show">
<ElRadio :value="true">是</ElRadio>
<ElRadio :value="false">否</ElRadio>
</ElRadioGroup>
</ElFormItem>
</template>
<ElFormItem v-if="formData.type === MenuTypeEnum.MENU" label="缓存页面">
<!-- 缓存页面(v-if) -->
<template v-if="formData.type === MenuTypeEnum.MENU" #keep_alive>
<ElRadioGroup v-model="formData.keep_alive">
<ElRadio :value="true">开启</ElRadio>
<ElRadio :value="false">关闭</ElRadio>
</ElRadioGroup>
</ElFormItem>
</template>
<ElFormItem label="排序" prop="order">
<ElInputNumber v-model="formData.order" controls-position="right" :min="1" />
</ElFormItem>
<!-- 权限标识 -->
<ElFormItem
<!-- 权限标识(v-if) -->
<template
v-if="formData.type == MenuTypeEnum.BUTTON || formData.type === MenuTypeEnum.MENU"
label="权限标识"
prop="perm"
#permission
>
<ElInput v-model="formData.permission" placeholder="请输入权限标识,如sys:user:add" />
</ElFormItem>
</template>
<ElFormItem v-if="formData.type !== MenuTypeEnum.BUTTON" label="图标" prop="icon">
<!-- 图标选择器 -->
<!-- 图标(v-if) -->
<template v-if="formData.type !== MenuTypeEnum.BUTTON" #icon>
<FaIconSelect v-model="formData.icon" />
</ElFormItem>
</template>
<ElFormItem
<!-- 重定向(v-if, 动态placeholder) -->
<template
v-if="formData.type == MenuTypeEnum.CATALOG || formData.type === MenuTypeEnum.MENU"
label="重定向"
prop="redirect"
:required="formData.type === MenuTypeEnum.CATALOG"
#redirect
>
<ElInput
v-model="formData.redirect"
@@ -457,49 +294,16 @@
: '可选,请输入重定向路由'
"
/>
</ElFormItem>
</template>
<ElFormItem v-if="formData.type != MenuTypeEnum.BUTTON" label="常驻标签栏" prop="affix">
<!-- 常驻标签栏(v-if) -->
<template v-if="formData.type != MenuTypeEnum.BUTTON" #affix>
<ElRadioGroup v-model="formData.affix">
<ElRadio :value="true">是</ElRadio>
<ElRadio :value="false">否</ElRadio>
</ElRadioGroup>
</ElFormItem>
<ElFormItem label="状态" prop="status">
<ElRadioGroup v-model="formData.status">
<ElRadio value="0">启用</ElRadio>
<ElRadio value="1">禁用</ElRadio>
</ElRadioGroup>
</ElFormItem>
<ElFormItem label="描述" prop="description">
<ElInput
v-model="formData.description"
:rows="4"
:maxlength="100"
show-word-limit
type="textarea"
placeholder="请输入描述"
/>
</ElFormItem>
</ElForm>
</template>
<template #footer>
<div class="dialog-footer">
<!-- 详情弹窗不需要确定按钮的提交逻辑 -->
<ElButton
v-if="dialogVisible.type !== 'detail'"
type="primary"
:loading="submitLoading"
@click="handleSubmit"
>
确定
</ElButton>
<ElButton v-else type="primary" @click="handleCloseDialog">确定</ElButton>
<ElButton @click="handleCloseDialog">取消</ElButton>
</div>
</template>
</FaForm>
</template>
</FaDrawer>
</div>
@@ -511,8 +315,7 @@ defineOptions({
inheritAttrs: false,
});
import { h, ref, reactive, computed, nextTick, onMounted } from "vue";
import { CirclePlusFilled, DeleteFilled, QuestionFilled } from "@element-plus/icons-vue";
import { CirclePlusFilled, DeleteFilled } from "@element-plus/icons-vue";
import { useAppStore } from "@stores/modules/app.store";
import { useUserStore } from "@stores/modules/user.store";
import { DeviceEnum } from "@/enums/settings/device.enum";
@@ -524,15 +327,8 @@ import MenuAPI, {
} from "@/api/module_system/menu";
import { MenuClientEnum, MenuTypeEnum } from "@/enums/system/menu.enum";
import { formatTree } from "@utils/common";
import MenuRouteIcon from "@/components/others/fa-menu-routeIcon/index.vue";
import FaTable from "@/components/tables/fa-table/index.vue";
import FaTableHeader from "@/components/tables/fa-table-header/index.vue";
import FaTableHeaderLeft from "@/components/tables/fa-table-header-left/index.vue";
import FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
import FaDrawer from "@/components/modal/fa-drawer/index.vue";
import FaIconSelect from "@/components/others/fa-icon-select/index.vue";
import { ElMessage, ElMessageBox, ElTag, ElTooltip } from "element-plus";
import type { FormItem } from "@/components/forms/fa-form/index.vue";
import { useAuth } from "@/hooks/core/useAuth";
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
@@ -697,6 +493,108 @@ const createParentLocked = ref(false);
const detailFormData = ref<MenuTable>({});
const menuDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] =
[
{ label: "编号", prop: "id" },
{ label: "菜单名称", prop: "name" },
{ label: "菜单类型", prop: "type", slot: "type" },
{ label: "终端", prop: "client", slot: "client" },
{ label: "图标", prop: "icon", slot: "icon" },
{ label: "权限标识", prop: "permission" },
{ label: "路由名称", prop: "route_name" },
{ label: "路由路径", prop: "route_path" },
{ label: "组件路径", prop: "component_path" },
{ label: "重定向", prop: "redirect" },
{ label: "父级编号", prop: "parent_id" },
{ label: "父级菜单", prop: "parent_name" },
{
label: "是否缓存",
prop: "keep_alive",
tag: {
map: { true: { type: "success", text: "是" }, false: { type: "danger", text: "否" } },
},
},
{
label: "是否显示",
prop: "hidden",
tag: {
map: { true: { type: "success", text: "是" }, false: { type: "danger", text: "否" } },
},
},
{
label: "是否显示根路由",
prop: "always_show",
tag: {
map: { true: { type: "success", text: "是" }, false: { type: "danger", text: "否" } },
},
},
{ label: "菜单标题", prop: "title" },
{ label: "路由参数", prop: "params" },
{
label: "是否固定路由",
prop: "affix",
tag: {
map: { true: { type: "success", text: "是" }, false: { type: "danger", text: "否" } },
},
},
{
label: "状态",
prop: "status",
tag: {
map: { "0": { type: "success", text: "启用" }, "1": { type: "danger", text: "停用" } },
},
},
{ label: "排序", prop: "order" },
{ label: "创建时间", prop: "created_time" },
{ label: "更新时间", prop: "updated_time" },
{ label: "描述", prop: "description", span: 4 },
];
// 菜单新增/编辑表单 —— 简单字段走 items,条件渲染/Tooltip标签/复杂控件走插槽
const menuDialogFormItems: FormItem[] = [
{ key: "parent_id", label: "父级菜单", type: "input" /* #parent_id 插槽 */ },
{ key: "name", label: "菜单名称", type: "input", props: { placeholder: "请输入菜单名称" } },
{ key: "title", label: "菜单标题", type: "input", props: { placeholder: "请输入菜单标题" } },
{ key: "type", label: "菜单类型", type: "input" /* #type 插槽 */ },
{ key: "client", label: "终端", type: "input" /* #client 插槽 */ },
{ key: "extlink_path", label: "外链地址", type: "input" /* #extlink_path 插槽 */ },
{ key: "route_name", label: "路由名称", type: "input" /* #route_name 插槽 */ },
{ key: "route_path", label: "路由路径", type: "input" /* #route_path 插槽 */ },
{ key: "component_path", label: "组件路径", type: "input" /* #component_path 插槽 */ },
{ key: "params", label: "路由参数", type: "input" /* #params 插槽 */ },
{ key: "hidden", label: "是否隐藏", type: "input" /* #hidden 插槽 */ },
{ key: "always_show", label: "始终显示", type: "input" /* #always_show 插槽 */ },
{ key: "keep_alive", label: "缓存页面", type: "input" /* #keep_alive 插槽 */ },
{ key: "order", label: "排序", type: "number", props: { controlsPosition: "right", min: 1 } },
{ key: "permission", label: "权限标识", type: "input" /* #permission 插槽 */ },
{ key: "icon", label: "图标", type: "input" /* #icon 插槽 */ },
{ key: "redirect", label: "重定向", type: "input" /* #redirect 插槽 */ },
{ key: "affix", label: "常驻标签栏", type: "input" /* #affix 插槽 */ },
{
key: "status",
label: "状态",
type: "radiogroup",
props: {
options: [
{ label: "启用", value: "0" },
{ label: "禁用", value: "1" },
],
},
},
{
key: "description",
label: "描述",
type: "input",
props: {
type: "textarea",
rows: 4,
maxlength: 100,
showWordLimit: true,
placeholder: "请输入描述",
},
},
];
const formData = ref<MenuForm>({
id: undefined,
name: undefined,
@@ -1004,7 +902,8 @@ const initialFormData: MenuForm = {
client: MenuClientEnum.PC,
};
const dataFormRef = ref();
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
const menuFormRenderKey = ref(0);
async function resetForm() {
if (dataFormRef.value) {
@@ -1052,6 +951,7 @@ async function handleOpenDialog(
} else {
dialogVisible.title = "新增菜单";
formData.value = { ...initialFormData };
menuFormRenderKey.value += 1;
if (parentRow?.id != null) {
formData.value.parent_id = parentRow.id;
formData.value.client = (parentRow.client as MenuClientEnum) || menuClientTab.value;
@@ -1075,7 +975,7 @@ function handleMenuTypeChange() {
nextTick(() => {
dataFormRef.value?.clearValidate("redirect");
if (formData.value.type === MenuTypeEnum.CATALOG) {
dataFormRef.value?.validateField("redirect").catch(() => {});
dataFormRef.value?.validateField("redirect")?.catch(() => {});
}
});
}
@@ -46,7 +46,7 @@
:perm-patch="['module_system:notice:patch']"
:delete-loading="batchDeleting"
@add="handleOpenDialog('create')"
@export="openExportModal"
@export="openExport"
@delete="handleBatchDelete"
@more="handleMoreClick"
/>
@@ -71,105 +71,74 @@
width="920px"
dialog-class="crud-embed-dialog"
modal-class="crud-embed-dialog"
@close="handleCloseDialog"
:form-mode="dialogVisible.type"
:confirm-loading="submitLoading"
@cancel="handleCloseDialog"
@confirm="dialogVisible.type === 'detail' ? handleCloseDialog() : handleSubmit()"
>
<template v-if="dialogVisible.type === 'detail'">
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
<ElDescriptions :column="4" border label-width="120px">
<ElDescriptionsItem label="标题" :span="2">
{{ detailFormData.notice_title }}
</ElDescriptionsItem>
<ElDescriptionsItem label="类型" :span="2">
<ElTag :type="detailFormData.notice_type === '1' ? 'primary' : 'warning'">
{{ noticeTypeLabel(detailFormData.notice_type) }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="状态" :span="2">
<ElTag :type="detailFormData.status === '0' ? 'success' : 'danger'">
{{ detailFormData.status === "0" ? "启用" : "停用" }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="描述" :span="2">
{{ detailFormData.description }}
</ElDescriptionsItem>
<ElDescriptionsItem label="内容" :span="4">
<div class="notice-html-preview">
<template v-if="detailHasRenderableContent">
<div v-html="detailContentHtml" />
</template>
<p v-else class="notice-html-empty">暂无内容</p>
</div>
</ElDescriptionsItem>
<ElDescriptionsItem label="创建人" :span="2">
{{ detailFormData.created_by?.name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="更新人" :span="2">
{{ detailFormData.updated_by?.name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="创建时间" :span="2">
{{ detailFormData.created_time }}
</ElDescriptionsItem>
<ElDescriptionsItem label="更新时间" :span="2">
{{ detailFormData.updated_time }}
</ElDescriptionsItem>
</ElDescriptions>
</ElScrollbar>
<FaDescriptions
:column="4"
:data="detailFormData"
:items="noticeDetailItems"
label-width="120px"
max-height="75vh"
>
<template #notice_type="{ row }">
<ElTag :type="row?.notice_type === '1' ? 'primary' : 'warning'">
{{ noticeTypeLabel(row?.notice_type as string) }}
</ElTag>
</template>
<template #notice_content>
<div class="notice-html-preview">
<template v-if="detailHasRenderableContent">
<div v-html="detailContentHtml" />
</template>
<p v-else class="notice-html-empty">暂无内容</p>
</div>
</template>
</FaDescriptions>
</template>
<template v-else>
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
<!-- FaForm + items + 栅格;弹窗内关闭内置提交/重置,仍用底部按钮 -->
<FaForm
:key="noticeFormRenderKey"
ref="dataFormRef"
v-model="formData"
:items="noticeDialogFormItems"
:rules="rules"
label-suffix=":"
:label-width="100"
label-position="right"
:span="24"
:gutter="16"
:show-reset="false"
:show-submit="false"
class="crud-dialog-art-form"
>
<template #status>
<ElRadioGroup v-model="formData.status">
<ElRadio value="0">启用</ElRadio>
<ElRadio value="1">停用</ElRadio>
</ElRadioGroup>
</template>
<template #notice_content>
<FaWangEditor
:model-value="formData.notice_content ?? ''"
height="min(38vh, 280px)"
placeholder="请输入公告内容,支持完整排版与插入..."
:exclude-keys="[]"
@update:model-value="(v: string) => (formData.notice_content = v)"
/>
</template>
</FaForm>
</ElScrollbar>
</template>
<template #footer>
<div class="dialog-footer" :style="'padding-right: var(--el-dialog-padding-primary)'">
<ElButton @click="handleCloseDialog">取消</ElButton>
<ElButton
v-if="dialogVisible.type !== 'detail'"
type="primary"
:loading="submitLoading"
@click="handleSubmit"
>
确定
</ElButton>
<ElButton v-else type="primary" @click="handleCloseDialog">确定</ElButton>
</div>
<!-- FaForm + items + 栅格;弹窗内关闭内置提交/重置,仍用底部按钮 -->
<FaForm
:key="noticeFormRenderKey"
scrollbar
max-height="75vh"
ref="dataFormRef"
v-model="formData"
:items="noticeDialogFormItems"
:rules="rules"
label-suffix=":"
:label-width="100"
label-position="right"
:span="24"
:gutter="16"
:show-reset="false"
:show-submit="false"
class="crud-dialog-art-form"
>
<template #status>
<ElRadioGroup v-model="formData.status">
<ElRadio value="0">启用</ElRadio>
<ElRadio value="1">停用</ElRadio>
</ElRadioGroup>
</template>
<template #notice_content>
<FaWangEditor
:model-value="formData.notice_content ?? ''"
height="min(38vh, 280px)"
placeholder="请输入公告内容,支持完整排版与插入..."
:exclude-keys="[]"
@update:model-value="(v: string) => (formData.notice_content = v)"
/>
</template>
</FaForm>
</template>
</FaDialog>
<FaExportDialog
v-model="exportModalVisible"
v-model="exportVisible"
:content-config="noticeExportContentConfig"
:query-params="exportQueryParams"
:page-data="data"
@@ -179,30 +148,21 @@
</template>
<script setup lang="ts">
import { h, computed, ref, reactive, nextTick, onMounted } from "vue";
import { useTable } from "@/hooks/core/useTable";
import FaTable from "@/components/tables/fa-table/index.vue";
import FaTableHeader from "@/components/tables/fa-table-header/index.vue";
import FaTableHeaderLeft from "@/components/tables/fa-table-header-left/index.vue";
import FaExportDialog from "@/components/modal/fa-export-dialog/index.vue";
import { useImportExport } from "@/hooks/core/useImportExport";
import type { IObject } from "@/components/modal/types";
import FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
import FaDialog from "@/components/modal/fa-dialog/index.vue";
import type { ColumnOption } from "@/types/component";
import type { FormItem } from "@/components/forms/fa-form/index.vue";
import NoticeAPI, {
type NoticeForm,
type NoticePageQuery,
type NoticeTable,
} from "@/api/module_system/notice";
import { ElMessage, ElMessageBox, ElTag } from "element-plus";
import { useAuth } from "@/hooks/core/useAuth";
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
import { useDictStore, useNoticeStore } from "@stores/index";
import UserTableSelect from "@views/module_system/user/components/UserTableSelect.vue";
import FaWangEditor from "@/components/forms/fa-wang-editor/index.vue";
import FaForm from "@/components/forms/fa-form/index.vue";
import type { FormItem } from "@/components/forms/fa-form/index.vue";
defineOptions({
name: "Notice",
@@ -436,6 +396,25 @@ const noticeExportContentConfig = computed(() => ({
const detailFormData = ref<NoticeTable>({});
const noticeDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] =
[
{ label: "标题", prop: "notice_title" },
{ label: "类型", prop: "notice_type", slot: "notice_type" },
{
label: "状态",
prop: "status",
tag: {
map: { "0": { type: "success", text: "启用" }, "1": { type: "danger", text: "停用" } },
},
},
{ label: "描述", prop: "description" },
{ label: "内容", prop: "notice_content", slot: "notice_content", span: 4 },
{ label: "创建人", prop: "created_by.name" },
{ label: "更新人", prop: "updated_by.name" },
{ label: "创建时间", prop: "created_time" },
{ label: "更新时间", prop: "updated_time" },
];
/** 详情富文本 HTML(用于预览) */
const detailContentHtml = computed({
get: () => detailFormData.value.notice_content ?? "",
@@ -545,7 +524,7 @@ const initialFormData: NoticeForm = {
description: undefined,
};
const exportModalVisible = ref(false);
const { exportVisible, openExport } = useImportExport();
function buildNoticeReplaceParams(p: NoticeSearchForm): Record<string, unknown> {
return {
@@ -743,10 +722,6 @@ async function handleMoreClick(status: string) {
}
}
function openExportModal() {
exportModalVisible.value = true;
}
onMounted(async () => {
await dictStore.getDict(["sys_notice_type"]);
});
@@ -36,7 +36,7 @@
:perm-delete="['module_system:param:delete']"
:delete-loading="batchDeleting"
@add="handleOpenDialog('create')"
@export="openExportModal"
@export="openExport"
@delete="handleBatchDelete"
/>
</template>
@@ -60,81 +60,49 @@
width="640px"
dialog-class="crud-embed-dialog"
modal-class="crud-embed-dialog"
@close="handleCloseDialog"
:form-mode="dialogVisible.type"
:confirm-loading="submitLoading"
@cancel="handleCloseDialog"
@confirm="dialogVisible.type === 'detail' ? handleCloseDialog() : handleSubmit()"
>
<template v-if="dialogVisible.type === 'detail'">
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
<ElDescriptions :column="4" border>
<ElDescriptionsItem label="配置名称" :span="2">
{{ detailFormData.config_name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="系统内置" :span="2">
<ElTag v-if="detailFormData.config_type" type="success">是</ElTag>
<ElTag v-else type="danger">否</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="配置键" :span="2">
{{ detailFormData.config_key }}
</ElDescriptionsItem>
<ElDescriptionsItem label="配置值" :span="2">
{{ detailFormData.config_value }}
</ElDescriptionsItem>
<ElDescriptionsItem label="描述" :span="2">
{{ detailFormData.description }}
</ElDescriptionsItem>
<ElDescriptionsItem label="创建时间" :span="2">
{{ detailFormData.created_time }}
</ElDescriptionsItem>
<ElDescriptionsItem label="更新时间" :span="2">
{{ detailFormData.updated_time }}
</ElDescriptionsItem>
</ElDescriptions>
</ElScrollbar>
<FaDescriptions
:column="4"
:data="detailFormData"
:items="paramDetailItems"
max-height="75vh"
/>
</template>
<template v-else>
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
<FaForm
:key="paramFormRenderKey"
ref="dataFormRef"
v-model="formData"
:items="paramDialogFormItems"
:rules="rules"
label-suffix=":"
:label-width="100"
label-position="right"
:span="24"
:gutter="16"
:show-reset="false"
:show-submit="false"
class="crud-dialog-art-form"
>
<template #config_type>
<ElRadioGroup v-model="formData.config_type">
<ElRadio :value="true">是</ElRadio>
<ElRadio :value="false">否</ElRadio>
</ElRadioGroup>
</template>
</FaForm>
</ElScrollbar>
</template>
<template #footer>
<div class="dialog-footer" :style="'padding-right: var(--el-dialog-padding-primary)'">
<ElButton @click="handleCloseDialog">取消</ElButton>
<ElButton
v-if="dialogVisible.type !== 'detail'"
type="primary"
:loading="submitLoading"
@click="handleSubmit"
>
确定
</ElButton>
<ElButton v-else type="primary" @click="handleCloseDialog">确定</ElButton>
</div>
<FaForm
:key="paramFormRenderKey"
scrollbar
max-height="75vh"
ref="dataFormRef"
v-model="formData"
:items="paramDialogFormItems"
:rules="rules"
label-suffix=":"
:label-width="100"
label-position="right"
:span="24"
:gutter="16"
:show-reset="false"
:show-submit="false"
class="crud-dialog-art-form"
>
<template #config_type>
<ElRadioGroup v-model="formData.config_type">
<ElRadio :value="true">是</ElRadio>
<ElRadio :value="false">否</ElRadio>
</ElRadioGroup>
</template>
</FaForm>
</template>
</FaDialog>
<FaExportDialog
v-model="exportModalVisible"
v-model="exportVisible"
:content-config="paramExportContentConfig"
:query-params="exportQueryParams"
:page-data="data"
@@ -144,17 +112,10 @@
</template>
<script setup lang="ts">
import { h, computed, ref, reactive } from "vue";
import { useTable } from "@/hooks/core/useTable";
import FaTable from "@/components/tables/fa-table/index.vue";
import FaTableHeader from "@/components/tables/fa-table-header/index.vue";
import FaTableHeaderLeft from "@/components/tables/fa-table-header-left/index.vue";
import FaExportDialog from "@/components/modal/fa-export-dialog/index.vue";
import { useImportExport } from "@/hooks/core/useImportExport";
import type { IObject } from "@/components/modal/types";
import FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
import FaDialog from "@/components/modal/fa-dialog/index.vue";
import FaForm from "@/components/forms/fa-form/index.vue";
import type { FormItem } from "@/components/forms/fa-form/index.vue";
import type { ColumnOption } from "@/types/component";
import ParamsAPI, {
@@ -162,7 +123,6 @@ import ParamsAPI, {
type ConfigPageQuery,
type ConfigTable,
} from "@/api/module_system/params";
import { ElMessage, ElMessageBox, ElTag } from "element-plus";
import { useAuth } from "@/hooks/core/useAuth";
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
import { useConfigStore } from "@stores";
@@ -362,6 +322,23 @@ const paramExportContentConfig = computed(() => ({
const detailFormData = ref<ConfigTable>({} as ConfigTable);
const paramDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] =
[
{ label: "配置名称", prop: "config_name" },
{
label: "系统内置",
prop: "config_type",
tag: {
map: { true: { type: "success", text: "是" }, false: { type: "danger", text: "否" } },
},
},
{ label: "配置键", prop: "config_key" },
{ label: "配置值", prop: "config_value" },
{ label: "描述", prop: "description" },
{ label: "创建时间", prop: "created_time" },
{ label: "更新时间", prop: "updated_time" },
];
const formData = ref<ConfigForm>({
id: undefined,
config_name: "",
@@ -441,7 +418,7 @@ const initialFormData: ConfigForm = {
description: "",
};
const exportModalVisible = ref(false);
const { exportVisible, openExport } = useImportExport();
async function handleSearchBarSearch(params: ParamSearchForm) {
await searchBarRef.value?.validate?.();
@@ -460,8 +437,8 @@ function onResetSearch() {
}
async function resetForm() {
dataFormRef.value?.ref?.resetFields();
dataFormRef.value?.ref?.clearValidate();
dataFormRef.value?.resetFields();
dataFormRef.value?.clearValidate();
Object.assign(formData, initialFormData);
}
@@ -597,10 +574,6 @@ async function handleBatchDelete() {
batchDeleting.value = false;
}
}
function openExportModal() {
exportModalVisible.value = true;
}
</script>
<style scoped lang="scss">
@@ -1,4 +1,4 @@
<!-- 岗位管理:Art + useTable;操作列前 3 个为 ArtButtonTable,其余收入「更多」下拉 -->
<!-- 岗位管理:FA + useTable;操作列前 3 个为 ArtButtonTable,其余收入「更多」下拉 -->
<template>
<div class="fa-full-height">
<FaSearchBar
@@ -46,7 +46,7 @@
:perm-patch="['module_system:position:patch']"
:delete-loading="batchDeleting"
@add="handleOpenDialog('create')"
@export="openExportModal"
@export="openExport"
@delete="handleBatchDelete"
@more="handleMoreClick"
/>
@@ -71,84 +71,49 @@
width="720px"
dialog-class="crud-embed-dialog"
modal-class="crud-embed-dialog"
@close="handleCloseDialog"
:form-mode="dialogVisible.type"
:confirm-loading="submitLoading"
@cancel="handleCloseDialog"
@confirm="dialogVisible.type === 'detail' ? handleCloseDialog() : handleSubmit()"
>
<template v-if="dialogVisible.type === 'detail'">
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
<ElDescriptions :column="4" border>
<ElDescriptionsItem label="岗位名称" :span="2">
{{ detailFormData.name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="排序" :span="2">
{{ detailFormData.order }}
</ElDescriptionsItem>
<ElDescriptionsItem label="状态" :span="2">
<ElTag v-if="detailFormData.status === '0'" type="success">启用</ElTag>
<ElTag v-else type="danger">停用</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="创建人" :span="2">
{{ detailFormData.created_by?.name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="更新人" :span="2">
{{ detailFormData.updated_by?.name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="创建时间" :span="2">
{{ detailFormData.created_time }}
</ElDescriptionsItem>
<ElDescriptionsItem label="更新时间" :span="2">
{{ detailFormData.updated_time }}
</ElDescriptionsItem>
<ElDescriptionsItem label="描述" :span="4">
{{ detailFormData.description }}
</ElDescriptionsItem>
</ElDescriptions>
</ElScrollbar>
<FaDescriptions
:column="4"
:data="detailFormData"
:items="positionDetailItems"
max-height="75vh"
/>
</template>
<template v-else>
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
<FaForm
:key="positionFormRenderKey"
ref="dataFormRef"
v-model="formData"
:items="positionDialogFormItems"
:rules="rules"
label-suffix=":"
:label-width="'auto'"
label-position="right"
:span="24"
:gutter="16"
:show-reset="false"
:show-submit="false"
class="crud-dialog-art-form"
>
<template #status>
<ElRadioGroup v-model="formData.status">
<ElRadio value="0">启用</ElRadio>
<ElRadio value="1">停用</ElRadio>
</ElRadioGroup>
</template>
</FaForm>
</ElScrollbar>
</template>
<template #footer>
<div class="dialog-footer" :style="'padding-right: var(--el-dialog-padding-primary)'">
<ElButton @click="handleCloseDialog">取消</ElButton>
<ElButton
v-if="dialogVisible.type !== 'detail'"
type="primary"
:loading="submitLoading"
@click="handleSubmit"
>
确定
</ElButton>
<ElButton v-else type="primary" @click="handleCloseDialog">确定</ElButton>
</div>
<FaForm
scrollbar
max-height="75vh"
:key="positionFormRenderKey"
ref="dataFormRef"
v-model="formData"
:items="positionDialogFormItems"
:rules="rules"
label-suffix=":"
:label-width="'auto'"
label-position="right"
:span="24"
:gutter="16"
:show-reset="false"
:show-submit="false"
class="crud-dialog-art-form"
>
<template #status>
<ElRadioGroup v-model="formData.status">
<ElRadio value="0">启用</ElRadio>
<ElRadio value="1">停用</ElRadio>
</ElRadioGroup>
</template>
</FaForm>
</template>
</FaDialog>
<FaExportDialog
v-model="exportModalVisible"
v-model="exportVisible"
:content-config="positionExportContentConfig"
:query-params="exportQueryParams"
:page-data="data"
@@ -158,18 +123,10 @@
</template>
<script setup lang="ts">
import { h, computed, ref, reactive, nextTick } from "vue";
import { useTable } from "@/hooks/core/useTable";
import FaTable from "@/components/tables/fa-table/index.vue";
import FaTableHeader from "@/components/tables/fa-table-header/index.vue";
import FaTableHeaderLeft from "@/components/tables/fa-table-header-left/index.vue";
import FaExportDialog from "@/components/modal/fa-export-dialog/index.vue";
import ArtButtonTable from "@/components/forms/fa-button-table/index.vue";
import { useImportExport } from "@/hooks/core/useImportExport";
import type { IObject } from "@/components/modal/types";
import FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
import FaDialog from "@/components/modal/fa-dialog/index.vue";
import FaForm from "@/components/forms/fa-form/index.vue";
import type { FormItem } from "@/components/forms/fa-form/index.vue";
import type { ColumnOption } from "@/types/component";
import PositionAPI, {
@@ -177,15 +134,6 @@ import PositionAPI, {
type PositionPageQuery,
type PositionTable,
} from "@/api/module_system/position";
import {
ElMessage,
ElMessageBox,
ElTag,
ElTooltip,
ElDropdown,
ElDropdownMenu,
ElDropdownItem,
} from "element-plus";
import { useAuth } from "@/hooks/core/useAuth";
import { useUserStore } from "@stores";
import UserTableSelect from "@views/module_system/user/components/UserTableSelect.vue";
@@ -281,7 +229,7 @@ function formatPositionOperationCell(
const inlineNodes = inline.map((a) =>
h(ElTooltip, { content: a.label, placement: "top" }, () =>
h("span", { class: "inline-flex" }, [
h(ArtButtonTable, {
h(FaButtonTable, {
type: a.artType,
icon: a.icon,
onClick: a.run,
@@ -305,7 +253,7 @@ function formatPositionOperationCell(
default: () =>
h(ElTooltip, { content: "更多", placement: "top" }, () =>
h("span", { class: "inline-flex align-middle" }, [
h(ArtButtonTable, {
h(FaButtonTable, {
type: "more",
onClick: () => {},
}),
@@ -531,6 +479,24 @@ const positionExportContentConfig = computed(() => ({
const detailFormData = ref<PositionTable>({});
const positionDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] =
[
{ label: "岗位名称", prop: "name" },
{ label: "排序", prop: "order" },
{
label: "状态",
prop: "status",
tag: {
map: { "0": { type: "success", text: "启用" }, "1": { type: "danger", text: "停用" } },
},
},
{ label: "创建人", prop: "created_by.name" },
{ label: "更新人", prop: "updated_by.name" },
{ label: "创建时间", prop: "created_time" },
{ label: "更新时间", prop: "updated_time" },
{ label: "描述", prop: "description", span: 4 },
];
const formData = ref<PositionForm>({
id: undefined,
name: undefined,
@@ -599,11 +565,7 @@ const positionDialogFormItems = computed<FormItem[]>(() => [
},
]);
const submitLoading = ref(false);
const exportModalVisible = ref(false);
function openExportModal() {
exportModalVisible.value = true;
}
const { exportVisible, openExport } = useImportExport();
async function handleSearchBarSearch(params: PositionSearchForm) {
await searchBarRef.value?.validate?.();
@@ -633,8 +595,8 @@ function onResetSearch() {
}
async function resetForm() {
dataFormRef.value?.ref?.resetFields();
dataFormRef.value?.ref?.clearValidate();
dataFormRef.value?.resetFields();
dataFormRef.value?.clearValidate();
Object.assign(formData, initialFormData);
}
@@ -37,7 +37,7 @@
:perm-patch="['module_system:role:patch']"
:delete-loading="batchDeleting"
@add="handleOpenDialog('create')"
@export="openExportModal"
@export="openExport"
@delete="handleBatchDelete"
@more="handleMoreClick"
/>
@@ -62,99 +62,65 @@
width="720px"
dialog-class="crud-embed-dialog"
modal-class="crud-embed-dialog"
@close="handleCloseDialog"
:form-mode="dialogVisible.type"
:confirm-loading="submitLoading"
@cancel="handleCloseDialog"
@confirm="dialogVisible.type === 'detail' ? handleCloseDialog() : handleSubmit()"
>
<template v-if="dialogVisible.type === 'detail'">
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
<ElDescriptions :column="4" border>
<ElDescriptionsItem label="角色名称" :span="2">
{{ detailFormData.name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="排序" :span="2">
{{ detailFormData.order }}
</ElDescriptionsItem>
<ElDescriptionsItem label="角色编码" :span="2">
{{ detailFormData.code }}
</ElDescriptionsItem>
<ElDescriptionsItem label="数据权限" :span="2">
<ElTag v-if="detailFormData.data_scope === 1" type="primary">仅本人数据权限</ElTag>
<ElTag v-else-if="detailFormData.data_scope === 2" type="info">本部门数据权限</ElTag>
<ElTag v-else-if="detailFormData.data_scope === 3" type="warning">
本部门及以下数据权限
<FaDescriptions
:column="4"
:data="detailFormData"
:items="roleDetailItems"
max-height="75vh"
>
<template #data_scope="{ row }">
<ElTag v-if="row?.data_scope === 1" type="primary">仅本人数据权限</ElTag>
<ElTag v-else-if="row?.data_scope === 2" type="info">本部门数据权限</ElTag>
<ElTag v-else-if="row?.data_scope === 3" type="warning">本部门及以下数据权限</ElTag>
<ElTag v-else-if="row?.data_scope === 4" type="success">全部数据权限</ElTag>
<ElTag v-else type="danger">自定义数据权限</ElTag>
</template>
<template #depts="{ row }">
<template v-if="row?.depts && (row.depts as any[]).length > 0">
<ElTag
v-for="dept in row.depts as any[]"
:key="dept.id"
type="info"
:style="'margin-right: 4px; margin-bottom: 4px'"
>
{{ dept.name }}
</ElTag>
<ElTag v-else-if="detailFormData.data_scope === 4" type="success">全部数据权限</ElTag>
<ElTag v-else type="danger">自定义数据权限</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="所属部门" :span="2">
<template v-if="detailFormData.depts && detailFormData.depts.length > 0">
<ElTag
v-for="dept in detailFormData.depts"
:key="dept.id"
type="info"
:style="'margin-right: 4px; margin-bottom: 4px'"
>
{{ dept.name }}
</ElTag>
</template>
<span v-else :style="'color: var(--el-text-color-placeholder)'">-</span>
</ElDescriptionsItem>
<ElDescriptionsItem label="状态" :span="2">
<ElTag :type="detailFormData.status === '0' ? 'success' : 'danger'">
{{ detailFormData.status === "0" ? "启用" : "停用" }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="创建时间" :span="2">
{{ detailFormData.created_time }}
</ElDescriptionsItem>
<ElDescriptionsItem label="更新时间" :span="2">
{{ detailFormData.updated_time }}
</ElDescriptionsItem>
<ElDescriptionsItem label="描述" :span="4">
{{ detailFormData.description }}
</ElDescriptionsItem>
</ElDescriptions>
</ElScrollbar>
</template>
<span v-else :style="'color: var(--el-text-color-placeholder)'">-</span>
</template>
</FaDescriptions>
</template>
<template v-else>
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
<FaForm
:key="roleFormRenderKey"
ref="dataFormRef"
v-model="formData"
:items="roleDialogFormItems"
:rules="rules"
label-suffix=":"
:label-width="'auto'"
label-position="right"
:span="24"
:gutter="16"
:show-reset="false"
:show-submit="false"
class="crud-dialog-art-form"
>
<template #status>
<ElRadioGroup v-model="formData.status">
<ElRadio value="0">启用</ElRadio>
<ElRadio value="1">停用</ElRadio>
</ElRadioGroup>
</template>
</FaForm>
</ElScrollbar>
</template>
<template #footer>
<div class="dialog-footer" :style="'padding-right: var(--el-dialog-padding-primary)'">
<ElButton @click="handleCloseDialog">取消</ElButton>
<ElButton
v-if="dialogVisible.type !== 'detail'"
type="primary"
:loading="submitLoading"
@click="handleSubmit"
>
确定
</ElButton>
<ElButton v-else type="primary" @click="handleCloseDialog">确定</ElButton>
</div>
<FaForm
:key="roleFormRenderKey"
scrollbar
max-height="75vh"
ref="dataFormRef"
v-model="formData"
:items="roleDialogFormItems"
:rules="rules"
label-suffix=":"
:label-width="'auto'"
label-position="right"
:span="24"
:gutter="16"
:show-reset="false"
:show-submit="false"
class="crud-dialog-art-form"
>
<template #status>
<ElRadioGroup v-model="formData.status">
<ElRadio value="0">启用</ElRadio>
<ElRadio value="1">停用</ElRadio>
</ElRadioGroup>
</template>
</FaForm>
</template>
</FaDialog>
@@ -167,7 +133,7 @@
/>
<FaExportDialog
v-model="exportModalVisible"
v-model="exportVisible"
:content-config="roleExportContentConfig"
:query-params="exportQueryParams"
:page-data="data"
@@ -177,18 +143,11 @@
</template>
<script setup lang="ts">
import { h, computed, ref, reactive } from "vue";
import { useTable } from "@/hooks/core/useTable";
import FaTable from "@/components/tables/fa-table/index.vue";
import FaTableHeader from "@/components/tables/fa-table-header/index.vue";
import FaTableHeaderLeft from "@/components/tables/fa-table-header-left/index.vue";
import FaExportDialog from "@/components/modal/fa-export-dialog/index.vue";
import { useImportExport } from "@/hooks/core/useImportExport";
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
import type { IObject } from "@/components/modal/types";
import FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
import FaDialog from "@/components/modal/fa-dialog/index.vue";
import FaForm from "@/components/forms/fa-form/index.vue";
import type { FormItem } from "@/components/forms/fa-form/index.vue";
import type { ColumnOption } from "@/types/component";
import RoleAPI, {
@@ -196,7 +155,6 @@ import RoleAPI, {
type RoleTable,
type TablePageQuery,
} from "@/api/module_system/role";
import { ElMessage, ElMessageBox, ElTag } from "element-plus";
import { useAuth } from "@/hooks/core/useAuth";
import { useUserStore } from "@stores";
import PermissonDrawer from "./components/PermissonDrawer.vue";
@@ -553,6 +511,25 @@ const roleExportContentConfig = computed(() => ({
const detailFormData = ref<RoleTable>({} as RoleTable);
const roleDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] =
[
{ label: "角色名称", prop: "name" },
{ label: "排序", prop: "order" },
{ label: "角色编码", prop: "code" },
{ label: "数据权限", prop: "data_scope", slot: "data_scope" },
{ label: "所属部门", prop: "depts", slot: "depts" },
{
label: "状态",
prop: "status",
tag: {
map: { "0": { type: "success", text: "启用" }, "1": { type: "danger", text: "停用" } },
},
},
{ label: "创建时间", prop: "created_time" },
{ label: "更新时间", prop: "updated_time" },
{ label: "描述", prop: "description", span: 4 },
];
const formData = ref<RoleForm>({
id: undefined,
name: undefined,
@@ -649,7 +626,7 @@ const initialFormData: RoleForm = {
description: undefined,
};
const exportModalVisible = ref(false);
const { exportVisible, openExport } = useImportExport();
async function handleSearchBarSearch(params: RoleSearchForm) {
await searchBarRef.value?.validate?.();
@@ -667,8 +644,8 @@ function onResetSearch() {
}
async function resetForm() {
dataFormRef.value?.ref?.resetFields();
dataFormRef.value?.ref?.clearValidate();
dataFormRef.value?.resetFields();
dataFormRef.value?.clearValidate();
Object.assign(formData, initialFormData);
}
@@ -765,10 +742,6 @@ async function handleMoreClick(status: string) {
// 用户取消
}
}
function openExportModal() {
exportModalVisible.value = true;
}
</script>
<style scoped lang="scss">
@@ -58,86 +58,45 @@
width="640px"
dialog-class="crud-embed-dialog"
modal-class="crud-embed-dialog"
@close="handleCloseDialog"
:form-mode="dialogVisible.type"
:confirm-loading="submitLoading"
@cancel="handleCloseDialog"
@confirm="dialogVisible.type === 'detail' ? handleCloseDialog() : handleSubmit()"
>
<template v-if="dialogVisible.type === 'detail'">
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
<ElDescriptions :column="2" border>
<ElDescriptionsItem label="租户名称" :span="2">
{{ detailFormData.name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="租户编码" :span="2">
{{ detailFormData.code }}
</ElDescriptionsItem>
<ElDescriptionsItem label="状态">
<ElTag :type="detailFormData.status === '0' ? 'success' : 'danger'">
{{ detailFormData.status === "0" ? "正常" : "禁用" }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="开始时间">
{{ detailFormData.start_time }}
</ElDescriptionsItem>
<ElDescriptionsItem label="结束时间">
{{ detailFormData.end_time }}
</ElDescriptionsItem>
<ElDescriptionsItem label="描述" :span="2">
{{ detailFormData.description }}
</ElDescriptionsItem>
<ElDescriptionsItem label="创建时间" :span="2">
{{ detailFormData.created_time }}
</ElDescriptionsItem>
</ElDescriptions>
</ElScrollbar>
<FaDescriptions
:column="2"
:data="detailFormData"
:items="tenantDetailItems"
max-height="75vh"
/>
</template>
<template v-else>
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
<FaForm
:key="tenantFormRenderKey"
ref="dataFormRef"
v-model="formData"
:items="tenantDialogFormItems"
:rules="rules"
label-suffix=":"
:label-width="'auto'"
label-position="right"
:span="24"
:gutter="16"
:show-reset="false"
:show-submit="false"
class="crud-dialog-art-form"
/>
</ElScrollbar>
</template>
<template #footer>
<div class="dialog-footer" :style="'padding-right: var(--el-dialog-padding-primary)'">
<ElButton @click="handleCloseDialog">取消</ElButton>
<ElButton
v-if="dialogVisible.type !== 'detail'"
type="primary"
:loading="submitLoading"
@click="handleSubmit"
>
确定
</ElButton>
<ElButton v-else type="primary" @click="handleCloseDialog">确定</ElButton>
</div>
<FaForm
:key="tenantFormRenderKey"
scrollbar
max-height="75vh"
ref="dataFormRef"
v-model="formData"
:items="tenantDialogFormItems"
:rules="rules"
label-suffix=":"
:label-width="'auto'"
label-position="right"
:span="24"
:gutter="16"
:show-reset="false"
:show-submit="false"
class="crud-dialog-art-form"
/>
</template>
</FaDialog>
</div>
</template>
<script setup lang="ts">
import { h, computed, ref, reactive } from "vue";
import { useTable } from "@/hooks/core/useTable";
import FaTable from "@/components/tables/fa-table/index.vue";
import FaTableHeader from "@/components/tables/fa-table-header/index.vue";
import FaTableHeaderLeft from "@/components/tables/fa-table-header-left/index.vue";
import ArtButtonTable from "@/components/forms/fa-button-table/index.vue";
import FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
import FaDialog from "@/components/modal/fa-dialog/index.vue";
import FaForm from "@/components/forms/fa-form/index.vue";
import type { FormItem } from "@/components/forms/fa-form/index.vue";
import type { ColumnOption } from "@/types/component";
import TenantAPI, {
@@ -146,15 +105,6 @@ import TenantAPI, {
type TenantTable,
type TenantUpdateForm,
} from "@/api/module_system/tenant";
import {
ElMessage,
ElMessageBox,
ElTag,
ElTooltip,
ElDropdown,
ElDropdownMenu,
ElDropdownItem,
} from "element-plus";
import { useAuth } from "@/hooks/core/useAuth";
defineOptions({
@@ -240,7 +190,7 @@ function formatTenantOperationCell(
const inlineNodes = inline.map((a) =>
h(ElTooltip, { content: a.label, placement: "top" }, () =>
h("span", { class: "inline-flex" }, [
h(ArtButtonTable, {
h(FaButtonTable, {
type: a.artType,
icon: a.icon,
onClick: a.run,
@@ -264,7 +214,7 @@ function formatTenantOperationCell(
default: () =>
h(ElTooltip, { content: "更多", placement: "top" }, () =>
h("span", { class: "inline-flex align-middle" }, [
h(ArtButtonTable, {
h(FaButtonTable, {
type: "more",
onClick: () => {},
}),
@@ -445,6 +395,24 @@ const {
const detailFormData = ref<TenantTable>({ code: "", name: "", status: "0" });
const tenantDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] =
[
{ label: "租户名称", prop: "name" },
{ label: "租户编码", prop: "code" },
{
label: "状态",
prop: "status",
span: 1,
tag: {
map: { "0": { type: "success", text: "正常" }, "1": { type: "danger", text: "禁用" } },
},
},
{ label: "开始时间", prop: "start_time", span: 1 },
{ label: "结束时间", prop: "end_time", span: 1 },
{ label: "描述", prop: "description" },
{ label: "创建时间", prop: "created_time" },
];
const currentEditId = ref<number | null>(null);
const formData = ref<TenantForm>({
@@ -574,8 +542,8 @@ const tenantDialogFormItems = computed<FormItem[]>(() => [
]);
async function resetForm() {
dataFormRef.value?.ref?.resetFields();
dataFormRef.value?.ref?.clearValidate();
dataFormRef.value?.resetFields();
dataFormRef.value?.clearValidate();
Object.assign(formData, initialFormData);
currentEditId.value = null;
}
@@ -67,8 +67,8 @@
:import-loading="uploadLoading"
:delete-loading="batchDeleting"
@add="handleOpenDialog('create')"
@import="openImportModal"
@export="openExportModal"
@import="openImport"
@export="openExport"
@delete="handleBatchDelete"
@more="handleMoreClick"
/>
@@ -95,121 +95,60 @@
:title="dialogVisible.title"
append-to-body
:size="drawerSize"
@close="handleCloseDialog"
:form-mode="dialogVisible.type"
:confirm-loading="submitLoading"
@cancel="handleCloseDialog"
@confirm="dialogVisible.type === 'detail' ? handleCloseDialog() : handleSubmit()"
>
<template v-if="dialogVisible.type === 'detail'">
<ElDescriptions :column="2" border>
<ElDescriptionsItem label="编号" :span="2">
{{ detailFormData.id }}
</ElDescriptionsItem>
<ElDescriptionsItem label="头像" :span="2">
<template v-if="detailFormData.avatar">
<ElAvatar :src="detailFormData.avatar" size="small"></ElAvatar>
</template>
<template v-else>
<ElAvatar icon="UserFilled" size="small"></ElAvatar>
</template>
</ElDescriptionsItem>
<ElDescriptionsItem label="账号" :span="2">
{{ detailFormData.username }}
</ElDescriptionsItem>
<ElDescriptionsItem label="用户名" :span="2">
{{ detailFormData.name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="性别" :span="2">
<ElTag v-if="detailFormData.gender === '0'" type="success">男</ElTag>
<ElTag v-else-if="detailFormData.gender === '1'" type="warning">女</ElTag>
<FaDescriptions
:column="2"
:data="detailFormData"
:items="userDetailItems"
:scrollbar="false"
>
<!-- 头像 → 自定义渲染 -->
<template #avatar="{ row }">
<ElAvatar v-if="row?.avatar" :src="row?.avatar as string" size="small" />
<ElAvatar v-else icon="UserFilled" size="small" />
</template>
<!-- 性别 → 三种状态 Tag -->
<template #gender="{ row }">
<ElTag v-if="row?.gender === '0'" type="success">男</ElTag>
<ElTag v-else-if="row?.gender === '1'" type="warning">女</ElTag>
<ElTag v-else type="info">未知</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="部门" :span="2">
{{ detailFormData.dept ? detailFormData.dept.name : "" }}
</ElDescriptionsItem>
<ElDescriptionsItem label="角色" :span="2">
</template>
<!-- 角色 → 数组 join 渲染 -->
<template #roles="{ row }">
{{ row?.roles ? (row.roles as any[]).map((item: any) => item.name).join("、") : "" }}
</template>
<!-- 岗位 → 数组 join 渲染 -->
<template #positions="{ row }">
{{
detailFormData.roles ? detailFormData.roles.map((item) => item.name).join("、") : ""
}}
</ElDescriptionsItem>
<ElDescriptionsItem label="岗位" :span="2">
{{
detailFormData.positions
? detailFormData.positions.map((item) => item.name).join("、")
row?.positions
? (row.positions as any[]).map((item: any) => item.name).join("、")
: ""
}}
</ElDescriptionsItem>
<ElDescriptionsItem label="邮箱" :span="2">
{{ detailFormData.email }}
</ElDescriptionsItem>
<ElDescriptionsItem label="手机号" :span="2">
{{ detailFormData.mobile }}
</ElDescriptionsItem>
<ElDescriptionsItem label="是否超管" :span="2">
<ElTag :type="detailFormData.is_superuser ? 'success' : 'info'">
{{ detailFormData.is_superuser ? "是" : "否" }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="状态" :span="2">
<ElTag :type="detailFormData.status === '0' ? 'success' : 'danger'">
{{ detailFormData.status === "0" ? "启用" : "停用" }}
</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="上次登录时间" :span="2">
{{ detailFormData.last_login }}
</ElDescriptionsItem>
<ElDescriptionsItem label="创建人" :span="2">
{{ detailFormData.created_by?.name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="更新人" :span="2">
{{ detailFormData.updated_by?.name }}
</ElDescriptionsItem>
<ElDescriptionsItem label="创建时间" :span="2">
{{ detailFormData.created_time }}
</ElDescriptionsItem>
<ElDescriptionsItem label="更新时间" :span="2">
{{ detailFormData.updated_time }}
</ElDescriptionsItem>
<ElDescriptionsItem label="描述" :span="4">
{{ detailFormData.description }}
</ElDescriptionsItem>
</ElDescriptions>
</template>
</FaDescriptions>
</template>
<template v-else>
<ElForm
<FaForm
:key="userFormRenderKey"
ref="dataFormRef"
:model="formData"
v-model="formData"
:items="userDialogFormItems"
:rules="rules"
label-suffix=":"
label-width="100px"
:label-width="'auto'"
label-position="right"
:span="24"
:gutter="16"
:show-reset="false"
:show-submit="false"
class="crud-dialog-art-form"
>
<ElFormItem label="账号" prop="username">
<ElInput
v-model="formData.username"
:disabled="!!formData.id"
placeholder="请输入账号"
/>
</ElFormItem>
<ElFormItem label="用户名" prop="name">
<ElInput v-model="formData.name" placeholder="请输入用户名" />
</ElFormItem>
<ElFormItem label="性别" prop="gender">
<ElSelect v-model="formData.gender" placeholder="请选择性别">
<ElOption label="男" value="0" />
<ElOption label="女" value="1" />
<ElOption label="未知" value="2" />
</ElSelect>
</ElFormItem>
<ElFormItem label="手机号" prop="mobile">
<ElInput v-model="formData.mobile" placeholder="请输入手机号码" maxlength="11" />
</ElFormItem>
<ElFormItem label="邮箱" prop="email">
<ElInput v-model="formData.email" placeholder="请输入邮箱" maxlength="50" />
</ElFormItem>
<ElFormItem label="部门" prop="dept_id">
<template #dept_id>
<ElTreeSelect
v-model="formData.dept_id"
placeholder="请选择上级部门"
@@ -219,9 +158,8 @@
check-strictly
:render-after-expand="false"
/>
</ElFormItem>
<ElFormItem label="角色" prop="role_ids">
</template>
<template #role_ids>
<ElSelect v-model="formData.role_ids" multiple placeholder="请选择角色">
<ElOption
v-for="item in roleOptions"
@@ -231,9 +169,8 @@
:disabled="item.disabled"
/>
</ElSelect>
</ElFormItem>
<ElFormItem label="岗位" prop="position_ids">
</template>
<template #position_ids>
<ElSelect v-model="formData.position_ids" multiple placeholder="请选择岗位">
<ElOption
v-for="item in positionOptions"
@@ -243,60 +180,13 @@
:disabled="item.disabled"
/>
</ElSelect>
</ElFormItem>
<ElFormItem v-if="!formData.id" label="密码" prop="password">
<ElInput
v-model="formData.password"
placeholder="请输入密码"
type="password"
show-password
clearable
/>
</ElFormItem>
<ElFormItem label="是否超管" prop="is_superuser">
<ElSwitch v-model="formData.is_superuser" />
</ElFormItem>
<ElFormItem label="状态" prop="status">
<ElRadioGroup v-model="formData.status">
<ElRadio value="0">启用</ElRadio>
<ElRadio value="1">停用</ElRadio>
</ElRadioGroup>
</ElFormItem>
<ElFormItem label="描述" prop="description">
<ElInput
v-model="formData.description"
:rows="4"
:maxlength="100"
show-word-limit
type="textarea"
placeholder="请输入描述"
/>
</ElFormItem>
</ElForm>
</template>
<template #footer>
<div class="dialog-footer">
<ElButton @click="handleCloseDialog">取消</ElButton>
<ElButton
v-if="dialogVisible.type === 'create' || dialogVisible.type === 'update'"
type="primary"
:loading="submitLoading"
@click="handleSubmit"
>
确定
</ElButton>
<ElButton v-else type="primary" @click="handleCloseDialog">确定</ElButton>
</div>
</template>
</FaForm>
</template>
</FaDrawer>
<FaImportDialog
v-model="importModalVisible"
v-model="importVisible"
:content-config="userImportContentConfig"
default-template-file-name="user_import_template.xlsx"
:loading="uploadLoading"
@@ -304,7 +194,7 @@
/>
<FaExportDialog
v-model="exportModalVisible"
v-model="exportVisible"
:content-config="userExportContentConfig"
:query-params="exportQueryParams"
:page-data="data"
@@ -319,20 +209,14 @@ defineOptions({
inheritAttrs: false,
});
import { h, ref, reactive, computed, nextTick } from "vue";
import { UserFilled } from "@element-plus/icons-vue";
import { useAppStore } from "@stores/modules/app.store";
import { DeviceEnum } from "@/enums/settings/device.enum";
import { ResultEnum } from "@/enums/api/result.enum";
import { useTable } from "@/hooks/core/useTable";
import FaTable from "@/components/tables/fa-table/index.vue";
import FaTableHeader from "@/components/tables/fa-table-header/index.vue";
import FaTableHeaderLeft from "@/components/tables/fa-table-header-left/index.vue";
import FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
import { useImportExport } from "@/hooks/core/useImportExport";
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
import FaDrawer from "@/components/modal/fa-drawer/index.vue";
import FaImportDialog from "@/components/modal/fa-import-dialog/index.vue";
import FaExportDialog from "@/components/modal/fa-export-dialog/index.vue";
import type { FormItem } from "@/components/forms/fa-form/index.vue";
import type { IContentConfig, IObject } from "@/components/modal/types";
import UserAPI, {
type UserForm,
@@ -347,6 +231,7 @@ import DeptTree from "./components/DeptTree.vue";
import UserTableSelect from "./components/UserTableSelect.vue";
import { useUserStore } from "@stores";
import { ElMessage, ElMessageBox, ElTag, ElAvatar } from "element-plus";
import type { DescriptionsItem } from "@/components/others/fa-descriptions/index.vue";
import { useAuth } from "@/hooks/core/useAuth";
import type { ColumnOption } from "@/types/component";
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
@@ -455,7 +340,8 @@ function formatUserOperationCell(row: UserInfo, ctx: Parameters<typeof buildUser
});
}
const dataFormRef = ref();
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
const userFormRenderKey = ref(0);
const submitLoading = ref(false);
const uploadLoading = ref(false);
const batchDeleting = ref(false);
@@ -465,10 +351,113 @@ const drawerSize = computed(() => (appStore.device === DeviceEnum.DESKTOP ? "450
const deptOptions = ref<OptionType[]>();
const roleOptions = ref<Array<{ value: number; label: string; disabled?: boolean }>>();
const positionOptions = ref<Array<{ value: number; label: string; disabled?: boolean }>>();
const importModalVisible = ref(false);
const exportModalVisible = ref(false);
const { importVisible, exportVisible, openImport, openExport } = useImportExport();
const detailFormData = ref<UserInfo>({});
// 用户详情描述项配置 —— 数据驱动 + 关键字段用具名插槽覆盖
const userDetailItems: DescriptionsItem[] = [
{ label: "编号", prop: "id" },
{ label: "头像", prop: "avatar", slot: "avatar" }, // 自定义插槽渲染
{ label: "账号", prop: "username" },
{ label: "用户名", prop: "name" },
{ label: "性别", prop: "gender", slot: "gender" }, // 三种状态 Tag
{ label: "部门", prop: "dept.name" }, // 嵌套属性 a.b.c
{ label: "角色", prop: "roles", slot: "roles" }, // 数组 join 渲染
{ label: "岗位", prop: "positions", slot: "positions" }, // 数组 join 渲染
{ label: "邮箱", prop: "email" },
{ label: "手机号", prop: "mobile" },
{
label: "是否超管",
prop: "is_superuser",
tag: {
map: { true: { type: "success", text: "是" }, false: { type: "info", text: "否" } },
},
},
{
label: "状态",
prop: "status",
tag: {
map: { "0": { type: "success", text: "启用" }, "1": { type: "danger", text: "停用" } },
},
},
{ label: "上次登录时间", prop: "last_login" },
{ label: "创建人", prop: "created_by.name" },
{ label: "更新人", prop: "updated_by.name" },
{ label: "创建时间", prop: "created_time" },
{ label: "更新时间", prop: "updated_time" },
{ label: "描述", prop: "description", span: 4 },
];
// 用户新增/编辑表单配置 —— 三个复杂字段(部门树、角色多选、岗位多选)用插槽渲染
const userDialogFormItems = computed<FormItem[]>(() => [
{
key: "username",
label: "账号",
type: "input",
props: { placeholder: "请输入账号", disabled: !!formData.value.id },
},
{ key: "name", label: "用户名", type: "input", props: { placeholder: "请输入用户名" } },
{
key: "gender",
label: "性别",
type: "select",
props: {
placeholder: "请选择性别",
options: [
{ label: "男", value: "0" },
{ label: "女", value: "1" },
{ label: "未知", value: "2" },
],
},
},
{
key: "mobile",
label: "手机号",
type: "input",
props: { placeholder: "请输入手机号码", maxlength: 11 },
},
{
key: "email",
label: "邮箱",
type: "input",
props: { placeholder: "请输入邮箱", maxlength: 50 },
},
{ key: "dept_id", label: "部门", type: "input" /* 实际渲染由 #dept_id 插槽接管 */ },
{ key: "role_ids", label: "角色", type: "input" /* 实际渲染由 #role_ids 插槽接管 */ },
{ key: "position_ids", label: "岗位", type: "input" /* 实际渲染由 #position_ids 插槽接管 */ },
{
key: "password",
label: "密码",
type: "input",
hidden: !!formData.value.id,
props: { placeholder: "请输入密码", type: "password", showPassword: true, clearable: true },
},
{ key: "is_superuser", label: "是否超管", type: "switch" },
{
key: "status",
label: "状态",
type: "radiogroup",
props: {
options: [
{ label: "启用", value: "0" },
{ label: "停用", value: "1" },
],
},
},
{
key: "description",
label: "描述",
type: "input",
props: {
type: "textarea",
rows: 4,
maxlength: 100,
showWordLimit: true,
placeholder: "请输入描述",
},
},
]);
const searchForm = ref<UserSearchForm>({
username: undefined,
name: undefined,
@@ -836,21 +825,13 @@ async function handleDeptNodeClick() {
await getData();
}
function openImportModal() {
importModalVisible.value = true;
}
function openExportModal() {
exportModalVisible.value = true;
}
async function handleImportUpload(formDataUpload: FormData) {
uploadLoading.value = true;
try {
const response = await UserAPI.importUser(formDataUpload);
if (response.data.code === ResultEnum.SUCCESS) {
ElMessage.success(`${response.data.msg},${response.data.data}`);
importModalVisible.value = false;
importVisible.value = false;
await refreshData();
} else {
ElMessage.error(response.data.msg || "导入失败");
@@ -893,7 +874,9 @@ async function handleOpenDialog(type: "create" | "update" | "detail", id?: numbe
}
} else {
dialogVisible.title = "新增用户";
Object.assign(formData.value, initialFormData);
formData.value.id = undefined;
userFormRenderKey.value += 1;
}
dialogVisible.visible = true;
await nextTick();
@@ -928,7 +911,7 @@ async function handleOpenDialog(type: "create" | "update" | "detail", id?: numbe
}
async function handleSubmit() {
dataFormRef.value.validate(async (valid: boolean) => {
dataFormRef.value?.validate(async (valid: boolean) => {
if (!valid) return;
submitLoading.value = true;
const id = formData.value.id;
@@ -778,8 +778,8 @@ const initialFormData: Partial<NodeForm> = {
};
async function resetForm() {
dataFormRef.value?.ref?.resetFields();
dataFormRef.value?.ref?.clearValidate();
dataFormRef.value?.resetFields();
dataFormRef.value?.clearValidate();
Object.assign(formData, initialFormData);
argsList.value = [];
kwargsList.value = [];
@@ -886,7 +886,7 @@ function handleOpenExecuteDialog(row: NodeTable) {
function handleCloseExecuteDialog() {
executeDialogVisible.value = false;
currentExecuteNode.value = null;
executeFormRef.value?.ref?.resetFields();
executeFormRef.value?.resetFields();
}
async function handleExecuteNode() {
@@ -454,8 +454,8 @@ const rules: FormRules = {
function resetForm() {
Object.assign(formData.value, defaultForm());
editingId.value = null;
formRef.value?.ref?.resetFields();
formRef.value?.ref?.clearValidate();
formRef.value?.resetFields();
formRef.value?.clearValidate();
}
function handleCloseDialog() {