mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-10-07 02:01:37 +00:00
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:
@@ -15,6 +15,58 @@ const __dirname = dirname(__filename)
|
|||||||
const autoImportConfig = JSON.parse(
|
const autoImportConfig = JSON.parse(
|
||||||
fs.readFileSync(path.resolve(__dirname, ".auto-import.json"), "utf-8")
|
fs.readFileSync(path.resolve(__dirname, ".auto-import.json"), "utf-8")
|
||||||
)
|
)
|
||||||
|
// Fa- 组件全局配置(unplugin-vue-components 自动注册)
|
||||||
|
const faComponents = {
|
||||||
|
FaTable: "readonly",
|
||||||
|
FaTableHeader: "readonly",
|
||||||
|
FaTableHeaderLeft: "readonly",
|
||||||
|
FaSearchBar: "readonly",
|
||||||
|
FaSearchBarWithAudit: "readonly",
|
||||||
|
FaDialog: "readonly",
|
||||||
|
FaDrawer: "readonly",
|
||||||
|
FaForm: "readonly",
|
||||||
|
FaDescriptions: "readonly",
|
||||||
|
FaImportDialog: "readonly",
|
||||||
|
FaExportDialog: "readonly",
|
||||||
|
FaIconSelect: "readonly",
|
||||||
|
FaWangEditor: "readonly",
|
||||||
|
FaSvgIcon: "readonly",
|
||||||
|
FaButtonMore: "readonly",
|
||||||
|
FaButtonTable: "readonly",
|
||||||
|
ArtButtonTable: "readonly",
|
||||||
|
FaExcelImport: "readonly",
|
||||||
|
FaExcelExport: "readonly",
|
||||||
|
FaBreadcrumb: "readonly",
|
||||||
|
FaCardBanner: "readonly",
|
||||||
|
FaJsonPretty: "readonly",
|
||||||
|
JsonPretty: "readonly",
|
||||||
|
FaCopyButton: "readonly",
|
||||||
|
CopyButton: "readonly",
|
||||||
|
FaMenuRouteIcon: "readonly",
|
||||||
|
MenuRouteIcon: "readonly",
|
||||||
|
FaIntervalTab: "readonly",
|
||||||
|
FaPagination: "readonly",
|
||||||
|
FaSizeSelect: "readonly",
|
||||||
|
FaMarkdownRenderer: "readonly",
|
||||||
|
FaScreenLock: "readonly",
|
||||||
|
FaSettingsPanel: "readonly",
|
||||||
|
FaGlobalSearch: "readonly",
|
||||||
|
FaHeaderBar: "readonly",
|
||||||
|
FaFastEnter: "readonly",
|
||||||
|
FaFireworksEffect: "readonly",
|
||||||
|
FaGlobalComponent: "readonly",
|
||||||
|
FaWatermark: "readonly",
|
||||||
|
FaCountTo: "readonly",
|
||||||
|
FaECharts: "readonly",
|
||||||
|
FaIcon: "readonly",
|
||||||
|
FaChatWindow: "readonly",
|
||||||
|
FaBackToTop: "readonly",
|
||||||
|
FaCalender: "readonly",
|
||||||
|
FaDragVerify: "readonly",
|
||||||
|
FaException: "readonly",
|
||||||
|
FaVideoPlayer: "readonly",
|
||||||
|
FaCutterImg: "readonly",
|
||||||
|
}
|
||||||
|
|
||||||
// Element Plus 组件全局配置
|
// Element Plus 组件全局配置
|
||||||
const elementPlusComponents = {
|
const elementPlusComponents = {
|
||||||
@@ -115,6 +167,7 @@ export default [
|
|||||||
languageOptions: {
|
languageOptions: {
|
||||||
globals: {
|
globals: {
|
||||||
...autoImportConfig.globals,
|
...autoImportConfig.globals,
|
||||||
|
...faComponents,
|
||||||
Api: "readonly",
|
Api: "readonly",
|
||||||
...elementPlusComponents,
|
...elementPlusComponents,
|
||||||
// 全局类型定义
|
// 全局类型定义
|
||||||
|
|||||||
@@ -2,7 +2,106 @@
|
|||||||
<!-- 支持常用表单组件、自定义组件、插槽、校验、隐藏表单项 -->
|
<!-- 支持常用表单组件、自定义组件、插槽、校验、隐藏表单项 -->
|
||||||
<!-- 写法同 ElementPlus 官方文档组件,把属性写在 props 里面就可以了 -->
|
<!-- 写法同 ElementPlus 官方文档组件,把属性写在 props 里面就可以了 -->
|
||||||
<template>
|
<template>
|
||||||
<section class="px-4 pb-0 pt-4 md:px-4 md:pt-4">
|
<ElScrollbar v-if="scrollbar" :max-height="maxHeight" :view-style="{ overflowX: 'hidden' }">
|
||||||
|
<section class="px-4 pb-0 pt-4 md:px-4 md:pt-4">
|
||||||
|
<ElForm
|
||||||
|
ref="formRef"
|
||||||
|
:model="modelValue"
|
||||||
|
:label-position="labelPosition"
|
||||||
|
v-bind="{ ...$attrs }"
|
||||||
|
>
|
||||||
|
<ElRow class="flex flex-wrap" :gutter="gutter">
|
||||||
|
<ElCol
|
||||||
|
v-for="item in visibleFormItems"
|
||||||
|
:key="item.key"
|
||||||
|
:xs="getColSpan(item.span, 'xs')"
|
||||||
|
:sm="getColSpan(item.span, 'sm')"
|
||||||
|
:md="getColSpan(item.span, 'md')"
|
||||||
|
:lg="getColSpan(item.span, 'lg')"
|
||||||
|
:xl="getColSpan(item.span, 'xl')"
|
||||||
|
>
|
||||||
|
<ElFormItem
|
||||||
|
:prop="item.key"
|
||||||
|
:label-width="item.label ? item.labelWidth || labelWidth : undefined"
|
||||||
|
>
|
||||||
|
<template #label v-if="item.label">
|
||||||
|
<component v-if="typeof item.label !== 'string'" :is="item.label" />
|
||||||
|
<span v-else>{{ item.label }}</span>
|
||||||
|
</template>
|
||||||
|
<slot :name="item.key" :item="item" :modelValue="modelValue">
|
||||||
|
<component
|
||||||
|
:is="getComponent(item)"
|
||||||
|
:model-value="getFieldValue(item.key)"
|
||||||
|
@update:model-value="setFieldValue(item.key, $event)"
|
||||||
|
v-bind="getProps(item)"
|
||||||
|
>
|
||||||
|
<!-- 下拉选择 -->
|
||||||
|
<template v-if="item.type === 'select' && getProps(item)?.options">
|
||||||
|
<ElOption
|
||||||
|
v-for="option in getProps(item).options"
|
||||||
|
v-bind="option"
|
||||||
|
:key="option.value"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 复选框组 -->
|
||||||
|
<template v-if="item.type === 'checkboxgroup' && getProps(item)?.options">
|
||||||
|
<ElCheckbox
|
||||||
|
v-for="option in getProps(item).options"
|
||||||
|
v-bind="option"
|
||||||
|
:key="option.value"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 单选框组 -->
|
||||||
|
<template v-if="item.type === 'radiogroup' && getProps(item)?.options">
|
||||||
|
<ElRadio
|
||||||
|
v-for="option in getProps(item).options"
|
||||||
|
v-bind="option"
|
||||||
|
:key="option.value"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 动态插槽支持 -->
|
||||||
|
<template
|
||||||
|
v-for="(slotFn, slotName) in getSlots(item)"
|
||||||
|
:key="slotName"
|
||||||
|
#[slotName]
|
||||||
|
>
|
||||||
|
<component :is="slotFn" />
|
||||||
|
</template>
|
||||||
|
</component>
|
||||||
|
</slot>
|
||||||
|
</ElFormItem>
|
||||||
|
</ElCol>
|
||||||
|
<ElCol :xs="24" :sm="24" :md="span" :lg="span" :xl="span" class="max-w-full flex-1">
|
||||||
|
<div
|
||||||
|
class="mb-3 flex-c flex-wrap justify-end md:flex-row md:items-stretch md:gap-2"
|
||||||
|
:style="actionButtonsStyle"
|
||||||
|
>
|
||||||
|
<div class="flex gap-2 md:justify-center">
|
||||||
|
<ElButton v-if="showReset" class="reset-button" @click="handleReset" v-ripple>
|
||||||
|
{{ t("table.form.reset") }}
|
||||||
|
</ElButton>
|
||||||
|
<ElButton
|
||||||
|
v-if="showSubmit"
|
||||||
|
type="primary"
|
||||||
|
class="submit-button"
|
||||||
|
@click="handleSubmit"
|
||||||
|
v-ripple
|
||||||
|
:disabled="disabledSubmit"
|
||||||
|
>
|
||||||
|
{{ t("table.form.submit") }}
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ElCol>
|
||||||
|
</ElRow>
|
||||||
|
</ElForm>
|
||||||
|
</section>
|
||||||
|
</ElScrollbar>
|
||||||
|
<!-- 不使用滚动条时直接渲染 -->
|
||||||
|
<section v-else class="px-4 pb-0 pt-4 md:px-4 md:pt-4">
|
||||||
<ElForm
|
<ElForm
|
||||||
ref="formRef"
|
ref="formRef"
|
||||||
:model="modelValue"
|
:model="modelValue"
|
||||||
@@ -34,7 +133,6 @@
|
|||||||
@update:model-value="setFieldValue(item.key, $event)"
|
@update:model-value="setFieldValue(item.key, $event)"
|
||||||
v-bind="getProps(item)"
|
v-bind="getProps(item)"
|
||||||
>
|
>
|
||||||
<!-- 下拉选择 -->
|
|
||||||
<template v-if="item.type === 'select' && getProps(item)?.options">
|
<template v-if="item.type === 'select' && getProps(item)?.options">
|
||||||
<ElOption
|
<ElOption
|
||||||
v-for="option in getProps(item).options"
|
v-for="option in getProps(item).options"
|
||||||
@@ -42,8 +140,6 @@
|
|||||||
:key="option.value"
|
:key="option.value"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 复选框组 -->
|
|
||||||
<template v-if="item.type === 'checkboxgroup' && getProps(item)?.options">
|
<template v-if="item.type === 'checkboxgroup' && getProps(item)?.options">
|
||||||
<ElCheckbox
|
<ElCheckbox
|
||||||
v-for="option in getProps(item).options"
|
v-for="option in getProps(item).options"
|
||||||
@@ -51,8 +147,6 @@
|
|||||||
:key="option.value"
|
:key="option.value"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 单选框组 -->
|
|
||||||
<template v-if="item.type === 'radiogroup' && getProps(item)?.options">
|
<template v-if="item.type === 'radiogroup' && getProps(item)?.options">
|
||||||
<ElRadio
|
<ElRadio
|
||||||
v-for="option in getProps(item).options"
|
v-for="option in getProps(item).options"
|
||||||
@@ -60,8 +154,6 @@
|
|||||||
:key="option.value"
|
:key="option.value"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 动态插槽支持 -->
|
|
||||||
<template v-for="(slotFn, slotName) in getSlots(item)" :key="slotName" #[slotName]>
|
<template v-for="(slotFn, slotName) in getSlots(item)" :key="slotName" #[slotName]>
|
||||||
<component :is="slotFn" />
|
<component :is="slotFn" />
|
||||||
</template>
|
</template>
|
||||||
@@ -206,6 +298,10 @@ interface FormProps {
|
|||||||
disabledSubmit?: boolean;
|
disabledSubmit?: boolean;
|
||||||
/** 提交时是否清洗空值 */
|
/** 提交时是否清洗空值 */
|
||||||
sanitizeOutput?: Partial<SanitizeOutputOptions>;
|
sanitizeOutput?: Partial<SanitizeOutputOptions>;
|
||||||
|
/** 是否需要内置 ElScrollbar 包裹 */
|
||||||
|
scrollbar?: boolean;
|
||||||
|
/** ElScrollbar 最大高度 */
|
||||||
|
maxHeight?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SanitizeOutputOptions {
|
interface SanitizeOutputOptions {
|
||||||
@@ -234,6 +330,8 @@ const props = withDefaults(defineProps<FormProps>(), {
|
|||||||
showSubmit: true,
|
showSubmit: true,
|
||||||
disabledSubmit: false,
|
disabledSubmit: false,
|
||||||
sanitizeOutput: () => ({}),
|
sanitizeOutput: () => ({}),
|
||||||
|
scrollbar: false,
|
||||||
|
maxHeight: "75vh",
|
||||||
});
|
});
|
||||||
|
|
||||||
interface FormEmits {
|
interface FormEmits {
|
||||||
@@ -505,6 +603,12 @@ const handleSubmit = () => {
|
|||||||
defineExpose({
|
defineExpose({
|
||||||
ref: formInstance,
|
ref: formInstance,
|
||||||
validate: (...args: any[]) => formInstance.value?.validate(...args),
|
validate: (...args: any[]) => formInstance.value?.validate(...args),
|
||||||
|
/** 代理 ElForm.resetFields */
|
||||||
|
resetFields: (...args: any[]) => formInstance.value?.resetFields(...args),
|
||||||
|
/** 代理 ElForm.clearValidate */
|
||||||
|
clearValidate: (...args: any[]) => formInstance.value?.clearValidate(...args),
|
||||||
|
/** 代理 ElForm.validateField */
|
||||||
|
validateField: (...args: any[]) => formInstance.value?.validateField(...args),
|
||||||
reset: handleReset,
|
reset: handleReset,
|
||||||
// 允许外部在不触发提交事件时主动获取清洗后的输出。
|
// 允许外部在不触发提交事件时主动获取清洗后的输出。
|
||||||
getOutput: getSanitizedOutput,
|
getOutput: getSanitizedOutput,
|
||||||
|
|||||||
@@ -42,6 +42,16 @@
|
|||||||
<template v-if="$slots.footer" #footer>
|
<template v-if="$slots.footer" #footer>
|
||||||
<slot name="footer" />
|
<slot name="footer" />
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="formMode" #footer>
|
||||||
|
<div class="fa-dialog-footer" :style="'padding-right: var(--el-dialog-padding-primary)'">
|
||||||
|
<ElButton v-if="formMode !== 'detail'" @click="emit('cancel')">
|
||||||
|
{{ cancelText }}
|
||||||
|
</ElButton>
|
||||||
|
<ElButton type="primary" :loading="confirmLoading" @click="emit('confirm')">
|
||||||
|
{{ confirmText }}
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</ElDialog>
|
</ElDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -63,9 +73,19 @@ const props = withDefaults(
|
|||||||
dialogClass?: string;
|
dialogClass?: string;
|
||||||
/** 遮罩层自定义 class */
|
/** 遮罩层自定义 class */
|
||||||
modalClass?: string;
|
modalClass?: string;
|
||||||
|
/** 表单模式:detail 仅显示确定;create/update 显示取消+确定 */
|
||||||
|
formMode?: "detail" | "create" | "update";
|
||||||
|
/** 确定按钮 loading 状态 */
|
||||||
|
confirmLoading?: boolean;
|
||||||
|
/** 确定按钮文本 */
|
||||||
|
confirmText?: string;
|
||||||
|
/** 取消按钮文本 */
|
||||||
|
cancelText?: string;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
draggable: true,
|
draggable: true,
|
||||||
|
confirmText: "确定",
|
||||||
|
cancelText: "取消",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -74,6 +94,10 @@ const emit = defineEmits<{
|
|||||||
close: [];
|
close: [];
|
||||||
opened: [];
|
opened: [];
|
||||||
"fullscreen-change": [isFullscreen: boolean];
|
"fullscreen-change": [isFullscreen: boolean];
|
||||||
|
/** 点击取消按钮 */
|
||||||
|
cancel: [];
|
||||||
|
/** 点击确定按钮 */
|
||||||
|
confirm: [];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const attrs = useAttrs();
|
const attrs = useAttrs();
|
||||||
@@ -112,6 +136,13 @@ const dialogAttrs = computed(() => {
|
|||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fa-dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.core-overlay-dialog__actions {
|
.core-overlay-dialog__actions {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|||||||
@@ -26,6 +26,16 @@
|
|||||||
<template v-if="$slots.footer" #footer>
|
<template v-if="$slots.footer" #footer>
|
||||||
<slot name="footer" />
|
<slot name="footer" />
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="formMode" #footer>
|
||||||
|
<div class="fa-drawer-footer" :style="'padding-right: var(--el-drawer-padding-primary)'">
|
||||||
|
<ElButton v-if="formMode !== 'detail'" @click="emit('cancel')">
|
||||||
|
{{ cancelText }}
|
||||||
|
</ElButton>
|
||||||
|
<ElButton type="primary" :loading="confirmLoading" @click="emit('confirm')">
|
||||||
|
{{ confirmText }}
|
||||||
|
</ElButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</ElDrawer>
|
</ElDrawer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -44,9 +54,19 @@ const props = withDefaults(
|
|||||||
direction?: "rtl" | "ltr" | "ttb" | "btt";
|
direction?: "rtl" | "ltr" | "ttb" | "btt";
|
||||||
/** 透传到 el-drawer 的 class */
|
/** 透传到 el-drawer 的 class */
|
||||||
drawerClass?: string;
|
drawerClass?: string;
|
||||||
|
/** 表单模式:detail 仅显示确定;create/update 显示取消+确定 */
|
||||||
|
formMode?: "detail" | "create" | "update";
|
||||||
|
/** 确定按钮 loading 状态 */
|
||||||
|
confirmLoading?: boolean;
|
||||||
|
/** 确定按钮文本 */
|
||||||
|
confirmText?: string;
|
||||||
|
/** 取消按钮文本 */
|
||||||
|
cancelText?: string;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
direction: "rtl",
|
direction: "rtl",
|
||||||
|
confirmText: "确定",
|
||||||
|
cancelText: "取消",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -54,6 +74,10 @@ const emit = defineEmits<{
|
|||||||
"update:modelValue": [v: boolean];
|
"update:modelValue": [v: boolean];
|
||||||
close: [];
|
close: [];
|
||||||
opened: [];
|
opened: [];
|
||||||
|
/** 点击取消按钮 */
|
||||||
|
cancel: [];
|
||||||
|
/** 点击确定按钮 */
|
||||||
|
confirm: [];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const attrs = useAttrs();
|
const attrs = useAttrs();
|
||||||
@@ -92,6 +116,13 @@ const drawerAttrs = computed(() => {
|
|||||||
color: var(--el-text-color-primary);
|
color: var(--el-text-color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fa-drawer-footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.core-overlay-drawer__actions {
|
.core-overlay-drawer__actions {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|||||||
@@ -0,0 +1,245 @@
|
|||||||
|
<template>
|
||||||
|
<ElScrollbar v-if="scrollbar" :max-height="maxHeight" :view-style="{ overflowX: 'hidden' }">
|
||||||
|
<ElDescriptions v-bind="bindings" :class="ns.b()">
|
||||||
|
<template v-if="$slots.title" #title>
|
||||||
|
<slot name="title" />
|
||||||
|
</template>
|
||||||
|
<template v-if="!$slots.default">
|
||||||
|
<ElDescriptionsItem
|
||||||
|
v-for="item in items"
|
||||||
|
:key="item.prop"
|
||||||
|
:label="item.label"
|
||||||
|
:span="item.span || span"
|
||||||
|
:label-class-name="item.labelClassName"
|
||||||
|
:class-name="item.className"
|
||||||
|
>
|
||||||
|
<!-- 带 slot 名称的项:通过具名插槽自定义 -->
|
||||||
|
<slot
|
||||||
|
v-if="item.slot"
|
||||||
|
:name="item.slot"
|
||||||
|
:item="item"
|
||||||
|
:value="data ? getNestedValue(data, item.prop) : undefined"
|
||||||
|
:row="data"
|
||||||
|
/>
|
||||||
|
<!-- Tag 模式 -->
|
||||||
|
<template v-else-if="item.tag">
|
||||||
|
<ElTag
|
||||||
|
v-if="item.tag === true && data && data[item.prop] !== undefined"
|
||||||
|
:type="resolveTagType(getNestedValue(data, item.prop))"
|
||||||
|
>
|
||||||
|
{{ getNestedValue(data, item.prop) }}
|
||||||
|
</ElTag>
|
||||||
|
<ElTag
|
||||||
|
v-else-if="typeof item.tag === 'object' && data"
|
||||||
|
:type="resolveTagType(getNestedValue(data, item.prop), item.tag.type)"
|
||||||
|
>
|
||||||
|
{{ resolveTagText(getNestedValue(data, item.prop), item.tag) }}
|
||||||
|
</ElTag>
|
||||||
|
</template>
|
||||||
|
<!-- 纯文本模式 -->
|
||||||
|
<template v-else>
|
||||||
|
<slot
|
||||||
|
:name="item.prop"
|
||||||
|
:item="item"
|
||||||
|
:value="data ? getNestedValue(data, item.prop) : undefined"
|
||||||
|
:row="data"
|
||||||
|
>
|
||||||
|
{{ data ? getNestedValue(data, item.prop) : "" }}
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
</template>
|
||||||
|
<!-- 完全自定义模式 -->
|
||||||
|
<template v-else>
|
||||||
|
<slot />
|
||||||
|
</template>
|
||||||
|
</ElDescriptions>
|
||||||
|
</ElScrollbar>
|
||||||
|
<!-- 不使用滚动条时直接渲染 -->
|
||||||
|
<ElDescriptions v-else v-bind="bindings" :class="ns.b()">
|
||||||
|
<template v-if="$slots.title" #title>
|
||||||
|
<slot name="title" />
|
||||||
|
</template>
|
||||||
|
<template v-if="!$slots.default">
|
||||||
|
<ElDescriptionsItem
|
||||||
|
v-for="item in items"
|
||||||
|
:key="item.prop"
|
||||||
|
:label="item.label"
|
||||||
|
:span="item.span || span"
|
||||||
|
:label-class-name="item.labelClassName"
|
||||||
|
:class-name="item.className"
|
||||||
|
>
|
||||||
|
<slot
|
||||||
|
v-if="item.slot"
|
||||||
|
:name="item.slot"
|
||||||
|
:item="item"
|
||||||
|
:value="data ? getNestedValue(data, item.prop) : undefined"
|
||||||
|
:row="data"
|
||||||
|
/>
|
||||||
|
<template v-else-if="item.tag">
|
||||||
|
<ElTag
|
||||||
|
v-if="item.tag === true && data && data[item.prop] !== undefined"
|
||||||
|
:type="resolveTagType(getNestedValue(data, item.prop))"
|
||||||
|
>
|
||||||
|
{{ getNestedValue(data, item.prop) }}
|
||||||
|
</ElTag>
|
||||||
|
<ElTag
|
||||||
|
v-else-if="typeof item.tag === 'object' && data"
|
||||||
|
:type="resolveTagType(getNestedValue(data, item.prop), item.tag.type)"
|
||||||
|
>
|
||||||
|
{{ resolveTagText(getNestedValue(data, item.prop), item.tag) }}
|
||||||
|
</ElTag>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<slot
|
||||||
|
:name="item.prop"
|
||||||
|
:item="item"
|
||||||
|
:value="data ? getNestedValue(data, item.prop) : undefined"
|
||||||
|
:row="data"
|
||||||
|
>
|
||||||
|
{{ data ? getNestedValue(data, item.prop) : "" }}
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
</ElDescriptionsItem>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<slot />
|
||||||
|
</template>
|
||||||
|
</ElDescriptions>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineOptions({ name: "FaDescriptions" });
|
||||||
|
|
||||||
|
import { computed, useAttrs } from "vue";
|
||||||
|
import { useNamespace } from "element-plus";
|
||||||
|
// ── 组件类型定义(内联,避免跨文件引用导致 TS Server 延迟)──
|
||||||
|
/** ElTag 可接受的 type 值 */
|
||||||
|
export type TagType = "primary" | "success" | "warning" | "danger" | "info";
|
||||||
|
|
||||||
|
/** Tag 渲染配置 */
|
||||||
|
export interface TagConfig {
|
||||||
|
/**
|
||||||
|
* Tag 映射表:key 为数据值,value 为 { type: ElTag类型, text: 显示文本 }
|
||||||
|
* 例:{ '0': { type: 'success', text: '启用' }, '1': { type: 'danger', text: '停用' } }
|
||||||
|
*/
|
||||||
|
map?: Record<string, { type?: TagType; text?: string }>;
|
||||||
|
/** 默认 type,当 map 未匹配到时使用 */
|
||||||
|
type?: TagType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 单个描述项配置 */
|
||||||
|
export interface DescriptionsItem {
|
||||||
|
/** 标签文本 */
|
||||||
|
label: string;
|
||||||
|
/** 对应 data 中的属性名,支持 'a.b.c' 嵌套路径 */
|
||||||
|
prop: string;
|
||||||
|
/** 列跨度,不传则使用组件级 span 默认值 */
|
||||||
|
span?: number;
|
||||||
|
/**
|
||||||
|
* 渲染模式:
|
||||||
|
* - 不设置:纯文本
|
||||||
|
* - true:作为 ElTag 渲染,文本为 data[prop]
|
||||||
|
* - TagConfig 对象:通过 map 映射类型和文本
|
||||||
|
*/
|
||||||
|
tag?: boolean | TagConfig;
|
||||||
|
/** 自定义插槽名称,设置后该 item 将通过具名插槽渲染 */
|
||||||
|
slot?: string;
|
||||||
|
/** 传递给 el-descriptions-item 的 label-class-name */
|
||||||
|
labelClassName?: string;
|
||||||
|
/** 传递给 el-descriptions-item 的 class-name */
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const attrs = useAttrs();
|
||||||
|
const ns = useNamespace("descriptions");
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
/** 每行显示的描述项数量,默认 4 */
|
||||||
|
column?: number;
|
||||||
|
/** 是否展示边框,默认 true */
|
||||||
|
border?: boolean;
|
||||||
|
/** 列表尺寸,默认 'default' */
|
||||||
|
size?: "default" | "small";
|
||||||
|
/** 标签宽度 */
|
||||||
|
labelWidth?: string;
|
||||||
|
/** 描述项配置列表 */
|
||||||
|
items?: DescriptionsItem[];
|
||||||
|
/** 数据对象,items 模式下自动取 data[item.prop] 渲染 */
|
||||||
|
data?: Record<string, unknown> | null;
|
||||||
|
/** 默认 span,当 item 未指定 span 时生效,默认 2 */
|
||||||
|
span?: number;
|
||||||
|
/** 是否需要内置 ElScrollbar 包裹,默认 true */
|
||||||
|
scrollbar?: boolean;
|
||||||
|
/** ElScrollbar 最大高度,默认 '70vh' */
|
||||||
|
maxHeight?: string;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
column: 4,
|
||||||
|
border: true,
|
||||||
|
size: "default",
|
||||||
|
labelWidth: undefined,
|
||||||
|
items: () => [],
|
||||||
|
data: null,
|
||||||
|
span: 2,
|
||||||
|
scrollbar: true,
|
||||||
|
maxHeight: "70vh",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// 传递给 ElDescriptions 的绑定属性
|
||||||
|
const bindings = computed(() => {
|
||||||
|
const bind: Record<string, unknown> = {
|
||||||
|
column: props.column,
|
||||||
|
border: props.border,
|
||||||
|
...attrs, // 透传 class、style 等属性
|
||||||
|
};
|
||||||
|
if (props.size !== "default") bind.size = props.size;
|
||||||
|
if (props.labelWidth !== undefined) bind.labelWidth = props.labelWidth;
|
||||||
|
return bind;
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 获取嵌套属性值,支持 'a.b.c' 路径 */
|
||||||
|
function getNestedValue(obj: Record<string, unknown> | null, path: string): unknown {
|
||||||
|
if (!obj) return undefined;
|
||||||
|
return path.split(".").reduce((acc, key) => {
|
||||||
|
if (acc && typeof acc === "object" && key in acc) {
|
||||||
|
return (acc as Record<string, unknown>)[key];
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}, obj as unknown);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 解析 ElTag 的 type 属性,确保返回 ElTag 可接受的有效类型值 */
|
||||||
|
function resolveTagType(_value: unknown, fallback?: string): TagType {
|
||||||
|
// 优先级 1: fallback 本身就是合法 TagType → 直接使用
|
||||||
|
if (fallback && isValidTagType(fallback)) return fallback;
|
||||||
|
// 优先级 2: 无有效 fallback 时,用 'info' 作为兜底
|
||||||
|
return "info";
|
||||||
|
}
|
||||||
|
|
||||||
|
const TAG_TYPES: Set<string> = new Set(["primary", "success", "warning", "danger", "info"]);
|
||||||
|
function isValidTagType(v: string): v is TagType {
|
||||||
|
return TAG_TYPES.has(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 解析 ElTag 显示的文本,支持 tagMap 映射 */
|
||||||
|
function resolveTagText(value: unknown, tag: TagConfig): string {
|
||||||
|
const raw = value == null ? "" : String(value);
|
||||||
|
if (tag.map && raw in tag.map) {
|
||||||
|
return tag.map[raw].text ?? raw;
|
||||||
|
}
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/*
|
||||||
|
* fa-descriptions 在 ElScrollbar 内部包裹时,
|
||||||
|
* 需要占满宽度,避免 ElDescriptions 边框被截断。
|
||||||
|
*/
|
||||||
|
:deep(.fa-descriptions) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入/导出弹窗状态管理
|
||||||
|
* 减少各页面重复的 importModalVisible / exportModalVisible 及 open 函数
|
||||||
|
*/
|
||||||
|
export function useImportExport() {
|
||||||
|
const importVisible = ref(false);
|
||||||
|
const exportVisible = ref(false);
|
||||||
|
|
||||||
|
return {
|
||||||
|
importVisible,
|
||||||
|
exportVisible,
|
||||||
|
openImport: () => {
|
||||||
|
importVisible.value = true;
|
||||||
|
},
|
||||||
|
openExport: () => {
|
||||||
|
exportVisible.value = true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
../components/layouts/index.vue
|
|
||||||
@@ -312,11 +312,11 @@ export class MenuProcessor {
|
|||||||
private isNavigableRoute(route: AppRouteRecord): boolean {
|
private isNavigableRoute(route: AppRouteRecord): boolean {
|
||||||
return Boolean(
|
return Boolean(
|
||||||
route.path &&
|
route.path &&
|
||||||
route.path !== "/" &&
|
route.path !== "/" &&
|
||||||
!route.meta?.link &&
|
!route.meta?.link &&
|
||||||
route.meta?.isIframe !== true &&
|
route.meta?.isIframe !== true &&
|
||||||
route.component &&
|
route.component &&
|
||||||
route.component !== ""
|
route.component !== ""
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ export class ComponentLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadLayout(): () => Promise<any> {
|
loadLayout(): () => Promise<any> {
|
||||||
return () => import("@/layouts/index.vue");
|
return () => import("@/components/layouts/index.vue");
|
||||||
}
|
}
|
||||||
|
|
||||||
loadIframe(): () => Promise<any> {
|
loadIframe(): () => Promise<any> {
|
||||||
|
|||||||
@@ -102,8 +102,8 @@ export const ROUTE_COMPONENT_NESTED_PARENT = "/nested/router-view-parent";
|
|||||||
export const ROUTE_PATH_LOGIN_ALT = "/auth/login";
|
export const ROUTE_PATH_LOGIN_ALT = "/auth/login";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主框架布局:新版 art 体系(`src/layouts/index.vue` + `src/layouts/art-*` 组件)。
|
* 主框架布局:新版 art 体系(`src/components/layouts/index.vue` + `src/components/layouts/fa-*` 组件)。
|
||||||
* 旧版 Left/Top/Mix 壳子仍在 `@/layouts/index.vue`,路由不再默认使用。
|
* 旧版 Left/Top/Mix 壳子已移除,统一使用 `@/components/layouts/index.vue`。
|
||||||
*/
|
*/
|
||||||
export const Layout = () => import("@/components/layouts/index.vue");
|
export const Layout = () => import("@/components/layouts/index.vue");
|
||||||
|
|
||||||
@@ -442,7 +442,7 @@ export const staticRoutes: AppRouteRecordRaw[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/outside",
|
path: "/outside",
|
||||||
component: () => import("@/layouts/index.vue"),
|
component: () => import("@/components/layouts/index.vue"),
|
||||||
name: "Outside",
|
name: "Outside",
|
||||||
meta: { title: "menus.outside.title" },
|
meta: { title: "menus.outside.title" },
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -280,9 +280,7 @@ export interface MapChartProps extends BaseChartProps {
|
|||||||
|
|
||||||
// 双向堆叠柱状图 Props 接口(人口金字塔样式)
|
// 双向堆叠柱状图 Props 接口(人口金字塔样式)
|
||||||
export interface BidirectionalBarChartProps
|
export interface BidirectionalBarChartProps
|
||||||
extends BaseChartProps,
|
extends BaseChartProps, AxisDisplayProps, InteractionProps {
|
||||||
AxisDisplayProps,
|
|
||||||
InteractionProps {
|
|
||||||
/** 正向数据(向上显示) */
|
/** 正向数据(向上显示) */
|
||||||
positiveData: number[];
|
positiveData: number[];
|
||||||
/** 负向数据(向下显示) */
|
/** 负向数据(向下显示) */
|
||||||
|
|||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* 全局组件 / API 类型声明(手动维护,不会被 unplugin 覆盖)
|
||||||
|
* 仅用于 <script> 中通过 typeof / h() 等方式引用的组件和 API
|
||||||
|
* 模板中使用的组件由 unplugin-vue-components 自动注册,无需在此声明
|
||||||
|
*/
|
||||||
|
export {};
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
// ─── Fa 组件 ───
|
||||||
|
const FaButtonTable: (typeof import("./../components/forms/fa-button-table/index.vue"))["default"];
|
||||||
|
const FaForm: (typeof import("./../components/forms/fa-form/index.vue"))["default"];
|
||||||
|
const FaSearchBar: (typeof import("./../components/forms/fa-search-bar/index.vue"))["default"];
|
||||||
|
const FaSearchBarWithAudit: (typeof import("./../components/forms/fa-search-bar/FaSearchBarWithAudit.vue"))["default"];
|
||||||
|
|
||||||
|
// ─── 自定义组件 ───
|
||||||
|
const JsonPretty: (typeof import("./../components/others/fa-json-pretty/index.vue"))["default"];
|
||||||
|
const CopyButton: (typeof import("./../components/others/fa-copy-button/index.vue"))["default"];
|
||||||
|
const MenuRouteIcon: (typeof import("./../components/others/fa-menu-routeIcon/index.vue"))["default"];
|
||||||
|
|
||||||
|
// ─── Element Plus 组件(在 <script> 中通过 h() 使用) ───
|
||||||
|
const ElTag: (typeof import("element-plus/es"))["ElTag"];
|
||||||
|
const ElTooltip: (typeof import("element-plus/es"))["ElTooltip"];
|
||||||
|
const ElDropdown: (typeof import("element-plus/es"))["ElDropdown"];
|
||||||
|
const ElDropdownMenu: (typeof import("element-plus/es"))["ElDropdownMenu"];
|
||||||
|
const ElDropdownItem: (typeof import("element-plus/es"))["ElDropdownItem"];
|
||||||
|
|
||||||
|
// ─── Element Plus API ───
|
||||||
|
const ElMessageBox: (typeof import("element-plus/es"))["ElMessageBox"];
|
||||||
|
}
|
||||||
@@ -74,48 +74,24 @@
|
|||||||
width="920px"
|
width="920px"
|
||||||
dialog-class="session-detail-dialog"
|
dialog-class="session-detail-dialog"
|
||||||
modal-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'">
|
<template v-if="dialogVisible.type === 'detail'">
|
||||||
<ElScrollbar max-height="70vh" :view-style="{ overflowX: 'hidden' }">
|
<ElScrollbar max-height="70vh" :view-style="{ overflowX: 'hidden' }">
|
||||||
<ElDescriptions :column="2" border>
|
<FaDescriptions
|
||||||
<ElDescriptionsItem label="会话ID" :span="2">
|
:column="2"
|
||||||
{{ detailFormData.session_id }}
|
:data="detailFormData"
|
||||||
</ElDescriptionsItem>
|
:items="memoryDetailItems"
|
||||||
<ElDescriptionsItem label="标题" :span="2">
|
:scrollbar="false"
|
||||||
{{ detailFormData.title }}
|
>
|
||||||
</ElDescriptionsItem>
|
<template #metadata="{ row }">
|
||||||
<ElDescriptionsItem label="用户ID" :span="1">
|
<pre v-if="row?.metadata">{{ JSON.stringify(row?.metadata, null, 2) }}</pre>
|
||||||
{{ 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>
|
|
||||||
<span v-else>无</span>
|
<span v-else>无</span>
|
||||||
</ElDescriptionsItem>
|
</template>
|
||||||
</ElDescriptions>
|
</FaDescriptions>
|
||||||
|
|
||||||
<ElDivider content-position="left">消息记录</ElDivider>
|
<ElDivider content-position="left">消息记录</ElDivider>
|
||||||
<ElTimeline v-if="detailFormData.messages && detailFormData.messages.length > 0">
|
<ElTimeline v-if="detailFormData.messages && detailFormData.messages.length > 0">
|
||||||
@@ -158,16 +134,6 @@
|
|||||||
class="crud-dialog-art-form"
|
class="crud-dialog-art-form"
|
||||||
/>
|
/>
|
||||||
</template>
|
</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>
|
</FaDialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -184,13 +150,7 @@ import { ElMessage, ElMessageBox } from "element-plus";
|
|||||||
import AiChatAPI, { type ChatSession, type ChatSessionDetail } from "@/api/module_ai/chat";
|
import AiChatAPI, { type ChatSession, type ChatSessionDetail } from "@/api/module_ai/chat";
|
||||||
import { formatToDateTime } from "@utils/common";
|
import { formatToDateTime } from "@utils/common";
|
||||||
import { useTable } from "@/hooks/core/useTable";
|
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 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 { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||||
import type { ColumnOption } from "@/types/component";
|
import type { ColumnOption } from "@/types/component";
|
||||||
import { useAuth } from "@/hooks/core/useAuth";
|
import { useAuth } from "@/hooks/core/useAuth";
|
||||||
@@ -264,6 +224,7 @@ const memorySearchItems = computed<SearchFormItem[]>(() => [
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
||||||
|
const submitLoading = ref(false);
|
||||||
const memoryFormRenderKey = ref(0);
|
const memoryFormRenderKey = ref(0);
|
||||||
|
|
||||||
const memoryDialogFormItems = computed<FormItem[]>(() => [
|
const memoryDialogFormItems = computed<FormItem[]>(() => [
|
||||||
@@ -439,6 +400,21 @@ const dialogVisible = reactive({
|
|||||||
|
|
||||||
const detailFormData = ref<Partial<ChatSessionDetail>>({});
|
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({
|
const rules = reactive({
|
||||||
title: [{ required: true, message: "请输入标题", trigger: "blur" }],
|
title: [{ required: true, message: "请输入标题", trigger: "blur" }],
|
||||||
});
|
});
|
||||||
@@ -469,8 +445,8 @@ async function onResetSearch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function resetForm() {
|
async function resetForm() {
|
||||||
dataFormRef.value?.ref?.resetFields();
|
dataFormRef.value?.resetFields();
|
||||||
dataFormRef.value?.ref?.clearValidate();
|
dataFormRef.value?.clearValidate();
|
||||||
Object.assign(formData, initialFormData);
|
Object.assign(formData, initialFormData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -457,8 +457,8 @@ function resetForm() {
|
|||||||
status: "0",
|
status: "0",
|
||||||
description: "",
|
description: "",
|
||||||
});
|
});
|
||||||
formRef.value?.ref?.resetFields();
|
formRef.value?.resetFields();
|
||||||
formRef.value?.ref?.clearValidate();
|
formRef.value?.clearValidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCloseDialog() {
|
function handleCloseDialog() {
|
||||||
|
|||||||
@@ -38,8 +38,8 @@
|
|||||||
:perm-patch="['module_example:demo:patch']"
|
:perm-patch="['module_example:demo:patch']"
|
||||||
:delete-loading="batchDeleting"
|
:delete-loading="batchDeleting"
|
||||||
@add="openEditDialog('add')"
|
@add="openEditDialog('add')"
|
||||||
@import="openImportModal"
|
@import="openImport"
|
||||||
@export="openExportModal"
|
@export="openExport"
|
||||||
@delete="handleBatchDelete"
|
@delete="handleBatchDelete"
|
||||||
@more="runBatchStatus"
|
@more="runBatchStatus"
|
||||||
/>
|
/>
|
||||||
@@ -64,205 +64,86 @@
|
|||||||
width="920px"
|
width="920px"
|
||||||
dialog-class="crud-embed-dialog"
|
dialog-class="crud-embed-dialog"
|
||||||
modal-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'">
|
<template v-if="dialogVisible.type === 'detail'">
|
||||||
<ElScrollbar max-height="70vh" :view-style="{ overflowX: 'hidden' }">
|
<FaDescriptions
|
||||||
<ElDescriptions :column="4" border>
|
:column="4"
|
||||||
<ElDescriptionsItem label="名称" :span="2">
|
:data="detailFormData"
|
||||||
{{ detailFormData.name }}
|
:items="demoDetailItems"
|
||||||
</ElDescriptionsItem>
|
max-height="70vh"
|
||||||
<ElDescriptionsItem label="UUID" :span="2">
|
>
|
||||||
{{ detailFormData.uuid }}
|
<template #i="{ row }">
|
||||||
</ElDescriptionsItem>
|
<JsonPretty v-if="row?.i != null" :value="row?.i" height="140px" />
|
||||||
<ElDescriptionsItem label="状态" :span="2">
|
</template>
|
||||||
<ElTag :type="detailFormData.status === '0' ? 'success' : 'danger'">
|
</FaDescriptions>
|
||||||
{{ 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>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<ElScrollbar max-height="70vh" :view-style="{ overflowX: 'hidden' }">
|
<FaForm
|
||||||
<ElForm
|
:key="demoFormRenderKey"
|
||||||
ref="dataFormRef"
|
scrollbar
|
||||||
:model="formData"
|
max-height="70vh"
|
||||||
:rules="rules"
|
ref="dataFormRef"
|
||||||
label-suffix=":"
|
v-model="formData"
|
||||||
label-width="100px"
|
:items="demoDialogFormItems"
|
||||||
label-position="right"
|
:rules="rules"
|
||||||
inline
|
label-suffix=":"
|
||||||
>
|
:label-width="'auto'"
|
||||||
<ElFormItem label="名称" prop="name">
|
label-position="right"
|
||||||
<ElInput v-model="formData.name" placeholder="请输入名称" :maxlength="50" />
|
:span="24"
|
||||||
</ElFormItem>
|
:gutter="16"
|
||||||
<ElFormItem label="状态" prop="status">
|
:show-reset="false"
|
||||||
<ElRadioGroup v-model="formData.status">
|
:show-submit="false"
|
||||||
<ElRadio value="0">启用</ElRadio>
|
class="crud-dialog-art-form"
|
||||||
<ElRadio value="1">停用</ElRadio>
|
>
|
||||||
</ElRadioGroup>
|
<template #i>
|
||||||
</ElFormItem>
|
<div class="flex flex-col gap-2">
|
||||||
<ElFormItem label="整数" prop="a">
|
<div
|
||||||
<ElInputNumber v-model="formData.a" placeholder="请输入整数" />
|
v-for="(item, index) in metadataList"
|
||||||
</ElFormItem>
|
:key="index"
|
||||||
<ElFormItem label="大整数" prop="b">
|
class="flex items-center gap-2"
|
||||||
<ElInputNumber v-model="formData.b" placeholder="请输入大整数" />
|
>
|
||||||
</ElFormItem>
|
<ElInput v-model="item.key" placeholder="键" />
|
||||||
<ElFormItem label="浮点数" prop="c">
|
<ElInput v-model="item.value" placeholder="值" />
|
||||||
<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>
|
|
||||||
<ElButton
|
<ElButton
|
||||||
v-if="metadataList.length === 0"
|
|
||||||
type="primary"
|
type="primary"
|
||||||
icon="Plus"
|
icon="Plus"
|
||||||
|
circle
|
||||||
@click="metadataList.push({ key: '', value: '' })"
|
@click="metadataList.push({ key: '', value: '' })"
|
||||||
>
|
/>
|
||||||
添加元数据
|
<ElButton
|
||||||
</ElButton>
|
type="danger"
|
||||||
|
icon="Delete"
|
||||||
|
circle
|
||||||
|
@click="metadataList.splice(index, 1)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</ElFormItem>
|
<ElButton
|
||||||
</ElForm>
|
v-if="metadataList.length === 0"
|
||||||
</ElScrollbar>
|
type="primary"
|
||||||
</template>
|
icon="Plus"
|
||||||
|
@click="metadataList.push({ key: '', value: '' })"
|
||||||
<template #footer>
|
>
|
||||||
<div class="dialog-footer" :style="'padding-right: var(--el-dialog-padding-primary)'">
|
添加元数据
|
||||||
<ElButton @click="handleCloseDialog">取消</ElButton>
|
</ElButton>
|
||||||
<ElButton v-if="dialogVisible.type !== 'detail'" type="primary" @click="handleSubmit">
|
</div>
|
||||||
确定
|
</template>
|
||||||
</ElButton>
|
</FaForm>
|
||||||
<ElButton v-else type="primary" @click="handleCloseDialog">确定</ElButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</FaDialog>
|
</FaDialog>
|
||||||
|
|
||||||
<FaImportDialog
|
<FaImportDialog
|
||||||
v-model="importModalVisible"
|
v-model="importVisible"
|
||||||
:content-config="demoImportContentConfig"
|
:content-config="demoImportContentConfig"
|
||||||
default-template-file-name="demo_import_template.xlsx"
|
default-template-file-name="demo_import_template.xlsx"
|
||||||
@upload="handleCrudImportUpload"
|
@upload="handleCrudImportUpload"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FaExportDialog
|
<FaExportDialog
|
||||||
v-model="exportModalVisible"
|
v-model="exportVisible"
|
||||||
:content-config="demoExportContentConfig"
|
:content-config="demoExportContentConfig"
|
||||||
:query-params="exportQueryParams"
|
:query-params="exportQueryParams"
|
||||||
:page-data="data"
|
:page-data="data"
|
||||||
@@ -272,25 +153,19 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h, computed, ref } from "vue";
|
|
||||||
import { useAuth } from "@/hooks/core/useAuth";
|
import { useAuth } from "@/hooks/core/useAuth";
|
||||||
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
|
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
|
||||||
import { useTable } from "@/hooks/core/useTable";
|
import { useTable } from "@/hooks/core/useTable";
|
||||||
import FaTableHeaderLeft from "@/components/tables/fa-table-header-left/index.vue";
|
import { useImportExport } from "@/hooks/core/useImportExport";
|
||||||
import FaImportDialog from "@/components/modal/fa-import-dialog/index.vue";
|
|
||||||
import FaExportDialog from "@/components/modal/fa-export-dialog/index.vue";
|
|
||||||
import type { IContentConfig, IObject } from "@/components/modal/types";
|
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 type { AuditSearchFormParams } from "@/components/forms/fa-search-bar/auditSearchFormItems";
|
||||||
import FaDialog from "@/components/modal/fa-dialog/index.vue";
|
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||||
import JsonPretty from "@/components/others/fa-json-pretty/index.vue";
|
|
||||||
import type { ColumnOption } from "@/types/component";
|
import type { ColumnOption } from "@/types/component";
|
||||||
import DemoAPI, {
|
import DemoAPI, {
|
||||||
type DemoForm,
|
type DemoForm,
|
||||||
type DemoPageQuery,
|
type DemoPageQuery,
|
||||||
type DemoTable,
|
type DemoTable,
|
||||||
} from "@/api/module_example/demo";
|
} from "@/api/module_example/demo";
|
||||||
import { ElMessage, ElMessageBox, ElTag } from "element-plus";
|
|
||||||
import { ResultEnum } from "@/enums/api/result.enum";
|
import { ResultEnum } from "@/enums/api/result.enum";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@@ -502,6 +377,110 @@ const dialogVisible = reactive({
|
|||||||
|
|
||||||
const detailFormData = ref<DemoTable>({});
|
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>({
|
const formData = ref<DemoForm>({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
name: "",
|
name: "",
|
||||||
@@ -523,11 +502,12 @@ const rules = reactive({
|
|||||||
status: [{ required: true, message: "请选择状态", trigger: "blur" }],
|
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 metadataList = ref<{ key: string; value: string }[]>([]);
|
||||||
|
|
||||||
const importModalVisible = ref(false);
|
const { importVisible, exportVisible, openImport, openExport } = useImportExport();
|
||||||
const exportModalVisible = ref(false);
|
|
||||||
|
|
||||||
const initialFormData: DemoForm = {
|
const initialFormData: DemoForm = {
|
||||||
id: undefined,
|
id: undefined,
|
||||||
@@ -628,6 +608,7 @@ async function openEditDialog(type: "add" | "edit", row?: DemoTable) {
|
|||||||
Object.assign(formData.value, initialFormData);
|
Object.assign(formData.value, initialFormData);
|
||||||
formData.value.id = undefined;
|
formData.value.id = undefined;
|
||||||
metadataList.value = [];
|
metadataList.value = [];
|
||||||
|
demoFormRenderKey.value += 1;
|
||||||
} else if (row?.id) {
|
} else if (row?.id) {
|
||||||
dialogVisible.title = "修改";
|
dialogVisible.title = "修改";
|
||||||
const response = await DemoAPI.getDemoDetail(row.id);
|
const response = await DemoAPI.getDemoDetail(row.id);
|
||||||
@@ -659,7 +640,7 @@ async function handleCloseDialog() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
dataFormRef.value.validate(async (valid: boolean) => {
|
dataFormRef.value?.validate(async (valid: boolean) => {
|
||||||
if (!valid) return;
|
if (!valid) return;
|
||||||
const submitData = { ...formData.value };
|
const submitData = { ...formData.value };
|
||||||
if (metadataList.value.length > 0) {
|
if (metadataList.value.length > 0) {
|
||||||
@@ -757,10 +738,6 @@ async function runBatchStatus(status: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openImportModal() {
|
|
||||||
importModalVisible.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleCrudImportUpload(formData: FormData) {
|
async function handleCrudImportUpload(formData: FormData) {
|
||||||
try {
|
try {
|
||||||
const res = await DemoAPI.importDemo(formData);
|
const res = await DemoAPI.importDemo(formData);
|
||||||
@@ -769,15 +746,11 @@ async function handleCrudImportUpload(formData: FormData) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ElMessage.success(res.data.msg || "导入成功");
|
ElMessage.success(res.data.msg || "导入成功");
|
||||||
importModalVisible.value = false;
|
importVisible.value = false;
|
||||||
await refreshData();
|
await refreshData();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Import]", error);
|
console.error("[Import]", error);
|
||||||
ElMessage.error("导入失败");
|
ElMessage.error("导入失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openExportModal() {
|
|
||||||
exportModalVisible.value = true;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -38,8 +38,8 @@
|
|||||||
:perm-patch="['module_example:demo01:patch']"
|
:perm-patch="['module_example:demo01:patch']"
|
||||||
:delete-loading="batchDeleting"
|
:delete-loading="batchDeleting"
|
||||||
@add="openEditDialog('add')"
|
@add="openEditDialog('add')"
|
||||||
@import="openImportModal"
|
@import="openImport"
|
||||||
@export="openExportModal"
|
@export="openExport"
|
||||||
@delete="handleBatchDelete"
|
@delete="handleBatchDelete"
|
||||||
@more="runBatchStatus"
|
@more="runBatchStatus"
|
||||||
/>
|
/>
|
||||||
@@ -64,87 +64,56 @@
|
|||||||
width="768px"
|
width="768px"
|
||||||
dialog-class="crud-embed-dialog"
|
dialog-class="crud-embed-dialog"
|
||||||
modal-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'">
|
<template v-if="dialogVisible.type === 'detail'">
|
||||||
<ElScrollbar max-height="70vh" :view-style="{ overflowX: 'hidden' }">
|
<FaDescriptions
|
||||||
<ElDescriptions :column="2" border>
|
:column="2"
|
||||||
<ElDescriptionsItem label="名称" :span="2">
|
:data="detailFormData"
|
||||||
{{ detailFormData.name }}
|
:items="demo01DetailItems"
|
||||||
</ElDescriptionsItem>
|
max-height="70vh"
|
||||||
<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>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<ElScrollbar max-height="70vh" :view-style="{ overflowX: 'hidden' }">
|
<FaForm
|
||||||
<FaForm
|
:key="demo01FormRenderKey"
|
||||||
:key="demo01FormRenderKey"
|
scrollbar
|
||||||
ref="dataFormRef"
|
max-height="70vh"
|
||||||
v-model="formData"
|
ref="dataFormRef"
|
||||||
:items="demo01DialogFormItems"
|
v-model="formData"
|
||||||
:rules="rules"
|
:items="demo01DialogFormItems"
|
||||||
label-suffix=":"
|
:rules="rules"
|
||||||
:label-width="'auto'"
|
label-suffix=":"
|
||||||
label-position="right"
|
:label-width="'auto'"
|
||||||
:span="24"
|
label-position="right"
|
||||||
:gutter="16"
|
:span="24"
|
||||||
:show-reset="false"
|
:gutter="16"
|
||||||
:show-submit="false"
|
:show-reset="false"
|
||||||
class="crud-dialog-art-form"
|
:show-submit="false"
|
||||||
>
|
class="crud-dialog-art-form"
|
||||||
<template #status>
|
>
|
||||||
<ElRadioGroup v-model="formData.status">
|
<template #status>
|
||||||
<ElRadio value="0">正常</ElRadio>
|
<ElRadioGroup v-model="formData.status">
|
||||||
<ElRadio value="1">停用</ElRadio>
|
<ElRadio value="0">正常</ElRadio>
|
||||||
</ElRadioGroup>
|
<ElRadio value="1">停用</ElRadio>
|
||||||
</template>
|
</ElRadioGroup>
|
||||||
</FaForm>
|
</template>
|
||||||
</ElScrollbar>
|
</FaForm>
|
||||||
</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>
|
|
||||||
</template>
|
</template>
|
||||||
</FaDialog>
|
</FaDialog>
|
||||||
|
|
||||||
<FaImportDialog
|
<FaImportDialog
|
||||||
v-model="importModalVisible"
|
v-model="importVisible"
|
||||||
:content-config="demo01ImportContentConfig"
|
:content-config="demo01ImportContentConfig"
|
||||||
default-template-file-name="demo01_import_template.xlsx"
|
default-template-file-name="demo01_import_template.xlsx"
|
||||||
@upload="handleCrudImportUpload"
|
@upload="handleCrudImportUpload"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FaExportDialog
|
<FaExportDialog
|
||||||
v-model="exportModalVisible"
|
v-model="exportVisible"
|
||||||
:content-config="demo01ExportContentConfig"
|
:content-config="demo01ExportContentConfig"
|
||||||
:query-params="exportQueryParams"
|
:query-params="exportQueryParams"
|
||||||
:page-data="data"
|
:page-data="data"
|
||||||
@@ -154,18 +123,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h, computed, ref, reactive } from "vue";
|
|
||||||
import { useAuth } from "@/hooks/core/useAuth";
|
import { useAuth } from "@/hooks/core/useAuth";
|
||||||
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
|
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
|
||||||
import { useTable } from "@/hooks/core/useTable";
|
import { useTable } from "@/hooks/core/useTable";
|
||||||
import FaTableHeaderLeft from "@/components/tables/fa-table-header-left/index.vue";
|
import { useImportExport } from "@/hooks/core/useImportExport";
|
||||||
import FaImportDialog from "@/components/modal/fa-import-dialog/index.vue";
|
|
||||||
import FaExportDialog from "@/components/modal/fa-export-dialog/index.vue";
|
|
||||||
import type { IContentConfig, IObject } from "@/components/modal/types";
|
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 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 { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||||
import type { ColumnOption } from "@/types/component";
|
import type { ColumnOption } from "@/types/component";
|
||||||
import Demo01API, {
|
import Demo01API, {
|
||||||
@@ -373,6 +336,24 @@ const dialogVisible = reactive({
|
|||||||
|
|
||||||
const detailFormData = ref<Demo01Table>({});
|
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>({
|
const formData = ref<Demo01Form>({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
name: "",
|
name: "",
|
||||||
@@ -386,6 +367,7 @@ const rules = reactive({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
||||||
|
const submitLoading = ref(false);
|
||||||
const demo01FormRenderKey = ref(0);
|
const demo01FormRenderKey = ref(0);
|
||||||
|
|
||||||
const demo01DialogFormItems = computed<FormItem[]>(() => [
|
const demo01DialogFormItems = computed<FormItem[]>(() => [
|
||||||
@@ -412,8 +394,7 @@ const demo01DialogFormItems = computed<FormItem[]>(() => [
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const importModalVisible = ref(false);
|
const { importVisible, exportVisible, openImport, openExport } = useImportExport();
|
||||||
const exportModalVisible = ref(false);
|
|
||||||
|
|
||||||
const initialFormData: Demo01Form = {
|
const initialFormData: Demo01Form = {
|
||||||
id: undefined,
|
id: undefined,
|
||||||
@@ -516,8 +497,8 @@ async function openEditDialog(type: "add" | "edit", row?: Demo01Table) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function resetForm() {
|
async function resetForm() {
|
||||||
dataFormRef.value?.ref?.resetFields();
|
dataFormRef.value?.resetFields();
|
||||||
dataFormRef.value?.ref?.clearValidate();
|
dataFormRef.value?.clearValidate();
|
||||||
Object.assign(formData.value, initialFormData);
|
Object.assign(formData.value, initialFormData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -605,10 +586,6 @@ async function runBatchStatus(status: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openImportModal() {
|
|
||||||
importModalVisible.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleCrudImportUpload(formDataUpload: FormData) {
|
async function handleCrudImportUpload(formDataUpload: FormData) {
|
||||||
try {
|
try {
|
||||||
const res = await Demo01API.importDemo01(formDataUpload);
|
const res = await Demo01API.importDemo01(formDataUpload);
|
||||||
@@ -617,17 +594,13 @@ async function handleCrudImportUpload(formDataUpload: FormData) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ElMessage.success(res.data.msg || "导入成功");
|
ElMessage.success(res.data.msg || "导入成功");
|
||||||
importModalVisible.value = false;
|
importVisible.value = false;
|
||||||
await refreshData();
|
await refreshData();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Import]", error);
|
console.error("[Import]", error);
|
||||||
ElMessage.error("导入失败");
|
ElMessage.error("导入失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openExportModal() {
|
|
||||||
exportModalVisible.value = true;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -65,7 +65,7 @@
|
|||||||
<div v-show="editMode === 'visual'" class="visual-pane">
|
<div v-show="editMode === 'visual'" class="visual-pane">
|
||||||
<ElScrollbar max-height="min(58vh, 520px)" class="visual-pane-scroll">
|
<ElScrollbar max-height="min(58vh, 520px)" class="visual-pane-scroll">
|
||||||
<div class="visual-structure max-w-3xl">
|
<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>
|
<template #title>
|
||||||
<span class="text-sm font-medium text-[var(--el-text-color-primary)]">选项</span>
|
<span class="text-sm font-medium text-[var(--el-text-color-primary)]">选项</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -81,9 +81,9 @@
|
|||||||
<ElRadioButton value="masterSub">主表 + 子表明细</ElRadioButton>
|
<ElRadioButton value="masterSub">主表 + 子表明细</ElRadioButton>
|
||||||
</ElRadioGroup>
|
</ElRadioGroup>
|
||||||
</ElDescriptionsItem>
|
</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>
|
<template #title>
|
||||||
<span class="text-sm font-medium text-[var(--el-text-color-primary)]">主表</span>
|
<span class="text-sm font-medium text-[var(--el-text-color-primary)]">主表</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -101,14 +101,14 @@
|
|||||||
<ElDescriptionsItem label="说明" label-class-name="visual-desc-label">
|
<ElDescriptionsItem label="说明" label-class-name="visual-desc-label">
|
||||||
<ElInput v-model="visual.mainComment" placeholder="中文说明,可选" clearable />
|
<ElInput v-model="visual.mainComment" placeholder="中文说明,可选" clearable />
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
</ElDescriptions>
|
</FaDescriptions>
|
||||||
|
|
||||||
<ElDescriptions
|
<FaDescriptions
|
||||||
v-if="visual.subEnabled"
|
v-if="visual.subEnabled"
|
||||||
:column="1"
|
:column="1"
|
||||||
border
|
|
||||||
size="small"
|
size="small"
|
||||||
class="visual-desc mt-3"
|
class="visual-desc mt-3"
|
||||||
|
:scrollbar="false"
|
||||||
>
|
>
|
||||||
<template #title>
|
<template #title>
|
||||||
<span class="text-sm font-medium text-[var(--el-text-color-primary)]">子表</span>
|
<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">
|
<ElDescriptionsItem label="对应主表列" label-class-name="visual-desc-label">
|
||||||
<ElInput v-model="visual.fkRefColumn" placeholder="一般是 id" clearable />
|
<ElInput v-model="visual.fkRefColumn" placeholder="一般是 id" clearable />
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
</ElDescriptions>
|
</FaDescriptions>
|
||||||
|
|
||||||
<div class="mt-3">
|
<div class="mt-3">
|
||||||
<ElLink type="primary" underline="never" @click="syncVisualToSql">
|
<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 { ArrowDown, CopyDocument } from "@element-plus/icons-vue";
|
||||||
import { useClipboard } from "@vueuse/core";
|
import { useClipboard } from "@vueuse/core";
|
||||||
import FaDialog from "@/components/modal/fa-dialog/index.vue";
|
import FaDialog from "@/components/modal/fa-dialog/index.vue";
|
||||||
|
import FaDescriptions from "@/components/others/fa-descriptions/index.vue";
|
||||||
import { useSettingsStore } from "@stores";
|
import { useSettingsStore } from "@stores";
|
||||||
import { ThemeMode } from "@/enums/settings/theme.enum";
|
import { ThemeMode } from "@/enums/settings/theme.enum";
|
||||||
import { buildSqlFromVisual } from "../utils/buildCreateTableSql";
|
import { buildSqlFromVisual } from "../utils/buildCreateTableSql";
|
||||||
|
|||||||
+3
-2
@@ -13,7 +13,7 @@
|
|||||||
<span class="font-medium">Redis监控信息</span>
|
<span class="font-medium">Redis监控信息</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<ElDescriptions :column="6" border>
|
<FaDescriptions :column="6" :scrollbar="false">
|
||||||
<ElDescriptionsItem label="Redis版本">
|
<ElDescriptionsItem label="Redis版本">
|
||||||
{{ cache.info?.redis_version || "-" }}
|
{{ cache.info?.redis_version || "-" }}
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
@@ -55,7 +55,7 @@
|
|||||||
{{ cache.info?.instantaneous_input_kbps || 0 }}kps/
|
{{ cache.info?.instantaneous_input_kbps || 0 }}kps/
|
||||||
{{ cache.info?.instantaneous_output_kbps || 0 }}kps
|
{{ cache.info?.instantaneous_output_kbps || 0 }}kps
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
</ElDescriptions>
|
</FaDescriptions>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
</ElRow>
|
</ElRow>
|
||||||
@@ -262,6 +262,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import FaDescriptions from "@/components/others/fa-descriptions/index.vue";
|
||||||
import { ElMessageBox } from "element-plus";
|
import { ElMessageBox } from "element-plus";
|
||||||
import CacheAPI, {
|
import CacheAPI, {
|
||||||
type CacheInfo,
|
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 FaTableHeader from "@/components/tables/fa-table-header/index.vue";
|
||||||
import FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
|
import FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
|
||||||
import type { SearchFormItem } 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 CopyButton from "@/components/others/fa-copy-button/index.vue";
|
||||||
import OnlineAPI, { type OnlineUserTable } from "@/api/module_monitor/online";
|
import OnlineAPI, { type OnlineUserTable } from "@/api/module_monitor/online";
|
||||||
import type { ColumnOption } from "@/types/component";
|
import type { ColumnOption } from "@/types/component";
|
||||||
@@ -235,7 +234,7 @@ const {
|
|||||||
}
|
}
|
||||||
return h(ElTooltip, { content: "强退", placement: "top" }, () =>
|
return h(ElTooltip, { content: "强退", placement: "top" }, () =>
|
||||||
h("span", { class: "inline-flex" }, [
|
h("span", { class: "inline-flex" }, [
|
||||||
h(ArtButtonTable, {
|
h(FaButtonTable, {
|
||||||
type: "delete",
|
type: "delete",
|
||||||
onClick: () => kickSession(row.session_id),
|
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 FaTableHeader from "@/components/tables/fa-table-header/index.vue";
|
||||||
import FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
|
import FaSearchBar from "@/components/forms/fa-search-bar/index.vue";
|
||||||
import type { SearchFormItem } 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 FaDialog from "@/components/modal/fa-dialog/index.vue";
|
||||||
import { ResourceAPI, type ResourceItem } from "@/api/module_monitor/resource";
|
import { ResourceAPI, type ResourceItem } from "@/api/module_monitor/resource";
|
||||||
import type { ColumnOption } from "@/types/component";
|
import type { ColumnOption } from "@/types/component";
|
||||||
@@ -406,7 +405,7 @@ const {
|
|||||||
!row.is_dir && hasAuth("module_monitor:resource:download")
|
!row.is_dir && hasAuth("module_monitor:resource:download")
|
||||||
? h(ElTooltip, { content: "下载", placement: "top" }, () =>
|
? h(ElTooltip, { content: "下载", placement: "top" }, () =>
|
||||||
h("span", { class: "inline-flex" }, [
|
h("span", { class: "inline-flex" }, [
|
||||||
h(ArtButtonTable, {
|
h(FaButtonTable, {
|
||||||
type: "view",
|
type: "view",
|
||||||
icon: "ri:download-line",
|
icon: "ri:download-line",
|
||||||
onClick: () => void handleDownload(row),
|
onClick: () => void handleDownload(row),
|
||||||
@@ -417,7 +416,7 @@ const {
|
|||||||
hasAuth("module_monitor:resource:rename")
|
hasAuth("module_monitor:resource:rename")
|
||||||
? h(ElTooltip, { content: "重命名", placement: "top" }, () =>
|
? h(ElTooltip, { content: "重命名", placement: "top" }, () =>
|
||||||
h("span", { class: "inline-flex" }, [
|
h("span", { class: "inline-flex" }, [
|
||||||
h(ArtButtonTable, {
|
h(FaButtonTable, {
|
||||||
type: "edit",
|
type: "edit",
|
||||||
onClick: () => void handleRenameOpen(row),
|
onClick: () => void handleRenameOpen(row),
|
||||||
}),
|
}),
|
||||||
@@ -427,7 +426,7 @@ const {
|
|||||||
hasAuth("module_monitor:resource:delete")
|
hasAuth("module_monitor:resource:delete")
|
||||||
? h(ElTooltip, { content: "删除", placement: "top" }, () =>
|
? h(ElTooltip, { content: "删除", placement: "top" }, () =>
|
||||||
h("span", { class: "inline-flex" }, [
|
h("span", { class: "inline-flex" }, [
|
||||||
h(ArtButtonTable, {
|
h(FaButtonTable, {
|
||||||
type: "delete",
|
type: "delete",
|
||||||
onClick: () => void handleDelete(row),
|
onClick: () => void handleDelete(row),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
>
|
>
|
||||||
<span class="text-sm">{{ (server.cpu?.used || 0).toFixed(1) }}%</span>
|
<span class="text-sm">{{ (server.cpu?.used || 0).toFixed(1) }}%</span>
|
||||||
</ElProgress>
|
</ElProgress>
|
||||||
<ElDescriptions :column="2" border size="small">
|
<FaDescriptions :column="2" size="small" :scrollbar="false">
|
||||||
<ElDescriptionsItem label="核心数">
|
<ElDescriptionsItem label="核心数">
|
||||||
{{ server.cpu?.cpu_num || 0 }}
|
{{ server.cpu?.cpu_num || 0 }}
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
<ElDescriptionsItem label="系统使用率">
|
<ElDescriptionsItem label="系统使用率">
|
||||||
{{ (server.cpu?.sys || 0).toFixed(1) }}%
|
{{ (server.cpu?.sys || 0).toFixed(1) }}%
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
</ElDescriptions>
|
</FaDescriptions>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
>
|
>
|
||||||
<span class="text-sm">{{ (server.mem?.usage || 0).toFixed(1) }}%</span>
|
<span class="text-sm">{{ (server.mem?.usage || 0).toFixed(1) }}%</span>
|
||||||
</ElProgress>
|
</ElProgress>
|
||||||
<ElDescriptions :column="2" border size="small">
|
<FaDescriptions :column="2" size="small" :scrollbar="false">
|
||||||
<ElDescriptionsItem label="总内存">
|
<ElDescriptionsItem label="总内存">
|
||||||
{{ server.mem?.total }}
|
{{ server.mem?.total }}
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
<ElDescriptionsItem label="Python内存">
|
<ElDescriptionsItem label="Python内存">
|
||||||
{{ server.py?.memory_usage ? server.py.memory_usage.toFixed(1) + "%" : "-" }}
|
{{ server.py?.memory_usage ? server.py.memory_usage.toFixed(1) + "%" : "-" }}
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
</ElDescriptions>
|
</FaDescriptions>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
<span class="font-medium">服务器基本信息</span>
|
<span class="font-medium">服务器基本信息</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<ElDescriptions :column="2" border size="small">
|
<FaDescriptions :column="2" size="small" :scrollbar="false">
|
||||||
<ElDescriptionsItem label="服务器名称">
|
<ElDescriptionsItem label="服务器名称">
|
||||||
{{ server.sys?.computer_name || "-" }}
|
{{ server.sys?.computer_name || "-" }}
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
<ElDescriptionsItem label="系统架构">
|
<ElDescriptionsItem label="系统架构">
|
||||||
{{ server.sys?.os_arch || "-" }}
|
{{ server.sys?.os_arch || "-" }}
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
</ElDescriptions>
|
</FaDescriptions>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@
|
|||||||
<span class="font-medium">Python运行环境</span>
|
<span class="font-medium">Python运行环境</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<ElDescriptions :column="2" border size="small">
|
<FaDescriptions :column="2" size="small" :scrollbar="false">
|
||||||
<ElDescriptionsItem label="Python名称">
|
<ElDescriptionsItem label="Python名称">
|
||||||
{{ server.py?.name || "-" }}
|
{{ server.py?.name || "-" }}
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
@@ -143,7 +143,7 @@
|
|||||||
<ElDescriptionsItem label="项目路径" :span="2">
|
<ElDescriptionsItem label="项目路径" :span="2">
|
||||||
{{ server.sys?.user_dir || "-" }}
|
{{ server.sys?.user_dir || "-" }}
|
||||||
</ElDescriptionsItem>
|
</ElDescriptionsItem>
|
||||||
</ElDescriptions>
|
</FaDescriptions>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
</ElCol>
|
</ElCol>
|
||||||
</ElRow>
|
</ElRow>
|
||||||
@@ -186,6 +186,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import FaDescriptions from "@/components/others/fa-descriptions/index.vue";
|
||||||
import ServerAPI, { type ServerInfo } from "@/api/module_monitor/server";
|
import ServerAPI, { type ServerInfo } from "@/api/module_monitor/server";
|
||||||
|
|
||||||
defineOptions({ name: "ServerMonitor" });
|
defineOptions({ name: "ServerMonitor" });
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!-- 部门配置:Art + 树形表格(对齐 system/menu 模版) -->
|
<!-- 部门配置:FA + 树形表格(对齐 system/menu 模版) -->
|
||||||
<template>
|
<template>
|
||||||
<div class="fa-full-height">
|
<div class="fa-full-height">
|
||||||
<FaSearchBar
|
<FaSearchBar
|
||||||
@@ -63,97 +63,58 @@
|
|||||||
width="640px"
|
width="640px"
|
||||||
dialog-class="crud-embed-dialog"
|
dialog-class="crud-embed-dialog"
|
||||||
modal-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'">
|
<template v-if="dialogVisible.type === 'detail'">
|
||||||
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
|
<FaDescriptions
|
||||||
<ElDescriptions :column="4" border>
|
:column="4"
|
||||||
<ElDescriptionsItem label="部门名称" :span="2">
|
:data="detailFormData"
|
||||||
{{ detailFormData.name }}
|
:items="deptDetailItems"
|
||||||
</ElDescriptionsItem>
|
max-height="75vh"
|
||||||
<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>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
|
<FaForm
|
||||||
<FaForm
|
:key="deptFormRenderKey"
|
||||||
:key="deptFormRenderKey"
|
scrollbar
|
||||||
ref="dataFormRef"
|
max-height="75vh"
|
||||||
v-model="formData"
|
ref="dataFormRef"
|
||||||
:items="deptDialogFormItems"
|
v-model="formData"
|
||||||
:rules="rules"
|
:items="deptDialogFormItems"
|
||||||
label-suffix=":"
|
:rules="rules"
|
||||||
:label-width="'auto'"
|
label-suffix=":"
|
||||||
label-position="right"
|
:label-width="'auto'"
|
||||||
:span="24"
|
label-position="right"
|
||||||
:gutter="16"
|
:span="24"
|
||||||
:show-reset="false"
|
:gutter="16"
|
||||||
:show-submit="false"
|
:show-reset="false"
|
||||||
class="crud-dialog-art-form"
|
:show-submit="false"
|
||||||
>
|
class="crud-dialog-art-form"
|
||||||
<template #status>
|
>
|
||||||
<ElRadioGroup v-model="formData.status">
|
<template #status>
|
||||||
<ElRadio value="0">启用</ElRadio>
|
<ElRadioGroup v-model="formData.status">
|
||||||
<ElRadio value="1">停用</ElRadio>
|
<ElRadio value="0">启用</ElRadio>
|
||||||
</ElRadioGroup>
|
<ElRadio value="1">停用</ElRadio>
|
||||||
</template>
|
</ElRadioGroup>
|
||||||
</FaForm>
|
</template>
|
||||||
</ElScrollbar>
|
</FaForm>
|
||||||
</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>
|
|
||||||
</template>
|
</template>
|
||||||
</FaDialog>
|
</FaDialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h, ref, reactive, computed, nextTick, onMounted } from "vue";
|
|
||||||
import { useTableColumns } from "@/hooks/core/useTableColumns";
|
import { useTableColumns } from "@/hooks/core/useTableColumns";
|
||||||
import DeptAPI, {
|
import DeptAPI, {
|
||||||
type DeptForm,
|
type DeptForm,
|
||||||
type DeptPageQuery,
|
type DeptPageQuery,
|
||||||
type DeptTable,
|
type DeptTable,
|
||||||
} from "@/api/module_system/dept";
|
} 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 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 { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||||
import { ElMessage, ElMessageBox, ElTag } from "element-plus";
|
|
||||||
import { useAuth } from "@/hooks/core/useAuth";
|
import { useAuth } from "@/hooks/core/useAuth";
|
||||||
import { useUserStore } from "@stores";
|
import { useUserStore } from "@stores";
|
||||||
import { formatTree } from "@utils/common";
|
import { formatTree } from "@utils/common";
|
||||||
@@ -368,6 +329,24 @@ const { columnChecks, columns } = useTableColumns<DeptTable>(() => [
|
|||||||
|
|
||||||
const detailFormData = ref<DeptTable>({ code: "" });
|
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>({
|
const formData = ref<DeptForm>({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
@@ -411,6 +390,7 @@ const initialFormData: DeptForm = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
||||||
|
const submitLoading = ref(false);
|
||||||
const deptFormRenderKey = ref(0);
|
const deptFormRenderKey = ref(0);
|
||||||
|
|
||||||
const deptDialogFormItems = computed<FormItem[]>(() => [
|
const deptDialogFormItems = computed<FormItem[]>(() => [
|
||||||
@@ -494,8 +474,8 @@ function onResetSearch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function resetForm() {
|
async function resetForm() {
|
||||||
dataFormRef.value?.ref?.resetFields();
|
dataFormRef.value?.resetFields();
|
||||||
dataFormRef.value?.ref?.clearValidate();
|
dataFormRef.value?.clearValidate();
|
||||||
Object.assign(formData, initialFormData);
|
Object.assign(formData, initialFormData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
:perm-patch="['module_system:dict_data:patch']"
|
:perm-patch="['module_system:dict_data:patch']"
|
||||||
:delete-loading="batchDeleting"
|
:delete-loading="batchDeleting"
|
||||||
@add="handleOpenDialog('create')"
|
@add="handleOpenDialog('create')"
|
||||||
@export="openExportModal"
|
@export="openExport"
|
||||||
@delete="handleBatchDelete"
|
@delete="handleBatchDelete"
|
||||||
@more="handleMoreClick"
|
@more="handleMoreClick"
|
||||||
/>
|
/>
|
||||||
@@ -67,167 +67,125 @@
|
|||||||
width="720px"
|
width="720px"
|
||||||
dialog-class="crud-embed-dialog"
|
dialog-class="crud-embed-dialog"
|
||||||
modal-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'">
|
<template v-if="dialogVisible.type === 'detail'">
|
||||||
<ElScrollbar max-height="70vh" :view-style="{ overflowX: 'hidden' }">
|
<FaDescriptions
|
||||||
<ElDescriptions :column="2" border>
|
:column="2"
|
||||||
<ElDescriptionsItem label="数据标签" :span="2">
|
:data="detailFormData"
|
||||||
{{ detailFormData.dict_label }}
|
:items="dictDataDetailItems"
|
||||||
</ElDescriptionsItem>
|
max-height="70vh"
|
||||||
<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>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<ElScrollbar max-height="70vh" :view-style="{ overflowX: 'hidden' }">
|
<FaForm
|
||||||
<FaForm
|
:key="dictDataFormRenderKey"
|
||||||
:key="dictDataFormRenderKey"
|
scrollbar
|
||||||
ref="dataFormRef"
|
max-height="70vh"
|
||||||
v-model="formData"
|
ref="dataFormRef"
|
||||||
:items="dictDataDialogFormItems"
|
v-model="formData"
|
||||||
:rules="rules"
|
:items="dictDataDialogFormItems"
|
||||||
label-suffix=":"
|
:rules="rules"
|
||||||
:label-width="'auto'"
|
label-suffix=":"
|
||||||
label-position="right"
|
:label-width="'auto'"
|
||||||
:span="24"
|
label-position="right"
|
||||||
:gutter="16"
|
:span="24"
|
||||||
:show-reset="false"
|
:gutter="16"
|
||||||
:show-submit="false"
|
:show-reset="false"
|
||||||
class="crud-dialog-art-form"
|
:show-submit="false"
|
||||||
>
|
class="crud-dialog-art-form"
|
||||||
<template #css_class>
|
>
|
||||||
<ElSelect
|
<template #css_class>
|
||||||
v-model="formData.css_class"
|
<ElSelect
|
||||||
placeholder="请选择常用颜色或输入自定义"
|
v-model="formData.css_class"
|
||||||
clearable
|
placeholder="请选择常用颜色或输入自定义"
|
||||||
filterable
|
clearable
|
||||||
allow-create
|
filterable
|
||||||
default-first-option
|
allow-create
|
||||||
>
|
default-first-option
|
||||||
<ElOption value="primary" label="主要(primary)">
|
>
|
||||||
<span class="tag-option-preview" :style="getTagPreviewStyle('primary')">
|
<ElOption value="primary" label="主要(primary)">
|
||||||
主要(primary)
|
<span class="tag-option-preview" :style="getTagPreviewStyle('primary')">
|
||||||
</span>
|
主要(primary)
|
||||||
</ElOption>
|
</span>
|
||||||
<ElOption value="success" label="成功(success)">
|
</ElOption>
|
||||||
<span class="tag-option-preview" :style="getTagPreviewStyle('success')">
|
<ElOption value="success" label="成功(success)">
|
||||||
成功(success)
|
<span class="tag-option-preview" :style="getTagPreviewStyle('success')">
|
||||||
</span>
|
成功(success)
|
||||||
</ElOption>
|
</span>
|
||||||
<ElOption value="warning" label="警告(warning)">
|
</ElOption>
|
||||||
<span class="tag-option-preview" :style="getTagPreviewStyle('warning')">
|
<ElOption value="warning" label="警告(warning)">
|
||||||
警告(warning)
|
<span class="tag-option-preview" :style="getTagPreviewStyle('warning')">
|
||||||
</span>
|
警告(warning)
|
||||||
</ElOption>
|
</span>
|
||||||
<ElOption value="danger" label="危险(danger)">
|
</ElOption>
|
||||||
<span class="tag-option-preview" :style="getTagPreviewStyle('danger')">
|
<ElOption value="danger" label="危险(danger)">
|
||||||
危险(danger)
|
<span class="tag-option-preview" :style="getTagPreviewStyle('danger')">
|
||||||
</span>
|
危险(danger)
|
||||||
</ElOption>
|
</span>
|
||||||
<ElOption value="info" label="信息(info)">
|
</ElOption>
|
||||||
<span class="tag-option-preview" :style="getTagPreviewStyle('info')">
|
<ElOption value="info" label="信息(info)">
|
||||||
信息(info)
|
<span class="tag-option-preview" :style="getTagPreviewStyle('info')">
|
||||||
</span>
|
信息(info)
|
||||||
</ElOption>
|
</span>
|
||||||
</ElSelect>
|
</ElOption>
|
||||||
</template>
|
</ElSelect>
|
||||||
<template #list_class>
|
</template>
|
||||||
<ElSelect v-model="formData.list_class" placeholder="请选择列表类样式" clearable>
|
<template #list_class>
|
||||||
<ElOption value="default" label="默认(default)">
|
<ElSelect v-model="formData.list_class" placeholder="请选择列表类样式" clearable>
|
||||||
<span class="tag-option-preview tag-option-preview--default">
|
<ElOption value="default" label="默认(default)">
|
||||||
默认(default)
|
<span class="tag-option-preview tag-option-preview--default">默认(default)</span>
|
||||||
</span>
|
</ElOption>
|
||||||
</ElOption>
|
<ElOption value="primary" label="主要(primary)">
|
||||||
<ElOption value="primary" label="主要(primary)">
|
<span class="tag-option-preview" :style="getTagPreviewStyle('primary')">
|
||||||
<span class="tag-option-preview" :style="getTagPreviewStyle('primary')">
|
主要(primary)
|
||||||
主要(primary)
|
</span>
|
||||||
</span>
|
</ElOption>
|
||||||
</ElOption>
|
<ElOption value="success" label="成功(success)">
|
||||||
<ElOption value="success" label="成功(success)">
|
<span class="tag-option-preview" :style="getTagPreviewStyle('success')">
|
||||||
<span class="tag-option-preview" :style="getTagPreviewStyle('success')">
|
成功(success)
|
||||||
成功(success)
|
</span>
|
||||||
</span>
|
</ElOption>
|
||||||
</ElOption>
|
<ElOption value="warning" label="警告(warning)">
|
||||||
<ElOption value="warning" label="警告(warning)">
|
<span class="tag-option-preview" :style="getTagPreviewStyle('warning')">
|
||||||
<span class="tag-option-preview" :style="getTagPreviewStyle('warning')">
|
警告(warning)
|
||||||
警告(warning)
|
</span>
|
||||||
</span>
|
</ElOption>
|
||||||
</ElOption>
|
<ElOption value="danger" label="危险(danger)">
|
||||||
<ElOption value="danger" label="危险(danger)">
|
<span class="tag-option-preview" :style="getTagPreviewStyle('danger')">
|
||||||
<span class="tag-option-preview" :style="getTagPreviewStyle('danger')">
|
危险(danger)
|
||||||
危险(danger)
|
</span>
|
||||||
</span>
|
</ElOption>
|
||||||
</ElOption>
|
<ElOption value="info" label="信息(info)">
|
||||||
<ElOption value="info" label="信息(info)">
|
<span class="tag-option-preview" :style="getTagPreviewStyle('info')">
|
||||||
<span class="tag-option-preview" :style="getTagPreviewStyle('info')">
|
信息(info)
|
||||||
信息(info)
|
</span>
|
||||||
</span>
|
</ElOption>
|
||||||
</ElOption>
|
</ElSelect>
|
||||||
</ElSelect>
|
</template>
|
||||||
</template>
|
<template #is_default>
|
||||||
<template #is_default>
|
<ElRadioGroup v-model="formData.is_default">
|
||||||
<ElRadioGroup v-model="formData.is_default">
|
<ElRadio :value="true">是</ElRadio>
|
||||||
<ElRadio :value="true">是</ElRadio>
|
<ElRadio :value="false">否</ElRadio>
|
||||||
<ElRadio :value="false">否</ElRadio>
|
</ElRadioGroup>
|
||||||
</ElRadioGroup>
|
</template>
|
||||||
</template>
|
<template #status>
|
||||||
<template #status>
|
<ElSwitch
|
||||||
<ElSwitch
|
v-model="formData.status"
|
||||||
v-model="formData.status"
|
inline-prompt
|
||||||
inline-prompt
|
:active-value="'0'"
|
||||||
:active-value="'0'"
|
:inactive-value="'1'"
|
||||||
:inactive-value="'1'"
|
/>
|
||||||
/>
|
</template>
|
||||||
</template>
|
</FaForm>
|
||||||
</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>
|
|
||||||
</template>
|
</template>
|
||||||
</FaDialog>
|
</FaDialog>
|
||||||
|
|
||||||
<FaExportDialog
|
<FaExportDialog
|
||||||
v-model="exportModalVisible"
|
v-model="exportVisible"
|
||||||
:content-config="dictDataExportContentConfig"
|
:content-config="dictDataExportContentConfig"
|
||||||
:query-params="exportQueryParams"
|
:query-params="exportQueryParams"
|
||||||
:page-data="data"
|
:page-data="data"
|
||||||
@@ -238,16 +196,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h, computed, ref, reactive } from "vue";
|
|
||||||
import { useTable } from "@/hooks/core/useTable";
|
import { useTable } from "@/hooks/core/useTable";
|
||||||
import FaTableHeaderLeft from "@/components/tables/fa-table-header-left/index.vue";
|
import { useImportExport } from "@/hooks/core/useImportExport";
|
||||||
import FaExportDialog from "@/components/modal/fa-export-dialog/index.vue";
|
|
||||||
import type { IObject } from "@/components/modal/types";
|
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 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 { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||||
import type { ColumnOption } from "@/types/component";
|
import type { ColumnOption } from "@/types/component";
|
||||||
import DictAPI, {
|
import DictAPI, {
|
||||||
@@ -255,7 +207,6 @@ import DictAPI, {
|
|||||||
type DictDataPageQuery,
|
type DictDataPageQuery,
|
||||||
type DictDataTable,
|
type DictDataTable,
|
||||||
} from "@/api/module_system/dict";
|
} from "@/api/module_system/dict";
|
||||||
import { ElMessage, ElMessageBox, ElTag } from "element-plus";
|
|
||||||
import { useAuth } from "@/hooks/core/useAuth";
|
import { useAuth } from "@/hooks/core/useAuth";
|
||||||
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
|
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
|
||||||
import { useDictStore } from "@stores";
|
import { useDictStore } from "@stores";
|
||||||
@@ -542,6 +493,33 @@ const dialogVisible = reactive({
|
|||||||
|
|
||||||
const detailFormData = ref<DictDataTable>({});
|
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>({
|
const formData = ref<DictDataForm>({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
dict_sort: 1,
|
dict_sort: 1,
|
||||||
@@ -566,6 +544,7 @@ const rules = reactive({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
||||||
|
const submitLoading = ref(false);
|
||||||
const dictDataFormRenderKey = ref(0);
|
const dictDataFormRenderKey = ref(0);
|
||||||
|
|
||||||
const dictDataDialogFormItems = computed<FormItem[]>(() => [
|
const dictDataDialogFormItems = computed<FormItem[]>(() => [
|
||||||
@@ -662,7 +641,7 @@ const initialFormData: DictDataForm = {
|
|||||||
dict_type_id: props.dictTypeId,
|
dict_type_id: props.dictTypeId,
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportModalVisible = ref(false);
|
const { exportVisible, openExport } = useImportExport();
|
||||||
|
|
||||||
async function handleSearchBarSearch(params: DictDataSearchForm) {
|
async function handleSearchBarSearch(params: DictDataSearchForm) {
|
||||||
await searchBarRef.value?.validate?.();
|
await searchBarRef.value?.validate?.();
|
||||||
@@ -689,8 +668,8 @@ function onResetSearch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function resetForm() {
|
async function resetForm() {
|
||||||
dataFormRef.value?.ref?.resetFields();
|
dataFormRef.value?.resetFields();
|
||||||
dataFormRef.value?.ref?.clearValidate();
|
dataFormRef.value?.clearValidate();
|
||||||
Object.assign(formData, {
|
Object.assign(formData, {
|
||||||
...initialFormData,
|
...initialFormData,
|
||||||
dict_type_id: props.dictTypeId,
|
dict_type_id: props.dictTypeId,
|
||||||
@@ -848,10 +827,6 @@ async function handleMoreClick(status: string) {
|
|||||||
// 用户取消
|
// 用户取消
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openExportModal() {
|
|
||||||
exportModalVisible.value = true;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
:perm-patch="['module_system:dict_type:patch']"
|
:perm-patch="['module_system:dict_type:patch']"
|
||||||
:delete-loading="batchDeleting"
|
:delete-loading="batchDeleting"
|
||||||
@add="handleOpenDialog('create')"
|
@add="handleOpenDialog('create')"
|
||||||
@export="openExportModal"
|
@export="openExport"
|
||||||
@delete="handleBatchDelete"
|
@delete="handleBatchDelete"
|
||||||
@more="handleMoreClick"
|
@more="handleMoreClick"
|
||||||
/>
|
/>
|
||||||
@@ -62,73 +62,53 @@
|
|||||||
width="640px"
|
width="640px"
|
||||||
dialog-class="crud-embed-dialog"
|
dialog-class="crud-embed-dialog"
|
||||||
modal-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'">
|
<template v-if="dialogVisible.type === 'detail'">
|
||||||
<ElScrollbar max-height="70vh" :view-style="{ overflowX: 'hidden' }">
|
<FaDescriptions
|
||||||
<ElDescriptions :column="2" border>
|
:column="2"
|
||||||
<ElDescriptionsItem label="字典名称" :span="2">
|
:data="detailFormData"
|
||||||
{{ detailFormData.dict_name }}
|
:items="dictDetailItems"
|
||||||
</ElDescriptionsItem>
|
max-height="70vh"
|
||||||
<ElDescriptionsItem label="字典类型" :span="2">
|
>
|
||||||
<ElTag type="primary">{{ detailFormData.dict_type }}</ElTag>
|
<template #dict_type="{ row }">
|
||||||
</ElDescriptionsItem>
|
<ElTag type="primary">{{ row?.dict_type }}</ElTag>
|
||||||
<ElDescriptionsItem label="状态" :span="2">
|
</template>
|
||||||
<ElTag v-if="detailFormData.status === '0'" type="success">启用</ElTag>
|
</FaDescriptions>
|
||||||
<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>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<ElScrollbar max-height="70vh" :view-style="{ overflowX: 'hidden' }">
|
<FaForm
|
||||||
<FaForm
|
:key="dictFormRenderKey"
|
||||||
:key="dictFormRenderKey"
|
scrollbar
|
||||||
ref="dataFormRef"
|
max-height="70vh"
|
||||||
v-model="formData"
|
ref="dataFormRef"
|
||||||
:items="dictDialogFormItems"
|
v-model="formData"
|
||||||
:rules="rules"
|
:items="dictDialogFormItems"
|
||||||
label-suffix=":"
|
:rules="rules"
|
||||||
:label-width="'auto'"
|
label-suffix=":"
|
||||||
label-position="right"
|
:label-width="'auto'"
|
||||||
:span="24"
|
label-position="right"
|
||||||
:gutter="16"
|
:span="24"
|
||||||
:show-reset="false"
|
:gutter="16"
|
||||||
:show-submit="false"
|
:show-reset="false"
|
||||||
class="crud-dialog-art-form"
|
:show-submit="false"
|
||||||
>
|
class="crud-dialog-art-form"
|
||||||
<template #status>
|
>
|
||||||
<ElRadioGroup v-model="formData.status">
|
<template #status>
|
||||||
<ElRadio value="0">启用</ElRadio>
|
<ElRadioGroup v-model="formData.status">
|
||||||
<ElRadio value="1">停用</ElRadio>
|
<ElRadio value="0">启用</ElRadio>
|
||||||
</ElRadioGroup>
|
<ElRadio value="1">停用</ElRadio>
|
||||||
</template>
|
</ElRadioGroup>
|
||||||
</FaForm>
|
</template>
|
||||||
</ElScrollbar>
|
</FaForm>
|
||||||
</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>
|
|
||||||
</template>
|
</template>
|
||||||
</FaDialog>
|
</FaDialog>
|
||||||
|
|
||||||
<FaExportDialog
|
<FaExportDialog
|
||||||
v-model="exportModalVisible"
|
v-model="exportVisible"
|
||||||
:content-config="dictTypeExportContentConfig"
|
:content-config="dictTypeExportContentConfig"
|
||||||
:query-params="exportQueryParams"
|
:query-params="exportQueryParams"
|
||||||
:page-data="data"
|
:page-data="data"
|
||||||
@@ -146,15 +126,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h, computed, ref, reactive } from "vue";
|
|
||||||
import { useTable } from "@/hooks/core/useTable";
|
import { useTable } from "@/hooks/core/useTable";
|
||||||
import FaTableHeaderLeft from "@/components/tables/fa-table-header-left/index.vue";
|
import { useImportExport } from "@/hooks/core/useImportExport";
|
||||||
import FaExportDialog from "@/components/modal/fa-export-dialog/index.vue";
|
|
||||||
import type { IObject } from "@/components/modal/types";
|
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 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 { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||||
import type { ColumnOption } from "@/types/component";
|
import type { ColumnOption } from "@/types/component";
|
||||||
import DictAPI, {
|
import DictAPI, {
|
||||||
@@ -162,7 +137,6 @@ import DictAPI, {
|
|||||||
type DictPageQuery,
|
type DictPageQuery,
|
||||||
type DictTable,
|
type DictTable,
|
||||||
} from "@/api/module_system/dict";
|
} from "@/api/module_system/dict";
|
||||||
import { ElMessage, ElMessageBox, ElTag } from "element-plus";
|
|
||||||
import { useDictStore } from "@stores";
|
import { useDictStore } from "@stores";
|
||||||
import DataDrawer from "@views/module_system/dict/components/DataDrawer.vue";
|
import DataDrawer from "@views/module_system/dict/components/DataDrawer.vue";
|
||||||
import { useAuth } from "@/hooks/core/useAuth";
|
import { useAuth } from "@/hooks/core/useAuth";
|
||||||
@@ -364,6 +338,22 @@ const dialogVisible = reactive({
|
|||||||
|
|
||||||
const detailFormData = ref<DictTable>({});
|
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>({
|
const formData = ref<DictForm>({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
dict_name: "",
|
dict_name: "",
|
||||||
@@ -379,6 +369,7 @@ const rules = reactive({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
||||||
|
const submitLoading = ref(false);
|
||||||
const dictFormRenderKey = ref(0);
|
const dictFormRenderKey = ref(0);
|
||||||
|
|
||||||
const dictDialogFormItems = computed<FormItem[]>(() => [
|
const dictDialogFormItems = computed<FormItem[]>(() => [
|
||||||
@@ -426,7 +417,7 @@ const initialFormData: DictForm = {
|
|||||||
description: undefined,
|
description: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportModalVisible = ref(false);
|
const { exportVisible, openExport } = useImportExport();
|
||||||
|
|
||||||
const drawerVisible = ref(false);
|
const drawerVisible = ref(false);
|
||||||
const currentDictType = ref("");
|
const currentDictType = ref("");
|
||||||
@@ -511,8 +502,8 @@ function handleDictDataDrawer(dictTypeRow: DictTable) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function resetForm() {
|
async function resetForm() {
|
||||||
dataFormRef.value?.ref?.resetFields();
|
dataFormRef.value?.resetFields();
|
||||||
dataFormRef.value?.ref?.clearValidate();
|
dataFormRef.value?.clearValidate();
|
||||||
Object.assign(formData, initialFormData);
|
Object.assign(formData, initialFormData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -629,10 +620,6 @@ async function handleMoreClick(status: string) {
|
|||||||
// 用户取消
|
// 用户取消
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openExportModal() {
|
|
||||||
exportModalVisible.value = true;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
:perm-export="['module_system:log:export']"
|
:perm-export="['module_system:log:export']"
|
||||||
:perm-delete="['module_system:log:delete']"
|
:perm-delete="['module_system:log:delete']"
|
||||||
:delete-loading="batchDeleting"
|
:delete-loading="batchDeleting"
|
||||||
@export="openExportModal"
|
@export="openExport"
|
||||||
@delete="handleBatchDelete"
|
@delete="handleBatchDelete"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -67,77 +67,42 @@
|
|||||||
width="960px"
|
width="960px"
|
||||||
dialog-class="crud-embed-dialog"
|
dialog-class="crud-embed-dialog"
|
||||||
modal-class="crud-embed-dialog"
|
modal-class="crud-embed-dialog"
|
||||||
@close="handleCloseDialog"
|
form-mode="detail"
|
||||||
|
@confirm="handleCloseDialog"
|
||||||
>
|
>
|
||||||
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
|
<FaDescriptions
|
||||||
<ElDescriptions :column="8" border label-width="200px">
|
:column="8"
|
||||||
<ElDescriptionsItem label="日志类型" :span="2">
|
:data="formData"
|
||||||
<ElTag :type="formData.type === 1 ? 'success' : 'primary'">
|
:items="logDetailItems"
|
||||||
{{ formData.type === 1 ? "登录日志" : "操作日志" }}
|
label-width="200px"
|
||||||
</ElTag>
|
max-height="75vh"
|
||||||
</ElDescriptionsItem>
|
>
|
||||||
<ElDescriptionsItem label="请求路径" :span="2">
|
<template #type="{ row }">
|
||||||
{{ formData.request_path }}
|
<ElTag :type="row?.type === 1 ? 'success' : 'primary'">
|
||||||
</ElDescriptionsItem>
|
{{ row?.type === 1 ? "登录日志" : "操作日志" }}
|
||||||
<ElDescriptionsItem label="请求方法" :span="2">
|
</ElTag>
|
||||||
<ElTag :type="getMethodType(formData.request_method)">
|
</template>
|
||||||
{{ formData.request_method }}
|
<template #request_method="{ row }">
|
||||||
</ElTag>
|
<ElTag :type="getMethodType(row?.request_method as string)">
|
||||||
</ElDescriptionsItem>
|
{{ row?.request_method }}
|
||||||
<ElDescriptionsItem label="响应状态码" :span="2">
|
</ElTag>
|
||||||
<ElTag :type="getStatusCodeType(formData.response_code)">
|
</template>
|
||||||
{{ formData.response_code }}
|
<template #response_code="{ row }">
|
||||||
</ElTag>
|
<ElTag :type="getStatusCodeType(row?.response_code as number)">
|
||||||
</ElDescriptionsItem>
|
{{ row?.response_code }}
|
||||||
<ElDescriptionsItem label="请求IP" :span="2">
|
</ElTag>
|
||||||
{{ formData.request_ip }}
|
</template>
|
||||||
</ElDescriptionsItem>
|
<template #request_payload="{ row }">
|
||||||
<ElDescriptionsItem label="处理时间" :span="2">
|
<JsonPretty :value="row?.request_payload as any" height="80px" />
|
||||||
{{ formData.process_time }}
|
</template>
|
||||||
</ElDescriptionsItem>
|
<template #response_json="{ row }">
|
||||||
<ElDescriptionsItem label="浏览器" :span="2">
|
<JsonPretty :value="row?.response_json as any" height="140px" />
|
||||||
{{ formData.request_browser }}
|
</template>
|
||||||
</ElDescriptionsItem>
|
</FaDescriptions>
|
||||||
<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>
|
|
||||||
</FaDialog>
|
</FaDialog>
|
||||||
|
|
||||||
<FaExportDialog
|
<FaExportDialog
|
||||||
v-model="exportModalVisible"
|
v-model="exportVisible"
|
||||||
:content-config="logExportContentConfig"
|
:content-config="logExportContentConfig"
|
||||||
:query-params="exportQueryParams"
|
:query-params="exportQueryParams"
|
||||||
:page-data="data"
|
:page-data="data"
|
||||||
@@ -147,21 +112,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h, computed, ref, nextTick } from "vue";
|
|
||||||
import { useTable } from "@/hooks/core/useTable";
|
import { useTable } from "@/hooks/core/useTable";
|
||||||
import FaTable from "@/components/tables/fa-table/index.vue";
|
import { useImportExport } from "@/hooks/core/useImportExport";
|
||||||
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 type { IObject } from "@/components/modal/types";
|
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 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 type { ColumnOption } from "@/types/component";
|
||||||
import LogAPI, { type LogPageQuery, type LogTable } from "@/api/module_system/log";
|
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 { useAuth } from "@/hooks/core/useAuth";
|
||||||
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
|
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
|
||||||
import UserTableSelect from "@views/module_system/user/components/UserTableSelect.vue";
|
import UserTableSelect from "@views/module_system/user/components/UserTableSelect.vue";
|
||||||
@@ -400,6 +356,25 @@ const logExportContentConfig = computed(() => ({
|
|||||||
|
|
||||||
const formData = ref<LogTable>({});
|
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({
|
const dialogVisible = ref({
|
||||||
title: "",
|
title: "",
|
||||||
visible: false,
|
visible: false,
|
||||||
@@ -421,7 +396,7 @@ function getMethodType(method?: string) {
|
|||||||
return "info";
|
return "info";
|
||||||
}
|
}
|
||||||
|
|
||||||
const exportModalVisible = ref(false);
|
const { exportVisible, openExport } = useImportExport();
|
||||||
|
|
||||||
async function handleSearchBarSearch(params: LogSearchForm) {
|
async function handleSearchBarSearch(params: LogSearchForm) {
|
||||||
await searchBarRef.value?.validate?.();
|
await searchBarRef.value?.validate?.();
|
||||||
@@ -533,8 +508,4 @@ async function handleBatchDelete() {
|
|||||||
batchDeleting.value = false;
|
batchDeleting.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openExportModal() {
|
|
||||||
exportModalVisible.value = true;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -71,122 +71,57 @@
|
|||||||
v-model="dialogVisible.visible"
|
v-model="dialogVisible.visible"
|
||||||
:title="dialogVisible.title"
|
:title="dialogVisible.title"
|
||||||
:size="drawerSize"
|
: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'">
|
<template v-if="dialogVisible.type === 'detail'">
|
||||||
<ElDescriptions :column="4" border>
|
<FaDescriptions
|
||||||
<ElDescriptionsItem label="编号" :span="2">
|
:column="4"
|
||||||
{{ detailFormData.id }}
|
:data="detailFormData"
|
||||||
</ElDescriptionsItem>
|
:items="menuDetailItems"
|
||||||
<ElDescriptionsItem label="菜单名称" :span="2">
|
:scrollbar="false"
|
||||||
{{ detailFormData.name }}
|
>
|
||||||
</ElDescriptionsItem>
|
<template #type="{ row }">
|
||||||
<ElDescriptionsItem label="菜单类型" :span="2">
|
<ElTag v-if="row?.type === MenuTypeEnum.CATALOG" type="warning">目录</ElTag>
|
||||||
<ElTag v-if="detailFormData.type === MenuTypeEnum.CATALOG" type="warning">目录</ElTag>
|
<ElTag v-if="row?.type === MenuTypeEnum.MENU" type="success">菜单</ElTag>
|
||||||
<ElTag v-if="detailFormData.type === MenuTypeEnum.MENU" type="success">菜单</ElTag>
|
<ElTag v-if="row?.type === MenuTypeEnum.BUTTON" type="danger">按钮</ElTag>
|
||||||
<ElTag v-if="detailFormData.type === MenuTypeEnum.BUTTON" type="danger">按钮</ElTag>
|
<ElTag v-if="row?.type === MenuTypeEnum.EXTLINK" type="info">外链</ElTag>
|
||||||
<ElTag v-if="detailFormData.type === MenuTypeEnum.EXTLINK" type="info">外链</ElTag>
|
</template>
|
||||||
</ElDescriptionsItem>
|
<template #client="{ row }">
|
||||||
<ElDescriptionsItem label="终端" :span="2">
|
<ElTag v-if="row?.client === MenuClientEnum.PC" type="primary">PC</ElTag>
|
||||||
<ElTag v-if="detailFormData.client === MenuClientEnum.PC" type="primary">PC</ElTag>
|
<ElTag v-else-if="row?.client === MenuClientEnum.APP" type="success">APP</ElTag>
|
||||||
<ElTag v-else-if="detailFormData.client === MenuClientEnum.APP" type="success">
|
<ElTag v-else type="info">{{ row?.client || "—" }}</ElTag>
|
||||||
APP
|
</template>
|
||||||
</ElTag>
|
<template #icon="{ row }">
|
||||||
<ElTag v-else type="info">{{ detailFormData.client || "—" }}</ElTag>
|
<template v-if="row?.icon">
|
||||||
</ElDescriptionsItem>
|
<MenuRouteIcon :icon="row?.icon as string" :style="'vertical-align: -0.15em'" />
|
||||||
<ElDescriptionsItem label="图标" :span="2">
|
|
||||||
<template #default>
|
|
||||||
<template v-if="detailFormData.icon">
|
|
||||||
<MenuRouteIcon :icon="detailFormData.icon" :style="'vertical-align: -0.15em'" />
|
|
||||||
</template>
|
|
||||||
</template>
|
</template>
|
||||||
</ElDescriptionsItem>
|
</template>
|
||||||
<ElDescriptionsItem label="排序" :span="2">
|
</FaDescriptions>
|
||||||
{{ 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>
|
</template>
|
||||||
|
|
||||||
<!-- 新增、编辑表单 -->
|
<!-- 新增、编辑表单 -->
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<ElForm
|
<FaForm
|
||||||
|
:key="menuFormRenderKey"
|
||||||
ref="dataFormRef"
|
ref="dataFormRef"
|
||||||
:model="formData"
|
v-model="formData"
|
||||||
|
:items="menuDialogFormItems"
|
||||||
:rules="rules"
|
:rules="rules"
|
||||||
label-suffix=":"
|
label-suffix=":"
|
||||||
label-width="100px"
|
:label-width="'auto'"
|
||||||
label-position="right"
|
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"
|
<template v-if="formData.type !== MenuTypeEnum.CATALOG" #parent_id>
|
||||||
label="父级菜单"
|
|
||||||
prop="parent_id"
|
|
||||||
>
|
|
||||||
<ElTreeSelect
|
<ElTreeSelect
|
||||||
v-model="formData.parent_id"
|
v-model="formData.parent_id"
|
||||||
placeholder="选择上级菜单"
|
placeholder="选择上级菜单"
|
||||||
@@ -199,17 +134,10 @@
|
|||||||
<ElText v-if="createParentLocked" type="info" size="small" class="block mt-1">
|
<ElText v-if="createParentLocked" type="info" size="small" class="block mt-1">
|
||||||
在菜单下仅可新增按钮,父级已固定
|
在菜单下仅可新增按钮,父级已固定
|
||||||
</ElText>
|
</ElText>
|
||||||
</ElFormItem>
|
</template>
|
||||||
|
|
||||||
<ElFormItem label="菜单名称" prop="name">
|
<!-- 菜单类型(动态枚举按钮组) -->
|
||||||
<ElInput v-model="formData.name" placeholder="请输入菜单名称" />
|
<template #type>
|
||||||
</ElFormItem>
|
|
||||||
|
|
||||||
<ElFormItem label="菜单标题" prop="title">
|
|
||||||
<ElInput v-model="formData.title" placeholder="请输入菜单标题" />
|
|
||||||
</ElFormItem>
|
|
||||||
|
|
||||||
<ElFormItem label="菜单类型" prop="type">
|
|
||||||
<ElRadioGroup v-model="formData.type" @change="handleMenuTypeChange">
|
<ElRadioGroup v-model="formData.type" @change="handleMenuTypeChange">
|
||||||
<ElRadio
|
<ElRadio
|
||||||
v-if="allowedMenuTypeValues.includes(MenuTypeEnum.CATALOG)"
|
v-if="allowedMenuTypeValues.includes(MenuTypeEnum.CATALOG)"
|
||||||
@@ -236,9 +164,10 @@
|
|||||||
外链
|
外链
|
||||||
</ElRadio>
|
</ElRadio>
|
||||||
</ElRadioGroup>
|
</ElRadioGroup>
|
||||||
</ElFormItem>
|
</template>
|
||||||
|
|
||||||
<ElFormItem label="终端" prop="client">
|
<!-- 终端(禁用态+提示) -->
|
||||||
|
<template #client>
|
||||||
<ElRadioGroup v-model="formData.client" :disabled="createParentLocked">
|
<ElRadioGroup v-model="formData.client" :disabled="createParentLocked">
|
||||||
<ElRadio :value="MenuClientEnum.PC">PC 桌面</ElRadio>
|
<ElRadio :value="MenuClientEnum.PC">PC 桌面</ElRadio>
|
||||||
<ElRadio :value="MenuClientEnum.APP">APP 移动</ElRadio>
|
<ElRadio :value="MenuClientEnum.APP">APP 移动</ElRadio>
|
||||||
@@ -246,91 +175,41 @@
|
|||||||
<ElText v-if="createParentLocked" type="info" size="small" class="block mt-1">
|
<ElText v-if="createParentLocked" type="info" size="small" class="block mt-1">
|
||||||
子级终端与父菜单一致
|
子级终端与父菜单一致
|
||||||
</ElText>
|
</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="请输入外链完整路径" />
|
<ElInput v-model="formData.route_path" placeholder="请输入外链完整路径" />
|
||||||
</ElFormItem>
|
</template>
|
||||||
|
|
||||||
<ElFormItem v-if="formData.type !== MenuTypeEnum.BUTTON" prop="route_name">
|
<!-- 路由名称(Tooltip标签) -->
|
||||||
<template #label>
|
<template v-if="formData.type !== MenuTypeEnum.BUTTON" #route_name>
|
||||||
<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>
|
|
||||||
<ElInput v-model="formData.route_name" placeholder="请输入路由名称" />
|
<ElInput v-model="formData.route_name" placeholder="请输入路由名称" />
|
||||||
</ElFormItem>
|
</template>
|
||||||
|
|
||||||
<ElFormItem
|
<!-- 路由路径(Tooltip标签) -->
|
||||||
|
<template
|
||||||
v-if="formData.type == MenuTypeEnum.CATALOG || formData.type == MenuTypeEnum.MENU"
|
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" />
|
<ElInput v-model="formData.route_path" placeholder="请输入路由路径,如:/system" />
|
||||||
</ElFormItem>
|
</template>
|
||||||
|
|
||||||
<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>
|
|
||||||
|
|
||||||
|
<!-- 组件路径(Tooltip+prepend/append) -->
|
||||||
|
<template v-if="formData.type == MenuTypeEnum.MENU" #component_path>
|
||||||
<ElInput
|
<ElInput
|
||||||
v-model="formData.component_path"
|
v-model="formData.component_path"
|
||||||
placeholder="请输入组件路径,如system/user/index"
|
placeholder="请输入组件路径,如system/user/index"
|
||||||
:style="'width: 95%'"
|
:style="'width: 95%'"
|
||||||
>
|
>
|
||||||
<template v-if="formData.type == MenuTypeEnum.MENU" #prepend>src/views/</template>
|
<template #prepend>src/views/</template>
|
||||||
<template v-if="formData.type == MenuTypeEnum.MENU" #append>.vue</template>
|
<template #append>.vue</template>
|
||||||
</ElInput>
|
</ElInput>
|
||||||
</ElFormItem>
|
</template>
|
||||||
|
|
||||||
<ElFormItem v-if="formData.type == MenuTypeEnum.MENU">
|
<!-- 路由参数(动态键值编辑器) -->
|
||||||
<template #label>
|
<template v-if="formData.type == MenuTypeEnum.MENU" #params>
|
||||||
<div class="flex-y-center">
|
<template
|
||||||
路由参数
|
|
||||||
<ElTooltip placement="bottom" effect="light">
|
|
||||||
<template #content>
|
|
||||||
组件页面使用 `useRoute().query.参数名` 获取路由参数值。
|
|
||||||
</template>
|
|
||||||
<ElIcon class="ml-1 cursor-pointer">
|
|
||||||
<QuestionFilled />
|
|
||||||
</ElIcon>
|
|
||||||
</ElTooltip>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="
|
v-if="
|
||||||
!formData.params || (Array.isArray(formData.params) && formData.params.length === 0)
|
!formData.params || (Array.isArray(formData.params) && formData.params.length === 0)
|
||||||
"
|
"
|
||||||
@@ -338,16 +217,12 @@
|
|||||||
<ElButton type="success" plain @click="formData.params = [{ key: '', value: '' }]">
|
<ElButton type="success" plain @click="formData.params = [{ key: '', value: '' }]">
|
||||||
添加路由参数
|
添加路由参数
|
||||||
</ElButton>
|
</ElButton>
|
||||||
</div>
|
</template>
|
||||||
|
<template v-else>
|
||||||
<div v-else>
|
|
||||||
<div v-for="(item, index) in formData.params" :key="index">
|
<div v-for="(item, index) in formData.params" :key="index">
|
||||||
<ElInput v-model="item.key" placeholder="参数名" :style="'width: 100px'" />
|
<ElInput v-model="item.key" placeholder="参数名" :style="'width: 100px'" />
|
||||||
|
|
||||||
<span class="mx-1">=</span>
|
<span class="mx-1">=</span>
|
||||||
|
|
||||||
<ElInput v-model="item.value" placeholder="参数值" :style="'width: 100px'" />
|
<ElInput v-model="item.value" placeholder="参数值" :style="'width: 100px'" />
|
||||||
|
|
||||||
<ElIcon
|
<ElIcon
|
||||||
v-if="formData.params.indexOf(item) === formData.params.length - 1"
|
v-if="formData.params.indexOf(item) === formData.params.length - 1"
|
||||||
class="ml-2 cursor-pointer color-[var(--el-color-success)]"
|
class="ml-2 cursor-pointer color-[var(--el-color-success)]"
|
||||||
@@ -364,90 +239,52 @@
|
|||||||
<DeleteFilled />
|
<DeleteFilled />
|
||||||
</ElIcon>
|
</ElIcon>
|
||||||
</div>
|
</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>
|
||||||
|
</template>
|
||||||
|
<!-- 是否隐藏(Tooltip标签) -->
|
||||||
|
<template v-if="formData.type !== MenuTypeEnum.BUTTON" #hidden>
|
||||||
<ElRadioGroup v-model="formData.hidden">
|
<ElRadioGroup v-model="formData.hidden">
|
||||||
<ElRadio :value="true">是</ElRadio>
|
<ElRadio :value="true">是</ElRadio>
|
||||||
<ElRadio :value="false">否</ElRadio>
|
<ElRadio :value="false">否</ElRadio>
|
||||||
</ElRadioGroup>
|
</ElRadioGroup>
|
||||||
</ElFormItem>
|
</template>
|
||||||
|
|
||||||
<ElFormItem
|
<!-- 始终显示(Tooltip标签) -->
|
||||||
|
<template
|
||||||
v-if="formData.type === MenuTypeEnum.CATALOG || formData.type === MenuTypeEnum.MENU"
|
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">
|
<ElRadioGroup v-model="formData.always_show">
|
||||||
<ElRadio :value="true">是</ElRadio>
|
<ElRadio :value="true">是</ElRadio>
|
||||||
<ElRadio :value="false">否</ElRadio>
|
<ElRadio :value="false">否</ElRadio>
|
||||||
</ElRadioGroup>
|
</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">
|
<ElRadioGroup v-model="formData.keep_alive">
|
||||||
<ElRadio :value="true">开启</ElRadio>
|
<ElRadio :value="true">开启</ElRadio>
|
||||||
<ElRadio :value="false">关闭</ElRadio>
|
<ElRadio :value="false">关闭</ElRadio>
|
||||||
</ElRadioGroup>
|
</ElRadioGroup>
|
||||||
</ElFormItem>
|
</template>
|
||||||
|
|
||||||
<ElFormItem label="排序" prop="order">
|
<!-- 权限标识(v-if) -->
|
||||||
<ElInputNumber v-model="formData.order" controls-position="right" :min="1" />
|
<template
|
||||||
</ElFormItem>
|
|
||||||
|
|
||||||
<!-- 权限标识 -->
|
|
||||||
<ElFormItem
|
|
||||||
v-if="formData.type == MenuTypeEnum.BUTTON || formData.type === MenuTypeEnum.MENU"
|
v-if="formData.type == MenuTypeEnum.BUTTON || formData.type === MenuTypeEnum.MENU"
|
||||||
label="权限标识"
|
#permission
|
||||||
prop="perm"
|
|
||||||
>
|
>
|
||||||
<ElInput v-model="formData.permission" placeholder="请输入权限标识,如sys:user:add" />
|
<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" />
|
<FaIconSelect v-model="formData.icon" />
|
||||||
</ElFormItem>
|
</template>
|
||||||
|
|
||||||
<ElFormItem
|
<!-- 重定向(v-if, 动态placeholder) -->
|
||||||
|
<template
|
||||||
v-if="formData.type == MenuTypeEnum.CATALOG || formData.type === MenuTypeEnum.MENU"
|
v-if="formData.type == MenuTypeEnum.CATALOG || formData.type === MenuTypeEnum.MENU"
|
||||||
label="重定向"
|
#redirect
|
||||||
prop="redirect"
|
|
||||||
:required="formData.type === MenuTypeEnum.CATALOG"
|
|
||||||
>
|
>
|
||||||
<ElInput
|
<ElInput
|
||||||
v-model="formData.redirect"
|
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">
|
<ElRadioGroup v-model="formData.affix">
|
||||||
<ElRadio :value="true">是</ElRadio>
|
<ElRadio :value="true">是</ElRadio>
|
||||||
<ElRadio :value="false">否</ElRadio>
|
<ElRadio :value="false">否</ElRadio>
|
||||||
</ElRadioGroup>
|
</ElRadioGroup>
|
||||||
</ElFormItem>
|
</template>
|
||||||
|
</FaForm>
|
||||||
<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>
|
</template>
|
||||||
</FaDrawer>
|
</FaDrawer>
|
||||||
</div>
|
</div>
|
||||||
@@ -511,8 +315,7 @@ defineOptions({
|
|||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
import { h, ref, reactive, computed, nextTick, onMounted } from "vue";
|
import { CirclePlusFilled, DeleteFilled } from "@element-plus/icons-vue";
|
||||||
import { CirclePlusFilled, DeleteFilled, QuestionFilled } from "@element-plus/icons-vue";
|
|
||||||
import { useAppStore } from "@stores/modules/app.store";
|
import { useAppStore } from "@stores/modules/app.store";
|
||||||
import { useUserStore } from "@stores/modules/user.store";
|
import { useUserStore } from "@stores/modules/user.store";
|
||||||
import { DeviceEnum } from "@/enums/settings/device.enum";
|
import { DeviceEnum } from "@/enums/settings/device.enum";
|
||||||
@@ -524,15 +327,8 @@ import MenuAPI, {
|
|||||||
} from "@/api/module_system/menu";
|
} from "@/api/module_system/menu";
|
||||||
import { MenuClientEnum, MenuTypeEnum } from "@/enums/system/menu.enum";
|
import { MenuClientEnum, MenuTypeEnum } from "@/enums/system/menu.enum";
|
||||||
import { formatTree } from "@utils/common";
|
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 type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
||||||
import FaDrawer from "@/components/modal/fa-drawer/index.vue";
|
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||||
import FaIconSelect from "@/components/others/fa-icon-select/index.vue";
|
|
||||||
import { ElMessage, ElMessageBox, ElTag, ElTooltip } from "element-plus";
|
|
||||||
import { useAuth } from "@/hooks/core/useAuth";
|
import { useAuth } from "@/hooks/core/useAuth";
|
||||||
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
|
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
|
||||||
|
|
||||||
@@ -697,6 +493,108 @@ const createParentLocked = ref(false);
|
|||||||
|
|
||||||
const detailFormData = ref<MenuTable>({});
|
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>({
|
const formData = ref<MenuForm>({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
@@ -1004,7 +902,8 @@ const initialFormData: MenuForm = {
|
|||||||
client: MenuClientEnum.PC,
|
client: MenuClientEnum.PC,
|
||||||
};
|
};
|
||||||
|
|
||||||
const dataFormRef = ref();
|
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
||||||
|
const menuFormRenderKey = ref(0);
|
||||||
|
|
||||||
async function resetForm() {
|
async function resetForm() {
|
||||||
if (dataFormRef.value) {
|
if (dataFormRef.value) {
|
||||||
@@ -1052,6 +951,7 @@ async function handleOpenDialog(
|
|||||||
} else {
|
} else {
|
||||||
dialogVisible.title = "新增菜单";
|
dialogVisible.title = "新增菜单";
|
||||||
formData.value = { ...initialFormData };
|
formData.value = { ...initialFormData };
|
||||||
|
menuFormRenderKey.value += 1;
|
||||||
if (parentRow?.id != null) {
|
if (parentRow?.id != null) {
|
||||||
formData.value.parent_id = parentRow.id;
|
formData.value.parent_id = parentRow.id;
|
||||||
formData.value.client = (parentRow.client as MenuClientEnum) || menuClientTab.value;
|
formData.value.client = (parentRow.client as MenuClientEnum) || menuClientTab.value;
|
||||||
@@ -1075,7 +975,7 @@ function handleMenuTypeChange() {
|
|||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
dataFormRef.value?.clearValidate("redirect");
|
dataFormRef.value?.clearValidate("redirect");
|
||||||
if (formData.value.type === MenuTypeEnum.CATALOG) {
|
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']"
|
:perm-patch="['module_system:notice:patch']"
|
||||||
:delete-loading="batchDeleting"
|
:delete-loading="batchDeleting"
|
||||||
@add="handleOpenDialog('create')"
|
@add="handleOpenDialog('create')"
|
||||||
@export="openExportModal"
|
@export="openExport"
|
||||||
@delete="handleBatchDelete"
|
@delete="handleBatchDelete"
|
||||||
@more="handleMoreClick"
|
@more="handleMoreClick"
|
||||||
/>
|
/>
|
||||||
@@ -71,105 +71,74 @@
|
|||||||
width="920px"
|
width="920px"
|
||||||
dialog-class="crud-embed-dialog"
|
dialog-class="crud-embed-dialog"
|
||||||
modal-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'">
|
<template v-if="dialogVisible.type === 'detail'">
|
||||||
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
|
<FaDescriptions
|
||||||
<ElDescriptions :column="4" border label-width="120px">
|
:column="4"
|
||||||
<ElDescriptionsItem label="标题" :span="2">
|
:data="detailFormData"
|
||||||
{{ detailFormData.notice_title }}
|
:items="noticeDetailItems"
|
||||||
</ElDescriptionsItem>
|
label-width="120px"
|
||||||
<ElDescriptionsItem label="类型" :span="2">
|
max-height="75vh"
|
||||||
<ElTag :type="detailFormData.notice_type === '1' ? 'primary' : 'warning'">
|
>
|
||||||
{{ noticeTypeLabel(detailFormData.notice_type) }}
|
<template #notice_type="{ row }">
|
||||||
</ElTag>
|
<ElTag :type="row?.notice_type === '1' ? 'primary' : 'warning'">
|
||||||
</ElDescriptionsItem>
|
{{ noticeTypeLabel(row?.notice_type as string) }}
|
||||||
<ElDescriptionsItem label="状态" :span="2">
|
</ElTag>
|
||||||
<ElTag :type="detailFormData.status === '0' ? 'success' : 'danger'">
|
</template>
|
||||||
{{ detailFormData.status === "0" ? "启用" : "停用" }}
|
<template #notice_content>
|
||||||
</ElTag>
|
<div class="notice-html-preview">
|
||||||
</ElDescriptionsItem>
|
<template v-if="detailHasRenderableContent">
|
||||||
<ElDescriptionsItem label="描述" :span="2">
|
<div v-html="detailContentHtml" />
|
||||||
{{ detailFormData.description }}
|
</template>
|
||||||
</ElDescriptionsItem>
|
<p v-else class="notice-html-empty">暂无内容</p>
|
||||||
<ElDescriptionsItem label="内容" :span="4">
|
</div>
|
||||||
<div class="notice-html-preview">
|
</template>
|
||||||
<template v-if="detailHasRenderableContent">
|
</FaDescriptions>
|
||||||
<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>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
|
<!-- FaForm + items + 栅格;弹窗内关闭内置提交/重置,仍用底部按钮 -->
|
||||||
<!-- FaForm + items + 栅格;弹窗内关闭内置提交/重置,仍用底部按钮 -->
|
<FaForm
|
||||||
<FaForm
|
:key="noticeFormRenderKey"
|
||||||
:key="noticeFormRenderKey"
|
scrollbar
|
||||||
ref="dataFormRef"
|
max-height="75vh"
|
||||||
v-model="formData"
|
ref="dataFormRef"
|
||||||
:items="noticeDialogFormItems"
|
v-model="formData"
|
||||||
:rules="rules"
|
:items="noticeDialogFormItems"
|
||||||
label-suffix=":"
|
:rules="rules"
|
||||||
:label-width="100"
|
label-suffix=":"
|
||||||
label-position="right"
|
:label-width="100"
|
||||||
:span="24"
|
label-position="right"
|
||||||
:gutter="16"
|
:span="24"
|
||||||
:show-reset="false"
|
:gutter="16"
|
||||||
:show-submit="false"
|
:show-reset="false"
|
||||||
class="crud-dialog-art-form"
|
:show-submit="false"
|
||||||
>
|
class="crud-dialog-art-form"
|
||||||
<template #status>
|
>
|
||||||
<ElRadioGroup v-model="formData.status">
|
<template #status>
|
||||||
<ElRadio value="0">启用</ElRadio>
|
<ElRadioGroup v-model="formData.status">
|
||||||
<ElRadio value="1">停用</ElRadio>
|
<ElRadio value="0">启用</ElRadio>
|
||||||
</ElRadioGroup>
|
<ElRadio value="1">停用</ElRadio>
|
||||||
</template>
|
</ElRadioGroup>
|
||||||
<template #notice_content>
|
</template>
|
||||||
<FaWangEditor
|
<template #notice_content>
|
||||||
:model-value="formData.notice_content ?? ''"
|
<FaWangEditor
|
||||||
height="min(38vh, 280px)"
|
:model-value="formData.notice_content ?? ''"
|
||||||
placeholder="请输入公告内容,支持完整排版与插入..."
|
height="min(38vh, 280px)"
|
||||||
:exclude-keys="[]"
|
placeholder="请输入公告内容,支持完整排版与插入..."
|
||||||
@update:model-value="(v: string) => (formData.notice_content = v)"
|
:exclude-keys="[]"
|
||||||
/>
|
@update:model-value="(v: string) => (formData.notice_content = v)"
|
||||||
</template>
|
/>
|
||||||
</FaForm>
|
</template>
|
||||||
</ElScrollbar>
|
</FaForm>
|
||||||
</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>
|
|
||||||
</template>
|
</template>
|
||||||
</FaDialog>
|
</FaDialog>
|
||||||
|
|
||||||
<FaExportDialog
|
<FaExportDialog
|
||||||
v-model="exportModalVisible"
|
v-model="exportVisible"
|
||||||
:content-config="noticeExportContentConfig"
|
:content-config="noticeExportContentConfig"
|
||||||
:query-params="exportQueryParams"
|
:query-params="exportQueryParams"
|
||||||
:page-data="data"
|
:page-data="data"
|
||||||
@@ -179,30 +148,21 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h, computed, ref, reactive, nextTick, onMounted } from "vue";
|
|
||||||
import { useTable } from "@/hooks/core/useTable";
|
import { useTable } from "@/hooks/core/useTable";
|
||||||
import FaTable from "@/components/tables/fa-table/index.vue";
|
import { useImportExport } from "@/hooks/core/useImportExport";
|
||||||
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 type { IObject } from "@/components/modal/types";
|
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 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 { ColumnOption } from "@/types/component";
|
||||||
|
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||||
import NoticeAPI, {
|
import NoticeAPI, {
|
||||||
type NoticeForm,
|
type NoticeForm,
|
||||||
type NoticePageQuery,
|
type NoticePageQuery,
|
||||||
type NoticeTable,
|
type NoticeTable,
|
||||||
} from "@/api/module_system/notice";
|
} from "@/api/module_system/notice";
|
||||||
import { ElMessage, ElMessageBox, ElTag } from "element-plus";
|
|
||||||
import { useAuth } from "@/hooks/core/useAuth";
|
import { useAuth } from "@/hooks/core/useAuth";
|
||||||
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
|
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
|
||||||
import { useDictStore, useNoticeStore } from "@stores/index";
|
import { useDictStore, useNoticeStore } from "@stores/index";
|
||||||
import UserTableSelect from "@views/module_system/user/components/UserTableSelect.vue";
|
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({
|
defineOptions({
|
||||||
name: "Notice",
|
name: "Notice",
|
||||||
@@ -436,6 +396,25 @@ const noticeExportContentConfig = computed(() => ({
|
|||||||
|
|
||||||
const detailFormData = ref<NoticeTable>({});
|
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(用于预览) */
|
/** 详情富文本 HTML(用于预览) */
|
||||||
const detailContentHtml = computed({
|
const detailContentHtml = computed({
|
||||||
get: () => detailFormData.value.notice_content ?? "",
|
get: () => detailFormData.value.notice_content ?? "",
|
||||||
@@ -545,7 +524,7 @@ const initialFormData: NoticeForm = {
|
|||||||
description: undefined,
|
description: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportModalVisible = ref(false);
|
const { exportVisible, openExport } = useImportExport();
|
||||||
|
|
||||||
function buildNoticeReplaceParams(p: NoticeSearchForm): Record<string, unknown> {
|
function buildNoticeReplaceParams(p: NoticeSearchForm): Record<string, unknown> {
|
||||||
return {
|
return {
|
||||||
@@ -743,10 +722,6 @@ async function handleMoreClick(status: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openExportModal() {
|
|
||||||
exportModalVisible.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await dictStore.getDict(["sys_notice_type"]);
|
await dictStore.getDict(["sys_notice_type"]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
:perm-delete="['module_system:param:delete']"
|
:perm-delete="['module_system:param:delete']"
|
||||||
:delete-loading="batchDeleting"
|
:delete-loading="batchDeleting"
|
||||||
@add="handleOpenDialog('create')"
|
@add="handleOpenDialog('create')"
|
||||||
@export="openExportModal"
|
@export="openExport"
|
||||||
@delete="handleBatchDelete"
|
@delete="handleBatchDelete"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -60,81 +60,49 @@
|
|||||||
width="640px"
|
width="640px"
|
||||||
dialog-class="crud-embed-dialog"
|
dialog-class="crud-embed-dialog"
|
||||||
modal-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'">
|
<template v-if="dialogVisible.type === 'detail'">
|
||||||
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
|
<FaDescriptions
|
||||||
<ElDescriptions :column="4" border>
|
:column="4"
|
||||||
<ElDescriptionsItem label="配置名称" :span="2">
|
:data="detailFormData"
|
||||||
{{ detailFormData.config_name }}
|
:items="paramDetailItems"
|
||||||
</ElDescriptionsItem>
|
max-height="75vh"
|
||||||
<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>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
|
<FaForm
|
||||||
<FaForm
|
:key="paramFormRenderKey"
|
||||||
:key="paramFormRenderKey"
|
scrollbar
|
||||||
ref="dataFormRef"
|
max-height="75vh"
|
||||||
v-model="formData"
|
ref="dataFormRef"
|
||||||
:items="paramDialogFormItems"
|
v-model="formData"
|
||||||
:rules="rules"
|
:items="paramDialogFormItems"
|
||||||
label-suffix=":"
|
:rules="rules"
|
||||||
:label-width="100"
|
label-suffix=":"
|
||||||
label-position="right"
|
:label-width="100"
|
||||||
:span="24"
|
label-position="right"
|
||||||
:gutter="16"
|
:span="24"
|
||||||
:show-reset="false"
|
:gutter="16"
|
||||||
:show-submit="false"
|
:show-reset="false"
|
||||||
class="crud-dialog-art-form"
|
:show-submit="false"
|
||||||
>
|
class="crud-dialog-art-form"
|
||||||
<template #config_type>
|
>
|
||||||
<ElRadioGroup v-model="formData.config_type">
|
<template #config_type>
|
||||||
<ElRadio :value="true">是</ElRadio>
|
<ElRadioGroup v-model="formData.config_type">
|
||||||
<ElRadio :value="false">否</ElRadio>
|
<ElRadio :value="true">是</ElRadio>
|
||||||
</ElRadioGroup>
|
<ElRadio :value="false">否</ElRadio>
|
||||||
</template>
|
</ElRadioGroup>
|
||||||
</FaForm>
|
</template>
|
||||||
</ElScrollbar>
|
</FaForm>
|
||||||
</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>
|
|
||||||
</template>
|
</template>
|
||||||
</FaDialog>
|
</FaDialog>
|
||||||
|
|
||||||
<FaExportDialog
|
<FaExportDialog
|
||||||
v-model="exportModalVisible"
|
v-model="exportVisible"
|
||||||
:content-config="paramExportContentConfig"
|
:content-config="paramExportContentConfig"
|
||||||
:query-params="exportQueryParams"
|
:query-params="exportQueryParams"
|
||||||
:page-data="data"
|
:page-data="data"
|
||||||
@@ -144,17 +112,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h, computed, ref, reactive } from "vue";
|
|
||||||
import { useTable } from "@/hooks/core/useTable";
|
import { useTable } from "@/hooks/core/useTable";
|
||||||
import FaTable from "@/components/tables/fa-table/index.vue";
|
import { useImportExport } from "@/hooks/core/useImportExport";
|
||||||
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 type { IObject } from "@/components/modal/types";
|
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 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 { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||||
import type { ColumnOption } from "@/types/component";
|
import type { ColumnOption } from "@/types/component";
|
||||||
import ParamsAPI, {
|
import ParamsAPI, {
|
||||||
@@ -162,7 +123,6 @@ import ParamsAPI, {
|
|||||||
type ConfigPageQuery,
|
type ConfigPageQuery,
|
||||||
type ConfigTable,
|
type ConfigTable,
|
||||||
} from "@/api/module_system/params";
|
} from "@/api/module_system/params";
|
||||||
import { ElMessage, ElMessageBox, ElTag } from "element-plus";
|
|
||||||
import { useAuth } from "@/hooks/core/useAuth";
|
import { useAuth } from "@/hooks/core/useAuth";
|
||||||
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
|
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
|
||||||
import { useConfigStore } from "@stores";
|
import { useConfigStore } from "@stores";
|
||||||
@@ -362,6 +322,23 @@ const paramExportContentConfig = computed(() => ({
|
|||||||
|
|
||||||
const detailFormData = ref<ConfigTable>({} as ConfigTable);
|
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>({
|
const formData = ref<ConfigForm>({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
config_name: "",
|
config_name: "",
|
||||||
@@ -441,7 +418,7 @@ const initialFormData: ConfigForm = {
|
|||||||
description: "",
|
description: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportModalVisible = ref(false);
|
const { exportVisible, openExport } = useImportExport();
|
||||||
|
|
||||||
async function handleSearchBarSearch(params: ParamSearchForm) {
|
async function handleSearchBarSearch(params: ParamSearchForm) {
|
||||||
await searchBarRef.value?.validate?.();
|
await searchBarRef.value?.validate?.();
|
||||||
@@ -460,8 +437,8 @@ function onResetSearch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function resetForm() {
|
async function resetForm() {
|
||||||
dataFormRef.value?.ref?.resetFields();
|
dataFormRef.value?.resetFields();
|
||||||
dataFormRef.value?.ref?.clearValidate();
|
dataFormRef.value?.clearValidate();
|
||||||
Object.assign(formData, initialFormData);
|
Object.assign(formData, initialFormData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,10 +574,6 @@ async function handleBatchDelete() {
|
|||||||
batchDeleting.value = false;
|
batchDeleting.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openExportModal() {
|
|
||||||
exportModalVisible.value = true;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!-- 岗位管理:Art + useTable;操作列前 3 个为 ArtButtonTable,其余收入「更多」下拉 -->
|
<!-- 岗位管理:FA + useTable;操作列前 3 个为 ArtButtonTable,其余收入「更多」下拉 -->
|
||||||
<template>
|
<template>
|
||||||
<div class="fa-full-height">
|
<div class="fa-full-height">
|
||||||
<FaSearchBar
|
<FaSearchBar
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
:perm-patch="['module_system:position:patch']"
|
:perm-patch="['module_system:position:patch']"
|
||||||
:delete-loading="batchDeleting"
|
:delete-loading="batchDeleting"
|
||||||
@add="handleOpenDialog('create')"
|
@add="handleOpenDialog('create')"
|
||||||
@export="openExportModal"
|
@export="openExport"
|
||||||
@delete="handleBatchDelete"
|
@delete="handleBatchDelete"
|
||||||
@more="handleMoreClick"
|
@more="handleMoreClick"
|
||||||
/>
|
/>
|
||||||
@@ -71,84 +71,49 @@
|
|||||||
width="720px"
|
width="720px"
|
||||||
dialog-class="crud-embed-dialog"
|
dialog-class="crud-embed-dialog"
|
||||||
modal-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'">
|
<template v-if="dialogVisible.type === 'detail'">
|
||||||
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
|
<FaDescriptions
|
||||||
<ElDescriptions :column="4" border>
|
:column="4"
|
||||||
<ElDescriptionsItem label="岗位名称" :span="2">
|
:data="detailFormData"
|
||||||
{{ detailFormData.name }}
|
:items="positionDetailItems"
|
||||||
</ElDescriptionsItem>
|
max-height="75vh"
|
||||||
<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>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
|
<FaForm
|
||||||
<FaForm
|
scrollbar
|
||||||
:key="positionFormRenderKey"
|
max-height="75vh"
|
||||||
ref="dataFormRef"
|
:key="positionFormRenderKey"
|
||||||
v-model="formData"
|
ref="dataFormRef"
|
||||||
:items="positionDialogFormItems"
|
v-model="formData"
|
||||||
:rules="rules"
|
:items="positionDialogFormItems"
|
||||||
label-suffix=":"
|
:rules="rules"
|
||||||
:label-width="'auto'"
|
label-suffix=":"
|
||||||
label-position="right"
|
:label-width="'auto'"
|
||||||
:span="24"
|
label-position="right"
|
||||||
:gutter="16"
|
:span="24"
|
||||||
:show-reset="false"
|
:gutter="16"
|
||||||
:show-submit="false"
|
:show-reset="false"
|
||||||
class="crud-dialog-art-form"
|
:show-submit="false"
|
||||||
>
|
class="crud-dialog-art-form"
|
||||||
<template #status>
|
>
|
||||||
<ElRadioGroup v-model="formData.status">
|
<template #status>
|
||||||
<ElRadio value="0">启用</ElRadio>
|
<ElRadioGroup v-model="formData.status">
|
||||||
<ElRadio value="1">停用</ElRadio>
|
<ElRadio value="0">启用</ElRadio>
|
||||||
</ElRadioGroup>
|
<ElRadio value="1">停用</ElRadio>
|
||||||
</template>
|
</ElRadioGroup>
|
||||||
</FaForm>
|
</template>
|
||||||
</ElScrollbar>
|
</FaForm>
|
||||||
</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>
|
|
||||||
</template>
|
</template>
|
||||||
</FaDialog>
|
</FaDialog>
|
||||||
|
|
||||||
<FaExportDialog
|
<FaExportDialog
|
||||||
v-model="exportModalVisible"
|
v-model="exportVisible"
|
||||||
:content-config="positionExportContentConfig"
|
:content-config="positionExportContentConfig"
|
||||||
:query-params="exportQueryParams"
|
:query-params="exportQueryParams"
|
||||||
:page-data="data"
|
:page-data="data"
|
||||||
@@ -158,18 +123,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h, computed, ref, reactive, nextTick } from "vue";
|
|
||||||
import { useTable } from "@/hooks/core/useTable";
|
import { useTable } from "@/hooks/core/useTable";
|
||||||
import FaTable from "@/components/tables/fa-table/index.vue";
|
import { useImportExport } from "@/hooks/core/useImportExport";
|
||||||
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 type { IObject } from "@/components/modal/types";
|
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 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 { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||||
import type { ColumnOption } from "@/types/component";
|
import type { ColumnOption } from "@/types/component";
|
||||||
import PositionAPI, {
|
import PositionAPI, {
|
||||||
@@ -177,15 +134,6 @@ import PositionAPI, {
|
|||||||
type PositionPageQuery,
|
type PositionPageQuery,
|
||||||
type PositionTable,
|
type PositionTable,
|
||||||
} from "@/api/module_system/position";
|
} from "@/api/module_system/position";
|
||||||
import {
|
|
||||||
ElMessage,
|
|
||||||
ElMessageBox,
|
|
||||||
ElTag,
|
|
||||||
ElTooltip,
|
|
||||||
ElDropdown,
|
|
||||||
ElDropdownMenu,
|
|
||||||
ElDropdownItem,
|
|
||||||
} from "element-plus";
|
|
||||||
import { useAuth } from "@/hooks/core/useAuth";
|
import { useAuth } from "@/hooks/core/useAuth";
|
||||||
import { useUserStore } from "@stores";
|
import { useUserStore } from "@stores";
|
||||||
import UserTableSelect from "@views/module_system/user/components/UserTableSelect.vue";
|
import UserTableSelect from "@views/module_system/user/components/UserTableSelect.vue";
|
||||||
@@ -281,7 +229,7 @@ function formatPositionOperationCell(
|
|||||||
const inlineNodes = inline.map((a) =>
|
const inlineNodes = inline.map((a) =>
|
||||||
h(ElTooltip, { content: a.label, placement: "top" }, () =>
|
h(ElTooltip, { content: a.label, placement: "top" }, () =>
|
||||||
h("span", { class: "inline-flex" }, [
|
h("span", { class: "inline-flex" }, [
|
||||||
h(ArtButtonTable, {
|
h(FaButtonTable, {
|
||||||
type: a.artType,
|
type: a.artType,
|
||||||
icon: a.icon,
|
icon: a.icon,
|
||||||
onClick: a.run,
|
onClick: a.run,
|
||||||
@@ -305,7 +253,7 @@ function formatPositionOperationCell(
|
|||||||
default: () =>
|
default: () =>
|
||||||
h(ElTooltip, { content: "更多", placement: "top" }, () =>
|
h(ElTooltip, { content: "更多", placement: "top" }, () =>
|
||||||
h("span", { class: "inline-flex align-middle" }, [
|
h("span", { class: "inline-flex align-middle" }, [
|
||||||
h(ArtButtonTable, {
|
h(FaButtonTable, {
|
||||||
type: "more",
|
type: "more",
|
||||||
onClick: () => {},
|
onClick: () => {},
|
||||||
}),
|
}),
|
||||||
@@ -531,6 +479,24 @@ const positionExportContentConfig = computed(() => ({
|
|||||||
|
|
||||||
const detailFormData = ref<PositionTable>({});
|
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>({
|
const formData = ref<PositionForm>({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
@@ -599,11 +565,7 @@ const positionDialogFormItems = computed<FormItem[]>(() => [
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
const submitLoading = ref(false);
|
const submitLoading = ref(false);
|
||||||
const exportModalVisible = ref(false);
|
const { exportVisible, openExport } = useImportExport();
|
||||||
|
|
||||||
function openExportModal() {
|
|
||||||
exportModalVisible.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSearchBarSearch(params: PositionSearchForm) {
|
async function handleSearchBarSearch(params: PositionSearchForm) {
|
||||||
await searchBarRef.value?.validate?.();
|
await searchBarRef.value?.validate?.();
|
||||||
@@ -633,8 +595,8 @@ function onResetSearch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function resetForm() {
|
async function resetForm() {
|
||||||
dataFormRef.value?.ref?.resetFields();
|
dataFormRef.value?.resetFields();
|
||||||
dataFormRef.value?.ref?.clearValidate();
|
dataFormRef.value?.clearValidate();
|
||||||
Object.assign(formData, initialFormData);
|
Object.assign(formData, initialFormData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
:perm-patch="['module_system:role:patch']"
|
:perm-patch="['module_system:role:patch']"
|
||||||
:delete-loading="batchDeleting"
|
:delete-loading="batchDeleting"
|
||||||
@add="handleOpenDialog('create')"
|
@add="handleOpenDialog('create')"
|
||||||
@export="openExportModal"
|
@export="openExport"
|
||||||
@delete="handleBatchDelete"
|
@delete="handleBatchDelete"
|
||||||
@more="handleMoreClick"
|
@more="handleMoreClick"
|
||||||
/>
|
/>
|
||||||
@@ -62,99 +62,65 @@
|
|||||||
width="720px"
|
width="720px"
|
||||||
dialog-class="crud-embed-dialog"
|
dialog-class="crud-embed-dialog"
|
||||||
modal-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'">
|
<template v-if="dialogVisible.type === 'detail'">
|
||||||
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
|
<FaDescriptions
|
||||||
<ElDescriptions :column="4" border>
|
:column="4"
|
||||||
<ElDescriptionsItem label="角色名称" :span="2">
|
:data="detailFormData"
|
||||||
{{ detailFormData.name }}
|
:items="roleDetailItems"
|
||||||
</ElDescriptionsItem>
|
max-height="75vh"
|
||||||
<ElDescriptionsItem label="排序" :span="2">
|
>
|
||||||
{{ detailFormData.order }}
|
<template #data_scope="{ row }">
|
||||||
</ElDescriptionsItem>
|
<ElTag v-if="row?.data_scope === 1" type="primary">仅本人数据权限</ElTag>
|
||||||
<ElDescriptionsItem label="角色编码" :span="2">
|
<ElTag v-else-if="row?.data_scope === 2" type="info">本部门数据权限</ElTag>
|
||||||
{{ detailFormData.code }}
|
<ElTag v-else-if="row?.data_scope === 3" type="warning">本部门及以下数据权限</ElTag>
|
||||||
</ElDescriptionsItem>
|
<ElTag v-else-if="row?.data_scope === 4" type="success">全部数据权限</ElTag>
|
||||||
<ElDescriptionsItem label="数据权限" :span="2">
|
<ElTag v-else type="danger">自定义数据权限</ElTag>
|
||||||
<ElTag v-if="detailFormData.data_scope === 1" type="primary">仅本人数据权限</ElTag>
|
</template>
|
||||||
<ElTag v-else-if="detailFormData.data_scope === 2" type="info">本部门数据权限</ElTag>
|
<template #depts="{ row }">
|
||||||
<ElTag v-else-if="detailFormData.data_scope === 3" type="warning">
|
<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>
|
||||||
<ElTag v-else-if="detailFormData.data_scope === 4" type="success">全部数据权限</ElTag>
|
</template>
|
||||||
<ElTag v-else type="danger">自定义数据权限</ElTag>
|
<span v-else :style="'color: var(--el-text-color-placeholder)'">-</span>
|
||||||
</ElDescriptionsItem>
|
</template>
|
||||||
<ElDescriptionsItem label="所属部门" :span="2">
|
</FaDescriptions>
|
||||||
<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>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
|
<FaForm
|
||||||
<FaForm
|
:key="roleFormRenderKey"
|
||||||
:key="roleFormRenderKey"
|
scrollbar
|
||||||
ref="dataFormRef"
|
max-height="75vh"
|
||||||
v-model="formData"
|
ref="dataFormRef"
|
||||||
:items="roleDialogFormItems"
|
v-model="formData"
|
||||||
:rules="rules"
|
:items="roleDialogFormItems"
|
||||||
label-suffix=":"
|
:rules="rules"
|
||||||
:label-width="'auto'"
|
label-suffix=":"
|
||||||
label-position="right"
|
:label-width="'auto'"
|
||||||
:span="24"
|
label-position="right"
|
||||||
:gutter="16"
|
:span="24"
|
||||||
:show-reset="false"
|
:gutter="16"
|
||||||
:show-submit="false"
|
:show-reset="false"
|
||||||
class="crud-dialog-art-form"
|
:show-submit="false"
|
||||||
>
|
class="crud-dialog-art-form"
|
||||||
<template #status>
|
>
|
||||||
<ElRadioGroup v-model="formData.status">
|
<template #status>
|
||||||
<ElRadio value="0">启用</ElRadio>
|
<ElRadioGroup v-model="formData.status">
|
||||||
<ElRadio value="1">停用</ElRadio>
|
<ElRadio value="0">启用</ElRadio>
|
||||||
</ElRadioGroup>
|
<ElRadio value="1">停用</ElRadio>
|
||||||
</template>
|
</ElRadioGroup>
|
||||||
</FaForm>
|
</template>
|
||||||
</ElScrollbar>
|
</FaForm>
|
||||||
</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>
|
|
||||||
</template>
|
</template>
|
||||||
</FaDialog>
|
</FaDialog>
|
||||||
|
|
||||||
@@ -167,7 +133,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FaExportDialog
|
<FaExportDialog
|
||||||
v-model="exportModalVisible"
|
v-model="exportVisible"
|
||||||
:content-config="roleExportContentConfig"
|
:content-config="roleExportContentConfig"
|
||||||
:query-params="exportQueryParams"
|
:query-params="exportQueryParams"
|
||||||
:page-data="data"
|
:page-data="data"
|
||||||
@@ -177,18 +143,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h, computed, ref, reactive } from "vue";
|
|
||||||
import { useTable } from "@/hooks/core/useTable";
|
import { useTable } from "@/hooks/core/useTable";
|
||||||
import FaTable from "@/components/tables/fa-table/index.vue";
|
import { useImportExport } from "@/hooks/core/useImportExport";
|
||||||
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 { renderTableOperationCell, type TableOperationAction } from "@utils/table";
|
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
|
||||||
import type { IObject } from "@/components/modal/types";
|
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 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 { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||||
import type { ColumnOption } from "@/types/component";
|
import type { ColumnOption } from "@/types/component";
|
||||||
import RoleAPI, {
|
import RoleAPI, {
|
||||||
@@ -196,7 +155,6 @@ import RoleAPI, {
|
|||||||
type RoleTable,
|
type RoleTable,
|
||||||
type TablePageQuery,
|
type TablePageQuery,
|
||||||
} from "@/api/module_system/role";
|
} from "@/api/module_system/role";
|
||||||
import { ElMessage, ElMessageBox, ElTag } from "element-plus";
|
|
||||||
import { useAuth } from "@/hooks/core/useAuth";
|
import { useAuth } from "@/hooks/core/useAuth";
|
||||||
import { useUserStore } from "@stores";
|
import { useUserStore } from "@stores";
|
||||||
import PermissonDrawer from "./components/PermissonDrawer.vue";
|
import PermissonDrawer from "./components/PermissonDrawer.vue";
|
||||||
@@ -553,6 +511,25 @@ const roleExportContentConfig = computed(() => ({
|
|||||||
|
|
||||||
const detailFormData = ref<RoleTable>({} as RoleTable);
|
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>({
|
const formData = ref<RoleForm>({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
@@ -649,7 +626,7 @@ const initialFormData: RoleForm = {
|
|||||||
description: undefined,
|
description: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportModalVisible = ref(false);
|
const { exportVisible, openExport } = useImportExport();
|
||||||
|
|
||||||
async function handleSearchBarSearch(params: RoleSearchForm) {
|
async function handleSearchBarSearch(params: RoleSearchForm) {
|
||||||
await searchBarRef.value?.validate?.();
|
await searchBarRef.value?.validate?.();
|
||||||
@@ -667,8 +644,8 @@ function onResetSearch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function resetForm() {
|
async function resetForm() {
|
||||||
dataFormRef.value?.ref?.resetFields();
|
dataFormRef.value?.resetFields();
|
||||||
dataFormRef.value?.ref?.clearValidate();
|
dataFormRef.value?.clearValidate();
|
||||||
Object.assign(formData, initialFormData);
|
Object.assign(formData, initialFormData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -765,10 +742,6 @@ async function handleMoreClick(status: string) {
|
|||||||
// 用户取消
|
// 用户取消
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openExportModal() {
|
|
||||||
exportModalVisible.value = true;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -58,86 +58,45 @@
|
|||||||
width="640px"
|
width="640px"
|
||||||
dialog-class="crud-embed-dialog"
|
dialog-class="crud-embed-dialog"
|
||||||
modal-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'">
|
<template v-if="dialogVisible.type === 'detail'">
|
||||||
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
|
<FaDescriptions
|
||||||
<ElDescriptions :column="2" border>
|
:column="2"
|
||||||
<ElDescriptionsItem label="租户名称" :span="2">
|
:data="detailFormData"
|
||||||
{{ detailFormData.name }}
|
:items="tenantDetailItems"
|
||||||
</ElDescriptionsItem>
|
max-height="75vh"
|
||||||
<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>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<ElScrollbar max-height="75vh" :view-style="{ overflowX: 'hidden' }">
|
<FaForm
|
||||||
<FaForm
|
:key="tenantFormRenderKey"
|
||||||
:key="tenantFormRenderKey"
|
scrollbar
|
||||||
ref="dataFormRef"
|
max-height="75vh"
|
||||||
v-model="formData"
|
ref="dataFormRef"
|
||||||
:items="tenantDialogFormItems"
|
v-model="formData"
|
||||||
:rules="rules"
|
:items="tenantDialogFormItems"
|
||||||
label-suffix=":"
|
:rules="rules"
|
||||||
:label-width="'auto'"
|
label-suffix=":"
|
||||||
label-position="right"
|
:label-width="'auto'"
|
||||||
:span="24"
|
label-position="right"
|
||||||
:gutter="16"
|
:span="24"
|
||||||
:show-reset="false"
|
:gutter="16"
|
||||||
:show-submit="false"
|
:show-reset="false"
|
||||||
class="crud-dialog-art-form"
|
: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>
|
|
||||||
</template>
|
</template>
|
||||||
</FaDialog>
|
</FaDialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { h, computed, ref, reactive } from "vue";
|
|
||||||
import { useTable } from "@/hooks/core/useTable";
|
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 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 { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||||
import type { ColumnOption } from "@/types/component";
|
import type { ColumnOption } from "@/types/component";
|
||||||
import TenantAPI, {
|
import TenantAPI, {
|
||||||
@@ -146,15 +105,6 @@ import TenantAPI, {
|
|||||||
type TenantTable,
|
type TenantTable,
|
||||||
type TenantUpdateForm,
|
type TenantUpdateForm,
|
||||||
} from "@/api/module_system/tenant";
|
} from "@/api/module_system/tenant";
|
||||||
import {
|
|
||||||
ElMessage,
|
|
||||||
ElMessageBox,
|
|
||||||
ElTag,
|
|
||||||
ElTooltip,
|
|
||||||
ElDropdown,
|
|
||||||
ElDropdownMenu,
|
|
||||||
ElDropdownItem,
|
|
||||||
} from "element-plus";
|
|
||||||
import { useAuth } from "@/hooks/core/useAuth";
|
import { useAuth } from "@/hooks/core/useAuth";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@@ -240,7 +190,7 @@ function formatTenantOperationCell(
|
|||||||
const inlineNodes = inline.map((a) =>
|
const inlineNodes = inline.map((a) =>
|
||||||
h(ElTooltip, { content: a.label, placement: "top" }, () =>
|
h(ElTooltip, { content: a.label, placement: "top" }, () =>
|
||||||
h("span", { class: "inline-flex" }, [
|
h("span", { class: "inline-flex" }, [
|
||||||
h(ArtButtonTable, {
|
h(FaButtonTable, {
|
||||||
type: a.artType,
|
type: a.artType,
|
||||||
icon: a.icon,
|
icon: a.icon,
|
||||||
onClick: a.run,
|
onClick: a.run,
|
||||||
@@ -264,7 +214,7 @@ function formatTenantOperationCell(
|
|||||||
default: () =>
|
default: () =>
|
||||||
h(ElTooltip, { content: "更多", placement: "top" }, () =>
|
h(ElTooltip, { content: "更多", placement: "top" }, () =>
|
||||||
h("span", { class: "inline-flex align-middle" }, [
|
h("span", { class: "inline-flex align-middle" }, [
|
||||||
h(ArtButtonTable, {
|
h(FaButtonTable, {
|
||||||
type: "more",
|
type: "more",
|
||||||
onClick: () => {},
|
onClick: () => {},
|
||||||
}),
|
}),
|
||||||
@@ -445,6 +395,24 @@ const {
|
|||||||
|
|
||||||
const detailFormData = ref<TenantTable>({ code: "", name: "", status: "0" });
|
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 currentEditId = ref<number | null>(null);
|
||||||
|
|
||||||
const formData = ref<TenantForm>({
|
const formData = ref<TenantForm>({
|
||||||
@@ -574,8 +542,8 @@ const tenantDialogFormItems = computed<FormItem[]>(() => [
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
async function resetForm() {
|
async function resetForm() {
|
||||||
dataFormRef.value?.ref?.resetFields();
|
dataFormRef.value?.resetFields();
|
||||||
dataFormRef.value?.ref?.clearValidate();
|
dataFormRef.value?.clearValidate();
|
||||||
Object.assign(formData, initialFormData);
|
Object.assign(formData, initialFormData);
|
||||||
currentEditId.value = null;
|
currentEditId.value = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,8 +67,8 @@
|
|||||||
:import-loading="uploadLoading"
|
:import-loading="uploadLoading"
|
||||||
:delete-loading="batchDeleting"
|
:delete-loading="batchDeleting"
|
||||||
@add="handleOpenDialog('create')"
|
@add="handleOpenDialog('create')"
|
||||||
@import="openImportModal"
|
@import="openImport"
|
||||||
@export="openExportModal"
|
@export="openExport"
|
||||||
@delete="handleBatchDelete"
|
@delete="handleBatchDelete"
|
||||||
@more="handleMoreClick"
|
@more="handleMoreClick"
|
||||||
/>
|
/>
|
||||||
@@ -95,121 +95,60 @@
|
|||||||
:title="dialogVisible.title"
|
:title="dialogVisible.title"
|
||||||
append-to-body
|
append-to-body
|
||||||
:size="drawerSize"
|
: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'">
|
<template v-if="dialogVisible.type === 'detail'">
|
||||||
<ElDescriptions :column="2" border>
|
<FaDescriptions
|
||||||
<ElDescriptionsItem label="编号" :span="2">
|
:column="2"
|
||||||
{{ detailFormData.id }}
|
:data="detailFormData"
|
||||||
</ElDescriptionsItem>
|
:items="userDetailItems"
|
||||||
<ElDescriptionsItem label="头像" :span="2">
|
:scrollbar="false"
|
||||||
<template v-if="detailFormData.avatar">
|
>
|
||||||
<ElAvatar :src="detailFormData.avatar" size="small"></ElAvatar>
|
<!-- 头像 → 自定义渲染 -->
|
||||||
</template>
|
<template #avatar="{ row }">
|
||||||
<template v-else>
|
<ElAvatar v-if="row?.avatar" :src="row?.avatar as string" size="small" />
|
||||||
<ElAvatar icon="UserFilled" size="small"></ElAvatar>
|
<ElAvatar v-else icon="UserFilled" size="small" />
|
||||||
</template>
|
</template>
|
||||||
</ElDescriptionsItem>
|
<!-- 性别 → 三种状态 Tag -->
|
||||||
<ElDescriptionsItem label="账号" :span="2">
|
<template #gender="{ row }">
|
||||||
{{ detailFormData.username }}
|
<ElTag v-if="row?.gender === '0'" type="success">男</ElTag>
|
||||||
</ElDescriptionsItem>
|
<ElTag v-else-if="row?.gender === '1'" type="warning">女</ElTag>
|
||||||
<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>
|
|
||||||
<ElTag v-else type="info">未知</ElTag>
|
<ElTag v-else type="info">未知</ElTag>
|
||||||
</ElDescriptionsItem>
|
</template>
|
||||||
<ElDescriptionsItem label="部门" :span="2">
|
<!-- 角色 → 数组 join 渲染 -->
|
||||||
{{ detailFormData.dept ? detailFormData.dept.name : "" }}
|
<template #roles="{ row }">
|
||||||
</ElDescriptionsItem>
|
{{ row?.roles ? (row.roles as any[]).map((item: any) => item.name).join("、") : "" }}
|
||||||
<ElDescriptionsItem label="角色" :span="2">
|
</template>
|
||||||
|
<!-- 岗位 → 数组 join 渲染 -->
|
||||||
|
<template #positions="{ row }">
|
||||||
{{
|
{{
|
||||||
detailFormData.roles ? detailFormData.roles.map((item) => item.name).join("、") : ""
|
row?.positions
|
||||||
}}
|
? (row.positions as any[]).map((item: any) => item.name).join("、")
|
||||||
</ElDescriptionsItem>
|
|
||||||
<ElDescriptionsItem label="岗位" :span="2">
|
|
||||||
{{
|
|
||||||
detailFormData.positions
|
|
||||||
? detailFormData.positions.map((item) => item.name).join("、")
|
|
||||||
: ""
|
: ""
|
||||||
}}
|
}}
|
||||||
</ElDescriptionsItem>
|
</template>
|
||||||
<ElDescriptionsItem label="邮箱" :span="2">
|
</FaDescriptions>
|
||||||
{{ 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>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<ElForm
|
<FaForm
|
||||||
|
:key="userFormRenderKey"
|
||||||
ref="dataFormRef"
|
ref="dataFormRef"
|
||||||
:model="formData"
|
v-model="formData"
|
||||||
|
:items="userDialogFormItems"
|
||||||
:rules="rules"
|
:rules="rules"
|
||||||
label-suffix=":"
|
label-suffix=":"
|
||||||
label-width="100px"
|
:label-width="'auto'"
|
||||||
label-position="right"
|
label-position="right"
|
||||||
|
:span="24"
|
||||||
|
:gutter="16"
|
||||||
|
:show-reset="false"
|
||||||
|
:show-submit="false"
|
||||||
|
class="crud-dialog-art-form"
|
||||||
>
|
>
|
||||||
<ElFormItem label="账号" prop="username">
|
<template #dept_id>
|
||||||
<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">
|
|
||||||
<ElTreeSelect
|
<ElTreeSelect
|
||||||
v-model="formData.dept_id"
|
v-model="formData.dept_id"
|
||||||
placeholder="请选择上级部门"
|
placeholder="请选择上级部门"
|
||||||
@@ -219,9 +158,8 @@
|
|||||||
check-strictly
|
check-strictly
|
||||||
:render-after-expand="false"
|
:render-after-expand="false"
|
||||||
/>
|
/>
|
||||||
</ElFormItem>
|
</template>
|
||||||
|
<template #role_ids>
|
||||||
<ElFormItem label="角色" prop="role_ids">
|
|
||||||
<ElSelect v-model="formData.role_ids" multiple placeholder="请选择角色">
|
<ElSelect v-model="formData.role_ids" multiple placeholder="请选择角色">
|
||||||
<ElOption
|
<ElOption
|
||||||
v-for="item in roleOptions"
|
v-for="item in roleOptions"
|
||||||
@@ -231,9 +169,8 @@
|
|||||||
:disabled="item.disabled"
|
:disabled="item.disabled"
|
||||||
/>
|
/>
|
||||||
</ElSelect>
|
</ElSelect>
|
||||||
</ElFormItem>
|
</template>
|
||||||
|
<template #position_ids>
|
||||||
<ElFormItem label="岗位" prop="position_ids">
|
|
||||||
<ElSelect v-model="formData.position_ids" multiple placeholder="请选择岗位">
|
<ElSelect v-model="formData.position_ids" multiple placeholder="请选择岗位">
|
||||||
<ElOption
|
<ElOption
|
||||||
v-for="item in positionOptions"
|
v-for="item in positionOptions"
|
||||||
@@ -243,60 +180,13 @@
|
|||||||
:disabled="item.disabled"
|
:disabled="item.disabled"
|
||||||
/>
|
/>
|
||||||
</ElSelect>
|
</ElSelect>
|
||||||
</ElFormItem>
|
</template>
|
||||||
|
</FaForm>
|
||||||
<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>
|
</template>
|
||||||
</FaDrawer>
|
</FaDrawer>
|
||||||
|
|
||||||
<FaImportDialog
|
<FaImportDialog
|
||||||
v-model="importModalVisible"
|
v-model="importVisible"
|
||||||
:content-config="userImportContentConfig"
|
:content-config="userImportContentConfig"
|
||||||
default-template-file-name="user_import_template.xlsx"
|
default-template-file-name="user_import_template.xlsx"
|
||||||
:loading="uploadLoading"
|
:loading="uploadLoading"
|
||||||
@@ -304,7 +194,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FaExportDialog
|
<FaExportDialog
|
||||||
v-model="exportModalVisible"
|
v-model="exportVisible"
|
||||||
:content-config="userExportContentConfig"
|
:content-config="userExportContentConfig"
|
||||||
:query-params="exportQueryParams"
|
:query-params="exportQueryParams"
|
||||||
:page-data="data"
|
:page-data="data"
|
||||||
@@ -319,20 +209,14 @@ defineOptions({
|
|||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
import { h, ref, reactive, computed, nextTick } from "vue";
|
|
||||||
import { UserFilled } from "@element-plus/icons-vue";
|
import { UserFilled } from "@element-plus/icons-vue";
|
||||||
import { useAppStore } from "@stores/modules/app.store";
|
import { useAppStore } from "@stores/modules/app.store";
|
||||||
import { DeviceEnum } from "@/enums/settings/device.enum";
|
import { DeviceEnum } from "@/enums/settings/device.enum";
|
||||||
import { ResultEnum } from "@/enums/api/result.enum";
|
import { ResultEnum } from "@/enums/api/result.enum";
|
||||||
import { useTable } from "@/hooks/core/useTable";
|
import { useTable } from "@/hooks/core/useTable";
|
||||||
import FaTable from "@/components/tables/fa-table/index.vue";
|
import { useImportExport } from "@/hooks/core/useImportExport";
|
||||||
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 type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
||||||
import FaDrawer from "@/components/modal/fa-drawer/index.vue";
|
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||||
import FaImportDialog from "@/components/modal/fa-import-dialog/index.vue";
|
|
||||||
import FaExportDialog from "@/components/modal/fa-export-dialog/index.vue";
|
|
||||||
import type { IContentConfig, IObject } from "@/components/modal/types";
|
import type { IContentConfig, IObject } from "@/components/modal/types";
|
||||||
import UserAPI, {
|
import UserAPI, {
|
||||||
type UserForm,
|
type UserForm,
|
||||||
@@ -347,6 +231,7 @@ import DeptTree from "./components/DeptTree.vue";
|
|||||||
import UserTableSelect from "./components/UserTableSelect.vue";
|
import UserTableSelect from "./components/UserTableSelect.vue";
|
||||||
import { useUserStore } from "@stores";
|
import { useUserStore } from "@stores";
|
||||||
import { ElMessage, ElMessageBox, ElTag, ElAvatar } from "element-plus";
|
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 { useAuth } from "@/hooks/core/useAuth";
|
||||||
import type { ColumnOption } from "@/types/component";
|
import type { ColumnOption } from "@/types/component";
|
||||||
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
|
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 submitLoading = ref(false);
|
||||||
const uploadLoading = ref(false);
|
const uploadLoading = ref(false);
|
||||||
const batchDeleting = ref(false);
|
const batchDeleting = ref(false);
|
||||||
@@ -465,10 +351,113 @@ const drawerSize = computed(() => (appStore.device === DeviceEnum.DESKTOP ? "450
|
|||||||
const deptOptions = ref<OptionType[]>();
|
const deptOptions = ref<OptionType[]>();
|
||||||
const roleOptions = ref<Array<{ value: number; label: string; disabled?: boolean }>>();
|
const roleOptions = ref<Array<{ value: number; label: string; disabled?: boolean }>>();
|
||||||
const positionOptions = ref<Array<{ value: number; label: string; disabled?: boolean }>>();
|
const positionOptions = ref<Array<{ value: number; label: string; disabled?: boolean }>>();
|
||||||
const importModalVisible = ref(false);
|
const { importVisible, exportVisible, openImport, openExport } = useImportExport();
|
||||||
const exportModalVisible = ref(false);
|
|
||||||
const detailFormData = ref<UserInfo>({});
|
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>({
|
const searchForm = ref<UserSearchForm>({
|
||||||
username: undefined,
|
username: undefined,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
@@ -836,21 +825,13 @@ async function handleDeptNodeClick() {
|
|||||||
await getData();
|
await getData();
|
||||||
}
|
}
|
||||||
|
|
||||||
function openImportModal() {
|
|
||||||
importModalVisible.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function openExportModal() {
|
|
||||||
exportModalVisible.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleImportUpload(formDataUpload: FormData) {
|
async function handleImportUpload(formDataUpload: FormData) {
|
||||||
uploadLoading.value = true;
|
uploadLoading.value = true;
|
||||||
try {
|
try {
|
||||||
const response = await UserAPI.importUser(formDataUpload);
|
const response = await UserAPI.importUser(formDataUpload);
|
||||||
if (response.data.code === ResultEnum.SUCCESS) {
|
if (response.data.code === ResultEnum.SUCCESS) {
|
||||||
ElMessage.success(`${response.data.msg},${response.data.data}`);
|
ElMessage.success(`${response.data.msg},${response.data.data}`);
|
||||||
importModalVisible.value = false;
|
importVisible.value = false;
|
||||||
await refreshData();
|
await refreshData();
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error(response.data.msg || "导入失败");
|
ElMessage.error(response.data.msg || "导入失败");
|
||||||
@@ -893,7 +874,9 @@ async function handleOpenDialog(type: "create" | "update" | "detail", id?: numbe
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dialogVisible.title = "新增用户";
|
dialogVisible.title = "新增用户";
|
||||||
|
Object.assign(formData.value, initialFormData);
|
||||||
formData.value.id = undefined;
|
formData.value.id = undefined;
|
||||||
|
userFormRenderKey.value += 1;
|
||||||
}
|
}
|
||||||
dialogVisible.visible = true;
|
dialogVisible.visible = true;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
@@ -928,7 +911,7 @@ async function handleOpenDialog(type: "create" | "update" | "detail", id?: numbe
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
dataFormRef.value.validate(async (valid: boolean) => {
|
dataFormRef.value?.validate(async (valid: boolean) => {
|
||||||
if (!valid) return;
|
if (!valid) return;
|
||||||
submitLoading.value = true;
|
submitLoading.value = true;
|
||||||
const id = formData.value.id;
|
const id = formData.value.id;
|
||||||
|
|||||||
@@ -778,8 +778,8 @@ const initialFormData: Partial<NodeForm> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async function resetForm() {
|
async function resetForm() {
|
||||||
dataFormRef.value?.ref?.resetFields();
|
dataFormRef.value?.resetFields();
|
||||||
dataFormRef.value?.ref?.clearValidate();
|
dataFormRef.value?.clearValidate();
|
||||||
Object.assign(formData, initialFormData);
|
Object.assign(formData, initialFormData);
|
||||||
argsList.value = [];
|
argsList.value = [];
|
||||||
kwargsList.value = [];
|
kwargsList.value = [];
|
||||||
@@ -886,7 +886,7 @@ function handleOpenExecuteDialog(row: NodeTable) {
|
|||||||
function handleCloseExecuteDialog() {
|
function handleCloseExecuteDialog() {
|
||||||
executeDialogVisible.value = false;
|
executeDialogVisible.value = false;
|
||||||
currentExecuteNode.value = null;
|
currentExecuteNode.value = null;
|
||||||
executeFormRef.value?.ref?.resetFields();
|
executeFormRef.value?.resetFields();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleExecuteNode() {
|
async function handleExecuteNode() {
|
||||||
|
|||||||
@@ -454,8 +454,8 @@ const rules: FormRules = {
|
|||||||
function resetForm() {
|
function resetForm() {
|
||||||
Object.assign(formData.value, defaultForm());
|
Object.assign(formData.value, defaultForm());
|
||||||
editingId.value = null;
|
editingId.value = null;
|
||||||
formRef.value?.ref?.resetFields();
|
formRef.value?.resetFields();
|
||||||
formRef.value?.ref?.clearValidate();
|
formRef.value?.clearValidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCloseDialog() {
|
function handleCloseDialog() {
|
||||||
|
|||||||
@@ -241,7 +241,29 @@ export default ({ mode }: { mode: string }) => {
|
|||||||
vue(),
|
vue(),
|
||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
AutoImport({
|
AutoImport({
|
||||||
imports: ["vue", "vue-router", "pinia", "@vueuse/core", "vue-i18n"],
|
imports: [
|
||||||
|
"vue",
|
||||||
|
"vue-router",
|
||||||
|
"pinia",
|
||||||
|
"@vueuse/core",
|
||||||
|
"vue-i18n",
|
||||||
|
// Element Plus 组件/API:在 <script> 中通过 h() 或直接调用时需要自动导入
|
||||||
|
{
|
||||||
|
"element-plus": [
|
||||||
|
"ElMessageBox",
|
||||||
|
"ElTag",
|
||||||
|
"ElTooltip",
|
||||||
|
"ElDropdown",
|
||||||
|
"ElDropdownMenu",
|
||||||
|
"ElDropdownItem",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// 自定义组件:在 <script> 中通过 h() 使用时需要自动导入
|
||||||
|
{ "@/components/forms/fa-button-table/index.vue": [["default", "FaButtonTable"]] },
|
||||||
|
{ "@/components/others/fa-json-pretty/index.vue": [["default", "JsonPretty"]] },
|
||||||
|
{ "@/components/others/fa-copy-button/index.vue": [["default", "CopyButton"]] },
|
||||||
|
{ "@/components/others/fa-menu-routeIcon/index.vue": [["default", "MenuRouteIcon"]] },
|
||||||
|
],
|
||||||
dts: "src/types/import/auto-imports.d.ts",
|
dts: "src/types/import/auto-imports.d.ts",
|
||||||
resolvers: [ElementPlusResolver({ importStyle: "sass" })],
|
resolvers: [ElementPlusResolver({ importStyle: "sass" })],
|
||||||
eslintrc: {
|
eslintrc: {
|
||||||
@@ -254,7 +276,6 @@ export default ({ mode }: { mode: string }) => {
|
|||||||
Components({
|
Components({
|
||||||
dts: "src/types/import/components.d.ts",
|
dts: "src/types/import/components.d.ts",
|
||||||
resolvers: [ElementPlusResolver({ importStyle: "sass" })],
|
resolvers: [ElementPlusResolver({ importStyle: "sass" })],
|
||||||
/** art-* 布局组件以 src/layouts 为唯一源码目录(与 src/layouts/index.vue 一致),避免与旧路径重复 */
|
|
||||||
dirs: ["src/components", "src/**/components"],
|
dirs: ["src/components", "src/**/components"],
|
||||||
}),
|
}),
|
||||||
ElementPlus({
|
ElementPlus({
|
||||||
|
|||||||
Reference in New Issue
Block a user