mirror of
https://github.com/ccmjga/zhilu-admin
synced 2026-03-18 07:43:41 +08:00
add notify ai
This commit is contained in:
@@ -87,6 +87,7 @@ import LoadingIcon from "@/components/icons/LoadingIcon.vue";
|
||||
import { useAiAction } from "@/composables/ai/useAiAction";
|
||||
import { useDepartmentQuery } from "@/composables/department/useDepartmentQuery";
|
||||
import { useDepartmentUpsert } from "@/composables/department/useDepartmentUpsert";
|
||||
import { useActionExcStore } from "@/composables/store/useActionExcStore";
|
||||
import useAlertStore from "@/composables/store/useAlertStore";
|
||||
import type { DepartmentUpsertModel } from "@/types/department";
|
||||
import DOMPurify from "dompurify";
|
||||
@@ -118,7 +119,7 @@ const { deleteUserByUsername, deleteDepartmentByName } = useAiAction();
|
||||
const departmentDeleteModal = ref<ModalInterface>();
|
||||
const currentDeleteUsername = ref<string>();
|
||||
const currentDeleteDepartmentName = ref<string>();
|
||||
|
||||
const actionExcStore = useActionExcStore();
|
||||
const { availableDepartments, fetchAvailableDepartments } =
|
||||
useDepartmentQuery();
|
||||
|
||||
@@ -194,6 +195,7 @@ const handleUpsertUserSubmit = async (data: UserUpsertSubmitModel) => {
|
||||
content: "操作成功",
|
||||
level: "success",
|
||||
});
|
||||
actionExcStore.notify(true);
|
||||
};
|
||||
|
||||
const handleUpsertDepartmentSubmit = async (
|
||||
@@ -205,6 +207,7 @@ const handleUpsertDepartmentSubmit = async (
|
||||
content: "操作成功",
|
||||
level: "success",
|
||||
});
|
||||
actionExcStore.notify(true);
|
||||
};
|
||||
|
||||
const handleDeleteUserSubmit = async () => {
|
||||
@@ -214,6 +217,7 @@ const handleDeleteUserSubmit = async () => {
|
||||
content: "操作成功",
|
||||
level: "success",
|
||||
});
|
||||
actionExcStore.notify(true);
|
||||
};
|
||||
|
||||
const handleDeleteDepartmentSubmit = async () => {
|
||||
@@ -223,6 +227,7 @@ const handleDeleteDepartmentSubmit = async () => {
|
||||
content: "操作成功",
|
||||
level: "success",
|
||||
});
|
||||
actionExcStore.notify(true);
|
||||
};
|
||||
|
||||
watch(
|
||||
@@ -261,6 +266,7 @@ const chatByMode = async (
|
||||
await searchAction(message);
|
||||
} else if (mode === "execute") {
|
||||
await executeAction(message);
|
||||
actionExcStore.notify(true);
|
||||
} else {
|
||||
await chat(message);
|
||||
}
|
||||
|
||||
@@ -35,21 +35,21 @@ import { computed } from "vue";
|
||||
import type { RouteLocationRaw } from "vue-router";
|
||||
|
||||
interface BreadcrumbItem {
|
||||
name: string;
|
||||
route?: RouteLocationRaw;
|
||||
name: string;
|
||||
route?: RouteLocationRaw;
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
names: string[];
|
||||
routes?: RouteLocationRaw[];
|
||||
names: string[];
|
||||
routes?: RouteLocationRaw[];
|
||||
}>();
|
||||
|
||||
const breadcrumbs = computed<BreadcrumbItem[]>(() => {
|
||||
return props.names.map((name, index) => {
|
||||
return {
|
||||
name,
|
||||
route: props.routes?.[index]
|
||||
};
|
||||
});
|
||||
return props.names.map((name, index) => {
|
||||
return {
|
||||
name,
|
||||
route: props.routes?.[index],
|
||||
};
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
35
frontend/src/components/DateRangePicker.vue
Normal file
35
frontend/src/components/DateRangePicker.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div id="date-range-picker" date-rangepicker class="flex items-center">
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none">
|
||||
<svg class="w-4 h-4 text-gray-500 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M20 4a2 2 0 0 0-2-2h-2V1a1 1 0 0 0-2 0v1h-3V1a1 1 0 0 0-2 0v1H6V1a1 1 0 0 0-2 0v1H2a2 2 0 0 0-2 2v2h20V4ZM0 18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8H0v10Zm5-8h10a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<input id="datepicker-range-start" name="start" type="text"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="Select date start">
|
||||
</div>
|
||||
<span class="mx-4 text-gray-500">to</span>
|
||||
<div class="relative">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none">
|
||||
<svg class="w-4 h-4 text-gray-500 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor" viewBox="0 0 20 20">
|
||||
<path
|
||||
d="M20 4a2 2 0 0 0-2-2h-2V1a1 1 0 0 0-2 0v1h-3V1a1 1 0 0 0-2 0v1H6V1a1 1 0 0 0-2 0v1H2a2 2 0 0 0-2 2v2h20V4ZM0 18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8H0v10Zm5-8h10a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<input id="datepicker-range-end" name="end" type="text"
|
||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||
placeholder="Select date end">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center justify-start rtl:justify-end">
|
||||
<button type="button" @click="handleSidebarToggle"
|
||||
class="inline-flex items-center p-2 text-sm text-gray-500 rounded-lg sm:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200">
|
||||
class="inline-flex items-center p-2 text-sm text-gray-500 rounded-lg sm:hidden hover:bg-gray-100">
|
||||
<span class="sr-only">Open sidebar</span>
|
||||
<svg class="w-6 h-6" aria-hidden="true" fill="currentColor" viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
@@ -13,7 +13,7 @@
|
||||
</path>
|
||||
</svg>
|
||||
</button>
|
||||
<a href="https://github.com/ccmjga/zhilu-admin" target="_blank" class="flex items-center ms-2 md:me-24">
|
||||
<a href="https://github.com/ccmjga/zhilu-admin" target="_blank" class="flex items-center ms-2 md:me-24 ">
|
||||
<img class="me-3" src="/logo.svg" alt="logo">
|
||||
<span class="self-center text-lg sm:text-xl md:text-2xl font-semibold whitespace-nowrap">知路后台管理</span>
|
||||
</a>
|
||||
@@ -36,8 +36,7 @@
|
||||
</button>
|
||||
<div class="flex items-center ms-2 sm:ms-3">
|
||||
<div>
|
||||
<button type="button" id="dropdown-button"
|
||||
class="flex text-sm bg-gray-800 rounded-full focus:ring-4 focus:ring-gray-300 cursor-pointer"
|
||||
<button type="button" id="dropdown-button" class="flex text-sm bg-gray-800 rounded-full cursor-pointer"
|
||||
aria-expanded="false" data-dropdown-toggle="dropdown-user">
|
||||
<span class="sr-only">打开用户菜单</span>
|
||||
<img class="w-8 h-8 rounded-full" src="/java.svg" alt="user photo">
|
||||
|
||||
@@ -32,18 +32,18 @@
|
||||
</template>
|
||||
|
||||
<script setup generic="T" lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { ref } from "vue";
|
||||
|
||||
/** 通用对象类型 */
|
||||
type ItemRecord = Record<string, unknown>;
|
||||
|
||||
const props = defineProps<{
|
||||
/** 数据项数组 */
|
||||
items: T[] | undefined;
|
||||
/** 数据项ID字段名 */
|
||||
idField?: string;
|
||||
/** 数据项唯一键字段名 */
|
||||
keyField?: string;
|
||||
/** 数据项数组 */
|
||||
items: T[] | undefined;
|
||||
/** 数据项ID字段名 */
|
||||
idField?: string;
|
||||
/** 数据项唯一键字段名 */
|
||||
keyField?: string;
|
||||
}>();
|
||||
|
||||
/**
|
||||
@@ -53,17 +53,17 @@ const props = defineProps<{
|
||||
* @returns 唯一键
|
||||
*/
|
||||
const getItemKey = (item: T, index: number): string | number => {
|
||||
if (props.keyField) {
|
||||
const key = (item as ItemRecord)[props.keyField];
|
||||
if (key !== undefined) return String(key);
|
||||
}
|
||||
|
||||
if (props.idField) {
|
||||
const id = (item as ItemRecord)[props.idField];
|
||||
if (id !== undefined) return String(id);
|
||||
}
|
||||
|
||||
const id = (item as ItemRecord).id;
|
||||
return id !== undefined ? String(id) : index;
|
||||
if (props.keyField) {
|
||||
const key = (item as ItemRecord)[props.keyField];
|
||||
if (key !== undefined) return String(key);
|
||||
}
|
||||
|
||||
if (props.idField) {
|
||||
const id = (item as ItemRecord)[props.idField];
|
||||
if (id !== undefined) return String(id);
|
||||
}
|
||||
|
||||
const id = (item as ItemRecord).id;
|
||||
return id !== undefined ? String(id) : index;
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -37,24 +37,24 @@
|
||||
</template>
|
||||
|
||||
<script setup generic="T" lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { ref, watch } from "vue";
|
||||
|
||||
/** 通用对象类型 */
|
||||
type ItemRecord = Record<string, unknown>;
|
||||
|
||||
const props = defineProps<{
|
||||
/** 数据项数组 */
|
||||
items: T[] | undefined;
|
||||
/** 数据项ID字段名 */
|
||||
idField?: string;
|
||||
/** 数据项唯一键字段名 */
|
||||
keyField?: string;
|
||||
/** 选中项的值数组 */
|
||||
modelValue?: (string | number)[];
|
||||
/** 数据项数组 */
|
||||
items: T[] | undefined;
|
||||
/** 数据项ID字段名 */
|
||||
idField?: string;
|
||||
/** 数据项唯一键字段名 */
|
||||
keyField?: string;
|
||||
/** 选中项的值数组 */
|
||||
modelValue?: (string | number)[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [checkedItems: (string | number)[]];
|
||||
"update:modelValue": [checkedItems: (string | number)[]];
|
||||
}>();
|
||||
|
||||
const checkedItems = ref<(string | number)[]>(props.modelValue || []);
|
||||
@@ -66,13 +66,13 @@ const checkedItems = ref<(string | number)[]>(props.modelValue || []);
|
||||
* @returns 唯一键
|
||||
*/
|
||||
const getItemKey = (item: T, index: number): string | number => {
|
||||
if (props.keyField) {
|
||||
const key = (item as ItemRecord)[props.keyField];
|
||||
if (key !== undefined) return String(key);
|
||||
}
|
||||
|
||||
const id = getItemId(item);
|
||||
return id !== undefined ? id : index;
|
||||
if (props.keyField) {
|
||||
const key = (item as ItemRecord)[props.keyField];
|
||||
if (key !== undefined) return String(key);
|
||||
}
|
||||
|
||||
const id = getItemId(item);
|
||||
return id !== undefined ? id : index;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -81,21 +81,28 @@ const getItemKey = (item: T, index: number): string | number => {
|
||||
* @returns ID值
|
||||
*/
|
||||
const getItemId = (item: T): string | number => {
|
||||
if (props.idField) {
|
||||
return (item as ItemRecord)[props.idField] as string | number;
|
||||
}
|
||||
return (item as ItemRecord).id as string | number || item as unknown as string | number;
|
||||
if (props.idField) {
|
||||
return (item as ItemRecord)[props.idField] as string | number;
|
||||
}
|
||||
return (
|
||||
((item as ItemRecord).id as string | number) ||
|
||||
(item as unknown as string | number)
|
||||
);
|
||||
};
|
||||
|
||||
// 监听选中项变化
|
||||
watch(checkedItems, (newVal) => {
|
||||
emit('update:modelValue', newVal);
|
||||
emit("update:modelValue", newVal);
|
||||
});
|
||||
|
||||
// 监听modelValue变化
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
if (newVal) {
|
||||
checkedItems.value = newVal;
|
||||
}
|
||||
}, { deep: true });
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
checkedItems.value = newVal;
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -14,70 +14,78 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { computed } from "vue";
|
||||
|
||||
export type ButtonVariant = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info';
|
||||
export type ButtonSize = 'xs' | 'sm' | 'md' | 'lg';
|
||||
export type ButtonVariant =
|
||||
| "primary"
|
||||
| "secondary"
|
||||
| "success"
|
||||
| "danger"
|
||||
| "warning"
|
||||
| "info";
|
||||
export type ButtonSize = "xs" | "sm" | "md" | "lg";
|
||||
|
||||
const props = defineProps<{
|
||||
/** 按钮变体类型 */
|
||||
variant?: ButtonVariant;
|
||||
/** 按钮尺寸 */
|
||||
size?: ButtonSize;
|
||||
/** 是否禁用 */
|
||||
disabled?: boolean;
|
||||
/** 自定义CSS类名 */
|
||||
className?: string;
|
||||
/** 是否为移动端尺寸 */
|
||||
isMobile?: boolean;
|
||||
/** 按钮变体类型 */
|
||||
variant?: ButtonVariant;
|
||||
/** 按钮尺寸 */
|
||||
size?: ButtonSize;
|
||||
/** 是否禁用 */
|
||||
disabled?: boolean;
|
||||
/** 自定义CSS类名 */
|
||||
className?: string;
|
||||
/** 是否为移动端尺寸 */
|
||||
isMobile?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
'click': [event: MouseEvent];
|
||||
click: [event: MouseEvent];
|
||||
}>();
|
||||
|
||||
/** 按钮颜色样式映射 */
|
||||
const colorClasses = computed(() => {
|
||||
const variants: Record<ButtonVariant, string> = {
|
||||
primary: 'text-white bg-blue-700 hover:bg-blue-800 focus:ring-blue-300',
|
||||
secondary: 'text-gray-900 bg-white border border-gray-300 hover:bg-gray-100 focus:ring-gray-100',
|
||||
success: 'text-white bg-green-700 hover:bg-green-800 focus:ring-green-300',
|
||||
danger: 'text-white bg-red-700 hover:bg-red-800 focus:ring-red-300',
|
||||
warning: 'text-gray-900 bg-yellow-400 hover:bg-yellow-500 focus:ring-yellow-300',
|
||||
info: 'text-white bg-cyan-700 hover:bg-cyan-800 focus:ring-cyan-300'
|
||||
};
|
||||
|
||||
return variants[props.variant || 'primary'];
|
||||
const variants: Record<ButtonVariant, string> = {
|
||||
primary: "text-white bg-blue-700 hover:bg-blue-800 focus:ring-blue-300",
|
||||
secondary:
|
||||
"text-gray-900 bg-white border border-gray-300 hover:bg-gray-100 focus:ring-gray-100",
|
||||
success: "text-white bg-green-700 hover:bg-green-800 focus:ring-green-300",
|
||||
danger: "text-white bg-red-700 hover:bg-red-800 focus:ring-red-300",
|
||||
warning:
|
||||
"text-gray-900 bg-yellow-400 hover:bg-yellow-500 focus:ring-yellow-300",
|
||||
info: "text-white bg-cyan-700 hover:bg-cyan-800 focus:ring-cyan-300",
|
||||
};
|
||||
|
||||
return variants[props.variant || "primary"];
|
||||
});
|
||||
|
||||
/** 按钮尺寸样式映射 */
|
||||
const sizeClasses = computed(() => {
|
||||
// 移动端尺寸
|
||||
if (props.isMobile) {
|
||||
const sizes: Record<ButtonSize, string> = {
|
||||
xs: 'text-xs px-2 py-1',
|
||||
sm: 'text-xs px-3 py-1.5',
|
||||
md: 'text-sm px-3 py-2',
|
||||
lg: 'text-sm px-4 py-2.5'
|
||||
};
|
||||
return sizes[props.size || 'sm'];
|
||||
}
|
||||
|
||||
// PC端尺寸
|
||||
const sizes: Record<ButtonSize, string> = {
|
||||
xs: 'text-xs px-3 py-1.5',
|
||||
sm: 'text-sm px-3 py-2',
|
||||
md: 'text-sm px-4 py-2.5',
|
||||
lg: 'text-base px-5 py-3'
|
||||
};
|
||||
|
||||
return sizes[props.size || 'md'];
|
||||
// 移动端尺寸
|
||||
if (props.isMobile) {
|
||||
const sizes: Record<ButtonSize, string> = {
|
||||
xs: "text-xs px-2 py-1",
|
||||
sm: "text-xs px-3 py-1.5",
|
||||
md: "text-sm px-3 py-2",
|
||||
lg: "text-sm px-4 py-2.5",
|
||||
};
|
||||
return sizes[props.size || "sm"];
|
||||
}
|
||||
|
||||
// PC端尺寸
|
||||
const sizes: Record<ButtonSize, string> = {
|
||||
xs: "text-xs px-3 py-1.5",
|
||||
sm: "text-sm px-3 py-2",
|
||||
md: "text-sm px-4 py-2.5",
|
||||
lg: "text-base px-5 py-3",
|
||||
};
|
||||
|
||||
return sizes[props.size || "md"];
|
||||
});
|
||||
|
||||
/** 处理点击事件 */
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
if (!props.disabled) {
|
||||
emit('click', event);
|
||||
}
|
||||
if (!props.disabled) {
|
||||
emit("click", event);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -51,44 +51,44 @@
|
||||
</template>
|
||||
|
||||
<script setup generic="T" lang="ts">
|
||||
import { defineEmits, ref, watch } from 'vue';
|
||||
import { defineEmits, ref, watch } from "vue";
|
||||
|
||||
/**
|
||||
* 表格列配置接口
|
||||
*/
|
||||
export interface Column {
|
||||
/** 列标题 */
|
||||
title: string;
|
||||
/** 数据字段名 */
|
||||
field: string;
|
||||
/** 是否可排序 */
|
||||
sortable?: boolean;
|
||||
/** 自定义CSS类名 */
|
||||
class?: string;
|
||||
/** 列标题 */
|
||||
title: string;
|
||||
/** 数据字段名 */
|
||||
field: string;
|
||||
/** 是否可排序 */
|
||||
sortable?: boolean;
|
||||
/** 自定义CSS类名 */
|
||||
class?: string;
|
||||
}
|
||||
|
||||
/** 通用对象类型 */
|
||||
type ItemRecord = Record<string, unknown>;
|
||||
|
||||
const props = defineProps<{
|
||||
/** 数据项数组 */
|
||||
items: T[];
|
||||
/** 列配置数组 */
|
||||
columns: Column[];
|
||||
/** 是否显示复选框 */
|
||||
hasCheckbox?: boolean;
|
||||
/** 数据项ID字段名 */
|
||||
idField?: string;
|
||||
/** 数据项唯一键字段名 */
|
||||
keyField?: string;
|
||||
/** 选中项的值数组,用于v-model绑定 */
|
||||
modelValue?: (string | number)[];
|
||||
/** 数据项数组 */
|
||||
items: T[];
|
||||
/** 列配置数组 */
|
||||
columns: Column[];
|
||||
/** 是否显示复选框 */
|
||||
hasCheckbox?: boolean;
|
||||
/** 数据项ID字段名 */
|
||||
idField?: string;
|
||||
/** 数据项唯一键字段名 */
|
||||
keyField?: string;
|
||||
/** 选中项的值数组,用于v-model绑定 */
|
||||
modelValue?: (string | number)[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [checkedItems: (string | number)[]];
|
||||
'sort': [field: string];
|
||||
'all-checked-change': [checked: boolean];
|
||||
"update:modelValue": [checkedItems: (string | number)[]];
|
||||
sort: [field: string];
|
||||
"all-checked-change": [checked: boolean];
|
||||
}>();
|
||||
|
||||
const checkedItems = ref<(string | number)[]>(props.modelValue || []);
|
||||
@@ -101,13 +101,13 @@ const allChecked = ref(false);
|
||||
* @returns 唯一键
|
||||
*/
|
||||
const getItemKey = (item: T, index: number): string | number => {
|
||||
if (props.keyField) {
|
||||
const key = (item as ItemRecord)[props.keyField];
|
||||
if (key !== undefined) return String(key);
|
||||
}
|
||||
|
||||
const id = getItemId(item);
|
||||
return id !== undefined ? id : index;
|
||||
if (props.keyField) {
|
||||
const key = (item as ItemRecord)[props.keyField];
|
||||
if (key !== undefined) return String(key);
|
||||
}
|
||||
|
||||
const id = getItemId(item);
|
||||
return id !== undefined ? id : index;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -116,10 +116,13 @@ const getItemKey = (item: T, index: number): string | number => {
|
||||
* @returns ID值
|
||||
*/
|
||||
const getItemId = (item: T): string | number => {
|
||||
if (props.idField) {
|
||||
return (item as ItemRecord)[props.idField] as string | number;
|
||||
}
|
||||
return (item as ItemRecord).id as string | number || item as unknown as string | number;
|
||||
if (props.idField) {
|
||||
return (item as ItemRecord)[props.idField] as string | number;
|
||||
}
|
||||
return (
|
||||
((item as ItemRecord).id as string | number) ||
|
||||
(item as unknown as string | number)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -129,26 +132,34 @@ const getItemId = (item: T): string | number => {
|
||||
* @returns 字段值
|
||||
*/
|
||||
const getItemValue = (item: T, field: string): string => {
|
||||
if (!field) return '';
|
||||
|
||||
return String(field.split('.').reduce<unknown>((obj, key) =>
|
||||
obj && typeof obj === 'object' && key in (obj as Record<string, unknown>)
|
||||
? (obj as Record<string, unknown>)[key]
|
||||
: '',
|
||||
item as ItemRecord));
|
||||
if (!field) return "";
|
||||
|
||||
return String(
|
||||
field
|
||||
.split(".")
|
||||
.reduce<unknown>(
|
||||
(obj, key) =>
|
||||
obj &&
|
||||
typeof obj === "object" &&
|
||||
key in (obj as Record<string, unknown>)
|
||||
? (obj as Record<string, unknown>)[key]
|
||||
: "",
|
||||
item as ItemRecord,
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 处理全选/取消全选
|
||||
*/
|
||||
const handleAllCheckedChange = () => {
|
||||
if (allChecked.value) {
|
||||
checkedItems.value = props.items.map(getItemId);
|
||||
} else {
|
||||
checkedItems.value = [];
|
||||
}
|
||||
emit('all-checked-change', allChecked.value);
|
||||
emit('update:modelValue', checkedItems.value);
|
||||
if (allChecked.value) {
|
||||
checkedItems.value = props.items.map(getItemId);
|
||||
} else {
|
||||
checkedItems.value = [];
|
||||
}
|
||||
emit("all-checked-change", allChecked.value);
|
||||
emit("update:modelValue", checkedItems.value);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -156,32 +167,40 @@ const handleAllCheckedChange = () => {
|
||||
* @param field 排序字段
|
||||
*/
|
||||
const handleSortClick = (field: string) => {
|
||||
emit('sort', field);
|
||||
emit("sort", field);
|
||||
};
|
||||
|
||||
// 监听选中项变化
|
||||
watch(checkedItems, (newVal) => {
|
||||
emit('update:modelValue', newVal);
|
||||
// 更新全选状态
|
||||
if (props.items.length > 0) {
|
||||
allChecked.value = newVal.length === props.items.length;
|
||||
} else {
|
||||
allChecked.value = false;
|
||||
}
|
||||
emit("update:modelValue", newVal);
|
||||
// 更新全选状态
|
||||
if (props.items.length > 0) {
|
||||
allChecked.value = newVal.length === props.items.length;
|
||||
} else {
|
||||
allChecked.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
// 监听modelValue变化
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
if (newVal) {
|
||||
checkedItems.value = newVal;
|
||||
}
|
||||
}, { deep: true });
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
checkedItems.value = newVal;
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
// 监听items变化,重置选中状态
|
||||
watch(() => props.items, () => {
|
||||
if (allChecked.value) {
|
||||
checkedItems.value = props.items.map(getItemId);
|
||||
emit('update:modelValue', checkedItems.value);
|
||||
}
|
||||
}, { deep: true });
|
||||
watch(
|
||||
() => props.items,
|
||||
() => {
|
||||
if (allChecked.value) {
|
||||
checkedItems.value = props.items.map(getItemId);
|
||||
emit("update:modelValue", checkedItems.value);
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user