mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-10-13 12:49:30 +00:00
refactor(crud): 提取通用CRUD逻辑与清理样式
This commit is contained in:
@@ -205,7 +205,7 @@ export default [
|
|||||||
"no-unexpected-multiline": "error",
|
"no-unexpected-multiline": "error",
|
||||||
|
|
||||||
// TypeScript 规则
|
// TypeScript 规则
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
"@typescript-eslint/no-explicit-any": "warn",
|
||||||
"@typescript-eslint/ban-ts-comment": "off",
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
"@typescript-eslint/no-empty-function": "off",
|
"@typescript-eslint/no-empty-function": "off",
|
||||||
"@typescript-eslint/no-empty-object-type": "off",
|
"@typescript-eslint/no-empty-object-type": "off",
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* 确认弹窗 —— 封装 ElMessageBox.confirm 常用配置
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** 删除确认 */
|
||||||
|
export async function confirmDelete(message = "确认删除该项数据?"): Promise<void> {
|
||||||
|
await ElMessageBox.confirm(message, "警告", {
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 批量删除确认 */
|
||||||
|
export async function confirmBatchDelete(count: number): Promise<void> {
|
||||||
|
await ElMessageBox.confirm(`确定删除选中的 ${count} 条数据吗?`, "批量删除", {
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 状态切换确认 */
|
||||||
|
export async function confirmToggleStatus(status: string): Promise<void> {
|
||||||
|
await ElMessageBox.confirm(`确认${status === "0" ? "启用" : "停用"}该项数据?`, "警告", {
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 通用确认 */
|
||||||
|
export async function confirmAction(message: string, title = "警告"): Promise<void> {
|
||||||
|
await ElMessageBox.confirm(message, title, {
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
export type DialogType = "create" | "update" | "detail";
|
||||||
|
|
||||||
|
export interface CrudDialogState {
|
||||||
|
title: string;
|
||||||
|
visible: boolean;
|
||||||
|
type: DialogType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CRUD 对话框状态管理
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const { dialogVisible, openDialog, closeDialog } = useCrudDialog();
|
||||||
|
* openDialog("create"); // 新增
|
||||||
|
* openDialog("update", "修改XX"); // 编辑
|
||||||
|
* openDialog("detail", "XX详情"); // 详情
|
||||||
|
* closeDialog(); // 关闭
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useCrudDialog() {
|
||||||
|
const dialogVisible = reactive<CrudDialogState>({
|
||||||
|
title: "",
|
||||||
|
visible: false,
|
||||||
|
type: "create",
|
||||||
|
});
|
||||||
|
|
||||||
|
function openDialog(type: DialogType, title?: string) {
|
||||||
|
dialogVisible.type = type;
|
||||||
|
if (type === "create") {
|
||||||
|
dialogVisible.title = title ?? "新增";
|
||||||
|
} else if (type === "update") {
|
||||||
|
dialogVisible.title = title ?? "修改";
|
||||||
|
} else {
|
||||||
|
dialogVisible.title = title ?? "详情";
|
||||||
|
}
|
||||||
|
dialogVisible.visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDialog() {
|
||||||
|
dialogVisible.visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
dialogVisible,
|
||||||
|
openDialog,
|
||||||
|
closeDialog,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
import type { Ref } from "vue";
|
||||||
|
import type { CrudDialogState, DialogType } from "./useCrudDialog";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CRUD 表单管理
|
||||||
|
*
|
||||||
|
* 将对话框 + 表单 + 提交逻辑统一管理,减少每个 CRUD 页面 ~60 行重复代码
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const crud = useCrudForm<DeptForm>({
|
||||||
|
* formData: ref({ id: undefined, name: "", status: "0" }),
|
||||||
|
* initialFormData: { id: undefined, name: "", status: "0" },
|
||||||
|
* dialogVisible, // 来自 useCrudDialog()
|
||||||
|
* dataFormRef, // FaForm 的 ref
|
||||||
|
* formRenderKey, // 用于 :key 重新渲染
|
||||||
|
* detailApi: DeptAPI.detailDept,
|
||||||
|
* createApi: DeptAPI.createDept,
|
||||||
|
* updateApi: DeptAPI.updateDept,
|
||||||
|
* onCreateSuccess: async () => { await refreshCreate(); },
|
||||||
|
* onUpdateSuccess: async () => { await refreshUpdate(); },
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // 模板中
|
||||||
|
* crud.handleOpenDialog("create");
|
||||||
|
* crud.handleOpenDialog("update", row.id);
|
||||||
|
* crud.handleSubmit();
|
||||||
|
* crud.handleCloseDialog();
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useCrudForm<T extends object>(options: {
|
||||||
|
/** 响应式表单数据 */
|
||||||
|
formData: Ref<T>;
|
||||||
|
/** 初始表单值(用于重置) */
|
||||||
|
initialFormData: T;
|
||||||
|
/** 对话框状态(来自 useCrudDialog) */
|
||||||
|
dialogVisible: CrudDialogState;
|
||||||
|
/** FaForm 实例 ref */
|
||||||
|
dataFormRef: Ref<InstanceType<
|
||||||
|
(typeof import("./../../components/forms/fa-form/index.vue"))["default"]
|
||||||
|
> | null>;
|
||||||
|
/** 表单渲染 key(用于 :key 重新渲染) */
|
||||||
|
formRenderKey: Ref<number>;
|
||||||
|
/** 详情 API(传入 id,返回 response) */
|
||||||
|
detailApi?: (id: number) => Promise<{ data: { data?: T } }>;
|
||||||
|
/** 新增 API */
|
||||||
|
createApi?: (form: T) => Promise<unknown>;
|
||||||
|
/** 修改 API(接收 id + form) */
|
||||||
|
updateApi?: (id: number, form: T) => Promise<unknown>;
|
||||||
|
/** 各类型打开时的标题映射 */
|
||||||
|
titles?: Partial<Record<DialogType, string>>;
|
||||||
|
/** 详情数据(用于 FaDescriptions 展示) */
|
||||||
|
detailFormData?: Ref<Partial<T>>;
|
||||||
|
/** 新增成功回调 */
|
||||||
|
onCreateSuccess?: () => Promise<void> | void;
|
||||||
|
/** 修改成功回调 */
|
||||||
|
onUpdateSuccess?: () => Promise<void> | void;
|
||||||
|
/** 提交成功后额外操作(如刷新字典、配置等) */
|
||||||
|
onSubmitSuccess?: (formData: T) => Promise<void> | void;
|
||||||
|
}) {
|
||||||
|
const {
|
||||||
|
formData,
|
||||||
|
initialFormData,
|
||||||
|
dialogVisible,
|
||||||
|
dataFormRef,
|
||||||
|
formRenderKey,
|
||||||
|
detailApi,
|
||||||
|
createApi,
|
||||||
|
updateApi,
|
||||||
|
titles,
|
||||||
|
detailFormData,
|
||||||
|
onCreateSuccess,
|
||||||
|
onUpdateSuccess,
|
||||||
|
onSubmitSuccess,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
const submitLoading = ref(false);
|
||||||
|
|
||||||
|
/** 重置表单 */
|
||||||
|
async function resetForm() {
|
||||||
|
dataFormRef.value?.resetFields();
|
||||||
|
dataFormRef.value?.clearValidate();
|
||||||
|
Object.assign(formData.value, initialFormData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 关闭对话框并重置表单 */
|
||||||
|
async function handleCloseDialog() {
|
||||||
|
dialogVisible.visible = false;
|
||||||
|
await resetForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 打开对话框 */
|
||||||
|
async function handleOpenDialog(type: DialogType, id?: number, extra?: Record<string, unknown>) {
|
||||||
|
dialogVisible.type = type;
|
||||||
|
const titleMap = titles ?? {};
|
||||||
|
const defaultTitles: Record<DialogType, string> = {
|
||||||
|
create: "新增",
|
||||||
|
update: "修改",
|
||||||
|
detail: "详情",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (id && detailApi) {
|
||||||
|
const response = await detailApi(id);
|
||||||
|
const data = response.data.data;
|
||||||
|
if (type === "detail") {
|
||||||
|
dialogVisible.title = titleMap.detail ?? defaultTitles.detail;
|
||||||
|
if (detailFormData) {
|
||||||
|
Object.assign(detailFormData.value, data ?? {});
|
||||||
|
}
|
||||||
|
} else if (type === "update") {
|
||||||
|
dialogVisible.title = titleMap.update ?? defaultTitles.update;
|
||||||
|
Object.assign(formData.value, data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dialogVisible.title = titleMap.create ?? defaultTitles.create;
|
||||||
|
Object.assign(formData.value, initialFormData);
|
||||||
|
(formData.value as Record<string, unknown>).id = undefined;
|
||||||
|
if (extra) {
|
||||||
|
Object.assign(formData.value, extra);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
formRenderKey.value += 1;
|
||||||
|
dialogVisible.visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 提交表单 */
|
||||||
|
async function handleSubmit() {
|
||||||
|
const form = dataFormRef.value;
|
||||||
|
if (!form) return;
|
||||||
|
const valid = await (form.validate as () => Promise<boolean>)().catch(() => false);
|
||||||
|
if (!valid) return;
|
||||||
|
|
||||||
|
submitLoading.value = true;
|
||||||
|
const id = (formData.value as Record<string, unknown>).id as number | undefined;
|
||||||
|
try {
|
||||||
|
if (id && updateApi) {
|
||||||
|
await updateApi(id, { id, ...formData.value });
|
||||||
|
await onUpdateSuccess?.();
|
||||||
|
} else if (createApi) {
|
||||||
|
await createApi(formData.value);
|
||||||
|
await onCreateSuccess?.();
|
||||||
|
}
|
||||||
|
dialogVisible.visible = false;
|
||||||
|
await resetForm();
|
||||||
|
await onSubmitSuccess?.(formData.value);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
submitLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
submitLoading,
|
||||||
|
resetForm,
|
||||||
|
handleCloseDialog,
|
||||||
|
handleOpenDialog,
|
||||||
|
handleSubmit,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import type { ComputedRef, Ref } from "vue";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表格多选状态管理
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const { selectedRows, selectedIds, batchDeleting, onTableSelectionChange } =
|
||||||
|
* useTableSelection<DictTable>();
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function useTableSelection<T extends object>() {
|
||||||
|
const selectedRows = ref<T[]>([]) as Ref<T[]>;
|
||||||
|
const selectedIds = computed(() =>
|
||||||
|
selectedRows.value
|
||||||
|
.map((r) => (r as Record<string, unknown>).id)
|
||||||
|
.filter((id): id is number => id != null && !Number.isNaN(id))
|
||||||
|
) as ComputedRef<number[]>;
|
||||||
|
const batchDeleting = ref(false);
|
||||||
|
|
||||||
|
function onTableSelectionChange(rows: T[]) {
|
||||||
|
selectedRows.value = rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSelection() {
|
||||||
|
selectedRows.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
selectedRows,
|
||||||
|
selectedIds,
|
||||||
|
batchDeleting,
|
||||||
|
onTableSelectionChange,
|
||||||
|
clearSelection,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// CRUD 页面通用样式
|
||||||
|
// 从各 module_system 视图文件中提取的重复样式
|
||||||
|
|
||||||
|
// FaForm 在 Dialog 中的布局优化:隐藏最后一列操作按钮、内容区域自适应宽度
|
||||||
|
.crud-dialog-art-form {
|
||||||
|
:deep(.el-row > .el-col:last-child) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item__content) {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表格操作列按钮垂直居中
|
||||||
|
:deep(.inline-flex) {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
@@ -83,18 +83,11 @@
|
|||||||
|
|
||||||
// placeholder
|
// placeholder
|
||||||
@mixin placeholder($color: #bbb) {
|
@mixin placeholder($color: #bbb) {
|
||||||
// Firefox
|
|
||||||
&::placeholder {
|
&::placeholder {
|
||||||
color: $color;
|
color: $color;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internet Explorer 10–11
|
|
||||||
/* stylelint-disable-next-line selector-no-vendor-prefix */
|
|
||||||
&:-ms-input-placeholder {
|
|
||||||
color: $color;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Safari and Chrome (legacy)
|
// Safari and Chrome (legacy)
|
||||||
/* stylelint-disable-next-line selector-no-vendor-prefix */
|
/* stylelint-disable-next-line selector-no-vendor-prefix */
|
||||||
&::-webkit-input-placeholder {
|
&::-webkit-input-placeholder {
|
||||||
@@ -106,26 +99,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//背景透明,文字不透明。兼容IE8
|
|
||||||
@mixin betterTransparentize($color, $alpha) {
|
|
||||||
$c: rgba($color, $alpha);
|
|
||||||
$ie_c: ie_hex_str($c);
|
|
||||||
|
|
||||||
zoom: 1;
|
|
||||||
background: rgba($color, 1);
|
|
||||||
background: $c;
|
|
||||||
background: transparent \9;
|
|
||||||
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#{$ie_c}, endColorstr=#{$ie_c})";
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#{$ie_c}, endColorstr=#{$ie_c});
|
|
||||||
}
|
|
||||||
|
|
||||||
//添加浏览器前缀
|
|
||||||
@mixin browserPrefix($propertyName, $value) {
|
|
||||||
@each $prefix in -webkit-, -moz-, -ms-, -o-, "" {
|
|
||||||
#{$prefix}#{$propertyName}: $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 边框
|
// 边框
|
||||||
@mixin border($color: red) {
|
@mixin border($color: red) {
|
||||||
border: 1px solid $color;
|
border: 1px solid $color;
|
||||||
|
|||||||
@@ -21,3 +21,6 @@
|
|||||||
|
|
||||||
// 主题切换圆形扩散动画
|
// 主题切换圆形扩散动画
|
||||||
@use "./core/theme-animation";
|
@use "./core/theme-animation";
|
||||||
|
|
||||||
|
// CRUD 页面通用样式
|
||||||
|
@use "./core/crud";
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* 清理查询参数中的空数组字段,避免将 [] 传给后端
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const query = cleanEmptyArrayParams(searchParams, ["created_time", "updated_time"]);
|
||||||
|
* // { created_time: [], ... } → { created_time: undefined, ... }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function cleanEmptyArrayParams<T extends Record<string, unknown>>(
|
||||||
|
params: T,
|
||||||
|
arrayKeys: string[] = ["created_time", "updated_time"]
|
||||||
|
): T {
|
||||||
|
const p = { ...params } as Record<string, unknown>;
|
||||||
|
for (const key of arrayKeys) {
|
||||||
|
if (Array.isArray(p[key]) && (p[key] as unknown[]).length === 0) {
|
||||||
|
p[key] = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从搜索参数中移除分页字段,生成导出查询参数
|
||||||
|
*/
|
||||||
|
export function stripPaginationParams<T extends Record<string, unknown>>(
|
||||||
|
params: T,
|
||||||
|
pagKeys: string[] = ["current", "size", "page_no", "page_size"]
|
||||||
|
): Record<string, unknown> {
|
||||||
|
const p = { ...(params as object) } as Record<string, unknown>;
|
||||||
|
for (const key of pagKeys) {
|
||||||
|
delete p[key];
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
@@ -146,10 +146,13 @@ defineOptions({
|
|||||||
|
|
||||||
import { ref, reactive, computed, nextTick } from "vue";
|
import { ref, reactive, computed, nextTick } from "vue";
|
||||||
import { Edit } from "@element-plus/icons-vue";
|
import { Edit } from "@element-plus/icons-vue";
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
import { ElMessage } 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 { useCrudDialog } from "@/hooks/core/useCrudDialog";
|
||||||
|
import { useTableSelection } from "@/hooks/core/useTableSelection";
|
||||||
|
import { confirmDelete, confirmBatchDelete } from "@/hooks/core/useConfirm";
|
||||||
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
import type { SearchFormItem } from "@/components/forms/fa-search-bar/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";
|
||||||
@@ -241,23 +244,11 @@ const editingRowId = ref<string | null>(null);
|
|||||||
const editingTitle = ref("");
|
const editingTitle = ref("");
|
||||||
|
|
||||||
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
||||||
const selectedRows = ref<ChatSession[]>([]);
|
const { selectedIds, batchDeleting, onTableSelectionChange } = useTableSelection<ChatSession>();
|
||||||
const selectedIds = computed(() =>
|
|
||||||
selectedRows.value.map((r) => r.id).filter((id): id is string => Boolean(id))
|
|
||||||
);
|
|
||||||
const batchDeleting = ref(false);
|
|
||||||
|
|
||||||
function onTableSelectionChange(rows: ChatSession[]) {
|
|
||||||
selectedRows.value = rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteSessionRow(id: string) {
|
async function deleteSessionRow(id: string) {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm("确认删除该项数据?", "警告", {
|
await confirmDelete();
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
await AiChatAPI.deleteSession([id]);
|
await AiChatAPI.deleteSession([id]);
|
||||||
ElMessage.success("删除成功");
|
ElMessage.success("删除成功");
|
||||||
faTableRef.value?.elTableRef?.clearSelection();
|
faTableRef.value?.elTableRef?.clearSelection();
|
||||||
@@ -271,15 +262,11 @@ async function handleBatchDelete() {
|
|||||||
const ids = selectedIds.value;
|
const ids = selectedIds.value;
|
||||||
if (ids.length === 0) return;
|
if (ids.length === 0) return;
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 条数据吗?`, "批量删除", {
|
await confirmBatchDelete(ids.length);
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
batchDeleting.value = true;
|
batchDeleting.value = true;
|
||||||
await AiChatAPI.deleteSession(ids);
|
await AiChatAPI.deleteSession(ids as unknown as string[]);
|
||||||
ElMessage.success("删除成功");
|
ElMessage.success("删除成功");
|
||||||
selectedRows.value = [];
|
faTableRef.value?.elTableRef?.clearSelection();
|
||||||
await refreshRemove();
|
await refreshRemove();
|
||||||
} catch {
|
} catch {
|
||||||
// 用户取消
|
// 用户取消
|
||||||
@@ -392,11 +379,7 @@ const formData = ref({
|
|||||||
title: "",
|
title: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const dialogVisible = reactive({
|
const { dialogVisible, closeDialog } = useCrudDialog();
|
||||||
title: "",
|
|
||||||
visible: false,
|
|
||||||
type: "create" as "create" | "detail",
|
|
||||||
});
|
|
||||||
|
|
||||||
const detailFormData = ref<Partial<ChatSessionDetail>>({});
|
const detailFormData = ref<Partial<ChatSessionDetail>>({});
|
||||||
|
|
||||||
@@ -447,11 +430,11 @@ async function onResetSearch() {
|
|||||||
async function resetForm() {
|
async function resetForm() {
|
||||||
dataFormRef.value?.resetFields();
|
dataFormRef.value?.resetFields();
|
||||||
dataFormRef.value?.clearValidate();
|
dataFormRef.value?.clearValidate();
|
||||||
Object.assign(formData, initialFormData);
|
Object.assign(formData.value, initialFormData);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCloseDialog() {
|
async function handleCloseDialog() {
|
||||||
dialogVisible.visible = false;
|
closeDialog();
|
||||||
await resetForm();
|
await resetForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -610,12 +593,4 @@ pre {
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.crud-dialog-art-form :deep(.el-row > .el-col:last-child) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.crud-dialog-art-form :deep(.el-form-item__content) {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!-- 示例 CRUD:与角色页同一套 Art 布局;弹窗与 Crud 一致(FaDialog + crud-embed-dialog) -->
|
<!-- 示例 CRUD:与角色页同一套 Fa 布局;弹窗与 Crud 一致(FaDialog + crud-embed-dialog) -->
|
||||||
<template>
|
<template>
|
||||||
<div class="fa-full-height">
|
<div class="fa-full-height">
|
||||||
<FaSearchBarWithAudit
|
<FaSearchBarWithAudit
|
||||||
@@ -157,6 +157,10 @@ 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 { useImportExport } from "@/hooks/core/useImportExport";
|
import { useImportExport } from "@/hooks/core/useImportExport";
|
||||||
|
import { useCrudDialog } from "@/hooks/core/useCrudDialog";
|
||||||
|
import { useTableSelection } from "@/hooks/core/useTableSelection";
|
||||||
|
import { confirmDelete, confirmBatchDelete, confirmAction } from "@/hooks/core/useConfirm";
|
||||||
|
import { cleanEmptyArrayParams, stripPaginationParams } from "@/utils/query";
|
||||||
import type { IContentConfig, IObject } from "@/components/modal/types";
|
import type { IContentConfig, IObject } from "@/components/modal/types";
|
||||||
import type { AuditSearchFormParams } from "@/components/forms/fa-search-bar/auditSearchFormItems";
|
import type { AuditSearchFormParams } from "@/components/forms/fa-search-bar/auditSearchFormItems";
|
||||||
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||||
@@ -178,10 +182,10 @@ const { hasAuth } = useAuth();
|
|||||||
type DemoSearchFormParams = { name?: string; status?: string } & AuditSearchFormParams;
|
type DemoSearchFormParams = { name?: string; status?: string } & AuditSearchFormParams;
|
||||||
|
|
||||||
function normalizeDemoQuery(params: Record<string, unknown>): DemoPageQuery {
|
function normalizeDemoQuery(params: Record<string, unknown>): DemoPageQuery {
|
||||||
const p = { ...params } as Record<string, unknown>;
|
return cleanEmptyArrayParams({ ...params }, [
|
||||||
if (Array.isArray(p.created_time) && p.created_time.length === 0) p.created_time = undefined;
|
"created_time",
|
||||||
if (Array.isArray(p.updated_time) && p.updated_time.length === 0) p.updated_time = undefined;
|
"updated_time",
|
||||||
return p as unknown as DemoPageQuery;
|
]) as unknown as DemoPageQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchForm = ref<DemoSearchFormParams>({
|
const searchForm = ref<DemoSearchFormParams>({
|
||||||
@@ -226,15 +230,8 @@ const demoBusinessSearchItems = computed(() => [
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
||||||
const selectedRows = ref<DemoTable[]>([]);
|
const { selectedRows, selectedIds, batchDeleting, onTableSelectionChange } =
|
||||||
const selectedIds = computed(() =>
|
useTableSelection<DemoTable>();
|
||||||
selectedRows.value.map((r) => r.id).filter((id): id is number => id != null && !Number.isNaN(id))
|
|
||||||
);
|
|
||||||
const batchDeleting = ref(false);
|
|
||||||
|
|
||||||
function onTableSelectionChange(rows: DemoTable[]) {
|
|
||||||
selectedRows.value = rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
columns,
|
columns,
|
||||||
@@ -341,11 +338,7 @@ const demoCrudCols = computed(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
const exportQueryParams = computed(() => {
|
const exportQueryParams = computed(() => {
|
||||||
const sp = { ...(searchParams as object) } as Record<string, unknown>;
|
const sp = stripPaginationParams(searchParams as Record<string, unknown>);
|
||||||
delete sp.current;
|
|
||||||
delete sp.size;
|
|
||||||
delete sp.page_no;
|
|
||||||
delete sp.page_size;
|
|
||||||
return normalizeDemoQuery(sp);
|
return normalizeDemoQuery(sp);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -369,11 +362,7 @@ const demoExportContentConfig = computed(() => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const dialogVisible = reactive({
|
const { dialogVisible } = useCrudDialog();
|
||||||
title: "",
|
|
||||||
visible: false,
|
|
||||||
type: "create" as "create" | "update" | "detail",
|
|
||||||
});
|
|
||||||
|
|
||||||
const detailFormData = ref<DemoTable>({});
|
const detailFormData = ref<DemoTable>({});
|
||||||
|
|
||||||
@@ -674,21 +663,13 @@ async function handleSubmit() {
|
|||||||
const deleteDemoRow = async (row: DemoTable) => {
|
const deleteDemoRow = async (row: DemoTable) => {
|
||||||
if (!row.id) return;
|
if (!row.id) return;
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(
|
await confirmDelete(`确定删除「${row.name ?? row.id}」吗?此操作不可恢复!`);
|
||||||
`确定删除「${row.name ?? row.id}」吗?此操作不可恢复!`,
|
|
||||||
"删除确认",
|
|
||||||
{
|
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
await DemoAPI.deleteDemo([row.id!]);
|
await DemoAPI.deleteDemo([row.id!]);
|
||||||
ElMessage.success("删除成功");
|
ElMessage.success("删除成功");
|
||||||
faTableRef.value?.elTableRef?.clearSelection();
|
faTableRef.value?.elTableRef?.clearSelection();
|
||||||
await refreshRemove();
|
await refreshRemove();
|
||||||
} catch {
|
} catch {
|
||||||
ElMessage.info("已取消删除");
|
// 用户取消
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -696,22 +677,14 @@ async function handleBatchDelete() {
|
|||||||
const ids = selectedIds.value;
|
const ids = selectedIds.value;
|
||||||
if (ids.length === 0) return;
|
if (ids.length === 0) return;
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(
|
await confirmBatchDelete(ids.length);
|
||||||
`确定删除选中的 ${ids.length} 条数据吗?此操作不可恢复!`,
|
|
||||||
"批量删除",
|
|
||||||
{
|
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
batchDeleting.value = true;
|
batchDeleting.value = true;
|
||||||
await DemoAPI.deleteDemo(ids);
|
await DemoAPI.deleteDemo(ids);
|
||||||
ElMessage.success("删除成功");
|
ElMessage.success("删除成功");
|
||||||
faTableRef.value?.elTableRef?.clearSelection();
|
faTableRef.value?.elTableRef?.clearSelection();
|
||||||
await refreshRemove();
|
await refreshRemove();
|
||||||
} catch {
|
} catch {
|
||||||
ElMessage.info("已取消删除");
|
// 用户取消
|
||||||
} finally {
|
} finally {
|
||||||
batchDeleting.value = false;
|
batchDeleting.value = false;
|
||||||
}
|
}
|
||||||
@@ -724,10 +697,9 @@ async function runBatchStatus(status: string) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(
|
await confirmAction(
|
||||||
`确认对选中的 ${ids.length} 条数据${status === "0" ? "启用" : "停用"}?`,
|
`确认对选中的 ${ids.length} 条数据${status === "0" ? "启用" : "停用"}?`,
|
||||||
"批量设置",
|
"批量设置"
|
||||||
{ confirmButtonText: "确定", cancelButtonText: "取消", type: "warning" }
|
|
||||||
);
|
);
|
||||||
await DemoAPI.batchDemo({ ids, status });
|
await DemoAPI.batchDemo({ ids, status });
|
||||||
ElMessage.success("操作成功");
|
ElMessage.success("操作成功");
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!-- 示例01 CRUD:与 demo 同一套 Art 布局;权限与接口为 module_example:demo01 / Demo01API -->
|
<!-- 示例01 CRUD:与 demo 同一套 Fa 布局;权限与接口为 module_example:demo01 / Demo01API -->
|
||||||
<template>
|
<template>
|
||||||
<div class="fa-full-height">
|
<div class="fa-full-height">
|
||||||
<FaSearchBarWithAudit
|
<FaSearchBarWithAudit
|
||||||
@@ -127,6 +127,10 @@ 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 { useImportExport } from "@/hooks/core/useImportExport";
|
import { useImportExport } from "@/hooks/core/useImportExport";
|
||||||
|
import { useCrudDialog } from "@/hooks/core/useCrudDialog";
|
||||||
|
import { useTableSelection } from "@/hooks/core/useTableSelection";
|
||||||
|
import { confirmDelete, confirmBatchDelete, confirmAction } from "@/hooks/core/useConfirm";
|
||||||
|
import { cleanEmptyArrayParams, stripPaginationParams } from "@/utils/query";
|
||||||
import type { IContentConfig, IObject } from "@/components/modal/types";
|
import type { IContentConfig, IObject } from "@/components/modal/types";
|
||||||
import type { AuditSearchFormParams } from "@/components/forms/fa-search-bar/auditSearchFormItems";
|
import type { AuditSearchFormParams } from "@/components/forms/fa-search-bar/auditSearchFormItems";
|
||||||
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||||
@@ -136,7 +140,6 @@ import Demo01API, {
|
|||||||
type Demo01PageQuery,
|
type Demo01PageQuery,
|
||||||
type Demo01Table,
|
type Demo01Table,
|
||||||
} from "@/api/module_example/demo01";
|
} from "@/api/module_example/demo01";
|
||||||
import { ElMessage, ElMessageBox, ElTag } from "element-plus";
|
|
||||||
import { ResultEnum } from "@/enums/api/result.enum";
|
import { ResultEnum } from "@/enums/api/result.enum";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@@ -153,10 +156,10 @@ type Demo01SearchFormParams = {
|
|||||||
} & AuditSearchFormParams;
|
} & AuditSearchFormParams;
|
||||||
|
|
||||||
function normalizeDemo01Query(params: Record<string, unknown>): Demo01PageQuery {
|
function normalizeDemo01Query(params: Record<string, unknown>): Demo01PageQuery {
|
||||||
const p = { ...params } as Record<string, unknown>;
|
return cleanEmptyArrayParams({ ...params }, [
|
||||||
if (Array.isArray(p.created_time) && p.created_time.length === 0) p.created_time = undefined;
|
"created_time",
|
||||||
if (Array.isArray(p.updated_time) && p.updated_time.length === 0) p.updated_time = undefined;
|
"updated_time",
|
||||||
return p as unknown as Demo01PageQuery;
|
]) as unknown as Demo01PageQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchForm = ref<Demo01SearchFormParams>({
|
const searchForm = ref<Demo01SearchFormParams>({
|
||||||
@@ -210,15 +213,8 @@ const demo01BusinessSearchItems = computed(() => [
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
||||||
const selectedRows = ref<Demo01Table[]>([]);
|
const { selectedRows, selectedIds, batchDeleting, onTableSelectionChange } =
|
||||||
const selectedIds = computed(() =>
|
useTableSelection<Demo01Table>();
|
||||||
selectedRows.value.map((r) => r.id).filter((id): id is number => id != null && !Number.isNaN(id))
|
|
||||||
);
|
|
||||||
const batchDeleting = ref(false);
|
|
||||||
|
|
||||||
function onTableSelectionChange(rows: Demo01Table[]) {
|
|
||||||
selectedRows.value = rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
columns,
|
columns,
|
||||||
@@ -300,11 +296,7 @@ const demo01CrudCols = computed(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
const exportQueryParams = computed(() => {
|
const exportQueryParams = computed(() => {
|
||||||
const sp = { ...(searchParams as object) } as Record<string, unknown>;
|
const sp = stripPaginationParams(searchParams as Record<string, unknown>);
|
||||||
delete sp.current;
|
|
||||||
delete sp.size;
|
|
||||||
delete sp.page_no;
|
|
||||||
delete sp.page_size;
|
|
||||||
return normalizeDemo01Query(sp);
|
return normalizeDemo01Query(sp);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -328,11 +320,7 @@ const demo01ExportContentConfig = computed(() => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const dialogVisible = reactive({
|
const { dialogVisible } = useCrudDialog();
|
||||||
title: "",
|
|
||||||
visible: false,
|
|
||||||
type: "create" as "create" | "update" | "detail",
|
|
||||||
});
|
|
||||||
|
|
||||||
const detailFormData = ref<Demo01Table>({});
|
const detailFormData = ref<Demo01Table>({});
|
||||||
|
|
||||||
@@ -530,17 +518,13 @@ async function handleSubmit() {
|
|||||||
const deleteDemo01Row = async (row: Demo01Table) => {
|
const deleteDemo01Row = async (row: Demo01Table) => {
|
||||||
if (!row.id) return;
|
if (!row.id) return;
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(
|
await confirmDelete(`确定删除「${row.name ?? row.id}」吗?此操作不可恢复!`);
|
||||||
`确定删除「${row.name ?? row.id}」吗?此操作不可恢复!`,
|
|
||||||
"删除确认",
|
|
||||||
{ confirmButtonText: "确定", cancelButtonText: "取消", type: "warning" }
|
|
||||||
);
|
|
||||||
await Demo01API.deleteDemo01([row.id!]);
|
await Demo01API.deleteDemo01([row.id!]);
|
||||||
ElMessage.success("删除成功");
|
ElMessage.success("删除成功");
|
||||||
faTableRef.value?.elTableRef?.clearSelection();
|
faTableRef.value?.elTableRef?.clearSelection();
|
||||||
await refreshRemove();
|
await refreshRemove();
|
||||||
} catch {
|
} catch {
|
||||||
ElMessage.info("已取消删除");
|
// 用户取消
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -548,18 +532,14 @@ async function handleBatchDelete() {
|
|||||||
const ids = selectedIds.value;
|
const ids = selectedIds.value;
|
||||||
if (ids.length === 0) return;
|
if (ids.length === 0) return;
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(
|
await confirmBatchDelete(ids.length);
|
||||||
`确定删除选中的 ${ids.length} 条数据吗?此操作不可恢复!`,
|
|
||||||
"批量删除",
|
|
||||||
{ confirmButtonText: "确定", cancelButtonText: "取消", type: "warning" }
|
|
||||||
);
|
|
||||||
batchDeleting.value = true;
|
batchDeleting.value = true;
|
||||||
await Demo01API.deleteDemo01(ids);
|
await Demo01API.deleteDemo01(ids);
|
||||||
ElMessage.success("删除成功");
|
ElMessage.success("删除成功");
|
||||||
faTableRef.value?.elTableRef?.clearSelection();
|
faTableRef.value?.elTableRef?.clearSelection();
|
||||||
await refreshRemove();
|
await refreshRemove();
|
||||||
} catch {
|
} catch {
|
||||||
ElMessage.info("已取消删除");
|
// 用户取消
|
||||||
} finally {
|
} finally {
|
||||||
batchDeleting.value = false;
|
batchDeleting.value = false;
|
||||||
}
|
}
|
||||||
@@ -572,17 +552,16 @@ async function runBatchStatus(status: string) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(
|
await confirmAction(
|
||||||
`确认对选中的 ${ids.length} 条数据${status === "0" ? "启用" : "停用"}?`,
|
`确认对选中的 ${ids.length} 条数据${status === "0" ? "启用" : "停用"}?`,
|
||||||
"批量设置",
|
"批量设置"
|
||||||
{ confirmButtonText: "确定", cancelButtonText: "取消", type: "warning" }
|
|
||||||
);
|
);
|
||||||
await Demo01API.batchDemo01({ ids, status });
|
await Demo01API.batchDemo01({ ids, status });
|
||||||
ElMessage.success("操作成功");
|
ElMessage.success("操作成功");
|
||||||
faTableRef.value?.elTableRef?.clearSelection();
|
faTableRef.value?.elTableRef?.clearSelection();
|
||||||
await refreshData();
|
await refreshData();
|
||||||
} catch {
|
} catch {
|
||||||
// 用户取消操作,无需处理
|
// 用户取消
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -603,12 +582,4 @@ async function handleCrudImportUpload(formDataUpload: FormData) {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss"></style>
|
||||||
.crud-dialog-art-form :deep(.el-row > .el-col:last-child) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.crud-dialog-art-form :deep(.el-form-item__content) {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -108,6 +108,10 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useTableColumns } from "@/hooks/core/useTableColumns";
|
import { useTableColumns } from "@/hooks/core/useTableColumns";
|
||||||
|
import { useCrudDialog } from "@/hooks/core/useCrudDialog";
|
||||||
|
import { useTableSelection } from "@/hooks/core/useTableSelection";
|
||||||
|
import { useCrudForm } from "@/hooks/core/useCrudForm";
|
||||||
|
import { confirmDelete, confirmBatchDelete, confirmToggleStatus } from "@/hooks/core/useConfirm";
|
||||||
import DeptAPI, {
|
import DeptAPI, {
|
||||||
type DeptForm,
|
type DeptForm,
|
||||||
type DeptPageQuery,
|
type DeptPageQuery,
|
||||||
@@ -250,16 +254,11 @@ const tableRef = ref<{
|
|||||||
const tableData = ref<DeptTable[]>([]);
|
const tableData = ref<DeptTable[]>([]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const isExpanded = ref(false);
|
const isExpanded = ref(false);
|
||||||
const selectedRows = ref<DeptTable[]>([]);
|
|
||||||
const selectedIds = computed(() =>
|
|
||||||
selectedRows.value.map((r) => r.id).filter((id): id is number => id != null && !Number.isNaN(id))
|
|
||||||
);
|
|
||||||
const batchDeleting = ref(false);
|
|
||||||
const deptOptions = ref<OptionType[]>([]);
|
const deptOptions = ref<OptionType[]>([]);
|
||||||
|
|
||||||
function onTableSelectionChange(rows: DeptTable[]) {
|
// ─── 表格多选 ───
|
||||||
selectedRows.value = rows;
|
const { selectedRows, selectedIds, batchDeleting, onTableSelectionChange } =
|
||||||
}
|
useTableSelection<DeptTable>();
|
||||||
|
|
||||||
async function loadDeptData() {
|
async function loadDeptData() {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
@@ -277,11 +276,7 @@ async function loadDeptData() {
|
|||||||
|
|
||||||
async function deleteDeptRow(id: number) {
|
async function deleteDeptRow(id: number) {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm("确认删除该项数据?", "警告", {
|
await confirmDelete();
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
await DeptAPI.deleteDept([id]);
|
await DeptAPI.deleteDept([id]);
|
||||||
await userStore.getUserInfo();
|
await userStore.getUserInfo();
|
||||||
ElMessage.success("删除成功");
|
ElMessage.success("删除成功");
|
||||||
@@ -292,8 +287,94 @@ async function deleteDeptRow(id: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── 对话框状态 ───
|
||||||
|
const { dialogVisible } = useCrudDialog();
|
||||||
|
|
||||||
|
const detailFormData = ref<DeptTable>({ code: "" });
|
||||||
|
|
||||||
|
const deptDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] =
|
||||||
|
[
|
||||||
|
{ label: "部门名称", prop: "name" },
|
||||||
|
{ label: "部门编码", prop: "code" },
|
||||||
|
{ label: "上级部门", prop: "parent_name" },
|
||||||
|
{
|
||||||
|
label: "状态",
|
||||||
|
prop: "status",
|
||||||
|
tag: {
|
||||||
|
map: { "0": { type: "success", text: "启用" }, "1": { type: "danger", text: "停用" } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ label: "排序", prop: "order" },
|
||||||
|
{ label: "创建时间", prop: "created_time" },
|
||||||
|
{ label: "更新时间", prop: "updated_time" },
|
||||||
|
{ label: "描述", prop: "description", span: 4 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const formData = ref<DeptForm>({
|
||||||
|
id: undefined,
|
||||||
|
name: undefined,
|
||||||
|
code: "",
|
||||||
|
order: 1,
|
||||||
|
parent_id: undefined,
|
||||||
|
status: "0",
|
||||||
|
description: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const CODE_PATTERN = /^[A-Za-z][A-Za-z0-9_]{1,15}$/;
|
||||||
|
|
||||||
|
const rules = reactive({
|
||||||
|
name: [{ required: true, message: "请输入部门名称", trigger: "blur" }],
|
||||||
|
code: [
|
||||||
|
{ required: true, message: "请输入部门编码", trigger: "blur" },
|
||||||
|
{
|
||||||
|
pattern: CODE_PATTERN,
|
||||||
|
message: "字母开头,2-16位字母/数字/下划线",
|
||||||
|
trigger: "blur",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
order: [{ required: true, message: "请输入排序", trigger: "blur" }],
|
||||||
|
status: [{ required: true, message: "请选择状态", trigger: "blur" }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const initialFormData: DeptForm = {
|
||||||
|
id: undefined,
|
||||||
|
name: undefined,
|
||||||
|
code: "",
|
||||||
|
order: 1,
|
||||||
|
parent_id: undefined,
|
||||||
|
status: "0",
|
||||||
|
description: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
||||||
|
const deptFormRenderKey = ref(0);
|
||||||
|
|
||||||
|
// ─── CRUD 表单 ───
|
||||||
|
const { submitLoading, handleCloseDialog, handleOpenDialog, handleSubmit } = useCrudForm<DeptForm>({
|
||||||
|
formData,
|
||||||
|
initialFormData,
|
||||||
|
dialogVisible,
|
||||||
|
dataFormRef,
|
||||||
|
formRenderKey: deptFormRenderKey,
|
||||||
|
detailApi: DeptAPI.detailDept,
|
||||||
|
createApi: DeptAPI.createDept,
|
||||||
|
updateApi: DeptAPI.updateDept,
|
||||||
|
titles: { create: "新增部门", update: "修改部门", detail: "部门详情" },
|
||||||
|
detailFormData,
|
||||||
|
onCreateSuccess: async () => {
|
||||||
|
await loadDeptData();
|
||||||
|
},
|
||||||
|
onUpdateSuccess: async () => {
|
||||||
|
await loadDeptData();
|
||||||
|
},
|
||||||
|
onSubmitSuccess: async () => {
|
||||||
|
await userStore.getUserInfo();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const opCtx = {
|
const opCtx = {
|
||||||
onAddChild: (parentId: number) => void handleOpenDialog("create", undefined, parentId),
|
onAddChild: (parentId: number) =>
|
||||||
|
void handleOpenDialog("create", undefined, { parent_id: parentId }),
|
||||||
onDetail: (id: number) => void handleOpenDialog("detail", id),
|
onDetail: (id: number) => void handleOpenDialog("detail", id),
|
||||||
onEdit: (id: number) => void handleOpenDialog("update", id),
|
onEdit: (id: number) => void handleOpenDialog("update", id),
|
||||||
onDelete: deleteDeptRow,
|
onDelete: deleteDeptRow,
|
||||||
@@ -327,72 +408,6 @@ const { columnChecks, columns } = useTableColumns<DeptTable>(() => [
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const detailFormData = ref<DeptTable>({ code: "" });
|
|
||||||
|
|
||||||
const deptDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] =
|
|
||||||
[
|
|
||||||
{ label: "部门名称", prop: "name" },
|
|
||||||
{ label: "部门编码", prop: "code" },
|
|
||||||
{ label: "上级部门", prop: "parent_name" },
|
|
||||||
{
|
|
||||||
label: "状态",
|
|
||||||
prop: "status",
|
|
||||||
tag: {
|
|
||||||
map: { "0": { type: "success", text: "启用" }, "1": { type: "danger", text: "停用" } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ label: "排序", prop: "order" },
|
|
||||||
{ label: "创建时间", prop: "created_time" },
|
|
||||||
{ label: "更新时间", prop: "updated_time" },
|
|
||||||
{ label: "描述", prop: "description", span: 4 },
|
|
||||||
];
|
|
||||||
|
|
||||||
const formData = ref<DeptForm>({
|
|
||||||
id: undefined,
|
|
||||||
name: undefined,
|
|
||||||
code: "",
|
|
||||||
order: 1,
|
|
||||||
parent_id: undefined,
|
|
||||||
status: "0",
|
|
||||||
description: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const dialogVisible = reactive({
|
|
||||||
title: "",
|
|
||||||
visible: false,
|
|
||||||
type: "create" as "create" | "update" | "detail",
|
|
||||||
});
|
|
||||||
|
|
||||||
const CODE_PATTERN = /^[A-Za-z][A-Za-z0-9_]{1,15}$/;
|
|
||||||
|
|
||||||
const rules = reactive({
|
|
||||||
name: [{ required: true, message: "请输入部门名称", trigger: "blur" }],
|
|
||||||
code: [
|
|
||||||
{ required: true, message: "请输入部门编码", trigger: "blur" },
|
|
||||||
{
|
|
||||||
pattern: CODE_PATTERN,
|
|
||||||
message: "字母开头,2-16位字母/数字/下划线",
|
|
||||||
trigger: "blur",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
order: [{ required: true, message: "请输入排序", trigger: "blur" }],
|
|
||||||
status: [{ required: true, message: "请选择状态", trigger: "blur" }],
|
|
||||||
});
|
|
||||||
|
|
||||||
const initialFormData: DeptForm = {
|
|
||||||
id: undefined,
|
|
||||||
name: undefined,
|
|
||||||
code: "",
|
|
||||||
order: 1,
|
|
||||||
parent_id: undefined,
|
|
||||||
status: "0",
|
|
||||||
description: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
|
||||||
const submitLoading = ref(false);
|
|
||||||
const deptFormRenderKey = ref(0);
|
|
||||||
|
|
||||||
const deptDialogFormItems = computed<FormItem[]>(() => [
|
const deptDialogFormItems = computed<FormItem[]>(() => [
|
||||||
{
|
{
|
||||||
label: "部门名称",
|
label: "部门名称",
|
||||||
@@ -473,73 +488,11 @@ function onResetSearch() {
|
|||||||
void loadDeptData();
|
void loadDeptData();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resetForm() {
|
|
||||||
dataFormRef.value?.resetFields();
|
|
||||||
dataFormRef.value?.clearValidate();
|
|
||||||
Object.assign(formData, initialFormData);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleCloseDialog() {
|
|
||||||
dialogVisible.visible = false;
|
|
||||||
await resetForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleOpenDialog(
|
|
||||||
type: "create" | "update" | "detail",
|
|
||||||
id?: number,
|
|
||||||
parentId?: number
|
|
||||||
) {
|
|
||||||
dialogVisible.type = type;
|
|
||||||
if (id) {
|
|
||||||
const response = await DeptAPI.detailDept(id);
|
|
||||||
if (type === "detail") {
|
|
||||||
dialogVisible.title = "部门详情";
|
|
||||||
Object.assign(detailFormData.value, response.data.data ?? {});
|
|
||||||
} else if (type === "update") {
|
|
||||||
dialogVisible.title = "修改部门";
|
|
||||||
Object.assign(formData, response.data.data);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dialogVisible.title = "新增部门";
|
|
||||||
Object.assign(formData.value, initialFormData);
|
|
||||||
formData.value.id = undefined;
|
|
||||||
if (parentId) {
|
|
||||||
formData.value.parent_id = parentId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
deptFormRenderKey.value += 1;
|
|
||||||
dialogVisible.visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSubmit() {
|
|
||||||
dataFormRef.value?.validate(async (valid: boolean) => {
|
|
||||||
if (!valid) return;
|
|
||||||
const id = formData.value.id;
|
|
||||||
try {
|
|
||||||
if (id) {
|
|
||||||
await DeptAPI.updateDept(id, { id, ...formData.value });
|
|
||||||
} else {
|
|
||||||
await DeptAPI.createDept(formData.value);
|
|
||||||
}
|
|
||||||
dialogVisible.visible = false;
|
|
||||||
await resetForm();
|
|
||||||
await loadDeptData();
|
|
||||||
await userStore.getUserInfo();
|
|
||||||
} catch (error: unknown) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleBatchDelete() {
|
async function handleBatchDelete() {
|
||||||
const ids = selectedIds.value;
|
const ids = selectedIds.value;
|
||||||
if (ids.length === 0) return;
|
if (ids.length === 0) return;
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 条数据吗?`, "批量删除", {
|
await confirmBatchDelete(ids.length);
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
batchDeleting.value = true;
|
batchDeleting.value = true;
|
||||||
await DeptAPI.deleteDept(ids);
|
await DeptAPI.deleteDept(ids);
|
||||||
await userStore.getUserInfo();
|
await userStore.getUserInfo();
|
||||||
@@ -560,11 +513,7 @@ async function handleMoreClick(status: string) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确认${status === "0" ? "启用" : "停用"}该项数据?`, "警告", {
|
await confirmToggleStatus(status);
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
await DeptAPI.batchDept({ ids, status });
|
await DeptAPI.batchDept({ ids, status });
|
||||||
await loadDeptData();
|
await loadDeptData();
|
||||||
await userStore.getUserInfo();
|
await userStore.getUserInfo();
|
||||||
@@ -594,17 +543,3 @@ onMounted(() => {
|
|||||||
void loadDeptData();
|
void loadDeptData();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.crud-dialog-art-form :deep(.el-row > .el-col:last-child) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.crud-dialog-art-form :deep(.el-form-item__content) {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.dept-table-actions .inline-flex) {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -198,6 +198,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useTable } from "@/hooks/core/useTable";
|
import { useTable } from "@/hooks/core/useTable";
|
||||||
import { useImportExport } from "@/hooks/core/useImportExport";
|
import { useImportExport } from "@/hooks/core/useImportExport";
|
||||||
|
import { useCrudDialog } from "@/hooks/core/useCrudDialog";
|
||||||
|
import { useTableSelection } from "@/hooks/core/useTableSelection";
|
||||||
|
import { confirmDelete, confirmBatchDelete, confirmToggleStatus } from "@/hooks/core/useConfirm";
|
||||||
|
import { cleanEmptyArrayParams, stripPaginationParams } from "@/utils/query";
|
||||||
import type { IObject } from "@/components/modal/types";
|
import type { IObject } from "@/components/modal/types";
|
||||||
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
||||||
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||||
@@ -280,10 +284,10 @@ type DictDataSearchForm = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function normalizeDictDataQuery(params: Record<string, unknown>): DictDataPageQuery {
|
function normalizeDictDataQuery(params: Record<string, unknown>): DictDataPageQuery {
|
||||||
const p = { ...params } as Record<string, unknown>;
|
return cleanEmptyArrayParams({ ...params }, [
|
||||||
if (Array.isArray(p.created_time) && p.created_time.length === 0) p.created_time = undefined;
|
"created_time",
|
||||||
if (Array.isArray(p.updated_time) && p.updated_time.length === 0) p.updated_time = undefined;
|
"updated_time",
|
||||||
return p as unknown as DictDataPageQuery;
|
]) as unknown as DictDataPageQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchDictDataListMerged(params: Record<string, unknown>) {
|
async function fetchDictDataListMerged(params: Record<string, unknown>) {
|
||||||
@@ -360,15 +364,8 @@ const dictDataSearchItems = computed<SearchFormItem[]>(() => [
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
||||||
const selectedRows = ref<DictDataTable[]>([]);
|
const { selectedRows, selectedIds, batchDeleting, onTableSelectionChange } =
|
||||||
const selectedIds = computed(() =>
|
useTableSelection<DictDataTable>();
|
||||||
selectedRows.value.map((r) => r.id).filter((id): id is number => id != null && !Number.isNaN(id))
|
|
||||||
);
|
|
||||||
const batchDeleting = ref(false);
|
|
||||||
|
|
||||||
function onTableSelectionChange(rows: DictDataTable[]) {
|
|
||||||
selectedRows.value = rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
columns,
|
columns,
|
||||||
@@ -458,11 +455,7 @@ const dictDataCrudCols = computed(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
const exportQueryParams = computed(() => {
|
const exportQueryParams = computed(() => {
|
||||||
const sp = { ...(searchParams as object) } as Record<string, unknown>;
|
const sp = stripPaginationParams(searchParams as Record<string, unknown>);
|
||||||
delete sp.current;
|
|
||||||
delete sp.size;
|
|
||||||
delete sp.page_no;
|
|
||||||
delete sp.page_size;
|
|
||||||
return normalizeDictDataQuery({
|
return normalizeDictDataQuery({
|
||||||
...sp,
|
...sp,
|
||||||
dict_type: props.dictType,
|
dict_type: props.dictType,
|
||||||
@@ -485,11 +478,7 @@ const dictDataExportContentConfig = computed(() => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const dialogVisible = reactive({
|
const { dialogVisible } = useCrudDialog();
|
||||||
title: "",
|
|
||||||
visible: false,
|
|
||||||
type: "create" as "create" | "update" | "detail",
|
|
||||||
});
|
|
||||||
|
|
||||||
const detailFormData = ref<DictDataTable>({});
|
const detailFormData = ref<DictDataTable>({});
|
||||||
|
|
||||||
@@ -768,11 +757,7 @@ function formatDictDataOperationCell(row: DictDataTable) {
|
|||||||
|
|
||||||
async function deleteDictDataRow(id: number) {
|
async function deleteDictDataRow(id: number) {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm("确认删除该项数据?", "警告", {
|
await confirmDelete();
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
await DictAPI.deleteDictData([id]);
|
await DictAPI.deleteDictData([id]);
|
||||||
dictStore.clearDictData();
|
dictStore.clearDictData();
|
||||||
if (props.dictType) await dictStore.getDict([props.dictType]);
|
if (props.dictType) await dictStore.getDict([props.dictType]);
|
||||||
@@ -788,11 +773,7 @@ async function handleBatchDelete() {
|
|||||||
const ids = selectedIds.value;
|
const ids = selectedIds.value;
|
||||||
if (ids.length === 0) return;
|
if (ids.length === 0) return;
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 条数据吗?`, "批量删除", {
|
await confirmBatchDelete(ids.length);
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
batchDeleting.value = true;
|
batchDeleting.value = true;
|
||||||
await DictAPI.deleteDictData(ids);
|
await DictAPI.deleteDictData(ids);
|
||||||
dictStore.clearDictData();
|
dictStore.clearDictData();
|
||||||
@@ -814,11 +795,7 @@ async function handleMoreClick(status: string) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确认${status === "0" ? "启用" : "停用"}该项数据?`, "警告", {
|
await confirmToggleStatus(status);
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
await DictAPI.batchDictData({ ids, status });
|
await DictAPI.batchDictData({ ids, status });
|
||||||
await refreshData();
|
await refreshData();
|
||||||
dictStore.clearDictData();
|
dictStore.clearDictData();
|
||||||
@@ -860,12 +837,4 @@ async function handleMoreClick(status: string) {
|
|||||||
background: var(--el-fill-color-light);
|
background: var(--el-fill-color-light);
|
||||||
border-color: var(--el-border-color-lighter);
|
border-color: var(--el-border-color-lighter);
|
||||||
}
|
}
|
||||||
|
|
||||||
.crud-dialog-art-form :deep(.el-row > .el-col:last-child) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.crud-dialog-art-form :deep(.el-form-item__content) {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!-- 字典类型:Art 布局;操作列最多 3 个外露 +「更多」 -->
|
<!-- 字典类型:Fa 布局;操作列最多 3 个外露 +「更多」 -->
|
||||||
<template>
|
<template>
|
||||||
<div class="fa-full-height">
|
<div class="fa-full-height">
|
||||||
<FaSearchBar
|
<FaSearchBar
|
||||||
@@ -128,6 +128,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useTable } from "@/hooks/core/useTable";
|
import { useTable } from "@/hooks/core/useTable";
|
||||||
import { useImportExport } from "@/hooks/core/useImportExport";
|
import { useImportExport } from "@/hooks/core/useImportExport";
|
||||||
|
import { useCrudDialog } from "@/hooks/core/useCrudDialog";
|
||||||
|
import { useTableSelection } from "@/hooks/core/useTableSelection";
|
||||||
|
import { useCrudForm } from "@/hooks/core/useCrudForm";
|
||||||
|
import { confirmDelete, confirmBatchDelete, confirmToggleStatus } from "@/hooks/core/useConfirm";
|
||||||
|
import { cleanEmptyArrayParams, stripPaginationParams } from "@/utils/query";
|
||||||
import type { IObject } from "@/components/modal/types";
|
import type { IObject } from "@/components/modal/types";
|
||||||
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
||||||
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||||
@@ -154,13 +159,6 @@ type DictTypeSearchForm = {
|
|||||||
created_time?: string[];
|
created_time?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
function normalizeDictTypeQuery(params: Record<string, unknown>): DictPageQuery {
|
|
||||||
const p = { ...params } as Record<string, unknown>;
|
|
||||||
if (Array.isArray(p.created_time) && p.created_time.length === 0) p.created_time = undefined;
|
|
||||||
if (Array.isArray(p.updated_time) && p.updated_time.length === 0) p.updated_time = undefined;
|
|
||||||
return p as unknown as DictPageQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dictStore = useDictStore();
|
const dictStore = useDictStore();
|
||||||
const { hasAuth } = useAuth();
|
const { hasAuth } = useAuth();
|
||||||
|
|
||||||
@@ -226,15 +224,119 @@ const dictTypeSearchItems = computed<SearchFormItem[]>(() => [
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
||||||
const selectedRows = ref<DictTable[]>([]);
|
|
||||||
const selectedIds = computed(() =>
|
|
||||||
selectedRows.value.map((r) => r.id).filter((id): id is number => id != null && !Number.isNaN(id))
|
|
||||||
);
|
|
||||||
const batchDeleting = ref(false);
|
|
||||||
|
|
||||||
function onTableSelectionChange(rows: DictTable[]) {
|
// ─── 表格多选 ───
|
||||||
selectedRows.value = rows;
|
const { selectedRows, selectedIds, batchDeleting, onTableSelectionChange } =
|
||||||
}
|
useTableSelection<DictTable>();
|
||||||
|
|
||||||
|
// ─── 对话框状态 ───
|
||||||
|
const { dialogVisible } = useCrudDialog();
|
||||||
|
|
||||||
|
const detailFormData = ref<DictTable>({});
|
||||||
|
|
||||||
|
const dictDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] =
|
||||||
|
[
|
||||||
|
{ label: "字典名称", prop: "dict_name" },
|
||||||
|
{ label: "字典类型", prop: "dict_type", slot: "dict_type" },
|
||||||
|
{
|
||||||
|
label: "状态",
|
||||||
|
prop: "status",
|
||||||
|
tag: {
|
||||||
|
map: { "0": { type: "success", text: "启用" }, "1": { type: "danger", text: "停用" } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ label: "描述", prop: "description" },
|
||||||
|
{ label: "创建时间", prop: "created_time" },
|
||||||
|
{ label: "更新时间", prop: "updated_time" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const formData = ref<DictForm>({
|
||||||
|
id: undefined,
|
||||||
|
dict_name: "",
|
||||||
|
dict_type: "",
|
||||||
|
status: "0",
|
||||||
|
description: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const rules = reactive({
|
||||||
|
dict_name: [{ required: true, message: "请输入字典名称", trigger: "blur" }],
|
||||||
|
dict_type: [{ required: true, message: "请选择字典类型", trigger: "blur" }],
|
||||||
|
status: [{ required: true, message: "请选择字典状态", trigger: "blur" }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
||||||
|
const dictFormRenderKey = ref(0);
|
||||||
|
|
||||||
|
const initialFormData: DictForm = {
|
||||||
|
id: undefined,
|
||||||
|
dict_name: "",
|
||||||
|
dict_type: "",
|
||||||
|
status: "0",
|
||||||
|
description: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─── CRUD 表单 ───
|
||||||
|
const { submitLoading, handleCloseDialog, handleOpenDialog, handleSubmit } = useCrudForm<DictForm>({
|
||||||
|
formData,
|
||||||
|
initialFormData,
|
||||||
|
dialogVisible,
|
||||||
|
dataFormRef,
|
||||||
|
formRenderKey: dictFormRenderKey,
|
||||||
|
detailApi: DictAPI.detailDictType,
|
||||||
|
createApi: DictAPI.createDictType,
|
||||||
|
updateApi: DictAPI.updateDictType,
|
||||||
|
titles: { create: "新增字典", update: "修改字典", detail: "字典详情" },
|
||||||
|
detailFormData,
|
||||||
|
onCreateSuccess: async () => {
|
||||||
|
await refreshCreate();
|
||||||
|
},
|
||||||
|
onUpdateSuccess: async () => {
|
||||||
|
await refreshUpdate();
|
||||||
|
},
|
||||||
|
onSubmitSuccess: async () => {
|
||||||
|
dictStore.clearDictData();
|
||||||
|
if (formData.value.dict_type) {
|
||||||
|
await dictStore.getDict([formData.value.dict_type]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const dictDialogFormItems = computed<FormItem[]>(() => [
|
||||||
|
{
|
||||||
|
label: "字典名称",
|
||||||
|
key: "dict_name",
|
||||||
|
type: "input",
|
||||||
|
span: 24,
|
||||||
|
props: { placeholder: "请输入字典名称", maxlength: 50 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "字典类型",
|
||||||
|
key: "dict_type",
|
||||||
|
type: "input",
|
||||||
|
span: 24,
|
||||||
|
props: { placeholder: "请输入字典类型", maxlength: 50 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "状态",
|
||||||
|
key: "status",
|
||||||
|
type: "input",
|
||||||
|
span: 24,
|
||||||
|
placeholder: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "描述",
|
||||||
|
key: "description",
|
||||||
|
type: "input",
|
||||||
|
span: 24,
|
||||||
|
props: {
|
||||||
|
type: "textarea",
|
||||||
|
rows: 4,
|
||||||
|
maxlength: 100,
|
||||||
|
showWordLimit: true,
|
||||||
|
placeholder: "请输入描述",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
columns,
|
columns,
|
||||||
@@ -309,114 +411,23 @@ const dictTypeCrudCols = computed(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
const exportQueryParams = computed(() => {
|
const exportQueryParams = computed(() => {
|
||||||
const sp = { ...(searchParams as object) } as Record<string, unknown>;
|
const sp = stripPaginationParams(searchParams as Record<string, unknown>);
|
||||||
delete sp.current;
|
return cleanEmptyArrayParams(sp) as unknown as DictPageQuery;
|
||||||
delete sp.size;
|
|
||||||
delete sp.page_no;
|
|
||||||
delete sp.page_size;
|
|
||||||
return normalizeDictTypeQuery(sp);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const dictTypeExportContentConfig = computed(() => ({
|
const dictTypeExportContentConfig = computed(() => ({
|
||||||
permPrefix: "module_system:dict_type",
|
permPrefix: "module_system:dict_type",
|
||||||
cols: dictTypeCrudCols.value,
|
cols: dictTypeCrudCols.value,
|
||||||
exportsBlobAction: async (params: IObject) => {
|
exportsBlobAction: async (params: IObject) => {
|
||||||
const merged = normalizeDictTypeQuery({
|
const merged = cleanEmptyArrayParams({
|
||||||
...(exportQueryParams.value as unknown as Record<string, unknown>),
|
...(exportQueryParams.value as unknown as Record<string, unknown>),
|
||||||
...params,
|
...params,
|
||||||
} as Record<string, unknown>);
|
} as Record<string, unknown>);
|
||||||
const res = await DictAPI.exportDictType(merged as DictPageQuery);
|
const res = await DictAPI.exportDictType(merged as unknown as DictPageQuery);
|
||||||
return res.data as Blob;
|
return res.data as Blob;
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const dialogVisible = reactive({
|
|
||||||
title: "",
|
|
||||||
visible: false,
|
|
||||||
type: "create" as "create" | "update" | "detail",
|
|
||||||
});
|
|
||||||
|
|
||||||
const detailFormData = ref<DictTable>({});
|
|
||||||
|
|
||||||
const dictDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] =
|
|
||||||
[
|
|
||||||
{ label: "字典名称", prop: "dict_name" },
|
|
||||||
{ label: "字典类型", prop: "dict_type", slot: "dict_type" },
|
|
||||||
{
|
|
||||||
label: "状态",
|
|
||||||
prop: "status",
|
|
||||||
tag: {
|
|
||||||
map: { "0": { type: "success", text: "启用" }, "1": { type: "danger", text: "停用" } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ label: "描述", prop: "description" },
|
|
||||||
{ label: "创建时间", prop: "created_time" },
|
|
||||||
{ label: "更新时间", prop: "updated_time" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const formData = ref<DictForm>({
|
|
||||||
id: undefined,
|
|
||||||
dict_name: "",
|
|
||||||
dict_type: "",
|
|
||||||
status: "0",
|
|
||||||
description: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const rules = reactive({
|
|
||||||
dict_name: [{ required: true, message: "请输入字典名称", trigger: "blur" }],
|
|
||||||
dict_type: [{ required: true, message: "请选择字典类型", trigger: "blur" }],
|
|
||||||
status: [{ required: true, message: "请选择字典状态", trigger: "blur" }],
|
|
||||||
});
|
|
||||||
|
|
||||||
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
|
||||||
const submitLoading = ref(false);
|
|
||||||
const dictFormRenderKey = ref(0);
|
|
||||||
|
|
||||||
const dictDialogFormItems = computed<FormItem[]>(() => [
|
|
||||||
{
|
|
||||||
label: "字典名称",
|
|
||||||
key: "dict_name",
|
|
||||||
type: "input",
|
|
||||||
span: 24,
|
|
||||||
props: { placeholder: "请输入字典名称", maxlength: 50 },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "字典类型",
|
|
||||||
key: "dict_type",
|
|
||||||
type: "input",
|
|
||||||
span: 24,
|
|
||||||
props: { placeholder: "请输入字典类型", maxlength: 50 },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "状态",
|
|
||||||
key: "status",
|
|
||||||
type: "input",
|
|
||||||
span: 24,
|
|
||||||
placeholder: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "描述",
|
|
||||||
key: "description",
|
|
||||||
type: "input",
|
|
||||||
span: 24,
|
|
||||||
props: {
|
|
||||||
type: "textarea",
|
|
||||||
rows: 4,
|
|
||||||
maxlength: 100,
|
|
||||||
showWordLimit: true,
|
|
||||||
placeholder: "请输入描述",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const initialFormData: DictForm = {
|
|
||||||
id: undefined,
|
|
||||||
dict_name: "",
|
|
||||||
dict_type: "",
|
|
||||||
status: "0",
|
|
||||||
description: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { exportVisible, openExport } = useImportExport();
|
const { exportVisible, openExport } = useImportExport();
|
||||||
|
|
||||||
const drawerVisible = ref(false);
|
const drawerVisible = ref(false);
|
||||||
@@ -501,68 +512,9 @@ function handleDictDataDrawer(dictTypeRow: DictTable) {
|
|||||||
drawerVisible.value = true;
|
drawerVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resetForm() {
|
|
||||||
dataFormRef.value?.resetFields();
|
|
||||||
dataFormRef.value?.clearValidate();
|
|
||||||
Object.assign(formData, initialFormData);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleCloseDialog() {
|
|
||||||
dialogVisible.visible = false;
|
|
||||||
await resetForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleOpenDialog(type: "create" | "update" | "detail", id?: number) {
|
|
||||||
dialogVisible.type = type;
|
|
||||||
if (id) {
|
|
||||||
const response = await DictAPI.detailDictType(id);
|
|
||||||
if (type === "detail") {
|
|
||||||
dialogVisible.title = "字典详情";
|
|
||||||
detailFormData.value = response.data.data ?? {};
|
|
||||||
} else if (type === "update") {
|
|
||||||
dialogVisible.title = "修改字典";
|
|
||||||
Object.assign(formData, response.data.data);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dialogVisible.title = "新增字典";
|
|
||||||
Object.assign(formData.value, initialFormData);
|
|
||||||
formData.value.id = undefined;
|
|
||||||
}
|
|
||||||
dictFormRenderKey.value += 1;
|
|
||||||
dialogVisible.visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSubmit() {
|
|
||||||
dataFormRef.value?.validate(async (valid: boolean) => {
|
|
||||||
if (!valid) return;
|
|
||||||
const id = formData.value.id;
|
|
||||||
try {
|
|
||||||
if (id) {
|
|
||||||
await DictAPI.updateDictType(id, { id, ...formData.value });
|
|
||||||
await refreshUpdate();
|
|
||||||
} else {
|
|
||||||
await DictAPI.createDictType(formData.value);
|
|
||||||
await refreshCreate();
|
|
||||||
}
|
|
||||||
dialogVisible.visible = false;
|
|
||||||
await resetForm();
|
|
||||||
dictStore.clearDictData();
|
|
||||||
if (formData.value.dict_type) {
|
|
||||||
await dictStore.getDict([formData.value.dict_type]);
|
|
||||||
}
|
|
||||||
} catch (error: unknown) {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteDictTypeRow(id: number) {
|
async function deleteDictTypeRow(id: number) {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm("确认删除该项数据?", "警告", {
|
await confirmDelete();
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
await DictAPI.deleteDictType([id]);
|
await DictAPI.deleteDictType([id]);
|
||||||
dictStore.clearDictData();
|
dictStore.clearDictData();
|
||||||
const dictTypes = Object.keys(dictStore.dictData);
|
const dictTypes = Object.keys(dictStore.dictData);
|
||||||
@@ -579,11 +531,7 @@ async function handleBatchDelete() {
|
|||||||
const ids = selectedIds.value;
|
const ids = selectedIds.value;
|
||||||
if (ids.length === 0) return;
|
if (ids.length === 0) return;
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 条数据吗?`, "批量删除", {
|
await confirmBatchDelete(ids.length);
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
batchDeleting.value = true;
|
batchDeleting.value = true;
|
||||||
await DictAPI.deleteDictType(ids);
|
await DictAPI.deleteDictType(ids);
|
||||||
dictStore.clearDictData();
|
dictStore.clearDictData();
|
||||||
@@ -606,11 +554,7 @@ async function handleMoreClick(status: string) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确认${status === "0" ? "启用" : "停用"}该项数据?`, "警告", {
|
await confirmToggleStatus(status);
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
await DictAPI.batchDictType({ ids, status });
|
await DictAPI.batchDictType({ ids, status });
|
||||||
await refreshData();
|
await refreshData();
|
||||||
dictStore.clearDictData();
|
dictStore.clearDictData();
|
||||||
@@ -621,13 +565,3 @@ async function handleMoreClick(status: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.crud-dialog-art-form :deep(.el-row > .el-col:last-child) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.crud-dialog-art-form :deep(.el-form-item__content) {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!-- 日志管理:Art 布局 + useTable,与 dict 页一致 -->
|
<!-- 日志管理:Fa 布局 + useTable,与 dict 页一致 -->
|
||||||
<template>
|
<template>
|
||||||
<div class="fa-full-height">
|
<div class="fa-full-height">
|
||||||
<FaSearchBar
|
<FaSearchBar
|
||||||
@@ -114,6 +114,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useTable } from "@/hooks/core/useTable";
|
import { useTable } from "@/hooks/core/useTable";
|
||||||
import { useImportExport } from "@/hooks/core/useImportExport";
|
import { useImportExport } from "@/hooks/core/useImportExport";
|
||||||
|
import { useCrudDialog } from "@/hooks/core/useCrudDialog";
|
||||||
|
import { useTableSelection } from "@/hooks/core/useTableSelection";
|
||||||
|
import { confirmDelete, confirmBatchDelete } from "@/hooks/core/useConfirm";
|
||||||
|
import { cleanEmptyArrayParams, stripPaginationParams } from "@/utils/query";
|
||||||
import type { IObject } from "@/components/modal/types";
|
import type { IObject } from "@/components/modal/types";
|
||||||
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
||||||
import type { ColumnOption } from "@/types/component";
|
import type { ColumnOption } from "@/types/component";
|
||||||
@@ -137,10 +141,7 @@ type LogSearchForm = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function normalizeLogQuery(params: Record<string, unknown>): LogPageQuery {
|
function normalizeLogQuery(params: Record<string, unknown>): LogPageQuery {
|
||||||
const p = { ...params } as Record<string, unknown>;
|
return cleanEmptyArrayParams({ ...params }) as unknown as LogPageQuery;
|
||||||
if (Array.isArray(p.created_time) && p.created_time.length === 0) p.created_time = undefined;
|
|
||||||
if (Array.isArray(p.updated_time) && p.updated_time.length === 0) p.updated_time = undefined;
|
|
||||||
return p as unknown as LogPageQuery;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildLogReplaceParams(p: LogSearchForm): Record<string, unknown> {
|
function buildLogReplaceParams(p: LogSearchForm): Record<string, unknown> {
|
||||||
@@ -216,15 +217,8 @@ const logSearchItems = computed<SearchFormItem[]>(() => [
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
||||||
const selectedRows = ref<LogTable[]>([]);
|
const { selectedRows, selectedIds, batchDeleting, onTableSelectionChange } =
|
||||||
const selectedIds = computed(() =>
|
useTableSelection<LogTable>();
|
||||||
selectedRows.value.map((r) => r.id).filter((id): id is number => id != null && !Number.isNaN(id))
|
|
||||||
);
|
|
||||||
const batchDeleting = ref(false);
|
|
||||||
|
|
||||||
function onTableSelectionChange(rows: LogTable[]) {
|
|
||||||
selectedRows.value = rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
columns,
|
columns,
|
||||||
@@ -333,11 +327,7 @@ const logCrudCols = computed(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
const exportQueryParams = computed(() => {
|
const exportQueryParams = computed(() => {
|
||||||
const sp = { ...(searchParams as object) } as Record<string, unknown>;
|
const sp = stripPaginationParams(searchParams as Record<string, unknown>);
|
||||||
delete sp.current;
|
|
||||||
delete sp.size;
|
|
||||||
delete sp.page_no;
|
|
||||||
delete sp.page_size;
|
|
||||||
return normalizeLogQuery(sp);
|
return normalizeLogQuery(sp);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -375,10 +365,7 @@ const logDetailItems: import("@/components/others/fa-descriptions/index.vue").De
|
|||||||
{ label: "更新时间", prop: "updated_time" },
|
{ label: "更新时间", prop: "updated_time" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const dialogVisible = ref({
|
const { dialogVisible, closeDialog } = useCrudDialog();
|
||||||
title: "",
|
|
||||||
visible: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
function getStatusCodeType(code?: number) {
|
function getStatusCodeType(code?: number) {
|
||||||
if (code === undefined) return "info";
|
if (code === undefined) return "info";
|
||||||
@@ -430,24 +417,20 @@ async function resetForm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleCloseDialog() {
|
async function handleCloseDialog() {
|
||||||
dialogVisible.value.visible = false;
|
closeDialog();
|
||||||
await resetForm();
|
await resetForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleOpenDialog(id: number) {
|
async function handleOpenDialog(id: number) {
|
||||||
dialogVisible.value.title = "日志详情";
|
dialogVisible.title = "日志详情";
|
||||||
const response = await LogAPI.detailLog(id);
|
const response = await LogAPI.detailLog(id);
|
||||||
Object.assign(formData, response.data.data ?? {});
|
Object.assign(formData, response.data.data ?? {});
|
||||||
dialogVisible.value.visible = true;
|
dialogVisible.visible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteLogRow(id: number) {
|
async function deleteLogRow(id: number) {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm("确认删除该项数据?", "警告", {
|
await confirmDelete();
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
await LogAPI.deleteLog([id]);
|
await LogAPI.deleteLog([id]);
|
||||||
ElMessage.success("删除成功");
|
ElMessage.success("删除成功");
|
||||||
faTableRef.value?.elTableRef?.clearSelection();
|
faTableRef.value?.elTableRef?.clearSelection();
|
||||||
@@ -492,11 +475,7 @@ async function handleBatchDelete() {
|
|||||||
const ids = selectedIds.value;
|
const ids = selectedIds.value;
|
||||||
if (ids.length === 0) return;
|
if (ids.length === 0) return;
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 条数据吗?`, "批量删除", {
|
await confirmBatchDelete(ids.length);
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
batchDeleting.value = true;
|
batchDeleting.value = true;
|
||||||
await LogAPI.deleteLog(ids);
|
await LogAPI.deleteLog(ids);
|
||||||
ElMessage.success("删除成功");
|
ElMessage.success("删除成功");
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!-- 公告通知:Art 布局 + useTable,与 dict 页一致 -->
|
<!-- 公告通知:Fa 布局 + useTable,与 dict 页一致 -->
|
||||||
<template>
|
<template>
|
||||||
<div class="fa-full-height">
|
<div class="fa-full-height">
|
||||||
<FaSearchBar
|
<FaSearchBar
|
||||||
@@ -100,7 +100,6 @@
|
|||||||
</FaDescriptions>
|
</FaDescriptions>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<!-- FaForm + items + 栅格;弹窗内关闭内置提交/重置,仍用底部按钮 -->
|
|
||||||
<FaForm
|
<FaForm
|
||||||
:key="noticeFormRenderKey"
|
:key="noticeFormRenderKey"
|
||||||
scrollbar
|
scrollbar
|
||||||
@@ -150,6 +149,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useTable } from "@/hooks/core/useTable";
|
import { useTable } from "@/hooks/core/useTable";
|
||||||
import { useImportExport } from "@/hooks/core/useImportExport";
|
import { useImportExport } from "@/hooks/core/useImportExport";
|
||||||
|
import { useCrudDialog } from "@/hooks/core/useCrudDialog";
|
||||||
|
import { useTableSelection } from "@/hooks/core/useTableSelection";
|
||||||
|
import { useCrudForm } from "@/hooks/core/useCrudForm";
|
||||||
|
import { confirmDelete, confirmBatchDelete, confirmToggleStatus } from "@/hooks/core/useConfirm";
|
||||||
|
import { cleanEmptyArrayParams, stripPaginationParams } from "@/utils/query";
|
||||||
import type { IObject } from "@/components/modal/types";
|
import type { IObject } from "@/components/modal/types";
|
||||||
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
||||||
import type { ColumnOption } from "@/types/component";
|
import type { ColumnOption } from "@/types/component";
|
||||||
@@ -182,10 +186,7 @@ type NoticeSearchForm = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function normalizeNoticeQuery(params: Record<string, unknown>): NoticePageQuery {
|
function normalizeNoticeQuery(params: Record<string, unknown>): NoticePageQuery {
|
||||||
const p = { ...params } as Record<string, unknown>;
|
return cleanEmptyArrayParams({ ...params }) as unknown as NoticePageQuery;
|
||||||
if (Array.isArray(p.created_time) && p.created_time.length === 0) p.created_time = undefined;
|
|
||||||
if (Array.isArray(p.updated_time) && p.updated_time.length === 0) p.updated_time = undefined;
|
|
||||||
return p as unknown as NoticePageQuery;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function noticeTypeLabel(val?: string) {
|
function noticeTypeLabel(val?: string) {
|
||||||
@@ -274,15 +275,157 @@ const noticeSearchItems = computed<SearchFormItem[]>(() => [
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
||||||
const selectedRows = ref<NoticeTable[]>([]);
|
|
||||||
const selectedIds = computed(() =>
|
|
||||||
selectedRows.value.map((r) => r.id).filter((id): id is number => id != null && !Number.isNaN(id))
|
|
||||||
);
|
|
||||||
const batchDeleting = ref(false);
|
|
||||||
|
|
||||||
function onTableSelectionChange(rows: NoticeTable[]) {
|
// ─── 表格多选 ───
|
||||||
selectedRows.value = rows;
|
const { selectedRows, selectedIds, batchDeleting, onTableSelectionChange } =
|
||||||
}
|
useTableSelection<NoticeTable>();
|
||||||
|
|
||||||
|
// ─── 对话框状态 ───
|
||||||
|
const { dialogVisible } = useCrudDialog();
|
||||||
|
|
||||||
|
const detailFormData = ref<NoticeTable>({});
|
||||||
|
|
||||||
|
const noticeDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] =
|
||||||
|
[
|
||||||
|
{ label: "标题", prop: "notice_title" },
|
||||||
|
{ label: "类型", prop: "notice_type", slot: "notice_type" },
|
||||||
|
{
|
||||||
|
label: "状态",
|
||||||
|
prop: "status",
|
||||||
|
tag: {
|
||||||
|
map: { "0": { type: "success", text: "启用" }, "1": { type: "danger", text: "停用" } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ label: "描述", prop: "description" },
|
||||||
|
{ label: "内容", prop: "notice_content", slot: "notice_content", span: 4 },
|
||||||
|
{ label: "创建人", prop: "created_by.name" },
|
||||||
|
{ label: "更新人", prop: "updated_by.name" },
|
||||||
|
{ label: "创建时间", prop: "created_time" },
|
||||||
|
{ label: "更新时间", prop: "updated_time" },
|
||||||
|
];
|
||||||
|
|
||||||
|
/** 详情富文本 HTML(用于预览) */
|
||||||
|
const detailContentHtml = computed({
|
||||||
|
get: () => detailFormData.value.notice_content ?? "",
|
||||||
|
set: (v: string) => {
|
||||||
|
detailFormData.value.notice_content = v;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 详情是否有可视文本 */
|
||||||
|
const detailHasRenderableContent = computed(() => {
|
||||||
|
const raw = detailFormData.value.notice_content ?? "";
|
||||||
|
if (!raw.trim()) return false;
|
||||||
|
const plain = raw
|
||||||
|
.replace(/<[^>]+>/g, " ")
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.trim();
|
||||||
|
return plain.length > 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
const formData = ref<NoticeForm>({
|
||||||
|
id: undefined,
|
||||||
|
notice_title: "",
|
||||||
|
notice_type: "",
|
||||||
|
notice_content: "",
|
||||||
|
status: "0",
|
||||||
|
description: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const rules = reactive({
|
||||||
|
notice_title: [{ required: true, message: "请输入公告通知标题", trigger: "blur" }],
|
||||||
|
notice_type: [{ required: true, message: "请选择公告通知类型", trigger: "blur" }],
|
||||||
|
notice_content: [{ required: true, message: "请输入公告通知内容", trigger: "blur" }],
|
||||||
|
status: [{ required: true, message: "请选择公告通知状态", trigger: "blur" }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
||||||
|
const noticeFormRenderKey = ref(0);
|
||||||
|
|
||||||
|
const initialFormData: NoticeForm = {
|
||||||
|
id: undefined,
|
||||||
|
notice_title: "",
|
||||||
|
notice_type: "",
|
||||||
|
notice_content: "",
|
||||||
|
status: "0",
|
||||||
|
description: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─── CRUD 表单 ───
|
||||||
|
const { submitLoading, handleCloseDialog, handleOpenDialog, handleSubmit } =
|
||||||
|
useCrudForm<NoticeForm>({
|
||||||
|
formData,
|
||||||
|
initialFormData,
|
||||||
|
dialogVisible,
|
||||||
|
dataFormRef,
|
||||||
|
formRenderKey: noticeFormRenderKey,
|
||||||
|
detailApi: NoticeAPI.detailNotice,
|
||||||
|
createApi: NoticeAPI.createNotice,
|
||||||
|
updateApi: NoticeAPI.updateNotice,
|
||||||
|
titles: { create: "新增公告通知", update: "修改公告通知", detail: "公告通知详情" },
|
||||||
|
detailFormData,
|
||||||
|
onCreateSuccess: async () => {
|
||||||
|
await refreshCreate();
|
||||||
|
},
|
||||||
|
onUpdateSuccess: async () => {
|
||||||
|
await refreshUpdate();
|
||||||
|
},
|
||||||
|
onSubmitSuccess: async () => {
|
||||||
|
await noticeStore.getNotice();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const noticeDialogFormItems = computed<FormItem[]>(() => [
|
||||||
|
{
|
||||||
|
label: "标题",
|
||||||
|
key: "notice_title",
|
||||||
|
type: "input",
|
||||||
|
span: 24,
|
||||||
|
props: { placeholder: "请输入标题", maxlength: 50 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "描述",
|
||||||
|
key: "description",
|
||||||
|
type: "input",
|
||||||
|
span: 24,
|
||||||
|
props: {
|
||||||
|
type: "textarea",
|
||||||
|
rows: 2,
|
||||||
|
maxlength: 100,
|
||||||
|
showWordLimit: true,
|
||||||
|
placeholder: "请输入描述",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "类型",
|
||||||
|
key: "notice_type",
|
||||||
|
type: "select",
|
||||||
|
span: 24,
|
||||||
|
props: {
|
||||||
|
placeholder: "请选择类型",
|
||||||
|
clearable: true,
|
||||||
|
class: "!w-full max-w-md",
|
||||||
|
options: dictStore.getDictArray("sys_notice_type").map((item) => ({
|
||||||
|
label: item.dict_label,
|
||||||
|
value: item.dict_value,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "状态",
|
||||||
|
key: "status",
|
||||||
|
type: "input",
|
||||||
|
span: 24,
|
||||||
|
placeholder: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "内容",
|
||||||
|
key: "notice_content",
|
||||||
|
type: "input",
|
||||||
|
span: 24,
|
||||||
|
placeholder: "",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
columns,
|
columns,
|
||||||
@@ -373,11 +516,7 @@ const noticeCrudCols = computed(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
const exportQueryParams = computed(() => {
|
const exportQueryParams = computed(() => {
|
||||||
const sp = { ...(searchParams as object) } as Record<string, unknown>;
|
const sp = stripPaginationParams(searchParams as Record<string, unknown>);
|
||||||
delete sp.current;
|
|
||||||
delete sp.size;
|
|
||||||
delete sp.page_no;
|
|
||||||
delete sp.page_size;
|
|
||||||
return normalizeNoticeQuery(sp);
|
return normalizeNoticeQuery(sp);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -394,136 +533,6 @@ const noticeExportContentConfig = computed(() => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const detailFormData = ref<NoticeTable>({});
|
|
||||||
|
|
||||||
const noticeDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] =
|
|
||||||
[
|
|
||||||
{ label: "标题", prop: "notice_title" },
|
|
||||||
{ label: "类型", prop: "notice_type", slot: "notice_type" },
|
|
||||||
{
|
|
||||||
label: "状态",
|
|
||||||
prop: "status",
|
|
||||||
tag: {
|
|
||||||
map: { "0": { type: "success", text: "启用" }, "1": { type: "danger", text: "停用" } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ label: "描述", prop: "description" },
|
|
||||||
{ label: "内容", prop: "notice_content", slot: "notice_content", span: 4 },
|
|
||||||
{ label: "创建人", prop: "created_by.name" },
|
|
||||||
{ label: "更新人", prop: "updated_by.name" },
|
|
||||||
{ label: "创建时间", prop: "created_time" },
|
|
||||||
{ label: "更新时间", prop: "updated_time" },
|
|
||||||
];
|
|
||||||
|
|
||||||
/** 详情富文本 HTML(用于预览) */
|
|
||||||
const detailContentHtml = computed({
|
|
||||||
get: () => detailFormData.value.notice_content ?? "",
|
|
||||||
set: (v: string) => {
|
|
||||||
detailFormData.value.notice_content = v;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/** 详情是否有可视文本(排除仅空标签) */
|
|
||||||
const detailHasRenderableContent = computed(() => {
|
|
||||||
const raw = detailFormData.value.notice_content ?? "";
|
|
||||||
if (!raw.trim()) return false;
|
|
||||||
const plain = raw
|
|
||||||
.replace(/<[^>]+>/g, " ")
|
|
||||||
.replace(/\s+/g, " ")
|
|
||||||
.trim();
|
|
||||||
return plain.length > 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
const formData = ref<NoticeForm>({
|
|
||||||
id: undefined,
|
|
||||||
notice_title: "",
|
|
||||||
notice_type: "",
|
|
||||||
notice_content: "",
|
|
||||||
status: "0",
|
|
||||||
description: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const dialogVisible = reactive({
|
|
||||||
title: "",
|
|
||||||
visible: false,
|
|
||||||
type: "create" as "create" | "update" | "detail",
|
|
||||||
});
|
|
||||||
|
|
||||||
const rules = reactive({
|
|
||||||
notice_title: [{ required: true, message: "请输入公告通知标题", trigger: "blur" }],
|
|
||||||
notice_type: [{ required: true, message: "请选择公告通知类型", trigger: "blur" }],
|
|
||||||
notice_content: [{ required: true, message: "请输入公告通知内容", trigger: "blur" }],
|
|
||||||
status: [{ required: true, message: "请选择公告通知状态", trigger: "blur" }],
|
|
||||||
});
|
|
||||||
|
|
||||||
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
|
||||||
const submitLoading = ref(false);
|
|
||||||
|
|
||||||
/** 每次打开弹窗递增,令 FaForm 重新挂载并同步初始 model(与示例页声明式 items 一致) */
|
|
||||||
const noticeFormRenderKey = ref(0);
|
|
||||||
|
|
||||||
/** 公告编辑表单字段配置(FaForm + items) */
|
|
||||||
const noticeDialogFormItems = computed<FormItem[]>(() => [
|
|
||||||
{
|
|
||||||
label: "标题",
|
|
||||||
key: "notice_title",
|
|
||||||
type: "input",
|
|
||||||
span: 24,
|
|
||||||
props: { placeholder: "请输入标题", maxlength: 50 },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "描述",
|
|
||||||
key: "description",
|
|
||||||
type: "input",
|
|
||||||
span: 24,
|
|
||||||
props: {
|
|
||||||
type: "textarea",
|
|
||||||
rows: 2,
|
|
||||||
maxlength: 100,
|
|
||||||
showWordLimit: true,
|
|
||||||
placeholder: "请输入描述",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "类型",
|
|
||||||
key: "notice_type",
|
|
||||||
type: "select",
|
|
||||||
span: 24,
|
|
||||||
props: {
|
|
||||||
placeholder: "请选择类型",
|
|
||||||
clearable: true,
|
|
||||||
class: "!w-full max-w-md",
|
|
||||||
options: dictStore.getDictArray("sys_notice_type").map((item) => ({
|
|
||||||
label: item.dict_label,
|
|
||||||
value: item.dict_value,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "状态",
|
|
||||||
key: "status",
|
|
||||||
type: "input",
|
|
||||||
span: 24,
|
|
||||||
placeholder: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "内容",
|
|
||||||
key: "notice_content",
|
|
||||||
type: "input",
|
|
||||||
span: 24,
|
|
||||||
placeholder: "",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const initialFormData: NoticeForm = {
|
|
||||||
id: undefined,
|
|
||||||
notice_title: "",
|
|
||||||
notice_type: "",
|
|
||||||
notice_content: "",
|
|
||||||
status: "0",
|
|
||||||
description: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { exportVisible, openExport } = useImportExport();
|
const { exportVisible, openExport } = useImportExport();
|
||||||
|
|
||||||
function buildNoticeReplaceParams(p: NoticeSearchForm): Record<string, unknown> {
|
function buildNoticeReplaceParams(p: NoticeSearchForm): Record<string, unknown> {
|
||||||
@@ -565,70 +574,9 @@ function onResetSearch() {
|
|||||||
void resetSearchParams();
|
void resetSearchParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resetForm() {
|
|
||||||
const inner = dataFormRef.value?.ref;
|
|
||||||
inner?.resetFields();
|
|
||||||
inner?.clearValidate();
|
|
||||||
Object.assign(formData, initialFormData);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleCloseDialog() {
|
|
||||||
dialogVisible.visible = false;
|
|
||||||
await resetForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleOpenDialog(type: "create" | "update" | "detail", id?: number) {
|
|
||||||
dialogVisible.type = type;
|
|
||||||
if (id) {
|
|
||||||
const response = await NoticeAPI.detailNotice(id);
|
|
||||||
if (type === "detail") {
|
|
||||||
dialogVisible.title = "公告通知详情";
|
|
||||||
Object.assign(detailFormData.value, response.data.data ?? {});
|
|
||||||
} else if (type === "update") {
|
|
||||||
dialogVisible.title = "修改公告通知";
|
|
||||||
Object.assign(formData, response.data.data);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dialogVisible.title = "新增公告通知";
|
|
||||||
Object.assign(formData.value, initialFormData);
|
|
||||||
formData.value.id = undefined;
|
|
||||||
}
|
|
||||||
noticeFormRenderKey.value += 1;
|
|
||||||
dialogVisible.visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSubmit() {
|
|
||||||
dataFormRef.value?.validate(async (valid: boolean) => {
|
|
||||||
if (!valid) return;
|
|
||||||
submitLoading.value = true;
|
|
||||||
const id = formData.value.id;
|
|
||||||
try {
|
|
||||||
if (id) {
|
|
||||||
await NoticeAPI.updateNotice(id, { id, ...formData.value });
|
|
||||||
await refreshUpdate();
|
|
||||||
} else {
|
|
||||||
await NoticeAPI.createNotice(formData.value);
|
|
||||||
await refreshCreate();
|
|
||||||
}
|
|
||||||
dialogVisible.visible = false;
|
|
||||||
await resetForm();
|
|
||||||
await noticeStore.getNotice();
|
|
||||||
} catch (error: unknown) {
|
|
||||||
console.error(error);
|
|
||||||
} finally {
|
|
||||||
submitLoading.value = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteNoticeRow(id: number) {
|
async function deleteNoticeRow(id: number) {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm("确认删除该项数据?", "警告", {
|
await confirmDelete();
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
|
|
||||||
await NoticeAPI.deleteNotice([id]);
|
await NoticeAPI.deleteNotice([id]);
|
||||||
await noticeStore.getNotice();
|
await noticeStore.getNotice();
|
||||||
ElMessage.success("删除成功");
|
ElMessage.success("删除成功");
|
||||||
@@ -684,11 +632,7 @@ async function handleBatchDelete() {
|
|||||||
const ids = selectedIds.value;
|
const ids = selectedIds.value;
|
||||||
if (ids.length === 0) return;
|
if (ids.length === 0) return;
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 条数据吗?`, "批量删除", {
|
await confirmBatchDelete(ids.length);
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
batchDeleting.value = true;
|
batchDeleting.value = true;
|
||||||
await NoticeAPI.deleteNotice(ids);
|
await NoticeAPI.deleteNotice(ids);
|
||||||
await noticeStore.getNotice();
|
await noticeStore.getNotice();
|
||||||
@@ -708,12 +652,8 @@ async function handleMoreClick(status: string) {
|
|||||||
ElMessage.warning("请先选择要操作的数据");
|
ElMessage.warning("请先选择要操作的数据");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ElMessageBox.confirm(`确认${status === "0" ? "启用" : "停用"}该项数据?`, "警告", {
|
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
try {
|
try {
|
||||||
|
await confirmToggleStatus(status);
|
||||||
await NoticeAPI.batchNotice({ ids, status });
|
await NoticeAPI.batchNotice({ ids, status });
|
||||||
await refreshData();
|
await refreshData();
|
||||||
await noticeStore.getNotice();
|
await noticeStore.getNotice();
|
||||||
@@ -728,15 +668,6 @@ onMounted(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
/* FaForm 底部预留的操作栏列在弹窗内不需要占位 */
|
|
||||||
.crud-dialog-art-form :deep(.el-row > .el-col:last-child) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.crud-dialog-art-form :deep(.el-form-item__content) {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 富文本预览区域阅读样式(与 FaWangEditor 输出 HTML 展示一致) */
|
/* 富文本预览区域阅读样式(与 FaWangEditor 输出 HTML 展示一致) */
|
||||||
.notice-html-preview {
|
.notice-html-preview {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<!-- 系统配置:Art 布局 + useTable,与 notice 页一致 -->
|
<!-- 系统配置:Fa 布局 + useTable,与 notice 页一致 -->
|
||||||
<template>
|
<template>
|
||||||
<div class="fa-full-height">
|
<div class="fa-full-height">
|
||||||
<FaSearchBar
|
<FaSearchBar
|
||||||
@@ -114,6 +114,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useTable } from "@/hooks/core/useTable";
|
import { useTable } from "@/hooks/core/useTable";
|
||||||
import { useImportExport } from "@/hooks/core/useImportExport";
|
import { useImportExport } from "@/hooks/core/useImportExport";
|
||||||
|
import { useCrudDialog } from "@/hooks/core/useCrudDialog";
|
||||||
|
import { useTableSelection } from "@/hooks/core/useTableSelection";
|
||||||
|
import { useCrudForm } from "@/hooks/core/useCrudForm";
|
||||||
|
import { confirmDelete, confirmBatchDelete } from "@/hooks/core/useConfirm";
|
||||||
|
import { cleanEmptyArrayParams, stripPaginationParams } from "@/utils/query";
|
||||||
import type { IObject } from "@/components/modal/types";
|
import type { IObject } from "@/components/modal/types";
|
||||||
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
||||||
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||||
@@ -143,9 +148,7 @@ type ParamSearchForm = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function normalizeParamQuery(params: Record<string, unknown>): ConfigPageQuery {
|
function normalizeParamQuery(params: Record<string, unknown>): ConfigPageQuery {
|
||||||
const p = { ...params } as Record<string, unknown>;
|
const p = cleanEmptyArrayParams({ ...params });
|
||||||
if (Array.isArray(p.created_time) && p.created_time.length === 0) p.created_time = undefined;
|
|
||||||
if (Array.isArray(p.updated_time) && p.updated_time.length === 0) p.updated_time = undefined;
|
|
||||||
if (p.config_type === "true" || p.config_type === true) p.config_type = true;
|
if (p.config_type === "true" || p.config_type === true) p.config_type = true;
|
||||||
else if (p.config_type === "false" || p.config_type === false) p.config_type = false;
|
else if (p.config_type === "false" || p.config_type === false) p.config_type = false;
|
||||||
return p as unknown as ConfigPageQuery;
|
return p as unknown as ConfigPageQuery;
|
||||||
@@ -223,15 +226,129 @@ const paramSearchItems = computed<SearchFormItem[]>(() => [
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
||||||
const selectedRows = ref<ConfigTable[]>([]);
|
|
||||||
const selectedIds = computed(() =>
|
|
||||||
selectedRows.value.map((r) => r.id).filter((id): id is number => id != null && !Number.isNaN(id))
|
|
||||||
);
|
|
||||||
const batchDeleting = ref(false);
|
|
||||||
|
|
||||||
function onTableSelectionChange(rows: ConfigTable[]) {
|
// ─── 表格多选 ───
|
||||||
selectedRows.value = rows;
|
const { selectedRows, selectedIds, batchDeleting, onTableSelectionChange } =
|
||||||
}
|
useTableSelection<ConfigTable>();
|
||||||
|
|
||||||
|
// ─── 对话框状态 ───
|
||||||
|
const { dialogVisible } = useCrudDialog();
|
||||||
|
|
||||||
|
const detailFormData = ref<ConfigTable>({} as ConfigTable);
|
||||||
|
|
||||||
|
const paramDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] =
|
||||||
|
[
|
||||||
|
{ label: "配置名称", prop: "config_name" },
|
||||||
|
{
|
||||||
|
label: "系统内置",
|
||||||
|
prop: "config_type",
|
||||||
|
tag: {
|
||||||
|
map: { true: { type: "success", text: "是" }, false: { type: "danger", text: "否" } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ label: "配置键", prop: "config_key" },
|
||||||
|
{ label: "配置值", prop: "config_value" },
|
||||||
|
{ label: "描述", prop: "description" },
|
||||||
|
{ label: "创建时间", prop: "created_time" },
|
||||||
|
{ label: "更新时间", prop: "updated_time" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const formData = ref<ConfigForm>({
|
||||||
|
id: undefined,
|
||||||
|
config_name: "",
|
||||||
|
config_key: "",
|
||||||
|
config_value: "",
|
||||||
|
config_type: false,
|
||||||
|
description: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const rules = reactive({
|
||||||
|
config_name: [{ required: true, message: "请输入系统配置名称", trigger: "blur" }],
|
||||||
|
config_key: [{ required: true, message: "请输入系统配置键", trigger: "blur" }],
|
||||||
|
config_value: [{ required: true, message: "请输入系统配置值", trigger: "blur" }],
|
||||||
|
config_type: [{ required: true, message: "请选择系统配置类型", trigger: "blur" }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
||||||
|
const paramFormRenderKey = ref(0);
|
||||||
|
|
||||||
|
const initialFormData: ConfigForm = {
|
||||||
|
id: undefined,
|
||||||
|
config_name: "",
|
||||||
|
config_key: "",
|
||||||
|
config_value: "",
|
||||||
|
config_type: false,
|
||||||
|
description: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─── CRUD 表单 ───
|
||||||
|
const { submitLoading, handleCloseDialog, handleOpenDialog, handleSubmit } =
|
||||||
|
useCrudForm<ConfigForm>({
|
||||||
|
formData,
|
||||||
|
initialFormData,
|
||||||
|
dialogVisible,
|
||||||
|
dataFormRef,
|
||||||
|
formRenderKey: paramFormRenderKey,
|
||||||
|
detailApi: ParamsAPI.detailParams,
|
||||||
|
createApi: ParamsAPI.createParams,
|
||||||
|
updateApi: ParamsAPI.updateParams,
|
||||||
|
titles: { create: "新增系统配置", update: "修改系统配置", detail: "系统配置详情" },
|
||||||
|
detailFormData,
|
||||||
|
onCreateSuccess: async () => {
|
||||||
|
await refreshCreate();
|
||||||
|
},
|
||||||
|
onUpdateSuccess: async () => {
|
||||||
|
await refreshUpdate();
|
||||||
|
},
|
||||||
|
onSubmitSuccess: async () => {
|
||||||
|
configStore.isConfigLoaded = false;
|
||||||
|
await configStore.getConfig();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const paramDialogFormItems = computed<FormItem[]>(() => [
|
||||||
|
{
|
||||||
|
label: "配置名称",
|
||||||
|
key: "config_name",
|
||||||
|
type: "input",
|
||||||
|
span: 24,
|
||||||
|
props: { placeholder: "请输入配置名称", maxlength: 50 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "配置键",
|
||||||
|
key: "config_key",
|
||||||
|
type: "input",
|
||||||
|
span: 24,
|
||||||
|
props: { placeholder: "请输入配置键", maxlength: 50 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "配置值",
|
||||||
|
key: "config_value",
|
||||||
|
type: "input",
|
||||||
|
span: 24,
|
||||||
|
props: { placeholder: "请输入配置值", maxlength: 100 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "系统内置",
|
||||||
|
key: "config_type",
|
||||||
|
type: "input",
|
||||||
|
span: 24,
|
||||||
|
placeholder: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "描述",
|
||||||
|
key: "description",
|
||||||
|
type: "input",
|
||||||
|
span: 24,
|
||||||
|
props: {
|
||||||
|
type: "textarea",
|
||||||
|
rows: 4,
|
||||||
|
maxlength: 100,
|
||||||
|
showWordLimit: true,
|
||||||
|
placeholder: "请输入描述",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
columns,
|
columns,
|
||||||
@@ -299,11 +416,7 @@ const paramCrudCols = computed(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
const exportQueryParams = computed(() => {
|
const exportQueryParams = computed(() => {
|
||||||
const sp = { ...(searchParams as object) } as Record<string, unknown>;
|
const sp = stripPaginationParams(searchParams as Record<string, unknown>);
|
||||||
delete sp.current;
|
|
||||||
delete sp.size;
|
|
||||||
delete sp.page_no;
|
|
||||||
delete sp.page_size;
|
|
||||||
return normalizeParamQuery(sp);
|
return normalizeParamQuery(sp);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -320,104 +433,6 @@ const paramExportContentConfig = computed(() => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const detailFormData = ref<ConfigTable>({} as ConfigTable);
|
|
||||||
|
|
||||||
const paramDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] =
|
|
||||||
[
|
|
||||||
{ label: "配置名称", prop: "config_name" },
|
|
||||||
{
|
|
||||||
label: "系统内置",
|
|
||||||
prop: "config_type",
|
|
||||||
tag: {
|
|
||||||
map: { true: { type: "success", text: "是" }, false: { type: "danger", text: "否" } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ label: "配置键", prop: "config_key" },
|
|
||||||
{ label: "配置值", prop: "config_value" },
|
|
||||||
{ label: "描述", prop: "description" },
|
|
||||||
{ label: "创建时间", prop: "created_time" },
|
|
||||||
{ label: "更新时间", prop: "updated_time" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const formData = ref<ConfigForm>({
|
|
||||||
id: undefined,
|
|
||||||
config_name: "",
|
|
||||||
config_key: "",
|
|
||||||
config_value: "",
|
|
||||||
config_type: false,
|
|
||||||
description: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
const dialogVisible = reactive({
|
|
||||||
title: "",
|
|
||||||
visible: false,
|
|
||||||
type: "create" as "create" | "update" | "detail",
|
|
||||||
});
|
|
||||||
|
|
||||||
const rules = reactive({
|
|
||||||
config_name: [{ required: true, message: "请输入系统配置名称", trigger: "blur" }],
|
|
||||||
config_key: [{ required: true, message: "请输入系统配置键", trigger: "blur" }],
|
|
||||||
config_value: [{ required: true, message: "请输入系统配置值", trigger: "blur" }],
|
|
||||||
config_type: [{ required: true, message: "请选择系统配置类型", trigger: "blur" }],
|
|
||||||
});
|
|
||||||
|
|
||||||
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
|
||||||
const paramFormRenderKey = ref(0);
|
|
||||||
|
|
||||||
const paramDialogFormItems = computed<FormItem[]>(() => [
|
|
||||||
{
|
|
||||||
label: "配置名称",
|
|
||||||
key: "config_name",
|
|
||||||
type: "input",
|
|
||||||
span: 24,
|
|
||||||
props: { placeholder: "请输入配置名称", maxlength: 50 },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "配置键",
|
|
||||||
key: "config_key",
|
|
||||||
type: "input",
|
|
||||||
span: 24,
|
|
||||||
props: { placeholder: "请输入配置键", maxlength: 50 },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "配置值",
|
|
||||||
key: "config_value",
|
|
||||||
type: "input",
|
|
||||||
span: 24,
|
|
||||||
props: { placeholder: "请输入配置值", maxlength: 100 },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "系统内置",
|
|
||||||
key: "config_type",
|
|
||||||
type: "input",
|
|
||||||
span: 24,
|
|
||||||
placeholder: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "描述",
|
|
||||||
key: "description",
|
|
||||||
type: "input",
|
|
||||||
span: 24,
|
|
||||||
props: {
|
|
||||||
type: "textarea",
|
|
||||||
rows: 4,
|
|
||||||
maxlength: 100,
|
|
||||||
showWordLimit: true,
|
|
||||||
placeholder: "请输入描述",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
const submitLoading = ref(false);
|
|
||||||
|
|
||||||
const initialFormData: ConfigForm = {
|
|
||||||
id: undefined,
|
|
||||||
config_name: "",
|
|
||||||
config_key: "",
|
|
||||||
config_value: "",
|
|
||||||
config_type: false,
|
|
||||||
description: "",
|
|
||||||
};
|
|
||||||
|
|
||||||
const { exportVisible, openExport } = useImportExport();
|
const { exportVisible, openExport } = useImportExport();
|
||||||
|
|
||||||
async function handleSearchBarSearch(params: ParamSearchForm) {
|
async function handleSearchBarSearch(params: ParamSearchForm) {
|
||||||
@@ -436,70 +451,9 @@ function onResetSearch() {
|
|||||||
void resetSearchParams();
|
void resetSearchParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resetForm() {
|
|
||||||
dataFormRef.value?.resetFields();
|
|
||||||
dataFormRef.value?.clearValidate();
|
|
||||||
Object.assign(formData, initialFormData);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleCloseDialog() {
|
|
||||||
dialogVisible.visible = false;
|
|
||||||
await resetForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleOpenDialog(type: "create" | "update" | "detail", id?: number) {
|
|
||||||
dialogVisible.type = type;
|
|
||||||
if (id) {
|
|
||||||
const response = await ParamsAPI.detailParams(id);
|
|
||||||
if (type === "detail") {
|
|
||||||
dialogVisible.title = "系统配置详情";
|
|
||||||
Object.assign(detailFormData.value, response.data.data ?? {});
|
|
||||||
} else if (type === "update") {
|
|
||||||
dialogVisible.title = "修改系统配置";
|
|
||||||
Object.assign(formData, response.data.data);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dialogVisible.title = "新增系统配置";
|
|
||||||
Object.assign(formData.value, initialFormData);
|
|
||||||
formData.value.id = undefined;
|
|
||||||
}
|
|
||||||
paramFormRenderKey.value += 1;
|
|
||||||
dialogVisible.visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSubmit() {
|
|
||||||
dataFormRef.value?.validate(async (valid: boolean) => {
|
|
||||||
if (!valid) return;
|
|
||||||
submitLoading.value = true;
|
|
||||||
const id = formData.value.id;
|
|
||||||
try {
|
|
||||||
if (id) {
|
|
||||||
await ParamsAPI.updateParams(id, { id, ...formData.value });
|
|
||||||
await refreshUpdate();
|
|
||||||
} else {
|
|
||||||
await ParamsAPI.createParams(formData.value);
|
|
||||||
await refreshCreate();
|
|
||||||
}
|
|
||||||
dialogVisible.visible = false;
|
|
||||||
await resetForm();
|
|
||||||
configStore.isConfigLoaded = false;
|
|
||||||
await configStore.getConfig();
|
|
||||||
} catch (error: unknown) {
|
|
||||||
console.error(error);
|
|
||||||
} finally {
|
|
||||||
submitLoading.value = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteParamRow(id: number) {
|
async function deleteParamRow(id: number) {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm("确认删除该项数据?", "警告", {
|
await confirmDelete();
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
|
|
||||||
await ParamsAPI.deleteParams([id]);
|
await ParamsAPI.deleteParams([id]);
|
||||||
configStore.isConfigLoaded = false;
|
configStore.isConfigLoaded = false;
|
||||||
await configStore.getConfig();
|
await configStore.getConfig();
|
||||||
@@ -556,11 +510,7 @@ async function handleBatchDelete() {
|
|||||||
const ids = selectedIds.value;
|
const ids = selectedIds.value;
|
||||||
if (ids.length === 0) return;
|
if (ids.length === 0) return;
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 条数据吗?`, "批量删除", {
|
await confirmBatchDelete(ids.length);
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
batchDeleting.value = true;
|
batchDeleting.value = true;
|
||||||
await ParamsAPI.deleteParams(ids);
|
await ParamsAPI.deleteParams(ids);
|
||||||
configStore.isConfigLoaded = false;
|
configStore.isConfigLoaded = false;
|
||||||
@@ -575,13 +525,3 @@ async function handleBatchDelete() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.crud-dialog-art-form :deep(.el-row > .el-col:last-child) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.crud-dialog-art-form :deep(.el-form-item__content) {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -125,6 +125,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useTable } from "@/hooks/core/useTable";
|
import { useTable } from "@/hooks/core/useTable";
|
||||||
import { useImportExport } from "@/hooks/core/useImportExport";
|
import { useImportExport } from "@/hooks/core/useImportExport";
|
||||||
|
import { useCrudDialog } from "@/hooks/core/useCrudDialog";
|
||||||
|
import { useTableSelection } from "@/hooks/core/useTableSelection";
|
||||||
|
import { useCrudForm } from "@/hooks/core/useCrudForm";
|
||||||
|
import { confirmDelete, confirmBatchDelete, confirmToggleStatus } from "@/hooks/core/useConfirm";
|
||||||
|
import { cleanEmptyArrayParams, stripPaginationParams } from "@/utils/query";
|
||||||
import type { IObject } from "@/components/modal/types";
|
import type { IObject } from "@/components/modal/types";
|
||||||
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
||||||
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||||
@@ -155,10 +160,7 @@ type PositionSearchForm = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function normalizePositionQuery(params: Record<string, unknown>): PositionPageQuery {
|
function normalizePositionQuery(params: Record<string, unknown>): PositionPageQuery {
|
||||||
const p = { ...params } as Record<string, unknown>;
|
return cleanEmptyArrayParams({ ...params }) as unknown as PositionPageQuery;
|
||||||
if (Array.isArray(p.created_time) && p.created_time.length === 0) p.created_time = undefined;
|
|
||||||
if (Array.isArray(p.updated_time) && p.updated_time.length === 0) p.updated_time = undefined;
|
|
||||||
return p as unknown as PositionPageQuery;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildPositionReplaceParams(p: PositionSearchForm): Record<string, unknown> {
|
function buildPositionReplaceParams(p: PositionSearchForm): Record<string, unknown> {
|
||||||
@@ -345,33 +347,8 @@ const positionSearchItems = computed<SearchFormItem[]>(() => [
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
||||||
const selectedRows = ref<PositionTable[]>([]);
|
const { selectedRows, selectedIds, batchDeleting, onTableSelectionChange } =
|
||||||
const selectedIds = computed(() =>
|
useTableSelection<PositionTable>();
|
||||||
selectedRows.value.map((r) => r.id).filter((id): id is number => id != null && !Number.isNaN(id))
|
|
||||||
);
|
|
||||||
const batchDeleting = ref(false);
|
|
||||||
|
|
||||||
function onTableSelectionChange(rows: PositionTable[]) {
|
|
||||||
selectedRows.value = rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deletePositionRow(id: number) {
|
|
||||||
try {
|
|
||||||
await ElMessageBox.confirm("确认删除该项数据?", "警告", {
|
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
|
|
||||||
await PositionAPI.deletePosition([id]);
|
|
||||||
await userStore.getUserInfo();
|
|
||||||
ElMessage.success("删除成功");
|
|
||||||
faTableRef.value?.elTableRef?.clearSelection();
|
|
||||||
await refreshRemove();
|
|
||||||
} catch {
|
|
||||||
// 用户取消
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const opCtx = {
|
const opCtx = {
|
||||||
onDetail: (id: number) => void handleOpenDialog("detail", id),
|
onDetail: (id: number) => void handleOpenDialog("detail", id),
|
||||||
@@ -456,12 +433,10 @@ const positionCrudCols = computed(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
const exportQueryParams = computed(() => {
|
const exportQueryParams = computed(() => {
|
||||||
const sp = { ...(searchParams as object) } as Record<string, unknown>;
|
return normalizePositionQuery(stripPaginationParams(searchParams)) as unknown as Record<
|
||||||
delete sp.current;
|
string,
|
||||||
delete sp.size;
|
unknown
|
||||||
delete sp.page_no;
|
>;
|
||||||
delete sp.page_size;
|
|
||||||
return normalizePositionQuery(sp) as unknown as Record<string, unknown>;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const positionExportContentConfig = computed(() => ({
|
const positionExportContentConfig = computed(() => ({
|
||||||
@@ -505,11 +480,7 @@ const formData = ref<PositionForm>({
|
|||||||
description: undefined,
|
description: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const dialogVisible = reactive({
|
const { dialogVisible } = useCrudDialog();
|
||||||
title: "",
|
|
||||||
visible: false,
|
|
||||||
type: "create" as "create" | "update" | "detail",
|
|
||||||
});
|
|
||||||
|
|
||||||
const rules = reactive({
|
const rules = reactive({
|
||||||
name: [{ required: true, message: "请输入岗位名称", trigger: "blur" }],
|
name: [{ required: true, message: "请输入岗位名称", trigger: "blur" }],
|
||||||
@@ -528,6 +499,30 @@ const initialFormData: PositionForm = {
|
|||||||
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
||||||
const positionFormRenderKey = ref(0);
|
const positionFormRenderKey = ref(0);
|
||||||
|
|
||||||
|
// ─── CRUD 表单 ───
|
||||||
|
const { submitLoading, handleCloseDialog, handleOpenDialog, handleSubmit } =
|
||||||
|
useCrudForm<PositionForm>({
|
||||||
|
formData,
|
||||||
|
initialFormData,
|
||||||
|
dialogVisible,
|
||||||
|
dataFormRef,
|
||||||
|
formRenderKey: positionFormRenderKey,
|
||||||
|
detailApi: PositionAPI.detailPosition,
|
||||||
|
createApi: PositionAPI.createPosition,
|
||||||
|
updateApi: PositionAPI.updatePosition,
|
||||||
|
titles: { create: "新增岗位", update: "修改岗位", detail: "岗位详情" },
|
||||||
|
detailFormData,
|
||||||
|
onCreateSuccess: async () => {
|
||||||
|
await refreshCreate();
|
||||||
|
},
|
||||||
|
onUpdateSuccess: async () => {
|
||||||
|
await refreshUpdate();
|
||||||
|
},
|
||||||
|
onSubmitSuccess: async () => {
|
||||||
|
await userStore.getUserInfo();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const positionDialogFormItems = computed<FormItem[]>(() => [
|
const positionDialogFormItems = computed<FormItem[]>(() => [
|
||||||
{
|
{
|
||||||
label: "岗位名称",
|
label: "岗位名称",
|
||||||
@@ -564,7 +559,6 @@ const positionDialogFormItems = computed<FormItem[]>(() => [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
const submitLoading = ref(false);
|
|
||||||
const { exportVisible, openExport } = useImportExport();
|
const { exportVisible, openExport } = useImportExport();
|
||||||
|
|
||||||
async function handleSearchBarSearch(params: PositionSearchForm) {
|
async function handleSearchBarSearch(params: PositionSearchForm) {
|
||||||
@@ -594,70 +588,24 @@ function onResetSearch() {
|
|||||||
void resetSearchParams();
|
void resetSearchParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resetForm() {
|
async function deletePositionRow(id: number) {
|
||||||
dataFormRef.value?.resetFields();
|
try {
|
||||||
dataFormRef.value?.clearValidate();
|
await confirmDelete();
|
||||||
Object.assign(formData, initialFormData);
|
await PositionAPI.deletePosition([id]);
|
||||||
}
|
await userStore.getUserInfo();
|
||||||
|
ElMessage.success("删除成功");
|
||||||
async function handleCloseDialog() {
|
faTableRef.value?.elTableRef?.clearSelection();
|
||||||
dialogVisible.visible = false;
|
await refreshRemove();
|
||||||
await resetForm();
|
} catch {
|
||||||
}
|
// 用户取消
|
||||||
|
|
||||||
async function handleOpenDialog(type: "create" | "update" | "detail", id?: number) {
|
|
||||||
dialogVisible.type = type;
|
|
||||||
if (id) {
|
|
||||||
const response = await PositionAPI.detailPosition(id);
|
|
||||||
if (type === "detail") {
|
|
||||||
dialogVisible.title = "岗位详情";
|
|
||||||
Object.assign(detailFormData.value, response.data.data ?? {});
|
|
||||||
} else if (type === "update") {
|
|
||||||
dialogVisible.title = "修改岗位";
|
|
||||||
Object.assign(formData, response.data.data);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dialogVisible.title = "新增岗位";
|
|
||||||
Object.assign(formData.value, initialFormData);
|
|
||||||
formData.value.id = undefined;
|
|
||||||
}
|
}
|
||||||
positionFormRenderKey.value += 1;
|
|
||||||
dialogVisible.visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSubmit() {
|
|
||||||
dataFormRef.value?.validate(async (valid: boolean) => {
|
|
||||||
if (!valid) return;
|
|
||||||
submitLoading.value = true;
|
|
||||||
const id = formData.value.id;
|
|
||||||
try {
|
|
||||||
if (id) {
|
|
||||||
await PositionAPI.updatePosition(id, { id, ...formData.value });
|
|
||||||
await refreshUpdate();
|
|
||||||
} else {
|
|
||||||
await PositionAPI.createPosition(formData.value);
|
|
||||||
await refreshCreate();
|
|
||||||
}
|
|
||||||
dialogVisible.visible = false;
|
|
||||||
await resetForm();
|
|
||||||
await userStore.getUserInfo();
|
|
||||||
} catch (error: unknown) {
|
|
||||||
console.error(error);
|
|
||||||
} finally {
|
|
||||||
submitLoading.value = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleBatchDelete() {
|
async function handleBatchDelete() {
|
||||||
const ids = selectedIds.value;
|
const ids = selectedIds.value;
|
||||||
if (ids.length === 0) return;
|
if (ids.length === 0) return;
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 条数据吗?`, "批量删除", {
|
await confirmBatchDelete(ids.length);
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
batchDeleting.value = true;
|
batchDeleting.value = true;
|
||||||
await PositionAPI.deletePosition(ids);
|
await PositionAPI.deletePosition(ids);
|
||||||
await userStore.getUserInfo();
|
await userStore.getUserInfo();
|
||||||
@@ -678,11 +626,7 @@ async function handleMoreClick(status: string) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确认${status === "0" ? "启用" : "停用"}该项数据?`, "警告", {
|
await confirmToggleStatus(status);
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
await PositionAPI.batchPosition({ ids, status });
|
await PositionAPI.batchPosition({ ids, status });
|
||||||
await refreshData();
|
await refreshData();
|
||||||
await userStore.getUserInfo();
|
await userStore.getUserInfo();
|
||||||
@@ -693,14 +637,6 @@ async function handleMoreClick(status: string) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.crud-dialog-art-form :deep(.el-row > .el-col:last-child) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.crud-dialog-art-form :deep(.el-form-item__content) {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.position-table-actions .inline-flex) {
|
:deep(.position-table-actions .inline-flex) {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,6 +145,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useTable } from "@/hooks/core/useTable";
|
import { useTable } from "@/hooks/core/useTable";
|
||||||
import { useImportExport } from "@/hooks/core/useImportExport";
|
import { useImportExport } from "@/hooks/core/useImportExport";
|
||||||
|
import { useCrudDialog } from "@/hooks/core/useCrudDialog";
|
||||||
|
import { useTableSelection } from "@/hooks/core/useTableSelection";
|
||||||
|
import { useCrudForm } from "@/hooks/core/useCrudForm";
|
||||||
|
import { confirmDelete, confirmBatchDelete, confirmToggleStatus } from "@/hooks/core/useConfirm";
|
||||||
|
import { cleanEmptyArrayParams, stripPaginationParams } from "@/utils/query";
|
||||||
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 type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
||||||
@@ -173,14 +178,7 @@ type RoleSearchForm = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function normalizeRoleQuery(params: Record<string, unknown>): TablePageQuery {
|
function normalizeRoleQuery(params: Record<string, unknown>): TablePageQuery {
|
||||||
const p = { ...params } as Record<string, unknown>;
|
const p = cleanEmptyArrayParams({ ...params });
|
||||||
if (Array.isArray(p.created_time) && p.created_time.length === 0) p.created_time = undefined;
|
|
||||||
if (Array.isArray(p.updated_time) && p.updated_time.length === 0) p.updated_time = undefined;
|
|
||||||
if (typeof p.status === "string") {
|
|
||||||
if (p.status === "true" || p.status === "false") {
|
|
||||||
/* 与列表查询一致,保留字符串 */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return p as unknown as TablePageQuery;
|
return p as unknown as TablePageQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,15 +351,10 @@ const roleSearchItems = computed<SearchFormItem[]>(() => [
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
||||||
const selectedRows = ref<RoleTable[]>([]);
|
|
||||||
const selectedIds = computed(() =>
|
|
||||||
selectedRows.value.map((r) => r.id).filter((id): id is number => id != null && !Number.isNaN(id))
|
|
||||||
);
|
|
||||||
const batchDeleting = ref(false);
|
|
||||||
|
|
||||||
function onTableSelectionChange(rows: RoleTable[]) {
|
// ─── 表格多选 ───
|
||||||
selectedRows.value = rows;
|
const { selectedRows, selectedIds, batchDeleting, onTableSelectionChange } =
|
||||||
}
|
useTableSelection<RoleTable>();
|
||||||
|
|
||||||
const drawerVisible = ref(false);
|
const drawerVisible = ref(false);
|
||||||
const checkedRole = ref({ id: 0, name: "" });
|
const checkedRole = ref({ id: 0, name: "" });
|
||||||
@@ -373,11 +366,7 @@ function handleOpenAssignPermDialog(roleId: number, roleName: string) {
|
|||||||
|
|
||||||
async function deleteRoleRow(id: number) {
|
async function deleteRoleRow(id: number) {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm("确认删除该项数据?", "警告", {
|
await confirmDelete();
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
await RoleAPI.deleteRole([id]);
|
await RoleAPI.deleteRole([id]);
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
await userStore.getUserInfo();
|
await userStore.getUserInfo();
|
||||||
@@ -389,6 +378,91 @@ async function deleteRoleRow(id: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── 对话框状态 ───
|
||||||
|
const { dialogVisible } = useCrudDialog();
|
||||||
|
|
||||||
|
const detailFormData = ref<RoleTable>({} as RoleTable);
|
||||||
|
|
||||||
|
const roleDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] =
|
||||||
|
[
|
||||||
|
{ label: "角色名称", prop: "name" },
|
||||||
|
{ label: "排序", prop: "order" },
|
||||||
|
{ label: "角色编码", prop: "code" },
|
||||||
|
{ label: "数据权限", prop: "data_scope", slot: "data_scope" },
|
||||||
|
{ label: "所属部门", prop: "depts", slot: "depts" },
|
||||||
|
{
|
||||||
|
label: "状态",
|
||||||
|
prop: "status",
|
||||||
|
tag: {
|
||||||
|
map: { "0": { type: "success", text: "启用" }, "1": { type: "danger", text: "停用" } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ label: "创建时间", prop: "created_time" },
|
||||||
|
{ label: "更新时间", prop: "updated_time" },
|
||||||
|
{ label: "描述", prop: "description", span: 4 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const formData = ref<RoleForm>({
|
||||||
|
id: undefined,
|
||||||
|
name: undefined,
|
||||||
|
order: 1,
|
||||||
|
code: "",
|
||||||
|
status: "0",
|
||||||
|
description: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
const CODE_PATTERN = /^[A-Za-z][A-Za-z0-9_]{1,15}$/;
|
||||||
|
|
||||||
|
const rules = reactive({
|
||||||
|
name: [{ required: true, message: "请输入角色名称", trigger: "blur" }],
|
||||||
|
code: [
|
||||||
|
{ required: true, message: "请输入角色编码", trigger: "blur" },
|
||||||
|
{
|
||||||
|
pattern: CODE_PATTERN,
|
||||||
|
message: "字母开头,2-16位字母/数字/下划线",
|
||||||
|
trigger: "blur",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
order: [{ required: true, message: "请输入角色排序", trigger: "blur" }],
|
||||||
|
status: [{ required: true, message: "请选择状态", trigger: "blur" }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
||||||
|
const roleFormRenderKey = ref(0);
|
||||||
|
|
||||||
|
const initialFormData: RoleForm = {
|
||||||
|
id: undefined,
|
||||||
|
name: undefined,
|
||||||
|
order: 1,
|
||||||
|
code: "",
|
||||||
|
status: "0",
|
||||||
|
description: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ─── CRUD 表单 ───
|
||||||
|
const { submitLoading, handleCloseDialog, handleOpenDialog, handleSubmit } = useCrudForm<RoleForm>({
|
||||||
|
formData,
|
||||||
|
initialFormData,
|
||||||
|
dialogVisible,
|
||||||
|
dataFormRef,
|
||||||
|
formRenderKey: roleFormRenderKey,
|
||||||
|
detailApi: RoleAPI.detailRole,
|
||||||
|
createApi: RoleAPI.createRole,
|
||||||
|
updateApi: RoleAPI.updateRole,
|
||||||
|
titles: { create: "新增角色", update: "修改角色", detail: "角色详情" },
|
||||||
|
detailFormData,
|
||||||
|
onCreateSuccess: async () => {
|
||||||
|
await refreshCreate();
|
||||||
|
},
|
||||||
|
onUpdateSuccess: async () => {
|
||||||
|
await refreshUpdate();
|
||||||
|
},
|
||||||
|
onSubmitSuccess: async () => {
|
||||||
|
const userStore = useUserStore();
|
||||||
|
await userStore.getUserInfo();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const opCtx = {
|
const opCtx = {
|
||||||
onPerm: handleOpenAssignPermDialog,
|
onPerm: handleOpenAssignPermDialog,
|
||||||
onDetail: (id: number) => void handleOpenDialog("detail", id),
|
onDetail: (id: number) => void handleOpenDialog("detail", id),
|
||||||
@@ -396,6 +470,58 @@ const opCtx = {
|
|||||||
onDelete: deleteRoleRow,
|
onDelete: deleteRoleRow,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const roleDialogFormItems = computed<FormItem[]>(() => [
|
||||||
|
{
|
||||||
|
label: "角色名称",
|
||||||
|
key: "name",
|
||||||
|
type: "input",
|
||||||
|
span: 24,
|
||||||
|
props: { placeholder: "请输入角色名称" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "排序",
|
||||||
|
key: "order",
|
||||||
|
type: "number",
|
||||||
|
span: 24,
|
||||||
|
props: {
|
||||||
|
controlsPosition: "right",
|
||||||
|
min: 0,
|
||||||
|
style: { width: "100px" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "角色编码",
|
||||||
|
key: "code",
|
||||||
|
type: "input",
|
||||||
|
span: 24,
|
||||||
|
props: {
|
||||||
|
placeholder: "字母开头,2-16位字母/数字/下划线",
|
||||||
|
maxlength: 16,
|
||||||
|
showWordLimit: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "状态",
|
||||||
|
key: "status",
|
||||||
|
type: "input",
|
||||||
|
span: 24,
|
||||||
|
placeholder: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "描述",
|
||||||
|
key: "description",
|
||||||
|
type: "input",
|
||||||
|
span: 24,
|
||||||
|
props: {
|
||||||
|
type: "textarea",
|
||||||
|
rows: 4,
|
||||||
|
maxlength: 100,
|
||||||
|
showWordLimit: true,
|
||||||
|
placeholder: "请输入描述",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
columns,
|
columns,
|
||||||
columnChecks,
|
columnChecks,
|
||||||
@@ -474,19 +600,8 @@ const roleCrudCols = computed(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
const exportQueryParams = computed(() => {
|
const exportQueryParams = computed(() => {
|
||||||
const sp = { ...(searchParams as object) } as Record<string, unknown>;
|
const sp = stripPaginationParams(searchParams as Record<string, unknown>);
|
||||||
delete sp.current;
|
return normalizeRoleQuery(sp) as unknown as Record<string, unknown>;
|
||||||
delete sp.size;
|
|
||||||
delete sp.page_no;
|
|
||||||
delete sp.page_size;
|
|
||||||
const q = normalizeRoleQuery(sp);
|
|
||||||
if (typeof q.status === "string") {
|
|
||||||
const s = q.status;
|
|
||||||
if (s === "true" || s === "false") {
|
|
||||||
(q as unknown as Record<string, unknown>).status = s === "true";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return q as unknown as Record<string, unknown>;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const roleExportContentConfig = computed(() => ({
|
const roleExportContentConfig = computed(() => ({
|
||||||
@@ -494,138 +609,12 @@ const roleExportContentConfig = computed(() => ({
|
|||||||
cols: roleCrudCols.value,
|
cols: roleCrudCols.value,
|
||||||
exportsBlobAction: async (params: IObject) => {
|
exportsBlobAction: async (params: IObject) => {
|
||||||
const base = { ...(exportQueryParams.value as Record<string, unknown>) };
|
const base = { ...(exportQueryParams.value as Record<string, unknown>) };
|
||||||
const merged = normalizeRoleQuery({
|
const merged = normalizeRoleQuery({ ...base, ...params } as Record<string, unknown>);
|
||||||
...base,
|
|
||||||
...params,
|
|
||||||
} as Record<string, unknown>);
|
|
||||||
if (typeof merged.status === "string") {
|
|
||||||
const s = merged.status;
|
|
||||||
if (s === "true" || s === "false") {
|
|
||||||
(merged as unknown as Record<string, unknown>).status = s === "true";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const res = await RoleAPI.exportRole(merged as TablePageQuery);
|
const res = await RoleAPI.exportRole(merged as TablePageQuery);
|
||||||
return res.data as Blob;
|
return res.data as Blob;
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const detailFormData = ref<RoleTable>({} as RoleTable);
|
|
||||||
|
|
||||||
const roleDetailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] =
|
|
||||||
[
|
|
||||||
{ label: "角色名称", prop: "name" },
|
|
||||||
{ label: "排序", prop: "order" },
|
|
||||||
{ label: "角色编码", prop: "code" },
|
|
||||||
{ label: "数据权限", prop: "data_scope", slot: "data_scope" },
|
|
||||||
{ label: "所属部门", prop: "depts", slot: "depts" },
|
|
||||||
{
|
|
||||||
label: "状态",
|
|
||||||
prop: "status",
|
|
||||||
tag: {
|
|
||||||
map: { "0": { type: "success", text: "启用" }, "1": { type: "danger", text: "停用" } },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ label: "创建时间", prop: "created_time" },
|
|
||||||
{ label: "更新时间", prop: "updated_time" },
|
|
||||||
{ label: "描述", prop: "description", span: 4 },
|
|
||||||
];
|
|
||||||
|
|
||||||
const formData = ref<RoleForm>({
|
|
||||||
id: undefined,
|
|
||||||
name: undefined,
|
|
||||||
order: 1,
|
|
||||||
code: "",
|
|
||||||
status: "0",
|
|
||||||
description: undefined,
|
|
||||||
});
|
|
||||||
|
|
||||||
const dialogVisible = reactive({
|
|
||||||
title: "",
|
|
||||||
visible: false,
|
|
||||||
type: "create" as "create" | "update" | "detail",
|
|
||||||
});
|
|
||||||
|
|
||||||
const CODE_PATTERN = /^[A-Za-z][A-Za-z0-9_]{1,15}$/;
|
|
||||||
|
|
||||||
const rules = reactive({
|
|
||||||
name: [{ required: true, message: "请输入角色名称", trigger: "blur" }],
|
|
||||||
code: [
|
|
||||||
{ required: true, message: "请输入角色编码", trigger: "blur" },
|
|
||||||
{
|
|
||||||
pattern: CODE_PATTERN,
|
|
||||||
message: "字母开头,2-16位字母/数字/下划线",
|
|
||||||
trigger: "blur",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
order: [{ required: true, message: "请输入角色排序", trigger: "blur" }],
|
|
||||||
status: [{ required: true, message: "请选择状态", trigger: "blur" }],
|
|
||||||
});
|
|
||||||
|
|
||||||
const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
|
||||||
const roleFormRenderKey = ref(0);
|
|
||||||
|
|
||||||
const roleDialogFormItems = computed<FormItem[]>(() => [
|
|
||||||
{
|
|
||||||
label: "角色名称",
|
|
||||||
key: "name",
|
|
||||||
type: "input",
|
|
||||||
span: 24,
|
|
||||||
props: { placeholder: "请输入角色名称" },
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "排序",
|
|
||||||
key: "order",
|
|
||||||
type: "number",
|
|
||||||
span: 24,
|
|
||||||
props: {
|
|
||||||
controlsPosition: "right",
|
|
||||||
min: 0,
|
|
||||||
style: { width: "100px" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "角色编码",
|
|
||||||
key: "code",
|
|
||||||
type: "input",
|
|
||||||
span: 24,
|
|
||||||
props: {
|
|
||||||
placeholder: "字母开头,2-16位字母/数字/下划线",
|
|
||||||
maxlength: 16,
|
|
||||||
showWordLimit: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "状态",
|
|
||||||
key: "status",
|
|
||||||
type: "input",
|
|
||||||
span: 24,
|
|
||||||
placeholder: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "描述",
|
|
||||||
key: "description",
|
|
||||||
type: "input",
|
|
||||||
span: 24,
|
|
||||||
props: {
|
|
||||||
type: "textarea",
|
|
||||||
rows: 4,
|
|
||||||
maxlength: 100,
|
|
||||||
showWordLimit: true,
|
|
||||||
placeholder: "请输入描述",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
const submitLoading = ref(false);
|
|
||||||
|
|
||||||
const initialFormData: RoleForm = {
|
|
||||||
id: undefined,
|
|
||||||
name: undefined,
|
|
||||||
order: 1,
|
|
||||||
code: "",
|
|
||||||
status: "0",
|
|
||||||
description: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { exportVisible, openExport } = useImportExport();
|
const { exportVisible, openExport } = useImportExport();
|
||||||
|
|
||||||
async function handleSearchBarSearch(params: RoleSearchForm) {
|
async function handleSearchBarSearch(params: RoleSearchForm) {
|
||||||
@@ -643,71 +632,11 @@ function onResetSearch() {
|
|||||||
void resetSearchParams();
|
void resetSearchParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resetForm() {
|
|
||||||
dataFormRef.value?.resetFields();
|
|
||||||
dataFormRef.value?.clearValidate();
|
|
||||||
Object.assign(formData, initialFormData);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleCloseDialog() {
|
|
||||||
dialogVisible.visible = false;
|
|
||||||
await resetForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleOpenDialog(type: "create" | "update" | "detail", id?: number) {
|
|
||||||
dialogVisible.type = type;
|
|
||||||
if (id) {
|
|
||||||
const response = await RoleAPI.detailRole(id);
|
|
||||||
if (type === "detail") {
|
|
||||||
dialogVisible.title = "角色详情";
|
|
||||||
Object.assign(detailFormData.value, response.data.data ?? {});
|
|
||||||
} else if (type === "update") {
|
|
||||||
dialogVisible.title = "修改角色";
|
|
||||||
Object.assign(formData, response.data.data);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dialogVisible.title = "新增角色";
|
|
||||||
Object.assign(formData.value, initialFormData);
|
|
||||||
formData.value.id = undefined;
|
|
||||||
}
|
|
||||||
roleFormRenderKey.value += 1;
|
|
||||||
dialogVisible.visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSubmit() {
|
|
||||||
dataFormRef.value?.validate(async (valid: boolean) => {
|
|
||||||
if (!valid) return;
|
|
||||||
submitLoading.value = true;
|
|
||||||
const id = formData.value.id;
|
|
||||||
try {
|
|
||||||
if (id) {
|
|
||||||
await RoleAPI.updateRole(id, { id, ...formData.value });
|
|
||||||
await refreshUpdate();
|
|
||||||
} else {
|
|
||||||
await RoleAPI.createRole(formData.value);
|
|
||||||
await refreshCreate();
|
|
||||||
}
|
|
||||||
dialogVisible.visible = false;
|
|
||||||
await resetForm();
|
|
||||||
const userStore = useUserStore();
|
|
||||||
await userStore.getUserInfo();
|
|
||||||
} catch (error: unknown) {
|
|
||||||
console.error(error);
|
|
||||||
} finally {
|
|
||||||
submitLoading.value = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleBatchDelete() {
|
async function handleBatchDelete() {
|
||||||
const ids = selectedIds.value;
|
const ids = selectedIds.value;
|
||||||
if (ids.length === 0) return;
|
if (ids.length === 0) return;
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 条数据吗?`, "批量删除", {
|
await confirmBatchDelete(ids.length);
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
batchDeleting.value = true;
|
batchDeleting.value = true;
|
||||||
await RoleAPI.deleteRole(ids);
|
await RoleAPI.deleteRole(ids);
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
@@ -729,11 +658,7 @@ async function handleMoreClick(status: string) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确认${status === "0" ? "启用" : "停用"}该项数据?`, "警告", {
|
await confirmToggleStatus(status);
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
await RoleAPI.batchRole({ ids, status });
|
await RoleAPI.batchRole({ ids, status });
|
||||||
await refreshData();
|
await refreshData();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
@@ -743,17 +668,3 @@ async function handleMoreClick(status: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.crud-dialog-art-form :deep(.el-row > .el-col:last-child) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.crud-dialog-art-form :deep(.el-form-item__content) {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.role-table-actions .inline-flex) {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -96,6 +96,9 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useTable } from "@/hooks/core/useTable";
|
import { useTable } from "@/hooks/core/useTable";
|
||||||
|
import { useCrudDialog } from "@/hooks/core/useCrudDialog";
|
||||||
|
import { useTableSelection } from "@/hooks/core/useTableSelection";
|
||||||
|
import { confirmDelete, confirmBatchDelete } from "@/hooks/core/useConfirm";
|
||||||
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
import type { SearchFormItem } from "@/components/forms/fa-search-bar/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";
|
||||||
@@ -308,24 +311,13 @@ const tenantSearchItems = computed<SearchFormItem[]>(() => [
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
||||||
const selectedRows = ref<TenantTable[]>([]);
|
|
||||||
const selectedIds = computed(() =>
|
|
||||||
selectedRows.value.map((r) => r.id).filter((id): id is number => id != null && !Number.isNaN(id))
|
|
||||||
);
|
|
||||||
const batchDeleting = ref(false);
|
|
||||||
|
|
||||||
function onTableSelectionChange(rows: TenantTable[]) {
|
// ─── 表格多选 ───
|
||||||
selectedRows.value = rows;
|
const { selectedIds, batchDeleting, onTableSelectionChange } = useTableSelection<TenantTable>();
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteTenantRow(id: number) {
|
async function deleteTenantRow(id: number) {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm("确认删除该项数据?", "警告", {
|
await confirmDelete();
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
|
|
||||||
await TenantAPI.deleteTenant([id]);
|
await TenantAPI.deleteTenant([id]);
|
||||||
ElMessage.success("删除成功");
|
ElMessage.success("删除成功");
|
||||||
faTableRef.value?.elTableRef?.clearSelection();
|
faTableRef.value?.elTableRef?.clearSelection();
|
||||||
@@ -413,8 +405,6 @@ const tenantDetailItems: import("@/components/others/fa-descriptions/index.vue")
|
|||||||
{ label: "创建时间", prop: "created_time" },
|
{ label: "创建时间", prop: "created_time" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const currentEditId = ref<number | null>(null);
|
|
||||||
|
|
||||||
const formData = ref<TenantForm>({
|
const formData = ref<TenantForm>({
|
||||||
name: "",
|
name: "",
|
||||||
code: "",
|
code: "",
|
||||||
@@ -424,15 +414,12 @@ const formData = ref<TenantForm>({
|
|||||||
end_time: undefined,
|
end_time: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const dialogVisible = reactive({
|
// ─── 对话框状态 ───
|
||||||
title: "",
|
const { dialogVisible } = useCrudDialog();
|
||||||
visible: false,
|
|
||||||
type: "create" as "create" | "update" | "detail",
|
|
||||||
});
|
|
||||||
|
|
||||||
const CODE_PATTERN = /^[A-Za-z0-9]+$/;
|
const CODE_PATTERN = /^[A-Za-z0-9]+$/;
|
||||||
|
|
||||||
const validateTimeRange = (rule: unknown, value: unknown, callback: (e?: Error) => void) => {
|
const validateTimeRange = (_rule: unknown, _value: unknown, callback: (e?: Error) => void) => {
|
||||||
if (
|
if (
|
||||||
formData.value.start_time &&
|
formData.value.start_time &&
|
||||||
formData.value.end_time &&
|
formData.value.end_time &&
|
||||||
@@ -470,6 +457,33 @@ const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
|||||||
const submitLoading = ref(false);
|
const submitLoading = ref(false);
|
||||||
const tenantFormRenderKey = ref(0);
|
const tenantFormRenderKey = ref(0);
|
||||||
|
|
||||||
|
async function handleOpenDialog(type: "create" | "update" | "detail", id?: number) {
|
||||||
|
dialogVisible.type = type;
|
||||||
|
if (id) {
|
||||||
|
const response = await TenantAPI.detailTenant(id);
|
||||||
|
if (type === "detail") {
|
||||||
|
dialogVisible.title = "租户详情";
|
||||||
|
Object.assign(detailFormData.value, response.data.data);
|
||||||
|
} else if (type === "update") {
|
||||||
|
dialogVisible.title = "修改租户";
|
||||||
|
Object.assign(formData.value, response.data.data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dialogVisible.title = "新增租户";
|
||||||
|
Object.assign(formData.value, initialFormData);
|
||||||
|
formData.value.id = undefined;
|
||||||
|
}
|
||||||
|
tenantFormRenderKey.value += 1;
|
||||||
|
dialogVisible.visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCloseDialog() {
|
||||||
|
dialogVisible.visible = false;
|
||||||
|
dataFormRef.value?.resetFields();
|
||||||
|
dataFormRef.value?.clearValidate();
|
||||||
|
Object.assign(formData.value, initialFormData);
|
||||||
|
}
|
||||||
|
|
||||||
const tenantDialogFormItems = computed<FormItem[]>(() => [
|
const tenantDialogFormItems = computed<FormItem[]>(() => [
|
||||||
{
|
{
|
||||||
label: "租户名称",
|
label: "租户名称",
|
||||||
@@ -541,38 +555,6 @@ const tenantDialogFormItems = computed<FormItem[]>(() => [
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
async function resetForm() {
|
|
||||||
dataFormRef.value?.resetFields();
|
|
||||||
dataFormRef.value?.clearValidate();
|
|
||||||
Object.assign(formData, initialFormData);
|
|
||||||
currentEditId.value = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleCloseDialog() {
|
|
||||||
dialogVisible.visible = false;
|
|
||||||
await resetForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleOpenDialog(type: "create" | "update" | "detail", id?: number) {
|
|
||||||
dialogVisible.type = type;
|
|
||||||
if (id) {
|
|
||||||
const response = await TenantAPI.detailTenant(id);
|
|
||||||
if (type === "detail") {
|
|
||||||
dialogVisible.title = "租户详情";
|
|
||||||
Object.assign(detailFormData.value, response.data.data);
|
|
||||||
} else if (type === "update") {
|
|
||||||
dialogVisible.title = "修改租户";
|
|
||||||
Object.assign(formData, response.data.data);
|
|
||||||
currentEditId.value = id;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dialogVisible.title = "新增租户";
|
|
||||||
await resetForm();
|
|
||||||
}
|
|
||||||
tenantFormRenderKey.value += 1;
|
|
||||||
dialogVisible.visible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSearchBarSearch(params: TenantSearchForm) {
|
async function handleSearchBarSearch(params: TenantSearchForm) {
|
||||||
await searchBarRef.value?.validate?.();
|
await searchBarRef.value?.validate?.();
|
||||||
replaceSearchParams(buildTenantReplaceParams(params));
|
replaceSearchParams(buildTenantReplaceParams(params));
|
||||||
@@ -590,48 +572,45 @@ function onResetSearch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
dataFormRef.value?.validate(async (valid: boolean) => {
|
const valid = await dataFormRef.value!.validate().catch(() => false);
|
||||||
if (!valid) return;
|
if (!valid) return;
|
||||||
submitLoading.value = true;
|
submitLoading.value = true;
|
||||||
const id = currentEditId.value;
|
const id = formData.value.id as number | undefined;
|
||||||
try {
|
try {
|
||||||
if (id) {
|
if (id) {
|
||||||
const payload: TenantUpdateForm = {
|
const payload: TenantUpdateForm = {
|
||||||
name: formData.value.name,
|
name: formData.value.name,
|
||||||
start_time: formData.value.start_time,
|
start_time: formData.value.start_time,
|
||||||
end_time: formData.value.end_time,
|
end_time: formData.value.end_time,
|
||||||
};
|
};
|
||||||
await TenantAPI.updateTenant(id, payload);
|
await TenantAPI.updateTenant(id, payload);
|
||||||
await refreshUpdate();
|
await refreshUpdate();
|
||||||
} else {
|
} else {
|
||||||
const payload: TenantCreateForm = {
|
const payload: TenantCreateForm = {
|
||||||
name: formData.value.name as string,
|
name: formData.value.name as string,
|
||||||
code: formData.value.code as string,
|
code: formData.value.code as string,
|
||||||
start_time: formData.value.start_time,
|
start_time: formData.value.start_time,
|
||||||
end_time: formData.value.end_time,
|
end_time: formData.value.end_time,
|
||||||
};
|
};
|
||||||
await TenantAPI.createTenant(payload);
|
await TenantAPI.createTenant(payload);
|
||||||
await refreshCreate();
|
await refreshCreate();
|
||||||
}
|
|
||||||
dialogVisible.visible = false;
|
|
||||||
await resetForm();
|
|
||||||
} catch (error: unknown) {
|
|
||||||
console.error(error);
|
|
||||||
} finally {
|
|
||||||
submitLoading.value = false;
|
|
||||||
}
|
}
|
||||||
});
|
dialogVisible.visible = false;
|
||||||
|
dataFormRef.value?.resetFields();
|
||||||
|
dataFormRef.value?.clearValidate();
|
||||||
|
Object.assign(formData.value, initialFormData);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
submitLoading.value = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleBatchDelete() {
|
async function handleBatchDelete() {
|
||||||
const ids = selectedIds.value;
|
const ids = selectedIds.value;
|
||||||
if (ids.length === 0) return;
|
if (ids.length === 0) return;
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 条数据吗?`, "批量删除", {
|
await confirmBatchDelete(ids.length);
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
batchDeleting.value = true;
|
batchDeleting.value = true;
|
||||||
await TenantAPI.deleteTenant(ids);
|
await TenantAPI.deleteTenant(ids);
|
||||||
ElMessage.success("删除成功");
|
ElMessage.success("删除成功");
|
||||||
@@ -646,15 +625,7 @@ async function handleBatchDelete() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.crud-dialog-art-form :deep(.el-row > .el-col:last-child) {
|
::deep(.tenant-table-actions .inline-flex) {
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.crud-dialog-art-form :deep(.el-form-item__content) {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.tenant-table-actions .inline-flex) {
|
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -210,11 +210,16 @@ defineOptions({
|
|||||||
});
|
});
|
||||||
|
|
||||||
import { UserFilled } from "@element-plus/icons-vue";
|
import { UserFilled } from "@element-plus/icons-vue";
|
||||||
|
import { ElAvatar } from "element-plus";
|
||||||
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 { useImportExport } from "@/hooks/core/useImportExport";
|
import { useImportExport } from "@/hooks/core/useImportExport";
|
||||||
|
import { useTableSelection } from "@/hooks/core/useTableSelection";
|
||||||
|
import { useCrudDialog } from "@/hooks/core/useCrudDialog";
|
||||||
|
import { confirmDelete, confirmBatchDelete, confirmToggleStatus } from "@/hooks/core/useConfirm";
|
||||||
|
import { cleanEmptyArrayParams, stripPaginationParams } from "@/utils/query";
|
||||||
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
import type { SearchFormItem } from "@/components/forms/fa-search-bar/index.vue";
|
||||||
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||||
import type { IContentConfig, IObject } from "@/components/modal/types";
|
import type { IContentConfig, IObject } from "@/components/modal/types";
|
||||||
@@ -230,7 +235,6 @@ import RoleAPI from "@/api/module_system/role";
|
|||||||
import DeptTree from "./components/DeptTree.vue";
|
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 type { DescriptionsItem } from "@/components/others/fa-descriptions/index.vue";
|
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";
|
||||||
@@ -248,12 +252,6 @@ type UserSearchForm = {
|
|||||||
created_time?: string[];
|
created_time?: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
function normalizeUserQuery(params: Record<string, unknown>): UserPageQuery {
|
|
||||||
const p = { ...params } as Record<string, unknown>;
|
|
||||||
if (Array.isArray(p.created_time) && p.created_time.length === 0) p.created_time = undefined;
|
|
||||||
return p as unknown as UserPageQuery;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildUserReplaceParams(u: UserSearchForm): Record<string, unknown> {
|
function buildUserReplaceParams(u: UserSearchForm): Record<string, unknown> {
|
||||||
return {
|
return {
|
||||||
username: u.username,
|
username: u.username,
|
||||||
@@ -344,7 +342,6 @@ const dataFormRef = ref<InstanceType<typeof FaForm> | null>(null);
|
|||||||
const userFormRenderKey = ref(0);
|
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 deptFilterId = ref<string | number | undefined>(undefined);
|
const deptFilterId = ref<string | number | undefined>(undefined);
|
||||||
|
|
||||||
const drawerSize = computed(() => (appStore.device === DeviceEnum.DESKTOP ? "450px" : "90%"));
|
const drawerSize = computed(() => (appStore.device === DeviceEnum.DESKTOP ? "450px" : "90%"));
|
||||||
@@ -527,14 +524,8 @@ const userSearchItems = computed<SearchFormItem[]>(() => [
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
const faTableRef = ref<{ elTableRef?: { clearSelection: () => void } } | null>(null);
|
||||||
const selectedRows = ref<UserInfo[]>([]);
|
const { selectedRows, selectedIds, batchDeleting, onTableSelectionChange } =
|
||||||
const selectedIds = computed(() =>
|
useTableSelection<UserInfo>();
|
||||||
selectedRows.value.map((r) => r.id).filter((id): id is number => id != null && !Number.isNaN(id))
|
|
||||||
);
|
|
||||||
|
|
||||||
function onTableSelectionChange(rows: UserInfo[]) {
|
|
||||||
selectedRows.value = rows;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleResetPassword(row: UserInfo) {
|
async function handleResetPassword(row: UserInfo) {
|
||||||
try {
|
try {
|
||||||
@@ -556,11 +547,7 @@ async function handleResetPassword(row: UserInfo) {
|
|||||||
|
|
||||||
async function deleteUserRow(id: number) {
|
async function deleteUserRow(id: number) {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm("确认删除该项数据?", "警告", {
|
await confirmDelete();
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
await UserAPI.deleteUser([id]);
|
await UserAPI.deleteUser([id]);
|
||||||
const idSet = [id];
|
const idSet = [id];
|
||||||
if (userStore.basicInfo.id && idSet.includes(userStore.basicInfo.id)) {
|
if (userStore.basicInfo.id && idSet.includes(userStore.basicInfo.id)) {
|
||||||
@@ -677,26 +664,22 @@ const userCrudCols = computed(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
const exportQueryParams = computed(() => {
|
const exportQueryParams = computed(() => {
|
||||||
const sp = { ...(searchParams as object) } as Record<string, unknown>;
|
const sp = stripPaginationParams(searchParams);
|
||||||
delete sp.current;
|
|
||||||
delete sp.size;
|
|
||||||
delete sp.page_no;
|
|
||||||
delete sp.page_size;
|
|
||||||
if (
|
if (
|
||||||
deptFilterId.value !== undefined &&
|
deptFilterId.value !== undefined &&
|
||||||
deptFilterId.value !== null &&
|
deptFilterId.value !== null &&
|
||||||
deptFilterId.value !== ""
|
deptFilterId.value !== ""
|
||||||
) {
|
) {
|
||||||
sp.dept_id = Number(deptFilterId.value);
|
(sp as Record<string, unknown>).dept_id = Number(deptFilterId.value);
|
||||||
}
|
}
|
||||||
const q = normalizeUserQuery(sp);
|
const q = cleanEmptyArrayParams(sp) as Record<string, unknown>;
|
||||||
if (typeof q.status === "string") {
|
if (typeof q.status === "string") {
|
||||||
const s = q.status;
|
const s = q.status;
|
||||||
if (s === "true" || s === "false") {
|
if (s === "true" || s === "false") {
|
||||||
(q as unknown as Record<string, unknown>).status = s === "true";
|
q.status = s === "true";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return q as unknown as Record<string, unknown>;
|
return q;
|
||||||
});
|
});
|
||||||
|
|
||||||
const userImportContentConfig = computed<IContentConfig>(() => ({
|
const userImportContentConfig = computed<IContentConfig>(() => ({
|
||||||
@@ -710,14 +693,14 @@ const userExportContentConfig = computed(() => ({
|
|||||||
permPrefix: "module_system:user",
|
permPrefix: "module_system:user",
|
||||||
cols: userCrudCols.value,
|
cols: userCrudCols.value,
|
||||||
exportsBlobAction: async (params: IObject) => {
|
exportsBlobAction: async (params: IObject) => {
|
||||||
const merged = normalizeUserQuery({
|
const merged = cleanEmptyArrayParams({
|
||||||
...(exportQueryParams.value as Record<string, unknown>),
|
...(exportQueryParams.value as Record<string, unknown>),
|
||||||
...params,
|
...params,
|
||||||
} as Record<string, unknown>);
|
} as Record<string, unknown>) as Record<string, unknown>;
|
||||||
if (typeof merged.status === "string") {
|
if (typeof merged.status === "string") {
|
||||||
const s = merged.status;
|
const s = merged.status;
|
||||||
if (s === "true" || s === "false") {
|
if (s === "true" || s === "false") {
|
||||||
(merged as unknown as Record<string, unknown>).status = s === "true";
|
merged.status = s === "true";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const res = await UserAPI.exportUser(merged as unknown as UserPageQuery);
|
const res = await UserAPI.exportUser(merged as unknown as UserPageQuery);
|
||||||
@@ -744,11 +727,7 @@ const formData = ref<UserForm>({
|
|||||||
description: undefined,
|
description: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const dialogVisible = reactive({
|
const { dialogVisible } = useCrudDialog();
|
||||||
title: "",
|
|
||||||
visible: false,
|
|
||||||
type: "create" as "create" | "update" | "detail",
|
|
||||||
});
|
|
||||||
|
|
||||||
const rules = reactive({
|
const rules = reactive({
|
||||||
username: [{ required: true, message: "请输入账号", trigger: "blur" }],
|
username: [{ required: true, message: "请输入账号", trigger: "blur" }],
|
||||||
@@ -849,7 +828,7 @@ async function resetForm() {
|
|||||||
dataFormRef.value.resetFields();
|
dataFormRef.value.resetFields();
|
||||||
dataFormRef.value.clearValidate();
|
dataFormRef.value.clearValidate();
|
||||||
}
|
}
|
||||||
Object.assign(formData, initialFormData);
|
Object.assign(formData.value, initialFormData);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCloseDialog() {
|
async function handleCloseDialog() {
|
||||||
@@ -940,11 +919,7 @@ async function handleBatchDelete() {
|
|||||||
const ids = selectedIds.value;
|
const ids = selectedIds.value;
|
||||||
if (ids.length === 0) return;
|
if (ids.length === 0) return;
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 条数据吗?`, "批量删除", {
|
await confirmBatchDelete(ids.length);
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
batchDeleting.value = true;
|
batchDeleting.value = true;
|
||||||
await UserAPI.deleteUser(ids);
|
await UserAPI.deleteUser(ids);
|
||||||
if (userStore.basicInfo.id && ids.includes(userStore.basicInfo.id)) {
|
if (userStore.basicInfo.id && ids.includes(userStore.basicInfo.id)) {
|
||||||
@@ -968,11 +943,7 @@ async function handleMoreClick(status: string) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm("确认启用或停用该项数据?", "警告", {
|
await confirmToggleStatus(status);
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
batchDeleting.value = true;
|
batchDeleting.value = true;
|
||||||
await UserAPI.batchUser({ ids, status });
|
await UserAPI.batchUser({ ids, status });
|
||||||
await refreshData();
|
await refreshData();
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ export default ({ mode }: { mode: string }) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
target: "es2015",
|
target: "es2020",
|
||||||
outDir: "dist",
|
outDir: "dist",
|
||||||
chunkSizeWarningLimit: 4000,
|
chunkSizeWarningLimit: 4000,
|
||||||
minify: isProduction ? "terser" : false,
|
minify: isProduction ? "terser" : false,
|
||||||
|
|||||||
Reference in New Issue
Block a user