Merge remote-tracking branch 'yudao/dev' into dev-new
# Conflicts: # apps/web-antd/src/api/infra/codegen/index.ts # apps/web-antd/src/views/infra/codegen/data.ts
This commit is contained in:
@@ -300,4 +300,5 @@ export type OnActionClickParams<T = Recordable<any>> = {
|
|||||||
export type OnActionClickFn<T = Recordable<any>> = (
|
export type OnActionClickFn<T = Recordable<any>> = (
|
||||||
params: OnActionClickParams<T>,
|
params: OnActionClickParams<T>,
|
||||||
) => void;
|
) => void;
|
||||||
|
export * from '#/components/table-action';
|
||||||
export type * from '@vben/plugins/vxe-table';
|
export type * from '@vben/plugins/vxe-table';
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export function deleteDemo01Contact(id: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 批量删除示例联系人
|
// 批量删除示例联系人
|
||||||
|
// TODO @puhui999:注释风格哈。
|
||||||
export function deleteDemo01ContactByIds(ids: number[]) {
|
export function deleteDemo01ContactByIds(ids: number[]) {
|
||||||
return requestClient.delete(
|
return requestClient.delete(
|
||||||
`/infra/demo01-contact/delete-batch?ids=${ids.join(',')}`,
|
`/infra/demo01-contact/delete-batch?ids=${ids.join(',')}`,
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
// TODO @芋艿:是否有更好的组织形式?!
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { DataNode } from 'ant-design-vue/es/tree';
|
||||||
|
|
||||||
|
import type { SystemDeptApi } from '#/api/system/dept';
|
||||||
|
|
||||||
|
import { defineProps, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
import { handleTree } from '@vben/utils';
|
||||||
|
|
||||||
|
import { Button, Card, Col, Row, Tree } from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { getSimpleDeptList } from '#/api/system/dept';
|
||||||
|
|
||||||
|
defineOptions({ name: 'DeptSelectModal' });
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
// 取消按钮文本
|
||||||
|
cancelText?: string;
|
||||||
|
// checkable 状态下节点选择完全受控
|
||||||
|
checkStrictly?: boolean;
|
||||||
|
// 确认按钮文本
|
||||||
|
confirmText?: string;
|
||||||
|
// 是否支持多选
|
||||||
|
multiple?: boolean;
|
||||||
|
// 标题
|
||||||
|
title?: string;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
cancelText: '取消',
|
||||||
|
checkStrictly: false,
|
||||||
|
confirmText: '确认',
|
||||||
|
multiple: true,
|
||||||
|
title: '部门选择',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
confirm: [deptList: SystemDeptApi.Dept[]];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// 对话框配置
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
title: props.title,
|
||||||
|
async onOpenChange(isOpen: boolean) {
|
||||||
|
if (!isOpen) {
|
||||||
|
resetData();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
modalApi.setState({ loading: true });
|
||||||
|
try {
|
||||||
|
deptData.value = await getSimpleDeptList();
|
||||||
|
deptTree.value = handleTree(deptData.value) as DataNode[];
|
||||||
|
} finally {
|
||||||
|
modalApi.setState({ loading: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
destroyOnClose: true,
|
||||||
|
});
|
||||||
|
type checkedKeys = number[] | { checked: number[]; halfChecked: number[] };
|
||||||
|
// 部门树形结构
|
||||||
|
const deptTree = ref<DataNode[]>([]);
|
||||||
|
// 选中的部门 ID 列表
|
||||||
|
const selectedDeptIds = ref<checkedKeys>([]);
|
||||||
|
// 部门数据
|
||||||
|
const deptData = ref<SystemDeptApi.Dept[]>([]);
|
||||||
|
|
||||||
|
/** 打开对话框 */
|
||||||
|
const open = async (selectedList?: SystemDeptApi.Dept[]) => {
|
||||||
|
modalApi.open();
|
||||||
|
// // 设置已选择的部门
|
||||||
|
if (selectedList?.length) {
|
||||||
|
const selectedIds = selectedList
|
||||||
|
.map((dept) => dept.id)
|
||||||
|
.filter((id): id is number => id !== undefined);
|
||||||
|
selectedDeptIds.value = props.checkStrictly
|
||||||
|
? {
|
||||||
|
checked: selectedIds,
|
||||||
|
halfChecked: [],
|
||||||
|
}
|
||||||
|
: selectedIds;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 处理选中状态变化 */
|
||||||
|
const handleCheck = () => {
|
||||||
|
if (!props.multiple) {
|
||||||
|
// 单选模式下,只保留最后选择的节点
|
||||||
|
if (Array.isArray(selectedDeptIds.value)) {
|
||||||
|
const lastSelectedId =
|
||||||
|
selectedDeptIds.value[selectedDeptIds.value.length - 1];
|
||||||
|
if (lastSelectedId) {
|
||||||
|
selectedDeptIds.value = [lastSelectedId];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// checkStrictly 为 true 时,selectedDeptIds 是一个对象
|
||||||
|
const checked = selectedDeptIds.value.checked || [];
|
||||||
|
if (checked.length > 0) {
|
||||||
|
const lastSelectedId = checked[checked.length - 1];
|
||||||
|
selectedDeptIds.value = {
|
||||||
|
checked: [lastSelectedId!],
|
||||||
|
halfChecked: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 提交选择 */
|
||||||
|
const handleConfirm = async () => {
|
||||||
|
// 获取选中的部门ID
|
||||||
|
const selectedIds: number[] = Array.isArray(selectedDeptIds.value)
|
||||||
|
? selectedDeptIds.value
|
||||||
|
: selectedDeptIds.value.checked || [];
|
||||||
|
const deptArray = deptData.value.filter((dept) =>
|
||||||
|
selectedIds.includes(dept.id!),
|
||||||
|
);
|
||||||
|
// 关闭并提示
|
||||||
|
await modalApi.close();
|
||||||
|
emit('confirm', deptArray);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
modalApi.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 重置数据 */
|
||||||
|
const resetData = () => {
|
||||||
|
deptTree.value = [];
|
||||||
|
selectedDeptIds.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 提供 open 方法,用于打开对话框 */
|
||||||
|
defineExpose({ open });
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Modal>
|
||||||
|
<Row class="h-full">
|
||||||
|
<Col :span="24">
|
||||||
|
<Card class="h-full">
|
||||||
|
<Tree
|
||||||
|
:tree-data="deptTree"
|
||||||
|
v-if="deptTree.length > 0"
|
||||||
|
v-model:checked-keys="selectedDeptIds"
|
||||||
|
:checkable="true"
|
||||||
|
:check-strictly="checkStrictly"
|
||||||
|
:field-names="{ title: 'name', key: 'id' }"
|
||||||
|
:default-expand-all="true"
|
||||||
|
@check="handleCheck"
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<template #footer>
|
||||||
|
<Button @click="handleCancel">{{ cancelText }}</Button>
|
||||||
|
<Button type="primary" @click="handleConfirm">{{ confirmText }}</Button>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
1
apps/web-antd/src/components/dept-select-modal/index.ts
Normal file
1
apps/web-antd/src/components/dept-select-modal/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default as DeptSelectModal } from './dept-select-modal.vue';
|
||||||
12
apps/web-antd/src/components/table-action/icons.ts
Normal file
12
apps/web-antd/src/components/table-action/icons.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export const ACTION_ICON = {
|
||||||
|
DOWNLOAD: 'lucide:download',
|
||||||
|
UPLOAD: 'lucide:upload',
|
||||||
|
ADD: 'lucide:plus',
|
||||||
|
EDIT: 'lucide:edit',
|
||||||
|
DELETE: 'lucide:trash',
|
||||||
|
REFRESH: 'lucide:refresh-cw',
|
||||||
|
SEARCH: 'lucide:search',
|
||||||
|
FILTER: 'lucide:filter',
|
||||||
|
MORE: 'lucide:ellipsis-vertical',
|
||||||
|
VIEW: 'lucide:eye',
|
||||||
|
};
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
|
export * from './icons';
|
||||||
|
|
||||||
export { default as TableAction } from './table-action.vue';
|
export { default as TableAction } from './table-action.vue';
|
||||||
export * from './typing';
|
export * from './typing';
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
<!-- add by 星语:参考 vben2 的方式,增加 TableAction 组件 -->
|
<!-- add by 星语:参考 vben2 的方式,增加 TableAction 组件 -->
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ButtonType } from 'ant-design-vue/es/button';
|
|
||||||
|
|
||||||
import type { PropType } from 'vue';
|
import type { PropType } from 'vue';
|
||||||
|
|
||||||
import type { ActionItem, PopConfirm } from './typing';
|
import type { ActionItem, PopConfirm } from './typing';
|
||||||
@@ -69,8 +67,7 @@ const getActions = computed(() => {
|
|||||||
.map((action) => {
|
.map((action) => {
|
||||||
const { popConfirm } = action;
|
const { popConfirm } = action;
|
||||||
return {
|
return {
|
||||||
// getPopupContainer: document.body,
|
type: action.type || 'link',
|
||||||
type: 'link' as ButtonType,
|
|
||||||
...action,
|
...action,
|
||||||
...popConfirm,
|
...popConfirm,
|
||||||
onConfirm: popConfirm?.confirm,
|
onConfirm: popConfirm?.confirm,
|
||||||
@@ -135,7 +132,7 @@ function handleMenuClick(e: any) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="m-table-action">
|
<div class="table-actions">
|
||||||
<Space
|
<Space
|
||||||
:size="
|
:size="
|
||||||
getActions?.some((item: ActionItem) => item.type === 'link') ? 0 : 8
|
getActions?.some((item: ActionItem) => item.type === 'link') ? 0 : 8
|
||||||
@@ -184,10 +181,10 @@ function handleMenuClick(e: any) {
|
|||||||
|
|
||||||
<Dropdown v-if="getDropdownList.length > 0" :trigger="['hover']">
|
<Dropdown v-if="getDropdownList.length > 0" :trigger="['hover']">
|
||||||
<slot name="more">
|
<slot name="more">
|
||||||
<Button size="small" type="link">
|
<Button :type="getDropdownList[0].type">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
{{ $t('page.action.more') }}
|
{{ $t('page.action.more') }}
|
||||||
<IconifyIcon class="icon-more" icon="ant-design:more-outlined" />
|
<IconifyIcon icon="lucide:ellipsis-vertical" />
|
||||||
</template>
|
</template>
|
||||||
</Button>
|
</Button>
|
||||||
</slot>
|
</slot>
|
||||||
@@ -230,8 +227,8 @@ function handleMenuClick(e: any) {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.m-table-action {
|
.table-actions {
|
||||||
.ant-btn {
|
.ant-btn-link {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import type { ButtonProps } from 'ant-design-vue/es/button/buttonTypes';
|
import type {
|
||||||
|
ButtonProps,
|
||||||
|
ButtonType,
|
||||||
|
} from 'ant-design-vue/es/button/buttonTypes';
|
||||||
import type { TooltipProps } from 'ant-design-vue/es/tooltip/Tooltip';
|
import type { TooltipProps } from 'ant-design-vue/es/tooltip/Tooltip';
|
||||||
|
|
||||||
export interface PopConfirm {
|
export interface PopConfirm {
|
||||||
@@ -13,6 +16,7 @@ export interface PopConfirm {
|
|||||||
|
|
||||||
export interface ActionItem extends ButtonProps {
|
export interface ActionItem extends ButtonProps {
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
|
type?: ButtonType;
|
||||||
label?: string;
|
label?: string;
|
||||||
color?: 'error' | 'success' | 'warning';
|
color?: 'error' | 'success' | 'warning';
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { BpmCategoryApi } from '#/api/bpm/category';
|
import type { BpmCategoryApi } from '#/api/bpm/category';
|
||||||
import type { BpmProcessDefinitionApi } from '#/api/bpm/definition';
|
import type { BpmProcessDefinitionApi } from '#/api/bpm/definition';
|
||||||
|
import type { BpmFormApi } from '#/api/bpm/form';
|
||||||
import type { SystemDeptApi } from '#/api/system/dept';
|
import type { SystemDeptApi } from '#/api/system/dept';
|
||||||
import type { SystemUserApi } from '#/api/system/user';
|
import type { SystemUserApi } from '#/api/system/user';
|
||||||
|
|
||||||
@@ -12,10 +13,11 @@ import { useTabs } from '@vben/hooks';
|
|||||||
import { ArrowLeft } from '@vben/icons';
|
import { ArrowLeft } from '@vben/icons';
|
||||||
import { useUserStore } from '@vben/stores';
|
import { useUserStore } from '@vben/stores';
|
||||||
|
|
||||||
import { Button, message } from 'ant-design-vue';
|
import { Button, Card, message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { getCategorySimpleList } from '#/api/bpm/category';
|
import { getCategorySimpleList } from '#/api/bpm/category';
|
||||||
import { getProcessDefinition } from '#/api/bpm/definition';
|
import { getProcessDefinition } from '#/api/bpm/definition';
|
||||||
|
import { getFormSimpleList } from '#/api/bpm/form';
|
||||||
import {
|
import {
|
||||||
createModel,
|
createModel,
|
||||||
deployModel,
|
deployModel,
|
||||||
@@ -26,10 +28,12 @@ import { getSimpleDeptList } from '#/api/system/dept';
|
|||||||
import { getSimpleUserList } from '#/api/system/user';
|
import { getSimpleUserList } from '#/api/system/user';
|
||||||
|
|
||||||
import BasicInfo from './modules/basic-info.vue';
|
import BasicInfo from './modules/basic-info.vue';
|
||||||
|
import FormDesign from './modules/form-design.vue';
|
||||||
|
|
||||||
defineOptions({ name: 'BpmModelCreate' });
|
defineOptions({ name: 'BpmModelCreate' });
|
||||||
|
|
||||||
// TODO 这个常量是不是所有 apps 都可以使用, 放 @utils/constant.ts 不能共享, @芋艿 这些常量放哪里合适!
|
// TODO 这个常量是不是所有 apps 都可以使用, 放 @utils/constant.ts 不能共享, @芋艿 这些常量放哪里合适!
|
||||||
|
// TODO @jason:/Users/yunai/Java/yudao-ui-admin-vben-v5/apps/web-antd/src/utils/constants.ts;先不多个 apps 共享哈;
|
||||||
const BpmModelType = {
|
const BpmModelType = {
|
||||||
BPMN: 10, // BPMN 设计器
|
BPMN: 10, // BPMN 设计器
|
||||||
SIMPLE: 20, // 简易设计器
|
SIMPLE: 20, // 简易设计器
|
||||||
@@ -62,7 +66,9 @@ const route = useRoute();
|
|||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
// 基础信息组件引用
|
// 基础信息组件引用
|
||||||
const basicInfoRef = ref();
|
const basicInfoRef = ref<InstanceType<typeof BasicInfo>>();
|
||||||
|
// 表单设计组件引用
|
||||||
|
const formDesignRef = ref<InstanceType<typeof FormDesign>>();
|
||||||
|
|
||||||
/** 步骤校验函数 */
|
/** 步骤校验函数 */
|
||||||
const validateBasic = async () => {
|
const validateBasic = async () => {
|
||||||
@@ -71,7 +77,7 @@ const validateBasic = async () => {
|
|||||||
|
|
||||||
/** 表单设计校验 */
|
/** 表单设计校验 */
|
||||||
const validateForm = async () => {
|
const validateForm = async () => {
|
||||||
// TODO
|
await formDesignRef.value?.validate();
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 流程设计校验 */
|
/** 流程设计校验 */
|
||||||
@@ -132,7 +138,7 @@ provide('processData', processData);
|
|||||||
provide('modelData', formData);
|
provide('modelData', formData);
|
||||||
|
|
||||||
// 数据列表
|
// 数据列表
|
||||||
// const formList = ref([])
|
const formList = ref<BpmFormApi.FormVO[]>([]);
|
||||||
const categoryList = ref<BpmCategoryApi.CategoryVO[]>([]);
|
const categoryList = ref<BpmCategoryApi.CategoryVO[]>([]);
|
||||||
const userList = ref<SystemUserApi.User[]>([]);
|
const userList = ref<SystemUserApi.User[]>([]);
|
||||||
const deptList = ref<SystemDeptApi.Dept[]>([]);
|
const deptList = ref<SystemDeptApi.Dept[]>([]);
|
||||||
@@ -187,8 +193,8 @@ const initData = async () => {
|
|||||||
formData.value.managerUserIds.push(userStore.userInfo?.userId);
|
formData.value.managerUserIds.push(userStore.userInfo?.userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO 获取表单列表
|
// 获取表单列表
|
||||||
// formList.value = await getFormSimpleList()
|
formList.value = await getFormSimpleList();
|
||||||
categoryList.value = await getCategorySimpleList();
|
categoryList.value = await getCategorySimpleList();
|
||||||
// 获取用户列表
|
// 获取用户列表
|
||||||
userList.value = await getSimpleUserList();
|
userList.value = await getSimpleUserList();
|
||||||
@@ -393,9 +399,8 @@ onMounted(async () => {
|
|||||||
/** 添加组件卸载前的清理 */
|
/** 添加组件卸载前的清理 */
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
// 清理所有的引用
|
// 清理所有的引用
|
||||||
basicInfoRef.value = null;
|
basicInfoRef.value = undefined;
|
||||||
// TODO 后续加
|
formDesignRef.value = undefined;
|
||||||
// formDesignRef.value = null;
|
|
||||||
// processDesignRef.value = null;
|
// processDesignRef.value = null;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -467,27 +472,34 @@ onBeforeUnmount(() => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 主体内容 -->
|
<!-- 主体内容 -->
|
||||||
<div class="mt-[50px]">
|
<Card :body-style="{ padding: '10px' }" class="mb-4">
|
||||||
<!-- 第一步:基本信息 -->
|
<div class="mt-[50px]">
|
||||||
<div v-if="currentStep === 0" class="mx-auto w-[560px]">
|
<!-- 第一步:基本信息 -->
|
||||||
<BasicInfo
|
<div v-if="currentStep === 0" class="mx-auto w-4/6">
|
||||||
v-model="formData"
|
<BasicInfo
|
||||||
:category-list="categoryList"
|
v-model="formData"
|
||||||
:user-list="userList"
|
:category-list="categoryList"
|
||||||
:dept-list="deptList"
|
:user-list="userList"
|
||||||
ref="basicInfoRef"
|
:dept-list="deptList"
|
||||||
/>
|
ref="basicInfoRef"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- 第二步:表单设计 -->
|
||||||
|
<div v-show="currentStep === 1" class="mx-auto w-4/6">
|
||||||
|
<FormDesign
|
||||||
|
v-model="formData"
|
||||||
|
:form-list="formList"
|
||||||
|
ref="formDesignRef"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 第三步:流程设计 TODO -->
|
||||||
|
|
||||||
|
<!-- 第四步:更多设置 TODO -->
|
||||||
|
<div v-show="currentStep === 3" class="mx-auto w-4/6"></div>
|
||||||
</div>
|
</div>
|
||||||
|
</Card>
|
||||||
<!-- 第二步:表单设计 TODO -->
|
|
||||||
|
|
||||||
<!-- 第三步:流程设计 TODO -->
|
|
||||||
|
|
||||||
<!-- 第四步:更多设置 TODO -->
|
|
||||||
<div v-show="currentStep === 3" class="mx-auto w-[700px]"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -2,13 +2,15 @@
|
|||||||
import type { Rule } from 'ant-design-vue/es/form';
|
import type { Rule } from 'ant-design-vue/es/form';
|
||||||
import type { SelectValue } from 'ant-design-vue/es/select';
|
import type { SelectValue } from 'ant-design-vue/es/select';
|
||||||
|
|
||||||
|
import type { PropType } from 'vue';
|
||||||
|
|
||||||
import type { BpmCategoryApi } from '#/api/bpm/category';
|
import type { BpmCategoryApi } from '#/api/bpm/category';
|
||||||
import type { SystemDeptApi } from '#/api/system/dept';
|
import type { SystemDeptApi } from '#/api/system/dept';
|
||||||
import type { SystemUserApi } from '#/api/system/user';
|
import type { SystemUserApi } from '#/api/system/user';
|
||||||
|
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
import { IconifyIcon, Plus, ShieldQuestion, X } from '@vben/icons';
|
import { CircleHelp, IconifyIcon, Plus, X } from '@vben/icons';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Avatar,
|
Avatar,
|
||||||
@@ -20,18 +22,25 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
} from 'ant-design-vue';
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { DeptSelectModal } from '#/components/dept-select-modal';
|
||||||
import { ImageUpload } from '#/components/upload';
|
import { ImageUpload } from '#/components/upload';
|
||||||
import { UserSelectModal } from '#/components/user-select-modal';
|
import { UserSelectModal } from '#/components/user-select-modal';
|
||||||
import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '#/utils';
|
import { DICT_TYPE, getBoolDictOptions, getIntDictOptions } from '#/utils';
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = defineProps({
|
||||||
defineProps<{
|
categoryList: {
|
||||||
categoryList: BpmCategoryApi.CategoryVO[];
|
type: Array as PropType<BpmCategoryApi.CategoryVO[]>,
|
||||||
deptList: SystemDeptApi.Dept[];
|
required: true,
|
||||||
userList: SystemUserApi.User[];
|
},
|
||||||
}>(),
|
userList: {
|
||||||
{},
|
type: Array as PropType<SystemUserApi.User[]>,
|
||||||
);
|
required: true,
|
||||||
|
},
|
||||||
|
deptList: {
|
||||||
|
type: Array as PropType<SystemDeptApi.Dept[]>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// 表单引用
|
// 表单引用
|
||||||
const formRef = ref();
|
const formRef = ref();
|
||||||
@@ -45,6 +54,7 @@ const selectedStartDepts = ref<SystemDeptApi.Dept[]>([]);
|
|||||||
// 选中的流程管理员
|
// 选中的流程管理员
|
||||||
const selectedManagerUsers = ref<SystemUserApi.User[]>([]);
|
const selectedManagerUsers = ref<SystemUserApi.User[]>([]);
|
||||||
const userSelectFormRef = ref();
|
const userSelectFormRef = ref();
|
||||||
|
const deptSelectFormRef = ref();
|
||||||
const currentSelectType = ref<'manager' | 'start'>('start');
|
const currentSelectType = ref<'manager' | 'start'>('start');
|
||||||
// 选中的用户
|
// 选中的用户
|
||||||
const selectedUsers = ref<number[]>();
|
const selectedUsers = ref<number[]>();
|
||||||
@@ -55,6 +65,7 @@ const rules: Record<string, Rule[]> = {
|
|||||||
category: [{ required: true, message: '流程分类不能为空', trigger: 'blur' }],
|
category: [{ required: true, message: '流程分类不能为空', trigger: 'blur' }],
|
||||||
type: [{ required: true, message: '流程类型不能为空', trigger: 'blur' }],
|
type: [{ required: true, message: '流程类型不能为空', trigger: 'blur' }],
|
||||||
visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
|
visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }],
|
||||||
|
// TODO 这个的校验好像没有起作用
|
||||||
managerUserIds: [
|
managerUserIds: [
|
||||||
{ required: true, message: '流程管理员不能为空', trigger: 'blur' },
|
{ required: true, message: '流程管理员不能为空', trigger: 'blur' },
|
||||||
],
|
],
|
||||||
@@ -99,8 +110,14 @@ const openStartUserSelect = () => {
|
|||||||
|
|
||||||
/** 打开部门选择 */
|
/** 打开部门选择 */
|
||||||
const openStartDeptSelect = () => {
|
const openStartDeptSelect = () => {
|
||||||
// TODO 部门选择组件暂时还没有
|
deptSelectFormRef.value.open(selectedStartDepts.value);
|
||||||
console.warn('部门选择功能暂未实现');
|
};
|
||||||
|
/** 处理部门选择确认 */
|
||||||
|
const handleDeptSelectConfirm = (depts: SystemDeptApi.Dept[]) => {
|
||||||
|
modelData.value = {
|
||||||
|
...modelData.value,
|
||||||
|
startDeptIds: depts.map((d) => d.id),
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 打开管理员选择 */
|
/** 打开管理员选择 */
|
||||||
@@ -203,9 +220,7 @@ const validate = async () => {
|
|||||||
await formRef.value?.validate();
|
await formRef.value?.validate();
|
||||||
};
|
};
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({ validate });
|
||||||
validate,
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -213,8 +228,8 @@ defineExpose({
|
|||||||
ref="formRef"
|
ref="formRef"
|
||||||
:model="modelData"
|
:model="modelData"
|
||||||
:rules="rules"
|
:rules="rules"
|
||||||
:label-col="{ span: 6 }"
|
:label-col="{ span: 4 }"
|
||||||
:wrapper-col="{ span: 18 }"
|
:wrapper-col="{ span: 20 }"
|
||||||
class="mt-5"
|
class="mt-5"
|
||||||
>
|
>
|
||||||
<Form.Item label="流程标识" name="key" class="mb-5">
|
<Form.Item label="流程标识" name="key" class="mb-5">
|
||||||
@@ -231,7 +246,7 @@ defineExpose({
|
|||||||
"
|
"
|
||||||
placement="top"
|
placement="top"
|
||||||
>
|
>
|
||||||
<ShieldQuestion class="ml-1 text-gray-500" />
|
<CircleHelp class="ml-1 size-5 text-gray-900" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -343,7 +358,7 @@ defineExpose({
|
|||||||
:key="dept.id"
|
:key="dept.id"
|
||||||
class="relative flex h-9 items-center rounded-full bg-gray-100 pr-2"
|
class="relative flex h-9 items-center rounded-full bg-gray-100 pr-2"
|
||||||
>
|
>
|
||||||
<IconifyIcon icon="mdi:building-outline" class="size-5" />
|
<IconifyIcon icon="ep:office-building" class="size-6 px-1" />
|
||||||
{{ dept.name }}
|
{{ dept.name }}
|
||||||
<X
|
<X
|
||||||
class="ml-2 size-4 cursor-pointer text-gray-400 hover:text-red-500"
|
class="ml-2 size-4 cursor-pointer text-gray-400 hover:text-red-500"
|
||||||
@@ -408,6 +423,13 @@ defineExpose({
|
|||||||
@closed="handleUserSelectClosed"
|
@closed="handleUserSelectClosed"
|
||||||
@cancel="handleUserSelectCancel"
|
@cancel="handleUserSelectCancel"
|
||||||
/>
|
/>
|
||||||
|
<!-- 部门选择对话框 -->
|
||||||
|
<DeptSelectModal
|
||||||
|
ref="deptSelectFormRef"
|
||||||
|
title="发起人部门选择"
|
||||||
|
:check-strictly="true"
|
||||||
|
@confirm="handleDeptSelectConfirm"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
187
apps/web-antd/src/views/bpm/model/form/modules/form-design.vue
Normal file
187
apps/web-antd/src/views/bpm/model/form/modules/form-design.vue
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { Rule } from 'ant-design-vue/es/form';
|
||||||
|
|
||||||
|
import type { BpmFormApi } from '#/api/bpm/form';
|
||||||
|
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import { CircleHelp } from '@vben/icons';
|
||||||
|
|
||||||
|
import FormCreate from '@form-create/ant-design-vue';
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormItem,
|
||||||
|
Input,
|
||||||
|
Radio,
|
||||||
|
RadioGroup,
|
||||||
|
Select,
|
||||||
|
SelectOption,
|
||||||
|
Tooltip,
|
||||||
|
} from 'ant-design-vue';
|
||||||
|
|
||||||
|
import { getFormDetail } from '#/api/bpm/form';
|
||||||
|
import {
|
||||||
|
BpmModelFormType,
|
||||||
|
DICT_TYPE,
|
||||||
|
getDictOptions,
|
||||||
|
setConfAndFields2,
|
||||||
|
} from '#/utils';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
formList: {
|
||||||
|
type: Array<BpmFormApi.FormVO>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const formRef = ref();
|
||||||
|
|
||||||
|
// 创建本地数据副本
|
||||||
|
const modelData = defineModel<any>();
|
||||||
|
|
||||||
|
// 表单预览数据
|
||||||
|
const formPreview = ref({
|
||||||
|
formData: {} as any,
|
||||||
|
rule: [],
|
||||||
|
option: {
|
||||||
|
submitBtn: false,
|
||||||
|
resetBtn: false,
|
||||||
|
formData: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 监听表单ID变化,加载表单数据 */
|
||||||
|
watch(
|
||||||
|
() => modelData.value.formId,
|
||||||
|
async (newFormId) => {
|
||||||
|
if (newFormId && modelData.value.formType === BpmModelFormType.NORMAL) {
|
||||||
|
const data = await getFormDetail(newFormId);
|
||||||
|
setConfAndFields2(formPreview.value, data.conf, data.fields);
|
||||||
|
// 设置只读
|
||||||
|
formPreview.value.rule.forEach((item: any) => {
|
||||||
|
item.props = { ...item.props, disabled: true };
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
formPreview.value.rule = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
const rules: Record<string, Rule[]> = {
|
||||||
|
formType: [{ required: true, message: '表单类型不能为空', trigger: 'blur' }],
|
||||||
|
formId: [{ required: true, message: '流程表单不能为空', trigger: 'blur' }],
|
||||||
|
formCustomCreatePath: [
|
||||||
|
{ required: true, message: '表单提交路由不能为空', trigger: 'blur' },
|
||||||
|
],
|
||||||
|
formCustomViewPath: [
|
||||||
|
{ required: true, message: '表单查看地址不能为空', trigger: 'blur' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/** 表单校验 */
|
||||||
|
const validate = async () => {
|
||||||
|
await formRef.value?.validate();
|
||||||
|
};
|
||||||
|
|
||||||
|
defineExpose({ validate });
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Form
|
||||||
|
ref="formRef"
|
||||||
|
:model="modelData"
|
||||||
|
:rules="rules"
|
||||||
|
:label-col="{ span: 4 }"
|
||||||
|
:wrapper-col="{ span: 20 }"
|
||||||
|
class="mt-5"
|
||||||
|
>
|
||||||
|
<FormItem label="表单类型" name="formType" class="mb-5">
|
||||||
|
<RadioGroup v-model:value="modelData.formType">
|
||||||
|
<Radio
|
||||||
|
v-for="dict in getDictOptions(
|
||||||
|
DICT_TYPE.BPM_MODEL_FORM_TYPE,
|
||||||
|
'number',
|
||||||
|
)"
|
||||||
|
:key="dict.value as string"
|
||||||
|
:value="dict.value"
|
||||||
|
>
|
||||||
|
{{ dict.label }}
|
||||||
|
</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem
|
||||||
|
v-if="modelData.formType === BpmModelFormType.NORMAL"
|
||||||
|
label="流程表单"
|
||||||
|
name="formId"
|
||||||
|
class="mb-5"
|
||||||
|
>
|
||||||
|
<Select v-model:value="modelData.formId" clearable>
|
||||||
|
<SelectOption
|
||||||
|
v-for="form in props.formList"
|
||||||
|
:key="form.id"
|
||||||
|
:value="form.id"
|
||||||
|
>
|
||||||
|
{{ form.name }}
|
||||||
|
</SelectOption>
|
||||||
|
>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem
|
||||||
|
v-if="modelData.formType === BpmModelFormType.CUSTOM"
|
||||||
|
label="表单提交路由"
|
||||||
|
name="formCustomCreatePath"
|
||||||
|
class="mb-5"
|
||||||
|
>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<Input
|
||||||
|
v-model:value="modelData.formCustomCreatePath"
|
||||||
|
placeholder="请输入表单提交路由"
|
||||||
|
/>
|
||||||
|
<Tooltip
|
||||||
|
title="自定义表单的提交路径,使用 Vue 的路由地址, 例如说: bpm/oa/leave/create.vue"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<CircleHelp class="ml-1 size-5 text-gray-900" />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
<FormItem
|
||||||
|
v-if="modelData.formType === BpmModelFormType.CUSTOM"
|
||||||
|
label="表单查看地址"
|
||||||
|
name="formCustomViewPath"
|
||||||
|
class="mb-5"
|
||||||
|
>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<Input
|
||||||
|
v-model:value="modelData.formCustomViewPath"
|
||||||
|
placeholder="请输入表单查看的组件地址"
|
||||||
|
/>
|
||||||
|
<Tooltip
|
||||||
|
title="自定义表单的查看组件地址,使用 Vue 的组件地址,例如说:bpm/oa/leave/detail.vue"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<CircleHelp class="ml-1 size-5 text-gray-900" />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
<!-- 表单预览 -->
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
modelData.formType === BpmModelFormType.NORMAL &&
|
||||||
|
modelData.formId &&
|
||||||
|
formPreview.rule.length > 0
|
||||||
|
"
|
||||||
|
class="mb-5 mt-7 rounded-sm border border-solid border-gray-200 p-5"
|
||||||
|
>
|
||||||
|
<div class="mb-[15px] flex items-center">
|
||||||
|
<div class="mr-[10px] h-[15px] w-[4px] bg-[#1890ff]"></div>
|
||||||
|
<span class="text-[15px] font-bold">表单预览</span>
|
||||||
|
</div>
|
||||||
|
<FormCreate
|
||||||
|
v-model:api="formPreview.formData"
|
||||||
|
:rule="formPreview.rule"
|
||||||
|
:option="formPreview.option"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</template>
|
||||||
@@ -162,6 +162,7 @@ const handleCategorySortSubmit = async () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<Page auto-content-height>
|
||||||
|
<!-- TODO @jaosn:没头像的图标,展示文字头像哈 -->
|
||||||
<!-- 流程分类表单弹窗 -->
|
<!-- 流程分类表单弹窗 -->
|
||||||
<CategoryFormModal @success="getList" />
|
<CategoryFormModal @success="getList" />
|
||||||
<Card
|
<Card
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ export function useGridColumns(
|
|||||||
minWidth: 200,
|
minWidth: 200,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
|
// TODO @puhui999:headerAlign 要使用 headerAlign: 'center' 么?看着现在分成了 align 和 headerAlign 两种
|
||||||
headerAlign: 'center',
|
headerAlign: 'center',
|
||||||
showOverflow: false,
|
showOverflow: false,
|
||||||
cellRender: {
|
cellRender: {
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ async function onDelete(row: Demo01ContactApi.Demo01Contact) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO @puhui999::1)/** 批量删除示例联系人 */ 是不是放在 deleteIds 上面;2)showDeleteBatchBtn 是不是直接 disabled 哪里判断哈;
|
||||||
const deleteIds = ref<number[]>([]); // 待删除示例联系人 ID
|
const deleteIds = ref<number[]>([]); // 待删除示例联系人 ID
|
||||||
const showDeleteBatchBtn = computed(() => isEmpty(deleteIds.value));
|
const showDeleteBatchBtn = computed(() => isEmpty(deleteIds.value));
|
||||||
function setDeleteIds({
|
function setDeleteIds({
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ function onRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 查询 IP */
|
/** 查询 IP */
|
||||||
function onQueryIp() {
|
function handleQueryIp() {
|
||||||
formModalApi.setData(null).open();
|
formModalApi.setData(null).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
<FormModal @success="onRefresh" />
|
<FormModal @success="onRefresh" />
|
||||||
<Grid table-title="地区列表">
|
<Grid table-title="地区列表">
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<Button type="primary" @click="onQueryIp">
|
<Button type="primary" @click="handleQueryIp">
|
||||||
<Search class="size-5" />
|
<Search class="size-5" />
|
||||||
IP 查询
|
IP 查询
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { SystemDeptApi } from '#/api/system/dept';
|
import type { SystemDeptApi } from '#/api/system/dept';
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
|
||||||
import { handleTree } from '@vben/utils';
|
import { handleTree } from '@vben/utils';
|
||||||
|
|
||||||
import { z } from '#/adapter/form';
|
import { z } from '#/adapter/form';
|
||||||
@@ -10,8 +9,6 @@ import { getDeptList } from '#/api/system/dept';
|
|||||||
import { getSimpleUserList } from '#/api/system/user';
|
import { getSimpleUserList } from '#/api/system/user';
|
||||||
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
|
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
|
||||||
|
|
||||||
/** 新增/修改的表单 */
|
/** 新增/修改的表单 */
|
||||||
export function useFormSchema(): VbenFormSchema[] {
|
export function useFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
@@ -113,7 +110,6 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||||||
|
|
||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns(
|
export function useGridColumns(
|
||||||
onActionClick?: OnActionClickFn<SystemDeptApi.Dept>,
|
|
||||||
getLeaderName?: (userId: number) => string | undefined,
|
getLeaderName?: (userId: number) => string | undefined,
|
||||||
): VxeTableGridOptions<SystemDeptApi.Dept>['columns'] {
|
): VxeTableGridOptions<SystemDeptApi.Dept>['columns'] {
|
||||||
return [
|
return [
|
||||||
@@ -154,39 +150,10 @@ export function useGridColumns(
|
|||||||
formatter: 'formatDateTime',
|
formatter: 'formatDateTime',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'operation',
|
|
||||||
title: '操作',
|
title: '操作',
|
||||||
minWidth: 200,
|
width: 220,
|
||||||
align: 'right',
|
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
headerAlign: 'center',
|
slots: { default: 'actions' },
|
||||||
showOverflow: false,
|
|
||||||
cellRender: {
|
|
||||||
attrs: {
|
|
||||||
nameField: 'name',
|
|
||||||
nameTitle: '部门',
|
|
||||||
onClick: onActionClick,
|
|
||||||
},
|
|
||||||
name: 'CellOperation',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
code: 'append',
|
|
||||||
text: '新增下级',
|
|
||||||
show: hasAccessByCodes(['system:dept:create']),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'edit',
|
|
||||||
show: hasAccessByCodes(['system:dept:update']),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'delete',
|
|
||||||
show: hasAccessByCodes(['system:dept:delete']),
|
|
||||||
disabled: (row: SystemDeptApi.Dept) => {
|
|
||||||
return !!(row.children && row.children.length > 0);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,15 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { SystemDeptApi } from '#/api/system/dept';
|
import type { SystemDeptApi } from '#/api/system/dept';
|
||||||
import type { SystemUserApi } from '#/api/system/user';
|
import type { SystemUserApi } from '#/api/system/user';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { Plus } from '@vben/icons';
|
|
||||||
|
|
||||||
import { Button, message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { deleteDept, getDeptList } from '#/api/system/dept';
|
import { deleteDept, getDeptList } from '#/api/system/dept';
|
||||||
import { getSimpleUserList } from '#/api/system/user';
|
import { getSimpleUserList } from '#/api/system/user';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
@@ -29,9 +25,9 @@ const [FormModal, formModalApi] = useVbenModal({
|
|||||||
const userList = ref<SystemUserApi.User[]>([]);
|
const userList = ref<SystemUserApi.User[]>([]);
|
||||||
|
|
||||||
/** 获取负责人名称 */
|
/** 获取负责人名称 */
|
||||||
const getLeaderName = (userId: number) => {
|
function getLeaderName(userId: number) {
|
||||||
return userList.value.find((user) => user.id === userId)?.nickname;
|
return userList.value.find((user) => user.id === userId)?.nickname;
|
||||||
};
|
}
|
||||||
|
|
||||||
/** 刷新表格 */
|
/** 刷新表格 */
|
||||||
function onRefresh() {
|
function onRefresh() {
|
||||||
@@ -46,57 +42,41 @@ function toggleExpand() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 创建部门 */
|
/** 创建部门 */
|
||||||
function onCreate() {
|
function handleCreate() {
|
||||||
formModalApi.setData(null).open();
|
formModalApi.setData(null).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 添加下级部门 */
|
/** 添加下级部门 */
|
||||||
function onAppend(row: SystemDeptApi.Dept) {
|
function handleAppend(row: SystemDeptApi.Dept) {
|
||||||
formModalApi.setData({ parentId: row.id }).open();
|
formModalApi.setData({ parentId: row.id }).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编辑部门 */
|
/** 编辑部门 */
|
||||||
function onEdit(row: SystemDeptApi.Dept) {
|
function handleEdit(row: SystemDeptApi.Dept) {
|
||||||
formModalApi.setData(row).open();
|
formModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除部门 */
|
/** 删除部门 */
|
||||||
async function onDelete(row: SystemDeptApi.Dept) {
|
async function handleDelete(row: SystemDeptApi.Dept) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
duration: 0,
|
key: 'action_key_msg',
|
||||||
key: 'action_process_msg',
|
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteDept(row.id as number);
|
await deleteDept(row.id as number);
|
||||||
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
|
key: 'action_key_msg',
|
||||||
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} catch {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表格操作按钮的回调函数 */
|
|
||||||
function onActionClick({ code, row }: OnActionClickParams<SystemDeptApi.Dept>) {
|
|
||||||
switch (code) {
|
|
||||||
case 'append': {
|
|
||||||
onAppend(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'delete': {
|
|
||||||
onDelete(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'edit': {
|
|
||||||
onEdit(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(onActionClick, getLeaderName),
|
columns: useGridColumns(getLeaderName),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
pagerConfig: {
|
pagerConfig: {
|
||||||
@@ -136,17 +116,54 @@ onMounted(async () => {
|
|||||||
<FormModal @success="onRefresh" />
|
<FormModal @success="onRefresh" />
|
||||||
<Grid table-title="部门列表">
|
<Grid table-title="部门列表">
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<Button
|
<TableAction
|
||||||
type="primary"
|
:actions="[
|
||||||
@click="onCreate"
|
{
|
||||||
v-access:code="['system:dept:create']"
|
label: $t('ui.actionTitle.create', ['菜单']),
|
||||||
>
|
type: 'primary',
|
||||||
<Plus class="size-5" />
|
icon: ACTION_ICON.ADD,
|
||||||
{{ $t('ui.actionTitle.create', ['部门']) }}
|
auth: ['system:dept:create'],
|
||||||
</Button>
|
onClick: handleCreate,
|
||||||
<Button class="ml-2" @click="toggleExpand">
|
},
|
||||||
{{ isExpanded ? '收缩' : '展开' }}
|
{
|
||||||
</Button>
|
label: isExpanded ? '收缩' : '展开',
|
||||||
|
type: 'primary',
|
||||||
|
onClick: toggleExpand,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: '新增下级',
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.ADD,
|
||||||
|
auth: ['system:menu:create'],
|
||||||
|
onClick: handleAppend.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('common.edit'),
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.EDIT,
|
||||||
|
auth: ['system:menu:update'],
|
||||||
|
onClick: handleEdit.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('common.delete'),
|
||||||
|
type: 'link',
|
||||||
|
danger: true,
|
||||||
|
icon: ACTION_ICON.DELETE,
|
||||||
|
auth: ['system:menu:delete'],
|
||||||
|
disabled: !!(row.children && row.children.length > 0),
|
||||||
|
popConfirm: {
|
||||||
|
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||||
|
confirm: handleDelete.bind(null, row),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { SystemDictDataApi } from '#/api/system/dict/data';
|
|
||||||
import type { SystemDictTypeApi } from '#/api/system/dict/type';
|
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
|
||||||
|
|
||||||
import { z } from '#/adapter/form';
|
import { z } from '#/adapter/form';
|
||||||
import { getSimpleDictTypeList } from '#/api/system/dict/type';
|
import { getSimpleDictTypeList } from '#/api/system/dict/type';
|
||||||
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
|
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
|
||||||
|
|
||||||
// ============================== 字典类型 ==============================
|
// ============================== 字典类型 ==============================
|
||||||
|
|
||||||
/** 类型新增/修改的表单 */
|
/** 类型新增/修改的表单 */
|
||||||
@@ -96,9 +90,7 @@ export function useTypeGridFormSchema(): VbenFormSchema[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 类型列表的字段 */
|
/** 类型列表的字段 */
|
||||||
export function useTypeGridColumns<T = SystemDictTypeApi.DictType>(
|
export function useTypeGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
onActionClick: OnActionClickFn<T>,
|
|
||||||
): VxeTableGridOptions['columns'] {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
field: 'id',
|
field: 'id',
|
||||||
@@ -136,29 +128,10 @@ export function useTypeGridColumns<T = SystemDictTypeApi.DictType>(
|
|||||||
formatter: 'formatDateTime',
|
formatter: 'formatDateTime',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
minWidth: 120,
|
|
||||||
title: '操作',
|
title: '操作',
|
||||||
field: 'operation',
|
width: 160,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
align: 'center',
|
slots: { default: 'actions' },
|
||||||
cellRender: {
|
|
||||||
attrs: {
|
|
||||||
nameField: 'type',
|
|
||||||
nameTitle: '字典类型',
|
|
||||||
onClick: onActionClick,
|
|
||||||
},
|
|
||||||
name: 'CellOperation',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
code: 'edit',
|
|
||||||
show: hasAccessByCodes(['system:dict:update']),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'delete',
|
|
||||||
show: hasAccessByCodes(['system:dict:delete']),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -310,9 +283,7 @@ export function useDataGridFormSchema(): VbenFormSchema[] {
|
|||||||
/**
|
/**
|
||||||
* 字典数据表格列
|
* 字典数据表格列
|
||||||
*/
|
*/
|
||||||
export function useDataGridColumns<T = SystemDictDataApi.DictData>(
|
export function useDataGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
onActionClick: OnActionClickFn<T>,
|
|
||||||
): VxeTableGridOptions['columns'] {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
field: 'id',
|
field: 'id',
|
||||||
@@ -360,29 +331,10 @@ export function useDataGridColumns<T = SystemDictDataApi.DictData>(
|
|||||||
formatter: 'formatDateTime',
|
formatter: 'formatDateTime',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
minWidth: 120,
|
|
||||||
title: '操作',
|
title: '操作',
|
||||||
field: 'operation',
|
width: 160,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
align: 'center',
|
slots: { default: 'actions' },
|
||||||
cellRender: {
|
|
||||||
attrs: {
|
|
||||||
nameField: 'label',
|
|
||||||
nameTitle: '字典数据',
|
|
||||||
onClick: onActionClick,
|
|
||||||
},
|
|
||||||
name: 'CellOperation',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
code: 'edit',
|
|
||||||
show: hasAccessByCodes(['system:dict:update']),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'delete',
|
|
||||||
show: hasAccessByCodes(['system:dict:delete']),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import TypeGrid from './modules/type-grid.vue';
|
|||||||
|
|
||||||
const searchDictType = ref<string>(); // 搜索的字典类型
|
const searchDictType = ref<string>(); // 搜索的字典类型
|
||||||
|
|
||||||
function onDictTypeSelect(dictType: string) {
|
function handleDictTypeSelect(dictType: string) {
|
||||||
searchDictType.value = dictType;
|
searchDictType.value = dictType;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -24,7 +24,7 @@ function onDictTypeSelect(dictType: string) {
|
|||||||
<div class="flex h-full">
|
<div class="flex h-full">
|
||||||
<!-- 左侧字典类型列表 -->
|
<!-- 左侧字典类型列表 -->
|
||||||
<div class="w-1/2 pr-3">
|
<div class="w-1/2 pr-3">
|
||||||
<TypeGrid @select="onDictTypeSelect" />
|
<TypeGrid @select="handleDictTypeSelect" />
|
||||||
</div>
|
</div>
|
||||||
<!-- 右侧字典数据列表 -->
|
<!-- 右侧字典数据列表 -->
|
||||||
<div class="w-1/2">
|
<div class="w-1/2">
|
||||||
|
|||||||
@@ -1,19 +1,15 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { SystemDictDataApi } from '#/api/system/dict/data';
|
import type { SystemDictDataApi } from '#/api/system/dict/data';
|
||||||
|
|
||||||
import { watch } from 'vue';
|
import { watch } from 'vue';
|
||||||
|
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
import { Download, Plus } from '@vben/icons';
|
|
||||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||||
|
|
||||||
import { Button, message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import {
|
import {
|
||||||
deleteDictData,
|
deleteDictData,
|
||||||
exportDictData,
|
exportDictData,
|
||||||
@@ -42,33 +38,32 @@ function onRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 导出表格 */
|
/** 导出表格 */
|
||||||
async function onExport() {
|
async function handleExport() {
|
||||||
const data = await exportDictData(await gridApi.formApi.getValues());
|
const data = await exportDictData(await gridApi.formApi.getValues());
|
||||||
downloadFileFromBlobPart({ fileName: '字典数据.xls', source: data });
|
downloadFileFromBlobPart({ fileName: '字典数据.xls', source: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 创建字典数据 */
|
/** 创建字典数据 */
|
||||||
function onCreate() {
|
function handleCreate() {
|
||||||
dataFormModalApi.setData({ dictType: props.dictType }).open();
|
dataFormModalApi.setData({ dictType: props.dictType }).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编辑字典数据 */
|
/** 编辑字典数据 */
|
||||||
function onEdit(row: any) {
|
function handleEdit(row: SystemDictDataApi.DictData) {
|
||||||
dataFormModalApi.setData(row).open();
|
dataFormModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除字典数据 */
|
/** 删除字典数据 */
|
||||||
async function onDelete(row: any) {
|
async function handleDelete(row: SystemDictDataApi.DictData) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('common.processing'),
|
content: $t('ui.actionMessage.deleting', [row.label]),
|
||||||
duration: 0,
|
key: 'action_key_msg',
|
||||||
key: 'process_message',
|
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteDictData(row.id);
|
await deleteDictData(row.id as number);
|
||||||
message.success({
|
message.success({
|
||||||
content: $t('common.operationSuccess'),
|
content: $t('ui.actionMessage.deleteSuccess', [row.label]),
|
||||||
key: 'process_message',
|
key: 'action_key_msg',
|
||||||
});
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
@@ -76,26 +71,12 @@ async function onDelete(row: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表格操作按钮回调 */
|
|
||||||
function onActionClick({ code, row }: OnActionClickParams) {
|
|
||||||
switch (code) {
|
|
||||||
case 'delete': {
|
|
||||||
onDelete(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'edit': {
|
|
||||||
onEdit(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useDataGridFormSchema(),
|
schema: useDataGridFormSchema(),
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useDataGridColumns(onActionClick),
|
columns: useDataGridColumns(),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
@@ -137,23 +118,48 @@ watch(
|
|||||||
|
|
||||||
<Grid table-title="字典数据列表">
|
<Grid table-title="字典数据列表">
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<Button
|
<TableAction
|
||||||
type="primary"
|
:actions="[
|
||||||
@click="onCreate"
|
{
|
||||||
v-access:code="['system:dict:create']"
|
label: $t('ui.actionTitle.create', ['字典数据']),
|
||||||
>
|
type: 'primary',
|
||||||
<Plus class="size-5" />
|
icon: ACTION_ICON.ADD,
|
||||||
{{ $t('ui.actionTitle.create', ['字典数据']) }}
|
auth: ['system:dict:create'],
|
||||||
</Button>
|
onClick: handleCreate,
|
||||||
<Button
|
},
|
||||||
type="primary"
|
{
|
||||||
class="ml-2"
|
label: $t('ui.actionTitle.export'),
|
||||||
@click="onExport"
|
type: 'primary',
|
||||||
v-access:code="['system:dict:export']"
|
icon: ACTION_ICON.DOWNLOAD,
|
||||||
>
|
auth: ['system:dict:export'],
|
||||||
<Download class="size-5" />
|
onClick: handleExport,
|
||||||
{{ $t('ui.actionTitle.export') }}
|
},
|
||||||
</Button>
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: $t('common.edit'),
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.EDIT,
|
||||||
|
auth: ['system:dict:update'],
|
||||||
|
onClick: handleEdit.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('common.delete'),
|
||||||
|
type: 'link',
|
||||||
|
danger: true,
|
||||||
|
icon: ACTION_ICON.DELETE,
|
||||||
|
auth: ['system:dict:delete'],
|
||||||
|
popConfirm: {
|
||||||
|
title: $t('ui.actionMessage.deleteConfirm', [row.label]),
|
||||||
|
confirm: handleDelete.bind(null, row),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Grid>
|
</Grid>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,18 +1,16 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type {
|
||||||
OnActionClickParams,
|
|
||||||
VxeGridListeners,
|
VxeGridListeners,
|
||||||
VxeTableGridOptions,
|
VxeTableGridOptions,
|
||||||
} from '#/adapter/vxe-table';
|
} from '#/adapter/vxe-table';
|
||||||
import type { SystemDictTypeApi } from '#/api/system/dict/type';
|
import type { SystemDictTypeApi } from '#/api/system/dict/type';
|
||||||
|
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
import { Download, Plus } from '@vben/icons';
|
|
||||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||||
|
|
||||||
import { Button, message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import {
|
import {
|
||||||
deleteDictType,
|
deleteDictType,
|
||||||
exportDictType,
|
exportDictType,
|
||||||
@@ -36,33 +34,32 @@ function onRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 导出表格 */
|
/** 导出表格 */
|
||||||
async function onExport() {
|
async function handleExport() {
|
||||||
const data = await exportDictType(await gridApi.formApi.getValues());
|
const data = await exportDictType(await gridApi.formApi.getValues());
|
||||||
downloadFileFromBlobPart({ fileName: '字典类型.xls', source: data });
|
downloadFileFromBlobPart({ fileName: '字典类型.xls', source: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 创建字典类型 */
|
/** 创建字典类型 */
|
||||||
function onCreate() {
|
function handleCreate() {
|
||||||
typeFormModalApi.setData(null).open();
|
typeFormModalApi.setData(null).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编辑字典类型 */
|
/** 编辑字典类型 */
|
||||||
function onEdit(row: any) {
|
function handleEdit(row: any) {
|
||||||
typeFormModalApi.setData(row).open();
|
typeFormModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除字典类型 */
|
/** 删除字典类型 */
|
||||||
async function onDelete(row: SystemDictTypeApi.DictType) {
|
async function handleDelete(row: SystemDictTypeApi.DictType) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('common.processing'),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
duration: 0,
|
key: 'action_key_msg',
|
||||||
key: 'process_message',
|
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteDictType(row.id as number);
|
await deleteDictType(row.id as number);
|
||||||
message.success({
|
message.success({
|
||||||
content: $t('common.operationSuccess'),
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
key: 'process_message',
|
key: 'action_key_msg',
|
||||||
});
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
@@ -70,23 +67,6 @@ async function onDelete(row: SystemDictTypeApi.DictType) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表格操作按钮回调 */
|
|
||||||
function onActionClick({
|
|
||||||
code,
|
|
||||||
row,
|
|
||||||
}: OnActionClickParams<SystemDictTypeApi.DictType>) {
|
|
||||||
switch (code) {
|
|
||||||
case 'delete': {
|
|
||||||
onDelete(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'edit': {
|
|
||||||
onEdit(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 表格事件 */
|
/** 表格事件 */
|
||||||
const gridEvents: VxeGridListeners<SystemDictTypeApi.DictType> = {
|
const gridEvents: VxeGridListeners<SystemDictTypeApi.DictType> = {
|
||||||
cellClick: ({ row }) => {
|
cellClick: ({ row }) => {
|
||||||
@@ -99,7 +79,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
schema: useTypeGridFormSchema(),
|
schema: useTypeGridFormSchema(),
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useTypeGridColumns(onActionClick),
|
columns: useTypeGridColumns(),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
@@ -132,23 +112,48 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
|
|
||||||
<Grid table-title="字典类型列表">
|
<Grid table-title="字典类型列表">
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<Button
|
<TableAction
|
||||||
type="primary"
|
:actions="[
|
||||||
@click="onCreate"
|
{
|
||||||
v-access:code="['system:dict:create']"
|
label: $t('ui.actionTitle.create', ['字典类型']),
|
||||||
>
|
type: 'primary',
|
||||||
<Plus class="size-5" />
|
icon: ACTION_ICON.ADD,
|
||||||
{{ $t('ui.actionTitle.create', ['字典类型']) }}
|
auth: ['system:dict:create'],
|
||||||
</Button>
|
onClick: handleCreate,
|
||||||
<Button
|
},
|
||||||
type="primary"
|
{
|
||||||
class="ml-2"
|
label: $t('ui.actionTitle.export'),
|
||||||
@click="onExport"
|
type: 'primary',
|
||||||
v-access:code="['system:dict:export']"
|
icon: ACTION_ICON.DOWNLOAD,
|
||||||
>
|
auth: ['system:dict:export'],
|
||||||
<Download class="size-5" />
|
onClick: handleExport,
|
||||||
{{ $t('ui.actionTitle.export') }}
|
},
|
||||||
</Button>
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: $t('common.edit'),
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.EDIT,
|
||||||
|
auth: ['system:dict:update'],
|
||||||
|
onClick: handleEdit.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('common.delete'),
|
||||||
|
type: 'link',
|
||||||
|
danger: true,
|
||||||
|
icon: ACTION_ICON.DELETE,
|
||||||
|
auth: ['system:dict:delete'],
|
||||||
|
popConfirm: {
|
||||||
|
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||||
|
confirm: handleDelete.bind(null, row),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Grid>
|
</Grid>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { SystemLoginLogApi } from '#/api/system/login-log';
|
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
|
||||||
|
|
||||||
import { DICT_TYPE, getRangePickerDefaultProps } from '#/utils';
|
import { DICT_TYPE, getRangePickerDefaultProps } from '#/utils';
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
|
||||||
|
|
||||||
/** 列表的搜索表单 */
|
/** 列表的搜索表单 */
|
||||||
export function useGridFormSchema(): VbenFormSchema[] {
|
export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
@@ -42,9 +37,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns<T = SystemLoginLogApi.LoginLog>(
|
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
onActionClick: OnActionClickFn<T>,
|
|
||||||
): VxeTableGridOptions['columns'] {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
field: 'id',
|
field: 'id',
|
||||||
@@ -91,26 +84,10 @@ export function useGridColumns<T = SystemLoginLogApi.LoginLog>(
|
|||||||
formatter: 'formatDateTime',
|
formatter: 'formatDateTime',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'operation',
|
|
||||||
title: '操作',
|
title: '操作',
|
||||||
minWidth: 120,
|
width: 80,
|
||||||
align: 'center',
|
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
cellRender: {
|
slots: { default: 'actions' },
|
||||||
attrs: {
|
|
||||||
nameField: 'username',
|
|
||||||
nameTitle: '登录日志',
|
|
||||||
onClick: onActionClick,
|
|
||||||
},
|
|
||||||
name: 'CellOperation',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
code: 'detail',
|
|
||||||
text: '详情',
|
|
||||||
show: hasAccessByCodes(['system:login-log:query']),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { SystemLoginLogApi } from '#/api/system/login-log';
|
import type { SystemLoginLogApi } from '#/api/system/login-log';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { Download } from '@vben/icons';
|
|
||||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||||
|
|
||||||
import { Button } from 'ant-design-vue';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
|
||||||
import { exportLoginLog, getLoginLogPage } from '#/api/system/login-log';
|
import { exportLoginLog, getLoginLogPage } from '#/api/system/login-log';
|
||||||
import { DocAlert } from '#/components/doc-alert';
|
import { DocAlert } from '#/components/doc-alert';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
@@ -30,35 +24,22 @@ function onRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 导出表格 */
|
/** 导出表格 */
|
||||||
async function onExport() {
|
async function handleExport() {
|
||||||
const data = await exportLoginLog(await gridApi.formApi.getValues());
|
const data = await exportLoginLog(await gridApi.formApi.getValues());
|
||||||
downloadFileFromBlobPart({ fileName: '登录日志.xls', source: data });
|
downloadFileFromBlobPart({ fileName: '登录日志.xls', source: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 查看登录日志详情 */
|
/** 查看登录日志详情 */
|
||||||
function onDetail(row: SystemLoginLogApi.LoginLog) {
|
function handleDetail(row: SystemLoginLogApi.LoginLog) {
|
||||||
detailModalApi.setData(row).open();
|
detailModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表格操作按钮的回调函数 */
|
|
||||||
function onActionClick({
|
|
||||||
code,
|
|
||||||
row,
|
|
||||||
}: OnActionClickParams<SystemLoginLogApi.LoginLog>) {
|
|
||||||
switch (code) {
|
|
||||||
case 'detail': {
|
|
||||||
onDetail(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(onActionClick),
|
columns: useGridColumns(),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
@@ -92,15 +73,30 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
<DetailModal @success="onRefresh" />
|
<DetailModal @success="onRefresh" />
|
||||||
<Grid table-title="登录日志列表">
|
<Grid table-title="登录日志列表">
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<Button
|
<TableAction
|
||||||
type="primary"
|
:actions="[
|
||||||
class="ml-2"
|
{
|
||||||
@click="onExport"
|
label: $t('ui.actionTitle.export'),
|
||||||
v-access:code="['system:login-log:export']"
|
type: 'primary',
|
||||||
>
|
icon: ACTION_ICON.DOWNLOAD,
|
||||||
<Download class="size-5" />
|
auth: ['system:login-log:export'],
|
||||||
{{ $t('ui.actionTitle.export') }}
|
onClick: handleExport,
|
||||||
</Button>
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: $t('common.detail'),
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.VIEW,
|
||||||
|
auth: ['system:login-log:query'],
|
||||||
|
onClick: handleDetail.bind(null, row),
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { SystemMailAccountApi } from '#/api/system/mail/account';
|
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
|
||||||
|
|
||||||
import { z } from '#/adapter/form';
|
import { z } from '#/adapter/form';
|
||||||
import { DICT_TYPE, getDictOptions } from '#/utils';
|
import { DICT_TYPE, getDictOptions } from '#/utils';
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
|
||||||
|
|
||||||
/** 新增/修改的表单 */
|
/** 新增/修改的表单 */
|
||||||
export function useFormSchema(): VbenFormSchema[] {
|
export function useFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
@@ -125,9 +120,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns<T = SystemMailAccountApi.MailAccount>(
|
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
onActionClick: OnActionClickFn<T>,
|
|
||||||
): VxeTableGridOptions['columns'] {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
field: 'id',
|
field: 'id',
|
||||||
@@ -179,29 +172,10 @@ export function useGridColumns<T = SystemMailAccountApi.MailAccount>(
|
|||||||
formatter: 'formatDateTime',
|
formatter: 'formatDateTime',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'operation',
|
|
||||||
title: '操作',
|
title: '操作',
|
||||||
minWidth: 130,
|
width: 130,
|
||||||
align: 'center',
|
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
cellRender: {
|
slots: { default: 'actions' },
|
||||||
attrs: {
|
|
||||||
nameField: 'mail',
|
|
||||||
nameTitle: '邮箱账号',
|
|
||||||
onClick: onActionClick,
|
|
||||||
},
|
|
||||||
name: 'CellOperation',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
code: 'edit',
|
|
||||||
show: hasAccessByCodes(['system:mail-account:update']),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'delete',
|
|
||||||
show: hasAccessByCodes(['system:mail-account:delete']),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { SystemMailAccountApi } from '#/api/system/mail/account';
|
import type { SystemMailAccountApi } from '#/api/system/mail/account';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { Plus } from '@vben/icons';
|
|
||||||
|
|
||||||
import { Button, message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import {
|
import {
|
||||||
deleteMailAccount,
|
deleteMailAccount,
|
||||||
getMailAccountPage,
|
getMailAccountPage,
|
||||||
@@ -32,54 +28,39 @@ function onRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 创建邮箱账号 */
|
/** 创建邮箱账号 */
|
||||||
function onCreate() {
|
function handleCreate() {
|
||||||
formModalApi.setData(null).open();
|
formModalApi.setData(null).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编辑邮箱账号 */
|
/** 编辑邮箱账号 */
|
||||||
function onEdit(row: SystemMailAccountApi.MailAccount) {
|
function handleEdit(row: SystemMailAccountApi.MailAccount) {
|
||||||
formModalApi.setData(row).open();
|
formModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除邮箱账号 */
|
/** 删除邮箱账号 */
|
||||||
async function onDelete(row: SystemMailAccountApi.MailAccount) {
|
async function handleDelete(row: SystemMailAccountApi.MailAccount) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.mail]),
|
content: $t('ui.actionMessage.deleting', [row.mail]),
|
||||||
duration: 0,
|
key: 'action_key_msg',
|
||||||
key: 'action_process_msg',
|
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteMailAccount(row.id as number);
|
await deleteMailAccount(row.id as number);
|
||||||
message.success($t('ui.actionMessage.deleteSuccess', [row.mail]));
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.mail]),
|
||||||
|
key: 'action_key_msg',
|
||||||
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表格操作按钮的回调函数 */
|
|
||||||
function onActionClick({
|
|
||||||
code,
|
|
||||||
row,
|
|
||||||
}: OnActionClickParams<SystemMailAccountApi.MailAccount>) {
|
|
||||||
switch (code) {
|
|
||||||
case 'delete': {
|
|
||||||
onDelete(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'edit': {
|
|
||||||
onEdit(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(onActionClick),
|
columns: useGridColumns(),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
@@ -112,14 +93,41 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
<FormModal @success="onRefresh" />
|
<FormModal @success="onRefresh" />
|
||||||
<Grid table-title="邮箱账号列表">
|
<Grid table-title="邮箱账号列表">
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<Button
|
<TableAction
|
||||||
type="primary"
|
:actions="[
|
||||||
@click="onCreate"
|
{
|
||||||
v-access:code="['system:mail-account:create']"
|
label: $t('ui.actionTitle.create', ['邮箱账号']),
|
||||||
>
|
type: 'primary',
|
||||||
<Plus class="size-5" />
|
icon: ACTION_ICON.ADD,
|
||||||
{{ $t('ui.actionTitle.create', ['邮箱账号']) }}
|
auth: ['system:mail-account:create'],
|
||||||
</Button>
|
onClick: handleCreate,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: $t('common.edit'),
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.EDIT,
|
||||||
|
auth: ['system:mail-account:update'],
|
||||||
|
onClick: handleEdit.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('common.delete'),
|
||||||
|
type: 'link',
|
||||||
|
danger: true,
|
||||||
|
icon: ACTION_ICON.DELETE,
|
||||||
|
auth: ['system:mail-account:delete'],
|
||||||
|
popConfirm: {
|
||||||
|
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||||
|
confirm: handleDelete.bind(null, row),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { SystemMailLogApi } from '#/api/system/mail/log';
|
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
|
||||||
|
|
||||||
import { getSimpleMailAccountList } from '#/api/system/mail/account';
|
import { getSimpleMailAccountList } from '#/api/system/mail/account';
|
||||||
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
|
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
|
||||||
|
|
||||||
/** 列表的搜索表单 */
|
/** 列表的搜索表单 */
|
||||||
export function useGridFormSchema(): VbenFormSchema[] {
|
export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
@@ -75,9 +70,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns<T = SystemMailLogApi.MailLog>(
|
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
onActionClick: OnActionClickFn<T>,
|
|
||||||
): VxeTableGridOptions['columns'] {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
field: 'id',
|
field: 'id',
|
||||||
@@ -125,26 +118,10 @@ export function useGridColumns<T = SystemMailLogApi.MailLog>(
|
|||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'operation',
|
|
||||||
title: '操作',
|
title: '操作',
|
||||||
minWidth: 80,
|
width: 80,
|
||||||
align: 'center',
|
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
cellRender: {
|
slots: { default: 'actions' },
|
||||||
attrs: {
|
|
||||||
nameField: 'toMail',
|
|
||||||
nameTitle: '邮件日志',
|
|
||||||
onClick: onActionClick,
|
|
||||||
},
|
|
||||||
name: 'CellOperation',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
code: 'detail',
|
|
||||||
text: '查看',
|
|
||||||
show: hasAccessByCodes(['system:mail-log:query']),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { SystemMailLogApi } from '#/api/system/mail/log';
|
import type { SystemMailLogApi } from '#/api/system/mail/log';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { getMailLogPage } from '#/api/system/mail/log';
|
import { getMailLogPage } from '#/api/system/mail/log';
|
||||||
import { DocAlert } from '#/components/doc-alert';
|
import { DocAlert } from '#/components/doc-alert';
|
||||||
|
|
||||||
@@ -25,29 +22,16 @@ function onRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 查看邮件日志 */
|
/** 查看邮件日志 */
|
||||||
function onDetail(row: SystemMailLogApi.MailLog) {
|
function handleDetail(row: SystemMailLogApi.MailLog) {
|
||||||
detailModalApi.setData(row).open();
|
detailModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表格操作按钮的回调函数 */
|
|
||||||
function onActionClick({
|
|
||||||
code,
|
|
||||||
row,
|
|
||||||
}: OnActionClickParams<SystemMailLogApi.MailLog>) {
|
|
||||||
switch (code) {
|
|
||||||
case 'detail': {
|
|
||||||
onDetail(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(onActionClick),
|
columns: useGridColumns(),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
@@ -79,7 +63,19 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
|
|
||||||
<DetailModal @success="onRefresh" />
|
<DetailModal @success="onRefresh" />
|
||||||
<Grid table-title="邮件日志列表">
|
<Grid table-title="邮件日志列表">
|
||||||
<template #toolbar-tools> </template>
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: $t('common.detail'),
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.VIEW,
|
||||||
|
auth: ['system:mail-log:query'],
|
||||||
|
onClick: handleDetail.bind(null, row),
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { SystemMailTemplateApi } from '#/api/system/mail/template';
|
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
|
||||||
|
|
||||||
import { z } from '#/adapter/form';
|
import { z } from '#/adapter/form';
|
||||||
import { getSimpleMailAccountList } from '#/api/system/mail/account';
|
import { getSimpleMailAccountList } from '#/api/system/mail/account';
|
||||||
@@ -13,8 +10,6 @@ import {
|
|||||||
getRangePickerDefaultProps,
|
getRangePickerDefaultProps,
|
||||||
} from '#/utils';
|
} from '#/utils';
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
|
||||||
|
|
||||||
/** 新增/修改的表单 */
|
/** 新增/修改的表单 */
|
||||||
export function useFormSchema(): VbenFormSchema[] {
|
export function useFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
@@ -193,8 +188,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns<T = SystemMailTemplateApi.MailTemplate>(
|
export function useGridColumns(
|
||||||
onActionClick: OnActionClickFn<T>,
|
|
||||||
getAccountMail?: (accountId: number) => string | undefined,
|
getAccountMail?: (accountId: number) => string | undefined,
|
||||||
): VxeTableGridOptions['columns'] {
|
): VxeTableGridOptions['columns'] {
|
||||||
return [
|
return [
|
||||||
@@ -245,34 +239,10 @@ export function useGridColumns<T = SystemMailTemplateApi.MailTemplate>(
|
|||||||
formatter: 'formatDateTime',
|
formatter: 'formatDateTime',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'operation',
|
|
||||||
title: '操作',
|
title: '操作',
|
||||||
minWidth: 150,
|
width: 220,
|
||||||
align: 'center',
|
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
cellRender: {
|
slots: { default: 'actions' },
|
||||||
attrs: {
|
|
||||||
nameField: 'name',
|
|
||||||
nameTitle: '邮件模板',
|
|
||||||
onClick: onActionClick,
|
|
||||||
},
|
|
||||||
name: 'CellOperation',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
code: 'edit',
|
|
||||||
show: hasAccessByCodes(['system:mail-template:update']),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'delete',
|
|
||||||
show: hasAccessByCodes(['system:mail-template:delete']),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'send',
|
|
||||||
text: '测试',
|
|
||||||
show: hasAccessByCodes(['system:mail-template:send-mail']),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,15 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { SystemMailAccountApi } from '#/api/system/mail/account';
|
import type { SystemMailAccountApi } from '#/api/system/mail/account';
|
||||||
import type { SystemMailTemplateApi } from '#/api/system/mail/template';
|
import type { SystemMailTemplateApi } from '#/api/system/mail/template';
|
||||||
|
|
||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { Plus } from '@vben/icons';
|
|
||||||
|
|
||||||
import { Button, message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { getSimpleMailAccountList } from '#/api/system/mail/account';
|
import { getSimpleMailAccountList } from '#/api/system/mail/account';
|
||||||
import {
|
import {
|
||||||
deleteMailTemplate,
|
deleteMailTemplate,
|
||||||
@@ -28,11 +24,6 @@ import SendForm from './modules/send-form.vue';
|
|||||||
|
|
||||||
const accountList = ref<SystemMailAccountApi.MailAccount[]>([]);
|
const accountList = ref<SystemMailAccountApi.MailAccount[]>([]);
|
||||||
|
|
||||||
/** 获取邮箱账号 */
|
|
||||||
const getAccountMail = (accountId: number) => {
|
|
||||||
return accountList.value.find((account) => account.id === accountId)?.mail;
|
|
||||||
};
|
|
||||||
|
|
||||||
const [FormModal, formModalApi] = useVbenModal({
|
const [FormModal, formModalApi] = useVbenModal({
|
||||||
connectedComponent: Form,
|
connectedComponent: Form,
|
||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
@@ -49,55 +40,38 @@ function onRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 创建邮件模板 */
|
/** 创建邮件模板 */
|
||||||
function onCreate() {
|
function handleCreate() {
|
||||||
formModalApi.setData(null).open();
|
formModalApi.setData(null).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编辑邮件模板 */
|
/** 编辑邮件模板 */
|
||||||
function onEdit(row: SystemMailTemplateApi.MailTemplate) {
|
function handleEdit(row: SystemMailTemplateApi.MailTemplate) {
|
||||||
formModalApi.setData(row).open();
|
formModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 发送测试邮件 */
|
/** 发送测试邮件 */
|
||||||
function onSend(row: SystemMailTemplateApi.MailTemplate) {
|
function handleSend(row: SystemMailTemplateApi.MailTemplate) {
|
||||||
sendModalApi.setData(row).open();
|
sendModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除邮件模板 */
|
/** 删除邮件模板 */
|
||||||
async function onDelete(row: SystemMailTemplateApi.MailTemplate) {
|
async function handleDelete(row: SystemMailTemplateApi.MailTemplate) {
|
||||||
const hideLoading = message.loading({
|
message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
duration: 0,
|
duration: 0,
|
||||||
key: 'action_process_msg',
|
key: 'action_process_msg',
|
||||||
});
|
});
|
||||||
try {
|
await deleteMailTemplate(row.id as number);
|
||||||
await deleteMailTemplate(row.id as number);
|
message.success({
|
||||||
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
onRefresh();
|
key: 'action_key_msg',
|
||||||
} finally {
|
});
|
||||||
hideLoading();
|
onRefresh();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表格操作按钮的回调函数 */
|
/** 获取邮箱账号 */
|
||||||
function onActionClick({
|
function getAccountMail(accountId: number) {
|
||||||
code,
|
return accountList.value.find((account) => account.id === accountId)?.mail;
|
||||||
row,
|
|
||||||
}: OnActionClickParams<SystemMailTemplateApi.MailTemplate>) {
|
|
||||||
switch (code) {
|
|
||||||
case 'delete': {
|
|
||||||
onDelete(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'edit': {
|
|
||||||
onEdit(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'send': {
|
|
||||||
onSend(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
@@ -105,7 +79,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(onActionClick, getAccountMail),
|
columns: useGridColumns(getAccountMail),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
@@ -144,14 +118,48 @@ onMounted(async () => {
|
|||||||
<SendModal />
|
<SendModal />
|
||||||
<Grid table-title="邮件模板列表">
|
<Grid table-title="邮件模板列表">
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<Button
|
<TableAction
|
||||||
type="primary"
|
:actions="[
|
||||||
@click="onCreate"
|
{
|
||||||
v-access:code="['system:mail-template:create']"
|
label: $t('ui.actionTitle.create', ['邮件模板']),
|
||||||
>
|
type: 'primary',
|
||||||
<Plus class="size-5" />
|
icon: ACTION_ICON.ADD,
|
||||||
{{ $t('ui.actionTitle.create', ['邮件模板']) }}
|
auth: ['system:mail-template:create'],
|
||||||
</Button>
|
onClick: handleCreate,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: $t('common.edit'),
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.EDIT,
|
||||||
|
auth: ['system:mail-template:update'],
|
||||||
|
onClick: handleEdit.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '测试',
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.EDIT,
|
||||||
|
auth: ['system:mail-template:send-mail'],
|
||||||
|
onClick: handleSend.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('common.delete'),
|
||||||
|
type: 'link',
|
||||||
|
danger: true,
|
||||||
|
icon: ACTION_ICON.DELETE,
|
||||||
|
auth: ['system:mail-template:delete'],
|
||||||
|
popConfirm: {
|
||||||
|
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||||
|
confirm: handleDelete.bind(null, row),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
});
|
});
|
||||||
|
|
||||||
/** 动态构建表单 schema */
|
/** 动态构建表单 schema */
|
||||||
const buildFormSchema = () => {
|
function buildFormSchema() {
|
||||||
const schema = useSendMailFormSchema();
|
const schema = useSendMailFormSchema();
|
||||||
if (formData.value?.params) {
|
if (formData.value?.params) {
|
||||||
formData.value.params?.forEach((param: string) => {
|
formData.value.params?.forEach((param: string) => {
|
||||||
@@ -99,7 +99,7 @@ const buildFormSchema = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
return schema;
|
return schema;
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import type { Recordable } from '@vben/types';
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { SystemMenuApi } from '#/api/system/menu';
|
import type { SystemMenuApi } from '#/api/system/menu';
|
||||||
|
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
|
||||||
import { IconifyIcon } from '@vben/icons';
|
import { IconifyIcon } from '@vben/icons';
|
||||||
import { handleTree, isHttpUrl } from '@vben/utils';
|
import { handleTree, isHttpUrl } from '@vben/utils';
|
||||||
|
|
||||||
@@ -21,8 +20,6 @@ import {
|
|||||||
SystemMenuTypeEnum,
|
SystemMenuTypeEnum,
|
||||||
} from '#/utils';
|
} from '#/utils';
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
|
||||||
|
|
||||||
/** 新增/修改的表单 */
|
/** 新增/修改的表单 */
|
||||||
export function useFormSchema(): VbenFormSchema[] {
|
export function useFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
@@ -269,9 +266,7 @@ export function useFormSchema(): VbenFormSchema[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns(
|
export function useGridColumns(): VxeTableGridOptions<SystemMenuApi.Menu>['columns'] {
|
||||||
onActionClick: OnActionClickFn<SystemMenuApi.Menu>,
|
|
||||||
): VxeTableGridOptions<SystemMenuApi.Menu>['columns'] {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
field: 'name',
|
field: 'name',
|
||||||
@@ -321,34 +316,10 @@ export function useGridColumns(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'operation',
|
|
||||||
title: '操作',
|
title: '操作',
|
||||||
minWidth: 200,
|
width: 220,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
align: 'center',
|
slots: { default: 'actions' },
|
||||||
showOverflow: false,
|
|
||||||
cellRender: {
|
|
||||||
attrs: {
|
|
||||||
nameField: 'name',
|
|
||||||
onClick: onActionClick,
|
|
||||||
},
|
|
||||||
name: 'CellOperation',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
code: 'append',
|
|
||||||
text: '新增下级',
|
|
||||||
show: hasAccessByCodes(['system:menu:create']),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'edit',
|
|
||||||
show: hasAccessByCodes(['system:menu:update']),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'delete',
|
|
||||||
show: hasAccessByCodes(['system:menu:delete']),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { SystemMenuApi } from '#/api/system/menu';
|
import type { SystemMenuApi } from '#/api/system/menu';
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { IconifyIcon, Plus } from '@vben/icons';
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
|
||||||
import { Button, message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { deleteMenu, getMenuList } from '#/api/system/menu';
|
import { deleteMenu, getMenuList } from '#/api/system/menu';
|
||||||
import { DocAlert } from '#/components/doc-alert';
|
import { DocAlert } from '#/components/doc-alert';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
@@ -32,54 +29,38 @@ function onRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 创建菜单 */
|
/** 创建菜单 */
|
||||||
function onCreate() {
|
function handleCreate() {
|
||||||
formModalApi.setData({}).open();
|
formModalApi.setData({}).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 添加下级菜单 */
|
/** 添加下级菜单 */
|
||||||
function onAppend(row: SystemMenuApi.Menu) {
|
function handleAppend(row: SystemMenuApi.Menu) {
|
||||||
formModalApi.setData({ pid: row.id }).open();
|
formModalApi.setData({ pid: row.id }).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编辑菜单 */
|
/** 编辑菜单 */
|
||||||
function onEdit(row: SystemMenuApi.Menu) {
|
function handleEdit(row: SystemMenuApi.Menu) {
|
||||||
formModalApi.setData(row).open();
|
formModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除菜单 */
|
/** 删除菜单 */
|
||||||
async function onDelete(row: SystemMenuApi.Menu) {
|
async function handleDelete(row: SystemMenuApi.Menu) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
duration: 0,
|
key: 'action_key_msg',
|
||||||
key: 'action_process_msg',
|
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteMenu(row.id as number);
|
await deleteMenu(row.id as number);
|
||||||
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
|
key: 'action_key_msg',
|
||||||
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} catch {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表格操作按钮的回调函数 */
|
|
||||||
function onActionClick({ code, row }: OnActionClickParams<SystemMenuApi.Menu>) {
|
|
||||||
switch (code) {
|
|
||||||
case 'append': {
|
|
||||||
onAppend(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'delete': {
|
|
||||||
onDelete(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'edit': {
|
|
||||||
onEdit(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 切换树形展开/收缩状态 */
|
/** 切换树形展开/收缩状态 */
|
||||||
const isExpanded = ref(false);
|
const isExpanded = ref(false);
|
||||||
function toggleExpand() {
|
function toggleExpand() {
|
||||||
@@ -89,7 +70,7 @@ function toggleExpand() {
|
|||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(onActionClick),
|
columns: useGridColumns(),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
pagerConfig: {
|
pagerConfig: {
|
||||||
@@ -131,17 +112,22 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
<FormModal @success="onRefresh" />
|
<FormModal @success="onRefresh" />
|
||||||
<Grid>
|
<Grid>
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<Button
|
<TableAction
|
||||||
type="primary"
|
:actions="[
|
||||||
@click="onCreate"
|
{
|
||||||
v-access:code="['system:menu:create']"
|
label: $t('ui.actionTitle.create', ['菜单']),
|
||||||
>
|
type: 'primary',
|
||||||
<Plus class="size-5" />
|
icon: ACTION_ICON.ADD,
|
||||||
{{ $t('ui.actionTitle.create', ['菜单']) }}
|
auth: ['system:menu:create'],
|
||||||
</Button>
|
onClick: handleCreate,
|
||||||
<Button class="ml-2" @click="toggleExpand">
|
},
|
||||||
{{ isExpanded ? '收缩' : '展开' }}
|
{
|
||||||
</Button>
|
label: isExpanded ? '收缩' : '展开',
|
||||||
|
type: 'primary',
|
||||||
|
onClick: toggleExpand,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #name="{ row }">
|
<template #name="{ row }">
|
||||||
<div class="flex w-full items-center gap-1">
|
<div class="flex w-full items-center gap-1">
|
||||||
@@ -161,6 +147,37 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
<div class="items-center justify-end"></div>
|
<div class="items-center justify-end"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: '新增下级',
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.ADD,
|
||||||
|
auth: ['system:menu:create'],
|
||||||
|
onClick: handleAppend.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('common.edit'),
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.EDIT,
|
||||||
|
auth: ['system:menu:update'],
|
||||||
|
onClick: handleEdit.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('common.delete'),
|
||||||
|
type: 'link',
|
||||||
|
danger: true,
|
||||||
|
icon: ACTION_ICON.DELETE,
|
||||||
|
auth: ['system:menu:delete'],
|
||||||
|
popConfirm: {
|
||||||
|
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||||
|
confirm: handleDelete.bind(null, row),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { SystemNoticeApi } from '#/api/system/notice';
|
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
|
||||||
|
|
||||||
import { z } from '#/adapter/form';
|
import { z } from '#/adapter/form';
|
||||||
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
|
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
|
||||||
|
|
||||||
/** 新增/修改的表单 */
|
/** 新增/修改的表单 */
|
||||||
export function useFormSchema(): VbenFormSchema[] {
|
export function useFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
@@ -91,9 +86,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns<T = SystemNoticeApi.Notice>(
|
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
onActionClick: OnActionClickFn<T>,
|
|
||||||
): VxeTableGridOptions['columns'] {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
field: 'id',
|
field: 'id',
|
||||||
@@ -130,34 +123,10 @@ export function useGridColumns<T = SystemNoticeApi.Notice>(
|
|||||||
formatter: 'formatDateTime',
|
formatter: 'formatDateTime',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'operation',
|
|
||||||
title: '操作',
|
title: '操作',
|
||||||
minWidth: 180,
|
width: 220,
|
||||||
align: 'center',
|
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
cellRender: {
|
slots: { default: 'actions' },
|
||||||
attrs: {
|
|
||||||
nameField: 'title',
|
|
||||||
nameTitle: '公告',
|
|
||||||
onClick: onActionClick,
|
|
||||||
},
|
|
||||||
name: 'CellOperation',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
code: 'edit',
|
|
||||||
show: hasAccessByCodes(['system:notice:update']),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'push',
|
|
||||||
text: '推送',
|
|
||||||
show: hasAccessByCodes(['system:notice:update']),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'delete',
|
|
||||||
show: hasAccessByCodes(['system:notice:delete']),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { SystemNoticeApi } from '#/api/system/notice';
|
import type { SystemNoticeApi } from '#/api/system/notice';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { Plus } from '@vben/icons';
|
|
||||||
|
|
||||||
import { Button, message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { deleteNotice, getNoticePage, pushNotice } from '#/api/system/notice';
|
import { deleteNotice, getNoticePage, pushNotice } from '#/api/system/notice';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
@@ -28,73 +24,56 @@ function onRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 创建公告 */
|
/** 创建公告 */
|
||||||
function onCreate() {
|
function handleCreate() {
|
||||||
formModalApi.setData(null).open();
|
formModalApi.setData(null).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编辑公告 */
|
/** 编辑公告 */
|
||||||
function onEdit(row: SystemNoticeApi.Notice) {
|
function handleEdit(row: SystemNoticeApi.Notice) {
|
||||||
formModalApi.setData(row).open();
|
formModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除公告 */
|
/** 删除公告 */
|
||||||
async function onDelete(row: SystemNoticeApi.Notice) {
|
async function handleDelete(row: SystemNoticeApi.Notice) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.title]),
|
content: $t('ui.actionMessage.deleting', [row.title]),
|
||||||
duration: 0,
|
key: 'action_key_msg',
|
||||||
key: 'action_process_msg',
|
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteNotice(row.id as number);
|
await deleteNotice(row.id as number);
|
||||||
message.success($t('ui.actionMessage.deleteSuccess', [row.title]));
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.title]),
|
||||||
|
key: 'action_key_msg',
|
||||||
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} catch {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 推送公告 */
|
/** 推送公告 */
|
||||||
async function onPush(row: SystemNoticeApi.Notice) {
|
async function handlePush(row: SystemNoticeApi.Notice) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.processing', ['推送']),
|
content: $t('ui.actionMessage.processing', ['推送']),
|
||||||
duration: 0,
|
|
||||||
key: 'action_process_msg',
|
key: 'action_process_msg',
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await pushNotice(row.id as number);
|
await pushNotice(row.id as number);
|
||||||
message.success($t('ui.actionMessage.operationSuccess'));
|
message.success({
|
||||||
} catch {
|
content: $t('ui.actionMessage.operationSuccess'),
|
||||||
|
key: 'action_key_msg',
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表格操作按钮的回调函数 */
|
|
||||||
function onActionClick({
|
|
||||||
code,
|
|
||||||
row,
|
|
||||||
}: OnActionClickParams<SystemNoticeApi.Notice>) {
|
|
||||||
switch (code) {
|
|
||||||
case 'delete': {
|
|
||||||
onDelete(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'edit': {
|
|
||||||
onEdit(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'push': {
|
|
||||||
onPush(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(onActionClick),
|
columns: useGridColumns(),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
@@ -124,14 +103,48 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
<FormModal @success="onRefresh" />
|
<FormModal @success="onRefresh" />
|
||||||
<Grid table-title="公告列表">
|
<Grid table-title="公告列表">
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<Button
|
<TableAction
|
||||||
type="primary"
|
:actions="[
|
||||||
@click="onCreate"
|
{
|
||||||
v-access:code="['system:notice:create']"
|
label: $t('ui.actionTitle.create', ['公告']),
|
||||||
>
|
type: 'primary',
|
||||||
<Plus class="size-5" />
|
icon: ACTION_ICON.ADD,
|
||||||
{{ $t('ui.actionTitle.create', ['公告']) }}
|
auth: ['system:notice:create'],
|
||||||
</Button>
|
onClick: handleCreate,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: $t('common.edit'),
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.EDIT,
|
||||||
|
auth: ['system:notice:update'],
|
||||||
|
onClick: handleEdit.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '推送',
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.ADD,
|
||||||
|
auth: ['system:notice:update'],
|
||||||
|
onClick: handlePush.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('common.delete'),
|
||||||
|
type: 'link',
|
||||||
|
danger: true,
|
||||||
|
icon: ACTION_ICON.DELETE,
|
||||||
|
auth: ['system:notice:delete'],
|
||||||
|
popConfirm: {
|
||||||
|
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||||
|
confirm: handleDelete.bind(null, row),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { SystemNotifyMessageApi } from '#/api/system/notify/message';
|
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
|
||||||
|
|
||||||
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
|
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
|
||||||
|
|
||||||
/** 列表的搜索表单 */
|
/** 列表的搜索表单 */
|
||||||
export function useGridFormSchema(): VbenFormSchema[] {
|
export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
@@ -65,9 +60,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns<T = SystemNotifyMessageApi.NotifyMessage>(
|
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
onActionClick: OnActionClickFn<T>,
|
|
||||||
): VxeTableGridOptions['columns'] {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
field: 'id',
|
field: 'id',
|
||||||
@@ -146,26 +139,10 @@ export function useGridColumns<T = SystemNotifyMessageApi.NotifyMessage>(
|
|||||||
formatter: 'formatDateTime',
|
formatter: 'formatDateTime',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'operation',
|
|
||||||
title: '操作',
|
title: '操作',
|
||||||
minWidth: 180,
|
width: 80,
|
||||||
align: 'center',
|
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
cellRender: {
|
slots: { default: 'actions' },
|
||||||
attrs: {
|
|
||||||
nameField: 'id',
|
|
||||||
nameTitle: '站内信',
|
|
||||||
onClick: onActionClick,
|
|
||||||
},
|
|
||||||
name: 'CellOperation',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
code: 'detail',
|
|
||||||
text: '详情',
|
|
||||||
show: hasAccessByCodes(['system:notify-message:query']),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { SystemNotifyMessageApi } from '#/api/system/notify/message';
|
import type { SystemNotifyMessageApi } from '#/api/system/notify/message';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { getNotifyMessagePage } from '#/api/system/notify/message';
|
import { getNotifyMessagePage } from '#/api/system/notify/message';
|
||||||
import { DocAlert } from '#/components/doc-alert';
|
import { DocAlert } from '#/components/doc-alert';
|
||||||
|
|
||||||
@@ -25,29 +22,16 @@ function onRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 查看站内信详情 */
|
/** 查看站内信详情 */
|
||||||
function onDetail(row: SystemNotifyMessageApi.NotifyMessage) {
|
function handleDetail(row: SystemNotifyMessageApi.NotifyMessage) {
|
||||||
detailModalApi.setData(row).open();
|
detailModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表格操作按钮的回调函数 */
|
|
||||||
function onActionClick({
|
|
||||||
code,
|
|
||||||
row,
|
|
||||||
}: OnActionClickParams<SystemNotifyMessageApi.NotifyMessage>) {
|
|
||||||
switch (code) {
|
|
||||||
case 'detail': {
|
|
||||||
onDetail(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(onActionClick),
|
columns: useGridColumns(),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
@@ -79,6 +63,20 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<DetailModal @success="onRefresh" />
|
<DetailModal @success="onRefresh" />
|
||||||
<Grid table-title="站内信列表" />
|
<Grid table-title="站内信列表">
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: $t('common.detail'),
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.VIEW,
|
||||||
|
auth: ['system:notify-message:query'],
|
||||||
|
onClick: handleDetail.bind(null, row),
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { SystemNotifyMessageApi } from '#/api/system/notify/message';
|
|
||||||
import type { DescriptionItemSchema } from '#/components/description';
|
import type { DescriptionItemSchema } from '#/components/description';
|
||||||
|
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
@@ -36,9 +35,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns<T = SystemNotifyMessageApi.NotifyMessage>(
|
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
onActionClick: OnActionClickFn<T>,
|
|
||||||
): VxeTableGridOptions['columns'] {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
title: '',
|
title: '',
|
||||||
@@ -86,31 +83,10 @@ export function useGridColumns<T = SystemNotifyMessageApi.NotifyMessage>(
|
|||||||
formatter: 'formatDateTime',
|
formatter: 'formatDateTime',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'operation',
|
|
||||||
title: '操作',
|
title: '操作',
|
||||||
minWidth: 180,
|
width: 130,
|
||||||
align: 'center',
|
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
cellRender: {
|
slots: { default: 'actions' },
|
||||||
attrs: {
|
|
||||||
nameField: 'id',
|
|
||||||
nameTitle: '站内信',
|
|
||||||
onClick: onActionClick,
|
|
||||||
},
|
|
||||||
name: 'CellOperation',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
code: 'detail',
|
|
||||||
text: '查看',
|
|
||||||
show: (row: any) => row.readStatus,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'read',
|
|
||||||
text: '已读',
|
|
||||||
show: (row: any) => !row.readStatus,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { SystemNotifyMessageApi } from '#/api/system/notify/message';
|
import type { SystemNotifyMessageApi } from '#/api/system/notify/message';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { MdiCheckboxMarkedCircleOutline } from '@vben/icons';
|
|
||||||
|
|
||||||
import { Button, message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import {
|
import {
|
||||||
getMyNotifyMessagePage,
|
getMyNotifyMessagePage,
|
||||||
updateAllNotifyMessageRead,
|
updateAllNotifyMessageRead,
|
||||||
@@ -32,12 +28,12 @@ function onRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 查看站内信详情 */
|
/** 查看站内信详情 */
|
||||||
function onDetail(row: SystemNotifyMessageApi.NotifyMessage) {
|
function handleDetail(row: SystemNotifyMessageApi.NotifyMessage) {
|
||||||
detailModalApi.setData(row).open();
|
detailModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 标记一条站内信已读 */
|
/** 标记一条站内信已读 */
|
||||||
async function onRead(row: SystemNotifyMessageApi.NotifyMessage) {
|
async function handleRead(row: SystemNotifyMessageApi.NotifyMessage) {
|
||||||
message.loading({
|
message.loading({
|
||||||
content: '正在标记已读...',
|
content: '正在标记已读...',
|
||||||
duration: 0,
|
duration: 0,
|
||||||
@@ -46,15 +42,18 @@ async function onRead(row: SystemNotifyMessageApi.NotifyMessage) {
|
|||||||
// 执行标记已读操作
|
// 执行标记已读操作
|
||||||
await updateNotifyMessageRead([row.id]);
|
await updateNotifyMessageRead([row.id]);
|
||||||
// 提示成功
|
// 提示成功
|
||||||
message.success('标记已读成功');
|
message.success({
|
||||||
|
content: '标记已读成功',
|
||||||
|
key: 'action_process_msg',
|
||||||
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
|
|
||||||
// 打开详情
|
// 打开详情
|
||||||
onDetail(row);
|
handleDetail(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 标记选中的站内信为已读 */
|
/** 标记选中的站内信为已读 */
|
||||||
async function onMarkRead() {
|
async function handleMarkRead() {
|
||||||
const rows = gridApi.grid.getCheckboxRecords();
|
const rows = gridApi.grid.getCheckboxRecords();
|
||||||
if (!rows || rows.length === 0) {
|
if (!rows || rows.length === 0) {
|
||||||
message.warning('请选择需要标记的站内信');
|
message.warning('请选择需要标记的站内信');
|
||||||
@@ -62,48 +61,43 @@ async function onMarkRead() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ids = rows.map((row: SystemNotifyMessageApi.NotifyMessage) => row.id);
|
const ids = rows.map((row: SystemNotifyMessageApi.NotifyMessage) => row.id);
|
||||||
message.loading({
|
const hideLoading = message.loading({
|
||||||
content: '正在标记已读...',
|
content: '正在标记已读...',
|
||||||
duration: 0,
|
key: 'action_key_msg',
|
||||||
key: 'action_process_msg',
|
|
||||||
});
|
});
|
||||||
// 执行标记已读操作
|
try {
|
||||||
await updateNotifyMessageRead(ids);
|
// 执行标记已读操作
|
||||||
// 提示成功
|
await updateNotifyMessageRead(ids);
|
||||||
message.success('标记已读成功');
|
// 提示成功
|
||||||
await gridApi.grid.setAllCheckboxRow(false);
|
message.success({
|
||||||
onRefresh();
|
content: '标记已读成功',
|
||||||
|
key: 'action_key_msg',
|
||||||
|
});
|
||||||
|
await gridApi.grid.setAllCheckboxRow(false);
|
||||||
|
onRefresh();
|
||||||
|
} finally {
|
||||||
|
hideLoading();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 标记所有站内信为已读 */
|
/** 标记所有站内信为已读 */
|
||||||
async function onMarkAllRead() {
|
async function handleMarkAllRead() {
|
||||||
message.loading({
|
const hideLoading = message.loading({
|
||||||
content: '正在标记全部已读...',
|
content: '正在标记全部已读...',
|
||||||
duration: 0,
|
key: 'action_key_msg',
|
||||||
key: 'action_process_msg',
|
|
||||||
});
|
});
|
||||||
// 执行标记已读操作
|
try {
|
||||||
await updateAllNotifyMessageRead();
|
// 执行标记已读操作
|
||||||
// 提示成功
|
await updateAllNotifyMessageRead();
|
||||||
message.success('全部标记已读成功');
|
// 提示成功
|
||||||
await gridApi.grid.setAllCheckboxRow(false);
|
message.success({
|
||||||
onRefresh();
|
content: '全部标记已读成功',
|
||||||
}
|
key: 'action_key_msg',
|
||||||
|
});
|
||||||
/** 表格操作按钮的回调函数 */
|
await gridApi.grid.setAllCheckboxRow(false);
|
||||||
function onActionClick({
|
onRefresh();
|
||||||
code,
|
} finally {
|
||||||
row,
|
hideLoading();
|
||||||
}: OnActionClickParams<SystemNotifyMessageApi.NotifyMessage>) {
|
|
||||||
switch (code) {
|
|
||||||
case 'detail': {
|
|
||||||
onDetail(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'read': {
|
|
||||||
onRead(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +106,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(onActionClick),
|
columns: useGridColumns(),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
@@ -150,14 +144,42 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
<DetailModal @success="onRefresh" />
|
<DetailModal @success="onRefresh" />
|
||||||
<Grid table-title="我的站内信">
|
<Grid table-title="我的站内信">
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<Button type="primary" @click="onMarkRead">
|
<TableAction
|
||||||
<MdiCheckboxMarkedCircleOutline />
|
:actions="[
|
||||||
标记已读
|
{
|
||||||
</Button>
|
label: '标记已读',
|
||||||
<Button type="primary" class="ml-2" @click="onMarkAllRead">
|
type: 'primary',
|
||||||
<MdiCheckboxMarkedCircleOutline />
|
icon: 'mdi:checkbox-marked-circle-outline',
|
||||||
全部已读
|
onClick: handleMarkRead,
|
||||||
</Button>
|
},
|
||||||
|
{
|
||||||
|
label: '全部已读',
|
||||||
|
type: 'primary',
|
||||||
|
icon: 'mdi:checkbox-marked-circle-outline',
|
||||||
|
onClick: handleMarkAllRead,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: '查看',
|
||||||
|
type: 'link',
|
||||||
|
ifShow: row.readStatus,
|
||||||
|
icon: ACTION_ICON.VIEW,
|
||||||
|
onClick: handleDetail.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '已读',
|
||||||
|
type: 'link',
|
||||||
|
ifShow: !row.readStatus,
|
||||||
|
icon: ACTION_ICON.DELETE,
|
||||||
|
onClick: handleRead.bind(null, row),
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { SystemNotifyTemplateApi } from '#/api/system/notify/template';
|
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
|
||||||
|
|
||||||
import { z } from '#/adapter/form';
|
import { z } from '#/adapter/form';
|
||||||
import { getSimpleUserList } from '#/api/system/user';
|
import { getSimpleUserList } from '#/api/system/user';
|
||||||
@@ -14,8 +11,6 @@ import {
|
|||||||
UserTypeEnum,
|
UserTypeEnum,
|
||||||
} from '#/utils';
|
} from '#/utils';
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
|
||||||
|
|
||||||
/** 新增/修改的表单 */
|
/** 新增/修改的表单 */
|
||||||
export function useFormSchema(): VbenFormSchema[] {
|
export function useFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
@@ -229,9 +224,7 @@ export function useSendNotifyFormSchema(): VbenFormSchema[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns<T = SystemNotifyTemplateApi.NotifyTemplate>(
|
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
onActionClick: OnActionClickFn<T>,
|
|
||||||
): VxeTableGridOptions['columns'] {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
field: 'id',
|
field: 'id',
|
||||||
@@ -288,34 +281,10 @@ export function useGridColumns<T = SystemNotifyTemplateApi.NotifyTemplate>(
|
|||||||
formatter: 'formatDateTime',
|
formatter: 'formatDateTime',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'operation',
|
|
||||||
title: '操作',
|
title: '操作',
|
||||||
minWidth: 180,
|
width: 220,
|
||||||
align: 'center',
|
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
cellRender: {
|
slots: { default: 'actions' },
|
||||||
attrs: {
|
|
||||||
nameField: 'name',
|
|
||||||
nameTitle: '站内信模板',
|
|
||||||
onClick: onActionClick,
|
|
||||||
},
|
|
||||||
name: 'CellOperation',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
code: 'edit',
|
|
||||||
show: hasAccessByCodes(['system:notify-template:update']),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'send',
|
|
||||||
text: '测试',
|
|
||||||
show: hasAccessByCodes(['system:notify-template:send-notify']),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'delete',
|
|
||||||
show: hasAccessByCodes(['system:notify-template:delete']),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { SystemNotifyTemplateApi } from '#/api/system/notify/template';
|
import type { SystemNotifyTemplateApi } from '#/api/system/notify/template';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { Download, Plus } from '@vben/icons';
|
|
||||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||||
|
|
||||||
import { Button, message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import {
|
import {
|
||||||
deleteNotifyTemplate,
|
deleteNotifyTemplate,
|
||||||
exportNotifyTemplate,
|
exportNotifyTemplate,
|
||||||
@@ -40,69 +36,50 @@ function onRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 导出表格 */
|
/** 导出表格 */
|
||||||
async function onExport() {
|
async function handleExport() {
|
||||||
const data = await exportNotifyTemplate(await gridApi.formApi.getValues());
|
const data = await exportNotifyTemplate(await gridApi.formApi.getValues());
|
||||||
downloadFileFromBlobPart({ fileName: '站内信模板.xls', source: data });
|
downloadFileFromBlobPart({ fileName: '站内信模板.xls', source: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 创建站内信模板 */
|
/** 创建站内信模板 */
|
||||||
function onCreate() {
|
function handleCreate() {
|
||||||
formModalApi.setData(null).open();
|
formModalApi.setData(null).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编辑站内信模板 */
|
/** 编辑站内信模板 */
|
||||||
function onEdit(row: SystemNotifyTemplateApi.NotifyTemplate) {
|
function handleEdit(row: SystemNotifyTemplateApi.NotifyTemplate) {
|
||||||
formModalApi.setData(row).open();
|
formModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 发送测试站内信 */
|
/** 发送测试站内信 */
|
||||||
function onSend(row: SystemNotifyTemplateApi.NotifyTemplate) {
|
function handleSend(row: SystemNotifyTemplateApi.NotifyTemplate) {
|
||||||
sendModalApi.setData(row).open();
|
sendModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除站内信模板 */
|
/** 删除站内信模板 */
|
||||||
async function onDelete(row: SystemNotifyTemplateApi.NotifyTemplate) {
|
async function handleDelete(row: SystemNotifyTemplateApi.NotifyTemplate) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
duration: 0,
|
key: 'action_key_msg',
|
||||||
key: 'action_process_msg',
|
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteNotifyTemplate(row.id as number);
|
await deleteNotifyTemplate(row.id as number);
|
||||||
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
|
key: 'action_key_msg',
|
||||||
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表格操作按钮的回调函数 */
|
|
||||||
function onActionClick({
|
|
||||||
code,
|
|
||||||
row,
|
|
||||||
}: OnActionClickParams<SystemNotifyTemplateApi.NotifyTemplate>) {
|
|
||||||
switch (code) {
|
|
||||||
case 'delete': {
|
|
||||||
onDelete(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'edit': {
|
|
||||||
onEdit(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'send': {
|
|
||||||
onSend(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(onActionClick),
|
columns: useGridColumns(),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
@@ -137,23 +114,55 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
<SendModal />
|
<SendModal />
|
||||||
<Grid table-title="站内信模板列表">
|
<Grid table-title="站内信模板列表">
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<Button
|
<TableAction
|
||||||
type="primary"
|
:actions="[
|
||||||
@click="onCreate"
|
{
|
||||||
v-access:code="['system:notify-template:create']"
|
label: $t('ui.actionTitle.create', ['短信渠道']),
|
||||||
>
|
type: 'primary',
|
||||||
<Plus class="size-5" />
|
icon: ACTION_ICON.ADD,
|
||||||
{{ $t('ui.actionTitle.create', ['站内信模板']) }}
|
auth: ['system:notify-template:create'],
|
||||||
</Button>
|
onClick: handleCreate,
|
||||||
<Button
|
},
|
||||||
type="primary"
|
{
|
||||||
class="ml-2"
|
label: $t('ui.actionTitle.export'),
|
||||||
@click="onExport"
|
type: 'primary',
|
||||||
v-access:code="['system:notify-template:export']"
|
icon: ACTION_ICON.DOWNLOAD,
|
||||||
>
|
auth: ['system:notify-template:export'],
|
||||||
<Download class="size-5" />
|
onClick: handleExport,
|
||||||
{{ $t('ui.actionTitle.export') }}
|
},
|
||||||
</Button>
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: $t('common.edit'),
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.EDIT,
|
||||||
|
auth: ['system:notify-template:update'],
|
||||||
|
onClick: handleEdit.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '测试',
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.ADD,
|
||||||
|
auth: ['system:notify-template:send-notify'],
|
||||||
|
onClick: handleSend.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('common.delete'),
|
||||||
|
type: 'link',
|
||||||
|
danger: true,
|
||||||
|
icon: ACTION_ICON.DELETE,
|
||||||
|
auth: ['system:notify-template:delete'],
|
||||||
|
popConfirm: {
|
||||||
|
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||||
|
confirm: handleDelete.bind(null, row),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ const [Modal, modalApi] = useVbenModal({
|
|||||||
});
|
});
|
||||||
|
|
||||||
/** 动态构建表单 schema */
|
/** 动态构建表单 schema */
|
||||||
const buildFormSchema = () => {
|
function buildFormSchema() {
|
||||||
const schema = useSendNotifyFormSchema();
|
const schema = useSendNotifyFormSchema();
|
||||||
if (formData.value?.params) {
|
if (formData.value?.params) {
|
||||||
formData.value.params.forEach((param: string) => {
|
formData.value.params.forEach((param: string) => {
|
||||||
@@ -102,7 +102,7 @@ const buildFormSchema = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
return schema;
|
return schema;
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { SystemOAuth2ClientApi } from '#/api/system/oauth2/client';
|
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
|
||||||
|
|
||||||
import { z } from '#/adapter/form';
|
import { z } from '#/adapter/form';
|
||||||
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
|
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
|
||||||
|
|
||||||
/** 新增/修改的表单 */
|
/** 新增/修改的表单 */
|
||||||
export function useFormSchema(): VbenFormSchema[] {
|
export function useFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
@@ -191,9 +186,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns<T = SystemOAuth2ClientApi.OAuth2Client>(
|
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
onActionClick: OnActionClickFn<T>,
|
|
||||||
): VxeTableGridOptions['columns'] {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
field: 'clientId',
|
field: 'clientId',
|
||||||
@@ -251,29 +244,10 @@ export function useGridColumns<T = SystemOAuth2ClientApi.OAuth2Client>(
|
|||||||
formatter: 'formatDateTime',
|
formatter: 'formatDateTime',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'operation',
|
|
||||||
title: '操作',
|
title: '操作',
|
||||||
minWidth: 130,
|
width: 130,
|
||||||
align: 'center',
|
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
cellRender: {
|
slots: { default: 'actions' },
|
||||||
attrs: {
|
|
||||||
nameField: 'name',
|
|
||||||
nameTitle: 'OAuth2 客户端',
|
|
||||||
onClick: onActionClick,
|
|
||||||
},
|
|
||||||
name: 'CellOperation',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
code: 'edit',
|
|
||||||
show: hasAccessByCodes(['system:oauth2-client:update']),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'delete',
|
|
||||||
show: hasAccessByCodes(['system:oauth2-client:delete']),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { SystemOAuth2ClientApi } from '#/api/system/oauth2/client';
|
import type { SystemOAuth2ClientApi } from '#/api/system/oauth2/client';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { Plus } from '@vben/icons';
|
|
||||||
|
|
||||||
import { Button, message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import {
|
import {
|
||||||
deleteOAuth2Client,
|
deleteOAuth2Client,
|
||||||
getOAuth2ClientPage,
|
getOAuth2ClientPage,
|
||||||
@@ -32,54 +28,39 @@ function onRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 创建 OAuth2 客户端 */
|
/** 创建 OAuth2 客户端 */
|
||||||
function onCreate() {
|
function handleCreate() {
|
||||||
formModalApi.setData(null).open();
|
formModalApi.setData(null).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编辑 OAuth2 客户端 */
|
/** 编辑 OAuth2 客户端 */
|
||||||
function onEdit(row: SystemOAuth2ClientApi.OAuth2Client) {
|
function handleEdit(row: SystemOAuth2ClientApi.OAuth2Client) {
|
||||||
formModalApi.setData(row).open();
|
formModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除 OAuth2 客户端 */
|
/** 删除 OAuth2 客户端 */
|
||||||
async function onDelete(row: SystemOAuth2ClientApi.OAuth2Client) {
|
async function handleDelete(row: SystemOAuth2ClientApi.OAuth2Client) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
duration: 0,
|
key: 'action_key_msg',
|
||||||
key: 'action_process_msg',
|
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteOAuth2Client(row.id as number);
|
await deleteOAuth2Client(row.id as number);
|
||||||
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
|
key: 'action_key_msg',
|
||||||
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} catch {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表格操作按钮的回调函数 */
|
|
||||||
function onActionClick({
|
|
||||||
code,
|
|
||||||
row,
|
|
||||||
}: OnActionClickParams<SystemOAuth2ClientApi.OAuth2Client>) {
|
|
||||||
switch (code) {
|
|
||||||
case 'delete': {
|
|
||||||
onDelete(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'edit': {
|
|
||||||
onEdit(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(onActionClick),
|
columns: useGridColumns(),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
@@ -116,14 +97,41 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
<FormModal @success="onRefresh" />
|
<FormModal @success="onRefresh" />
|
||||||
<Grid table-title="OAuth2 客户端列表">
|
<Grid table-title="OAuth2 客户端列表">
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<Button
|
<TableAction
|
||||||
type="primary"
|
:actions="[
|
||||||
@click="onCreate"
|
{
|
||||||
v-access:code="['system:oauth2-client:create']"
|
label: $t('ui.actionTitle.create', [' OAuth2.0 客户端']),
|
||||||
>
|
type: 'primary',
|
||||||
<Plus class="size-5" />
|
icon: ACTION_ICON.ADD,
|
||||||
{{ $t('ui.actionTitle.create', [' OAuth2.0 客户端']) }}
|
auth: ['system:oauth2-client:create'],
|
||||||
</Button>
|
onClick: handleCreate,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: $t('common.edit'),
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.EDIT,
|
||||||
|
auth: ['system:oauth2-client:update'],
|
||||||
|
onClick: handleEdit.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('common.delete'),
|
||||||
|
type: 'link',
|
||||||
|
danger: true,
|
||||||
|
icon: ACTION_ICON.DELETE,
|
||||||
|
auth: ['system:oauth2-client:delete'],
|
||||||
|
popConfirm: {
|
||||||
|
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||||
|
confirm: handleDelete.bind(null, row),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { SystemOAuth2TokenApi } from '#/api/system/oauth2/token';
|
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
|
||||||
|
|
||||||
import { DICT_TYPE, getDictOptions } from '#/utils';
|
import { DICT_TYPE, getDictOptions } from '#/utils';
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
|
||||||
|
|
||||||
/** 列表的搜索表单 */
|
/** 列表的搜索表单 */
|
||||||
export function useGridFormSchema(): VbenFormSchema[] {
|
export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
@@ -40,9 +35,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns<T = SystemOAuth2TokenApi.OAuth2Token>(
|
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
onActionClick: OnActionClickFn<T>,
|
|
||||||
): VxeTableGridOptions['columns'] {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
field: 'accessToken',
|
field: 'accessToken',
|
||||||
@@ -86,26 +79,10 @@ export function useGridColumns<T = SystemOAuth2TokenApi.OAuth2Token>(
|
|||||||
formatter: 'formatDateTime',
|
formatter: 'formatDateTime',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'operation',
|
|
||||||
title: '操作',
|
title: '操作',
|
||||||
minWidth: 100,
|
width: 80,
|
||||||
align: 'center',
|
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
cellRender: {
|
slots: { default: 'actions' },
|
||||||
attrs: {
|
|
||||||
nameField: 'accessToken',
|
|
||||||
nameTitle: 'OAuth2 令牌',
|
|
||||||
onClick: onActionClick,
|
|
||||||
},
|
|
||||||
name: 'CellOperation',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
code: 'delete',
|
|
||||||
text: '强退',
|
|
||||||
show: hasAccessByCodes(['system:oauth2-token:delete']),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { SystemOAuth2TokenApi } from '#/api/system/oauth2/token';
|
import type { SystemOAuth2TokenApi } from '#/api/system/oauth2/token';
|
||||||
|
|
||||||
import { Page } from '@vben/common-ui';
|
import { Page } from '@vben/common-ui';
|
||||||
|
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import {
|
import {
|
||||||
deleteOAuth2Token,
|
deleteOAuth2Token,
|
||||||
getOAuth2TokenPage,
|
getOAuth2TokenPage,
|
||||||
@@ -25,40 +22,29 @@ function onRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 删除 OAuth2 令牌 */
|
/** 删除 OAuth2 令牌 */
|
||||||
async function onDelete(row: SystemOAuth2TokenApi.OAuth2Token) {
|
async function handleDelete(row: SystemOAuth2TokenApi.OAuth2Token) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', ['令牌']),
|
content: $t('ui.actionMessage.deleting', ['令牌']),
|
||||||
duration: 0,
|
key: 'action_key_msg',
|
||||||
key: 'action_process_msg',
|
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteOAuth2Token(row.accessToken);
|
await deleteOAuth2Token(row.accessToken);
|
||||||
message.success($t('ui.actionMessage.operationSuccess'));
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', ['令牌']),
|
||||||
|
key: 'action_key_msg',
|
||||||
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} catch {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表格操作按钮的回调函数 */
|
|
||||||
function onActionClick({
|
|
||||||
code,
|
|
||||||
row,
|
|
||||||
}: OnActionClickParams<SystemOAuth2TokenApi.OAuth2Token>) {
|
|
||||||
switch (code) {
|
|
||||||
case 'delete': {
|
|
||||||
onDelete(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(onActionClick),
|
columns: useGridColumns(),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
@@ -92,6 +78,24 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<Grid table-title="令牌列表" />
|
<Grid table-title="令牌列表">
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: '强退',
|
||||||
|
type: 'link',
|
||||||
|
danger: true,
|
||||||
|
icon: ACTION_ICON.DELETE,
|
||||||
|
auth: ['system:oauth2-token:delete'],
|
||||||
|
popConfirm: {
|
||||||
|
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||||
|
confirm: handleDelete.bind(null, row),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { SystemOperateLogApi } from '#/api/system/operate-log';
|
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
|
||||||
|
|
||||||
import { getSimpleUserList } from '#/api/system/user';
|
import { getSimpleUserList } from '#/api/system/user';
|
||||||
import { getRangePickerDefaultProps } from '#/utils';
|
import { getRangePickerDefaultProps } from '#/utils';
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
|
||||||
|
|
||||||
/** 列表的搜索表单 */
|
/** 列表的搜索表单 */
|
||||||
export function useGridFormSchema(): VbenFormSchema[] {
|
export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
@@ -75,9 +70,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns<T = SystemOperateLogApi.OperateLog>(
|
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
onActionClick: OnActionClickFn<T>,
|
|
||||||
): VxeTableGridOptions['columns'] {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
field: 'id',
|
field: 'id',
|
||||||
@@ -121,26 +114,10 @@ export function useGridColumns<T = SystemOperateLogApi.OperateLog>(
|
|||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'operation',
|
|
||||||
title: '操作',
|
title: '操作',
|
||||||
minWidth: 120,
|
width: 80,
|
||||||
align: 'center',
|
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
cellRender: {
|
slots: { default: 'actions' },
|
||||||
attrs: {
|
|
||||||
nameField: 'action',
|
|
||||||
nameTitle: '操作日志',
|
|
||||||
onClick: onActionClick,
|
|
||||||
},
|
|
||||||
name: 'CellOperation',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
code: 'detail',
|
|
||||||
text: '详情',
|
|
||||||
show: hasAccessByCodes(['system:operate-log:query']),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { SystemOperateLogApi } from '#/api/system/operate-log';
|
import type { SystemOperateLogApi } from '#/api/system/operate-log';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { Download } from '@vben/icons';
|
|
||||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||||
|
|
||||||
import { Button } from 'ant-design-vue';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
|
||||||
import { exportOperateLog, getOperateLogPage } from '#/api/system/operate-log';
|
import { exportOperateLog, getOperateLogPage } from '#/api/system/operate-log';
|
||||||
import { DocAlert } from '#/components/doc-alert';
|
import { DocAlert } from '#/components/doc-alert';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
@@ -30,35 +24,22 @@ function onRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 导出表格 */
|
/** 导出表格 */
|
||||||
async function onExport() {
|
async function handleExport() {
|
||||||
const data = await exportOperateLog(await gridApi.formApi.getValues());
|
const data = await exportOperateLog(await gridApi.formApi.getValues());
|
||||||
downloadFileFromBlobPart({ fileName: '操作日志.xls', source: data });
|
downloadFileFromBlobPart({ fileName: '操作日志.xls', source: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 查看操作日志详情 */
|
/** 查看操作日志详情 */
|
||||||
function onDetail(row: SystemOperateLogApi.OperateLog) {
|
function handleDetail(row: SystemOperateLogApi.OperateLog) {
|
||||||
detailModalApi.setData(row).open();
|
detailModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表格操作按钮的回调函数 */
|
|
||||||
function onActionClick({
|
|
||||||
code,
|
|
||||||
row,
|
|
||||||
}: OnActionClickParams<SystemOperateLogApi.OperateLog>) {
|
|
||||||
switch (code) {
|
|
||||||
case 'detail': {
|
|
||||||
onDetail(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(onActionClick),
|
columns: useGridColumns(),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
@@ -92,15 +73,30 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
<DetailModal @success="onRefresh" />
|
<DetailModal @success="onRefresh" />
|
||||||
<Grid table-title="操作日志列表">
|
<Grid table-title="操作日志列表">
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<Button
|
<TableAction
|
||||||
type="primary"
|
:actions="[
|
||||||
class="ml-2"
|
{
|
||||||
@click="onExport"
|
label: $t('ui.actionTitle.export'),
|
||||||
v-access:code="['system:operate-log:export']"
|
type: 'primary',
|
||||||
>
|
icon: ACTION_ICON.DOWNLOAD,
|
||||||
<Download class="size-5" />
|
auth: ['system:operate-log:export'],
|
||||||
{{ $t('ui.actionTitle.export') }}
|
onClick: handleExport,
|
||||||
</Button>
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: $t('common.detail'),
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.VIEW,
|
||||||
|
auth: ['system:operate-log:query'],
|
||||||
|
onClick: handleDetail.bind(null, row),
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { SystemPostApi } from '#/api/system/post';
|
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
|
||||||
|
|
||||||
import { z } from '#/adapter/form';
|
import { z } from '#/adapter/form';
|
||||||
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
|
import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils';
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
|
||||||
|
|
||||||
/** 新增/修改的表单 */
|
/** 新增/修改的表单 */
|
||||||
export function useFormSchema(): VbenFormSchema[] {
|
export function useFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
@@ -86,9 +81,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns<T = SystemPostApi.Post>(
|
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
onActionClick: OnActionClickFn<T>,
|
|
||||||
): VxeTableGridOptions['columns'] {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
field: 'id',
|
field: 'id',
|
||||||
@@ -131,29 +124,10 @@ export function useGridColumns<T = SystemPostApi.Post>(
|
|||||||
formatter: 'formatDateTime',
|
formatter: 'formatDateTime',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'operation',
|
|
||||||
title: '操作',
|
title: '操作',
|
||||||
minWidth: 130,
|
width: 130,
|
||||||
align: 'center',
|
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
cellRender: {
|
slots: { default: 'actions' },
|
||||||
attrs: {
|
|
||||||
nameField: 'name',
|
|
||||||
nameTitle: '岗位',
|
|
||||||
onClick: onActionClick,
|
|
||||||
},
|
|
||||||
name: 'CellOperation',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
code: 'edit',
|
|
||||||
show: hasAccessByCodes(['system:post:update']),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'delete',
|
|
||||||
show: hasAccessByCodes(['system:post:delete']),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { SystemPostApi } from '#/api/system/post';
|
import type { SystemPostApi } from '#/api/system/post';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { Download, Plus } from '@vben/icons';
|
|
||||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||||
|
|
||||||
import { Button, message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { deletePost, exportPost, getPostPage } from '#/api/system/post';
|
import { deletePost, exportPost, getPostPage } from '#/api/system/post';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
@@ -29,57 +25,45 @@ function onRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 导出表格 */
|
/** 导出表格 */
|
||||||
async function onExport() {
|
async function handleExport() {
|
||||||
const data = await exportPost(await gridApi.formApi.getValues());
|
const data = await exportPost(await gridApi.formApi.getValues());
|
||||||
downloadFileFromBlobPart({ fileName: '岗位.xls', source: data });
|
downloadFileFromBlobPart({ fileName: '岗位.xls', source: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 创建岗位 */
|
/** 创建岗位 */
|
||||||
function onCreate() {
|
function handleCreate() {
|
||||||
formModalApi.setData(null).open();
|
formModalApi.setData(null).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编辑岗位 */
|
/** 编辑岗位 */
|
||||||
function onEdit(row: SystemPostApi.Post) {
|
function handleEdit(row: SystemPostApi.Post) {
|
||||||
formModalApi.setData(row).open();
|
formModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除岗位 */
|
/** 删除岗位 */
|
||||||
async function onDelete(row: SystemPostApi.Post) {
|
async function handleDelete(row: SystemPostApi.Post) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
duration: 0,
|
key: 'action_key_msg',
|
||||||
key: 'action_process_msg',
|
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deletePost(row.id as number);
|
await deletePost(row.id as number);
|
||||||
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
|
key: 'action_key_msg',
|
||||||
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} catch {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表格操作按钮的回调函数 */
|
|
||||||
function onActionClick({ code, row }: OnActionClickParams<SystemPostApi.Post>) {
|
|
||||||
switch (code) {
|
|
||||||
case 'delete': {
|
|
||||||
onDelete(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'edit': {
|
|
||||||
onEdit(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(onActionClick),
|
columns: useGridColumns(),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
@@ -109,23 +93,48 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
<FormModal @success="onRefresh" />
|
<FormModal @success="onRefresh" />
|
||||||
<Grid table-title="岗位列表">
|
<Grid table-title="岗位列表">
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<Button
|
<TableAction
|
||||||
type="primary"
|
:actions="[
|
||||||
@click="onCreate"
|
{
|
||||||
v-access:code="['system:post:create']"
|
label: $t('ui.actionTitle.create', ['短信渠道']),
|
||||||
>
|
type: 'primary',
|
||||||
<Plus class="size-5" />
|
icon: ACTION_ICON.ADD,
|
||||||
{{ $t('ui.actionTitle.create', ['岗位']) }}
|
auth: ['system:post:create'],
|
||||||
</Button>
|
onClick: handleCreate,
|
||||||
<Button
|
},
|
||||||
type="primary"
|
{
|
||||||
class="ml-2"
|
label: $t('ui.actionTitle.export'),
|
||||||
@click="onExport"
|
type: 'primary',
|
||||||
v-access:code="['system:post:export']"
|
icon: ACTION_ICON.DOWNLOAD,
|
||||||
>
|
auth: ['system:post:export'],
|
||||||
<Download class="size-5" />
|
onClick: handleExport,
|
||||||
{{ $t('ui.actionTitle.export') }}
|
},
|
||||||
</Button>
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: $t('common.edit'),
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.EDIT,
|
||||||
|
auth: ['system:post:update'],
|
||||||
|
onClick: handleEdit.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('common.delete'),
|
||||||
|
type: 'link',
|
||||||
|
danger: true,
|
||||||
|
icon: ACTION_ICON.DELETE,
|
||||||
|
auth: ['system:post:delete'],
|
||||||
|
popConfirm: {
|
||||||
|
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||||
|
confirm: handleDelete.bind(null, row),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -3,15 +3,13 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
|||||||
import type { SystemRoleApi } from '#/api/system/role';
|
import type { SystemRoleApi } from '#/api/system/role';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { Download, Plus } from '@vben/icons';
|
|
||||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||||
|
|
||||||
import { Button, message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { deleteRole, exportRole, getRolePage } from '#/api/system/role';
|
import { deleteRole, exportRole, getRolePage } from '#/api/system/role';
|
||||||
import { DocAlert } from '#/components/doc-alert';
|
import { DocAlert } from '#/components/doc-alert';
|
||||||
import { TableAction } from '#/components/table-action';
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import { useGridColumns, useGridFormSchema } from './data';
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
@@ -41,45 +39,46 @@ function onRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 导出表格 */
|
/** 导出表格 */
|
||||||
async function onExport() {
|
async function handleExport() {
|
||||||
const data = await exportRole(await gridApi.formApi.getValues());
|
const data = await exportRole(await gridApi.formApi.getValues());
|
||||||
downloadFileFromBlobPart({ fileName: '角色.xls', source: data });
|
downloadFileFromBlobPart({ fileName: '角色.xls', source: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编辑角色 */
|
/** 编辑角色 */
|
||||||
function onEdit(row: SystemRoleApi.Role) {
|
function handleEdit(row: SystemRoleApi.Role) {
|
||||||
formModalApi.setData(row).open();
|
formModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 创建角色 */
|
/** 创建角色 */
|
||||||
function onCreate() {
|
function handleCreate() {
|
||||||
formModalApi.setData(null).open();
|
formModalApi.setData(null).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除角色 */
|
/** 删除角色 */
|
||||||
async function onDelete(row: SystemRoleApi.Role) {
|
async function handleDelete(row: SystemRoleApi.Role) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
duration: 0,
|
key: 'action_key_msg',
|
||||||
key: 'action_process_msg',
|
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteRole(row.id as number);
|
await deleteRole(row.id as number);
|
||||||
hideLoading();
|
message.success({
|
||||||
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
|
key: 'action_key_msg',
|
||||||
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} catch {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 分配角色的数据权限 */
|
/** 分配角色的数据权限 */
|
||||||
function onAssignDataPermission(row: SystemRoleApi.Role) {
|
function handleAssignDataPermission(row: SystemRoleApi.Role) {
|
||||||
assignDataPermissionFormApi.setData(row).open();
|
assignDataPermissionFormApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 分配角色的菜单权限 */
|
/** 分配角色的菜单权限 */
|
||||||
function onAssignMenu(row: SystemRoleApi.Role) {
|
function handleAssignMenu(row: SystemRoleApi.Role) {
|
||||||
assignMenuFormApi.setData(row).open();
|
assignMenuFormApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,23 +127,24 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
<AssignMenuFormModel @success="onRefresh" />
|
<AssignMenuFormModel @success="onRefresh" />
|
||||||
<Grid table-title="角色列表">
|
<Grid table-title="角色列表">
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<Button
|
<TableAction
|
||||||
type="primary"
|
:actions="[
|
||||||
@click="onCreate"
|
{
|
||||||
v-access:code="['system:role:create']"
|
label: $t('ui.actionTitle.create', ['角色']),
|
||||||
>
|
type: 'primary',
|
||||||
<Plus class="size-5" />
|
icon: ACTION_ICON.ADD,
|
||||||
{{ $t('ui.actionTitle.create', ['角色']) }}
|
auth: ['system:role:create'],
|
||||||
</Button>
|
onClick: handleCreate,
|
||||||
<Button
|
},
|
||||||
type="primary"
|
{
|
||||||
class="ml-2"
|
label: $t('ui.actionTitle.export'),
|
||||||
@click="onExport"
|
type: 'primary',
|
||||||
v-access:code="['system:role:export']"
|
icon: ACTION_ICON.DOWNLOAD,
|
||||||
>
|
auth: ['system:role:export'],
|
||||||
<Download class="size-5" />
|
onClick: handleExport,
|
||||||
{{ $t('ui.actionTitle.export') }}
|
},
|
||||||
</Button>
|
]"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #actions="{ row }">
|
<template #actions="{ row }">
|
||||||
<TableAction
|
<TableAction
|
||||||
@@ -152,19 +152,19 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
{
|
{
|
||||||
label: $t('common.edit'),
|
label: $t('common.edit'),
|
||||||
type: 'link',
|
type: 'link',
|
||||||
icon: 'ant-design:edit-outlined',
|
icon: ACTION_ICON.EDIT,
|
||||||
auth: ['system:role:update'],
|
auth: ['system:role:update'],
|
||||||
onClick: onEdit.bind(null, row),
|
onClick: handleEdit.bind(null, row),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: $t('common.delete'),
|
label: $t('common.delete'),
|
||||||
type: 'link',
|
type: 'link',
|
||||||
danger: true,
|
danger: true,
|
||||||
icon: 'ant-design:delete-outlined',
|
icon: ACTION_ICON.DELETE,
|
||||||
auth: ['system:role:delete'],
|
auth: ['system:role:delete'],
|
||||||
popConfirm: {
|
popConfirm: {
|
||||||
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||||
confirm: onDelete.bind(null, row),
|
confirm: handleDelete.bind(null, row),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
@@ -173,13 +173,13 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
label: '数据权限',
|
label: '数据权限',
|
||||||
type: 'link',
|
type: 'link',
|
||||||
auth: ['system:permission:assign-role-data-scope'],
|
auth: ['system:permission:assign-role-data-scope'],
|
||||||
onClick: onAssignDataPermission.bind(null, row),
|
onClick: handleAssignDataPermission.bind(null, row),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '菜单权限',
|
label: '菜单权限',
|
||||||
type: 'link',
|
type: 'link',
|
||||||
auth: ['system:permission:assign-role-menu'],
|
auth: ['system:permission:assign-role-menu'],
|
||||||
onClick: onAssignMenu.bind(null, row),
|
onClick: handleAssignMenu.bind(null, row),
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { SystemSmsChannelApi } from '#/api/system/sms/channel';
|
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
|
||||||
|
|
||||||
import { z } from '#/adapter/form';
|
import { z } from '#/adapter/form';
|
||||||
import {
|
import {
|
||||||
@@ -12,8 +9,6 @@ import {
|
|||||||
getRangePickerDefaultProps,
|
getRangePickerDefaultProps,
|
||||||
} from '#/utils';
|
} from '#/utils';
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
|
||||||
|
|
||||||
/** 新增/修改的表单 */
|
/** 新增/修改的表单 */
|
||||||
export function useFormSchema(): VbenFormSchema[] {
|
export function useFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
@@ -135,9 +130,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns<T = SystemSmsChannelApi.SmsChannel>(
|
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
onActionClick: OnActionClickFn<T>,
|
|
||||||
): VxeTableGridOptions['columns'] {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
field: 'id',
|
field: 'id',
|
||||||
@@ -194,29 +187,10 @@ export function useGridColumns<T = SystemSmsChannelApi.SmsChannel>(
|
|||||||
formatter: 'formatDateTime',
|
formatter: 'formatDateTime',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'operation',
|
|
||||||
title: '操作',
|
title: '操作',
|
||||||
minWidth: 180,
|
width: 130,
|
||||||
align: 'center',
|
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
cellRender: {
|
slots: { default: 'actions' },
|
||||||
attrs: {
|
|
||||||
nameField: 'signature',
|
|
||||||
nameTitle: '短信渠道',
|
|
||||||
onClick: onActionClick,
|
|
||||||
},
|
|
||||||
name: 'CellOperation',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
code: 'edit',
|
|
||||||
show: hasAccessByCodes(['system:sms-channel:update']),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'delete',
|
|
||||||
show: hasAccessByCodes(['system:sms-channel:delete']),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { SystemSmsChannelApi } from '#/api/system/sms/channel';
|
import type { SystemSmsChannelApi } from '#/api/system/sms/channel';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { Download, Plus } from '@vben/icons';
|
|
||||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||||
|
|
||||||
import { Button, message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import {
|
import {
|
||||||
deleteSmsChannel,
|
deleteSmsChannel,
|
||||||
exportSmsChannel,
|
exportSmsChannel,
|
||||||
@@ -34,60 +30,45 @@ function onRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 导出表格 */
|
/** 导出表格 */
|
||||||
async function onExport() {
|
async function handleExport() {
|
||||||
const data = await exportSmsChannel(await gridApi.formApi.getValues());
|
const data = await exportSmsChannel(await gridApi.formApi.getValues());
|
||||||
downloadFileFromBlobPart({ fileName: '短信渠道.xls', source: data });
|
downloadFileFromBlobPart({ fileName: '短信渠道.xls', source: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 创建短信渠道 */
|
/** 创建短信渠道 */
|
||||||
function onCreate() {
|
function handleCreate() {
|
||||||
formModalApi.setData(null).open();
|
formModalApi.setData(null).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编辑短信渠道 */
|
/** 编辑短信渠道 */
|
||||||
function onEdit(row: SystemSmsChannelApi.SmsChannel) {
|
function handleEdit(row: SystemSmsChannelApi.SmsChannel) {
|
||||||
formModalApi.setData(row).open();
|
formModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除短信渠道 */
|
/** 删除短信渠道 */
|
||||||
async function onDelete(row: SystemSmsChannelApi.SmsChannel) {
|
async function handleDelete(row: SystemSmsChannelApi.SmsChannel) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.signature]),
|
content: $t('ui.actionMessage.deleting', [row.signature]),
|
||||||
duration: 0,
|
key: 'action_key_msg',
|
||||||
key: 'action_process_msg',
|
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteSmsChannel(row.id as number);
|
await deleteSmsChannel(row.id as number);
|
||||||
message.success($t('ui.actionMessage.deleteSuccess', [row.signature]));
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.signature]),
|
||||||
|
key: 'action_key_msg',
|
||||||
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} catch {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表格操作按钮的回调函数 */
|
|
||||||
function onActionClick({
|
|
||||||
code,
|
|
||||||
row,
|
|
||||||
}: OnActionClickParams<SystemSmsChannelApi.SmsChannel>) {
|
|
||||||
switch (code) {
|
|
||||||
case 'delete': {
|
|
||||||
onDelete(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'edit': {
|
|
||||||
onEdit(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(onActionClick),
|
columns: useGridColumns(),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
@@ -121,23 +102,48 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
<FormModal @success="onRefresh" />
|
<FormModal @success="onRefresh" />
|
||||||
<Grid table-title="短信渠道列表">
|
<Grid table-title="短信渠道列表">
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<Button
|
<TableAction
|
||||||
type="primary"
|
:actions="[
|
||||||
@click="onCreate"
|
{
|
||||||
v-access:code="['system:sms-channel:create']"
|
label: $t('ui.actionTitle.create', ['短信渠道']),
|
||||||
>
|
type: 'primary',
|
||||||
<Plus class="size-5" />
|
icon: ACTION_ICON.ADD,
|
||||||
{{ $t('ui.actionTitle.create', ['短信渠道']) }}
|
auth: ['system:sms-channel:create'],
|
||||||
</Button>
|
onClick: handleCreate,
|
||||||
<Button
|
},
|
||||||
type="primary"
|
{
|
||||||
class="ml-2"
|
label: $t('ui.actionTitle.export'),
|
||||||
@click="onExport"
|
type: 'primary',
|
||||||
v-access:code="['system:sms-channel:export']"
|
icon: ACTION_ICON.DOWNLOAD,
|
||||||
>
|
auth: ['system:sms-channel:export'],
|
||||||
<Download class="size-5" />
|
onClick: handleExport,
|
||||||
{{ $t('ui.actionTitle.export') }}
|
},
|
||||||
</Button>
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: $t('common.edit'),
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.EDIT,
|
||||||
|
auth: ['system:sms-channel:update'],
|
||||||
|
onClick: handleEdit.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('common.delete'),
|
||||||
|
type: 'link',
|
||||||
|
danger: true,
|
||||||
|
icon: ACTION_ICON.DELETE,
|
||||||
|
auth: ['system:sms-channel:delete'],
|
||||||
|
popConfirm: {
|
||||||
|
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||||
|
confirm: handleDelete.bind(null, row),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { SystemSmsLogApi } from '#/api/system/sms/log';
|
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
|
||||||
|
|
||||||
import { getSimpleSmsChannelList } from '#/api/system/sms/channel';
|
import { getSimpleSmsChannelList } from '#/api/system/sms/channel';
|
||||||
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
|
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
|
||||||
|
|
||||||
/** 列表的搜索表单 */
|
/** 列表的搜索表单 */
|
||||||
export function useGridFormSchema(): VbenFormSchema[] {
|
export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
@@ -84,9 +79,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns<T = SystemSmsLogApi.SmsLog>(
|
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
onActionClick: OnActionClickFn<T>,
|
|
||||||
): VxeTableGridOptions['columns'] {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
field: 'id',
|
field: 'id',
|
||||||
@@ -163,26 +156,10 @@ export function useGridColumns<T = SystemSmsLogApi.SmsLog>(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'operation',
|
|
||||||
title: '操作',
|
title: '操作',
|
||||||
minWidth: 120,
|
width: 80,
|
||||||
align: 'center',
|
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
cellRender: {
|
slots: { default: 'actions' },
|
||||||
attrs: {
|
|
||||||
nameField: 'mobile',
|
|
||||||
nameTitle: '短信日志',
|
|
||||||
onClick: onActionClick,
|
|
||||||
},
|
|
||||||
name: 'CellOperation',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
code: 'detail',
|
|
||||||
text: '详情',
|
|
||||||
show: hasAccessByCodes(['system:sms-log:query']),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,11 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { SystemSmsLogApi } from '#/api/system/sms/log';
|
import type { SystemSmsLogApi } from '#/api/system/sms/log';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { Download } from '@vben/icons';
|
|
||||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||||
|
|
||||||
import { Button } from 'ant-design-vue';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
|
||||||
import { exportSmsLog, getSmsLogPage } from '#/api/system/sms/log';
|
import { exportSmsLog, getSmsLogPage } from '#/api/system/sms/log';
|
||||||
import { DocAlert } from '#/components/doc-alert';
|
import { DocAlert } from '#/components/doc-alert';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
@@ -30,35 +24,22 @@ function onRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 导出表格 */
|
/** 导出表格 */
|
||||||
async function onExport() {
|
async function handleExport() {
|
||||||
const data = await exportSmsLog(await gridApi.formApi.getValues());
|
const data = await exportSmsLog(await gridApi.formApi.getValues());
|
||||||
downloadFileFromBlobPart({ fileName: '短信日志.xls', source: data });
|
downloadFileFromBlobPart({ fileName: '短信日志.xls', source: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 查看短信日志详情 */
|
/** 查看短信日志详情 */
|
||||||
function onDetail(row: SystemSmsLogApi.SmsLog) {
|
function handleDetail(row: SystemSmsLogApi.SmsLog) {
|
||||||
detailModalApi.setData(row).open();
|
detailModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表格操作按钮的回调函数 */
|
|
||||||
function onActionClick({
|
|
||||||
code,
|
|
||||||
row,
|
|
||||||
}: OnActionClickParams<SystemSmsLogApi.SmsLog>) {
|
|
||||||
switch (code) {
|
|
||||||
case 'detail': {
|
|
||||||
onDetail(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(onActionClick),
|
columns: useGridColumns(),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
@@ -92,15 +73,30 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
<DetailModal @success="onRefresh" />
|
<DetailModal @success="onRefresh" />
|
||||||
<Grid table-title="短信日志列表">
|
<Grid table-title="短信日志列表">
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<Button
|
<TableAction
|
||||||
type="primary"
|
:actions="[
|
||||||
class="ml-2"
|
{
|
||||||
@click="onExport"
|
label: $t('ui.actionTitle.export'),
|
||||||
v-access:code="['system:sms-log:export']"
|
type: 'primary',
|
||||||
>
|
icon: ACTION_ICON.DOWNLOAD,
|
||||||
<Download class="size-5" />
|
auth: ['system:sms-log:export'],
|
||||||
{{ $t('ui.actionTitle.export') }}
|
onClick: handleExport,
|
||||||
</Button>
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: $t('common.detail'),
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.VIEW,
|
||||||
|
auth: ['system:sms-log:query'],
|
||||||
|
onClick: handleDetail.bind(null, row),
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { SystemSmsTemplateApi } from '#/api/system/sms/template';
|
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
|
||||||
|
|
||||||
import { z } from '#/adapter/form';
|
import { z } from '#/adapter/form';
|
||||||
import { getSimpleSmsChannelList } from '#/api/system/sms/channel';
|
import { getSimpleSmsChannelList } from '#/api/system/sms/channel';
|
||||||
@@ -13,8 +10,6 @@ import {
|
|||||||
getRangePickerDefaultProps,
|
getRangePickerDefaultProps,
|
||||||
} from '#/utils';
|
} from '#/utils';
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
|
||||||
|
|
||||||
/** 新增/修改的表单 */
|
/** 新增/修改的表单 */
|
||||||
export function useFormSchema(): VbenFormSchema[] {
|
export function useFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
@@ -204,9 +199,7 @@ export function useSendSmsFormSchema(): VbenFormSchema[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns<T = SystemSmsTemplateApi.SmsTemplate>(
|
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
onActionClick: OnActionClickFn<T>,
|
|
||||||
): VxeTableGridOptions['columns'] {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
field: 'id',
|
field: 'id',
|
||||||
@@ -272,34 +265,10 @@ export function useGridColumns<T = SystemSmsTemplateApi.SmsTemplate>(
|
|||||||
formatter: 'formatDateTime',
|
formatter: 'formatDateTime',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'operation',
|
|
||||||
title: '操作',
|
title: '操作',
|
||||||
minWidth: 180,
|
width: 220,
|
||||||
align: 'center',
|
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
cellRender: {
|
slots: { default: 'actions' },
|
||||||
attrs: {
|
|
||||||
nameField: 'name',
|
|
||||||
nameTitle: '短信模板',
|
|
||||||
onClick: onActionClick,
|
|
||||||
},
|
|
||||||
name: 'CellOperation',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
code: 'edit',
|
|
||||||
show: hasAccessByCodes(['system:sms-template:update']),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'delete',
|
|
||||||
show: hasAccessByCodes(['system:sms-template:delete']),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'sms-send',
|
|
||||||
text: '发送短信',
|
|
||||||
show: hasAccessByCodes(['system:sms-template:send-sms']),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { SystemSmsTemplateApi } from '#/api/system/sms/template';
|
import type { SystemSmsTemplateApi } from '#/api/system/sms/template';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { Download, Plus } from '@vben/icons';
|
|
||||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||||
|
|
||||||
import { Button, message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import {
|
import {
|
||||||
deleteSmsTemplate,
|
deleteSmsTemplate,
|
||||||
exportSmsTemplate,
|
exportSmsTemplate,
|
||||||
@@ -40,69 +36,50 @@ function onRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 导出表格 */
|
/** 导出表格 */
|
||||||
async function onExport() {
|
async function handleExport() {
|
||||||
const data = await exportSmsTemplate(await gridApi.formApi.getValues());
|
const data = await exportSmsTemplate(await gridApi.formApi.getValues());
|
||||||
downloadFileFromBlobPart({ fileName: '短信模板.xls', source: data });
|
downloadFileFromBlobPart({ fileName: '短信模板.xls', source: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 创建短信模板 */
|
/** 创建短信模板 */
|
||||||
function onCreate() {
|
function handleCreate() {
|
||||||
formModalApi.setData(null).open();
|
formModalApi.setData(null).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编辑短信模板 */
|
/** 编辑短信模板 */
|
||||||
function onEdit(row: SystemSmsTemplateApi.SmsTemplate) {
|
function handleEdit(row: SystemSmsTemplateApi.SmsTemplate) {
|
||||||
formModalApi.setData(row).open();
|
formModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 发送测试短信 */
|
/** 发送测试短信 */
|
||||||
function onSend(row: SystemSmsTemplateApi.SmsTemplate) {
|
function handleSend(row: SystemSmsTemplateApi.SmsTemplate) {
|
||||||
sendModalApi.setData(row).open();
|
sendModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除短信模板 */
|
/** 删除短信模板 */
|
||||||
async function onDelete(row: SystemSmsTemplateApi.SmsTemplate) {
|
async function handleDelete(row: SystemSmsTemplateApi.SmsTemplate) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
duration: 0,
|
key: 'action_key_msg',
|
||||||
key: 'action_process_msg',
|
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteSmsTemplate(row.id as number);
|
await deleteSmsTemplate(row.id as number);
|
||||||
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
|
key: 'action_key_msg',
|
||||||
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} finally {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表格操作按钮的回调函数 */
|
|
||||||
function onActionClick({
|
|
||||||
code,
|
|
||||||
row,
|
|
||||||
}: OnActionClickParams<SystemSmsTemplateApi.SmsTemplate>) {
|
|
||||||
switch (code) {
|
|
||||||
case 'delete': {
|
|
||||||
onDelete(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'edit': {
|
|
||||||
onEdit(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'sms-send': {
|
|
||||||
onSend(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(onActionClick),
|
columns: useGridColumns(),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
@@ -137,23 +114,55 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
<SendModal />
|
<SendModal />
|
||||||
<Grid table-title="短信模板列表">
|
<Grid table-title="短信模板列表">
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<Button
|
<TableAction
|
||||||
type="primary"
|
:actions="[
|
||||||
@click="onCreate"
|
{
|
||||||
v-access:code="['system:sms-template:create']"
|
label: $t('ui.actionTitle.create', ['短信模板']),
|
||||||
>
|
type: 'primary',
|
||||||
<Plus class="size-5" />
|
icon: ACTION_ICON.ADD,
|
||||||
{{ $t('ui.actionTitle.create', ['短信模板']) }}
|
auth: ['system:sms-template:create'],
|
||||||
</Button>
|
onClick: handleCreate,
|
||||||
<Button
|
},
|
||||||
type="primary"
|
{
|
||||||
class="ml-2"
|
label: $t('ui.actionTitle.export'),
|
||||||
@click="onExport"
|
type: 'primary',
|
||||||
v-access:code="['system:sms-template:export']"
|
icon: ACTION_ICON.DOWNLOAD,
|
||||||
>
|
auth: ['system:sms-template:export'],
|
||||||
<Download class="size-5" />
|
onClick: handleExport,
|
||||||
{{ $t('ui.actionTitle.export') }}
|
},
|
||||||
</Button>
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: $t('common.edit'),
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.EDIT,
|
||||||
|
auth: ['system:sms-template:update'],
|
||||||
|
onClick: handleEdit.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '发送短信',
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.ADD,
|
||||||
|
auth: ['system:sms-template:send-sms'],
|
||||||
|
onClick: handleSend.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('common.delete'),
|
||||||
|
type: 'link',
|
||||||
|
danger: true,
|
||||||
|
icon: ACTION_ICON.DELETE,
|
||||||
|
auth: ['system:sms-template:delete'],
|
||||||
|
popConfirm: {
|
||||||
|
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||||
|
confirm: handleDelete.bind(null, row),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { SystemSocialClientApi } from '#/api/system/social/client';
|
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
|
||||||
|
|
||||||
import { z } from '#/adapter/form';
|
import { z } from '#/adapter/form';
|
||||||
import {
|
import {
|
||||||
@@ -12,8 +9,6 @@ import {
|
|||||||
SystemUserSocialTypeEnum,
|
SystemUserSocialTypeEnum,
|
||||||
} from '#/utils';
|
} from '#/utils';
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
|
||||||
|
|
||||||
/** 新增/修改的表单 */
|
/** 新增/修改的表单 */
|
||||||
export function useFormSchema(): VbenFormSchema[] {
|
export function useFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
@@ -152,9 +147,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns<T = SystemSocialClientApi.SocialClient>(
|
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
onActionClick: OnActionClickFn<T>,
|
|
||||||
): VxeTableGridOptions['columns'] {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
field: 'id',
|
field: 'id',
|
||||||
@@ -205,29 +198,10 @@ export function useGridColumns<T = SystemSocialClientApi.SocialClient>(
|
|||||||
formatter: 'formatDateTime',
|
formatter: 'formatDateTime',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'operation',
|
|
||||||
title: '操作',
|
title: '操作',
|
||||||
minWidth: 130,
|
width: 130,
|
||||||
align: 'center',
|
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
cellRender: {
|
slots: { default: 'actions' },
|
||||||
attrs: {
|
|
||||||
nameField: 'name',
|
|
||||||
nameTitle: '社交客户端',
|
|
||||||
onClick: onActionClick,
|
|
||||||
},
|
|
||||||
name: 'CellOperation',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
code: 'edit',
|
|
||||||
show: hasAccessByCodes(['system:social-client:update']),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: 'delete',
|
|
||||||
show: hasAccessByCodes(['system:social-client:delete']),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { SystemSocialClientApi } from '#/api/system/social/client';
|
import type { SystemSocialClientApi } from '#/api/system/social/client';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { Plus } from '@vben/icons';
|
|
||||||
|
|
||||||
import { Button, message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import {
|
import {
|
||||||
deleteSocialClient,
|
deleteSocialClient,
|
||||||
getSocialClientPage,
|
getSocialClientPage,
|
||||||
@@ -32,54 +28,39 @@ function onRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 创建社交客户端 */
|
/** 创建社交客户端 */
|
||||||
function onCreate() {
|
function handleCreate() {
|
||||||
formModalApi.setData(null).open();
|
formModalApi.setData(null).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编辑社交客户端 */
|
/** 编辑社交客户端 */
|
||||||
function onEdit(row: SystemSocialClientApi.SocialClient) {
|
function handleEdit(row: SystemSocialClientApi.SocialClient) {
|
||||||
formModalApi.setData(row).open();
|
formModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除社交客户端 */
|
/** 删除社交客户端 */
|
||||||
async function onDelete(row: SystemSocialClientApi.SocialClient) {
|
async function handleDelete(row: SystemSocialClientApi.SocialClient) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
duration: 0,
|
key: 'action_key_msg',
|
||||||
key: 'action_process_msg',
|
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteSocialClient(row.id as number);
|
await deleteSocialClient(row.id as number);
|
||||||
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
|
key: 'action_key_msg',
|
||||||
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} catch {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表格操作按钮的回调函数 */
|
|
||||||
function onActionClick({
|
|
||||||
code,
|
|
||||||
row,
|
|
||||||
}: OnActionClickParams<SystemSocialClientApi.SocialClient>) {
|
|
||||||
switch (code) {
|
|
||||||
case 'delete': {
|
|
||||||
onDelete(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'edit': {
|
|
||||||
onEdit(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(onActionClick),
|
columns: useGridColumns(),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
@@ -113,14 +94,41 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
<FormModal @success="onRefresh" />
|
<FormModal @success="onRefresh" />
|
||||||
<Grid table-title="社交客户端列表">
|
<Grid table-title="社交客户端列表">
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<Button
|
<TableAction
|
||||||
type="primary"
|
:actions="[
|
||||||
@click="onCreate"
|
{
|
||||||
v-access:code="['system:social-client:create']"
|
label: $t('ui.actionTitle.create', ['社交客户端']),
|
||||||
>
|
type: 'primary',
|
||||||
<Plus class="size-5" />
|
icon: ACTION_ICON.ADD,
|
||||||
{{ $t('ui.actionTitle.create', ['社交客户端']) }}
|
auth: ['system:social-client:create'],
|
||||||
</Button>
|
onClick: handleCreate,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: $t('common.edit'),
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.EDIT,
|
||||||
|
auth: ['system:social-client:update'],
|
||||||
|
onClick: handleEdit.bind(null, row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: $t('common.delete'),
|
||||||
|
type: 'link',
|
||||||
|
danger: true,
|
||||||
|
icon: ACTION_ICON.DELETE,
|
||||||
|
auth: ['system:social-client:delete'],
|
||||||
|
popConfirm: {
|
||||||
|
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||||
|
confirm: handleDelete.bind(null, row),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
import type { VbenFormSchema } from '#/adapter/form';
|
import type { VbenFormSchema } from '#/adapter/form';
|
||||||
import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
import type { SystemSocialUserApi } from '#/api/system/social/user';
|
|
||||||
|
|
||||||
import { useAccess } from '@vben/access';
|
|
||||||
|
|
||||||
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
|
import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils';
|
||||||
|
|
||||||
const { hasAccessByCodes } = useAccess();
|
|
||||||
|
|
||||||
/** 列表的搜索表单 */
|
/** 列表的搜索表单 */
|
||||||
export function useGridFormSchema(): VbenFormSchema[] {
|
export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
@@ -52,9 +47,7 @@ export function useGridFormSchema(): VbenFormSchema[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 列表的字段 */
|
/** 列表的字段 */
|
||||||
export function useGridColumns<T = SystemSocialUserApi.SocialUser>(
|
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||||
onActionClick: OnActionClickFn<T>,
|
|
||||||
): VxeTableGridOptions['columns'] {
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
field: 'type',
|
field: 'type',
|
||||||
@@ -96,26 +89,10 @@ export function useGridColumns<T = SystemSocialUserApi.SocialUser>(
|
|||||||
formatter: 'formatDateTime',
|
formatter: 'formatDateTime',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'operation',
|
|
||||||
title: '操作',
|
title: '操作',
|
||||||
minWidth: 100,
|
width: 80,
|
||||||
align: 'center',
|
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
cellRender: {
|
slots: { default: 'actions' },
|
||||||
attrs: {
|
|
||||||
nameField: 'nickname',
|
|
||||||
nameTitle: '社交用户',
|
|
||||||
onClick: onActionClick,
|
|
||||||
},
|
|
||||||
name: 'CellOperation',
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
code: 'detail',
|
|
||||||
text: '详情',
|
|
||||||
show: hasAccessByCodes(['system:social-user:query']),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type {
|
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||||
OnActionClickParams,
|
|
||||||
VxeTableGridOptions,
|
|
||||||
} from '#/adapter/vxe-table';
|
|
||||||
import type { SystemSocialUserApi } from '#/api/system/social/user';
|
import type { SystemSocialUserApi } from '#/api/system/social/user';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { getSocialUserPage } from '#/api/system/social/user';
|
import { getSocialUserPage } from '#/api/system/social/user';
|
||||||
import { DocAlert } from '#/components/doc-alert';
|
import { DocAlert } from '#/components/doc-alert';
|
||||||
|
|
||||||
@@ -19,35 +16,17 @@ const [DetailModal, detailModalApi] = useVbenModal({
|
|||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
/** 刷新表格 */
|
|
||||||
// function onRefresh() {
|
|
||||||
// gridApi.query();
|
|
||||||
// }
|
|
||||||
|
|
||||||
/** 查看详情 */
|
/** 查看详情 */
|
||||||
function onDetail(row: SystemSocialUserApi.SocialUser) {
|
function handleDetail(row: SystemSocialUserApi.SocialUser) {
|
||||||
detailModalApi.setData(row).open();
|
detailModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表格操作按钮的回调函数 */
|
|
||||||
function onActionClick({
|
|
||||||
code,
|
|
||||||
row,
|
|
||||||
}: OnActionClickParams<SystemSocialUserApi.SocialUser>) {
|
|
||||||
switch (code) {
|
|
||||||
case 'detail': {
|
|
||||||
onDetail(row);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [Grid] = useVbenVxeGrid({
|
const [Grid] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(onActionClick),
|
columns: useGridColumns(),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
@@ -79,6 +58,20 @@ const [Grid] = useVbenVxeGrid({
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<DetailModal />
|
<DetailModal />
|
||||||
<Grid table-title="社交用户列表" />
|
<Grid table-title="社交用户列表">
|
||||||
|
<template #actions="{ row }">
|
||||||
|
<TableAction
|
||||||
|
:actions="[
|
||||||
|
{
|
||||||
|
label: $t('common.detail'),
|
||||||
|
type: 'link',
|
||||||
|
icon: ACTION_ICON.VIEW,
|
||||||
|
auth: ['system:social-user:query'],
|
||||||
|
onClick: handleDetail.bind(null, row),
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ import type { SystemTenantPackageApi } from '#/api/system/tenant-package';
|
|||||||
import { onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { Download, Plus } from '@vben/icons';
|
|
||||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||||
|
|
||||||
import { Button, message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import { deleteTenant, exportTenant, getTenantPage } from '#/api/system/tenant';
|
import { deleteTenant, exportTenant, getTenantPage } from '#/api/system/tenant';
|
||||||
import { getTenantPackageList } from '#/api/system/tenant-package';
|
import { getTenantPackageList } from '#/api/system/tenant-package';
|
||||||
import { DocAlert } from '#/components/doc-alert';
|
import { DocAlert } from '#/components/doc-alert';
|
||||||
@@ -41,33 +40,35 @@ function onRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 导出表格 */
|
/** 导出表格 */
|
||||||
async function onExport() {
|
async function handleExport() {
|
||||||
const data = await exportTenant(await gridApi.formApi.getValues());
|
const data = await exportTenant(await gridApi.formApi.getValues());
|
||||||
downloadFileFromBlobPart({ fileName: '租户.xls', source: data });
|
downloadFileFromBlobPart({ fileName: '租户.xls', source: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 创建租户 */
|
/** 创建租户 */
|
||||||
function onCreate() {
|
function handleCreate() {
|
||||||
formModalApi.setData(null).open();
|
formModalApi.setData(null).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编辑租户 */
|
/** 编辑租户 */
|
||||||
function onEdit(row: SystemTenantApi.Tenant) {
|
function handleEdit(row: SystemTenantApi.Tenant) {
|
||||||
formModalApi.setData(row).open();
|
formModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除租户 */
|
/** 删除租户 */
|
||||||
async function onDelete(row: SystemTenantApi.Tenant) {
|
async function handleDelete(row: SystemTenantApi.Tenant) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
duration: 0,
|
key: 'action_key_msg',
|
||||||
key: 'action_process_msg',
|
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteTenant(row.id as number);
|
await deleteTenant(row.id as number);
|
||||||
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
|
key: 'action_key_msg',
|
||||||
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} catch {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,8 +79,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(getPackageName),
|
columns: useGridColumns(getPackageName),
|
||||||
height: 'auto',
|
|
||||||
keepSource: true,
|
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
ajax: {
|
ajax: {
|
||||||
query: async ({ page }, formValues) => {
|
query: async ({ page }, formValues) => {
|
||||||
@@ -115,23 +114,24 @@ onMounted(async () => {
|
|||||||
<FormModal @success="onRefresh" />
|
<FormModal @success="onRefresh" />
|
||||||
<Grid table-title="租户列表">
|
<Grid table-title="租户列表">
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<Button
|
<TableAction
|
||||||
type="primary"
|
:actions="[
|
||||||
@click="onCreate"
|
{
|
||||||
v-access:code="['system:tenant:create']"
|
label: $t('ui.actionTitle.create', ['租户']),
|
||||||
>
|
type: 'primary',
|
||||||
<Plus class="size-5" />
|
icon: ACTION_ICON.ADD,
|
||||||
{{ $t('ui.actionTitle.create', ['租户']) }}
|
auth: ['system:tenant:create'],
|
||||||
</Button>
|
onClick: handleCreate,
|
||||||
<Button
|
},
|
||||||
type="primary"
|
{
|
||||||
class="ml-2"
|
label: $t('ui.actionTitle.export'),
|
||||||
@click="onExport"
|
type: 'primary',
|
||||||
v-access:code="['system:tenant:export']"
|
icon: ACTION_ICON.DOWNLOAD,
|
||||||
>
|
auth: ['system:tenant:export'],
|
||||||
<Download class="size-5" />
|
onClick: handleExport,
|
||||||
{{ $t('ui.actionTitle.export') }}
|
},
|
||||||
</Button>
|
]"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #actions="{ row }">
|
<template #actions="{ row }">
|
||||||
<TableAction
|
<TableAction
|
||||||
@@ -139,19 +139,19 @@ onMounted(async () => {
|
|||||||
{
|
{
|
||||||
label: $t('common.edit'),
|
label: $t('common.edit'),
|
||||||
type: 'link',
|
type: 'link',
|
||||||
icon: 'ant-design:edit-outlined',
|
icon: ACTION_ICON.EDIT,
|
||||||
auth: ['system:role:update'],
|
auth: ['system:role:update'],
|
||||||
onClick: onEdit.bind(null, row),
|
onClick: handleEdit.bind(null, row),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: $t('common.delete'),
|
label: $t('common.delete'),
|
||||||
type: 'link',
|
type: 'link',
|
||||||
danger: true,
|
danger: true,
|
||||||
icon: 'ant-design:delete-outlined',
|
icon: ACTION_ICON.DELETE,
|
||||||
auth: ['system:role:delete'],
|
auth: ['system:role:delete'],
|
||||||
popConfirm: {
|
popConfirm: {
|
||||||
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||||
confirm: onDelete.bind(null, row),
|
confirm: handleDelete.bind(null, row),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
|
|||||||
@@ -3,11 +3,10 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
|||||||
import type { SystemTenantPackageApi } from '#/api/system/tenant-package';
|
import type { SystemTenantPackageApi } from '#/api/system/tenant-package';
|
||||||
|
|
||||||
import { Page, useVbenModal } from '@vben/common-ui';
|
import { Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { Plus } from '@vben/icons';
|
|
||||||
|
|
||||||
import { Button, message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import {
|
import {
|
||||||
deleteTenantPackage,
|
deleteTenantPackage,
|
||||||
getTenantPackagePage,
|
getTenantPackagePage,
|
||||||
@@ -29,27 +28,29 @@ function onRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 创建租户套餐 */
|
/** 创建租户套餐 */
|
||||||
function onCreate() {
|
function handleCreate() {
|
||||||
formModalApi.setData(null).open();
|
formModalApi.setData(null).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编辑租户套餐 */
|
/** 编辑租户套餐 */
|
||||||
function onEdit(row: SystemTenantPackageApi.TenantPackage) {
|
function handleEdit(row: SystemTenantPackageApi.TenantPackage) {
|
||||||
formModalApi.setData(row).open();
|
formModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除租户套餐 */
|
/** 删除租户套餐 */
|
||||||
async function onDelete(row: SystemTenantPackageApi.TenantPackage) {
|
async function handleDelete(row: SystemTenantPackageApi.TenantPackage) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||||
duration: 0,
|
key: 'action_key_msg',
|
||||||
key: 'action_process_msg',
|
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteTenantPackage(row.id as number);
|
await deleteTenantPackage(row.id as number);
|
||||||
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||||
|
key: 'action_key_msg',
|
||||||
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} catch {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,14 +95,17 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
<FormModal @success="onRefresh" />
|
<FormModal @success="onRefresh" />
|
||||||
<Grid table-title="租户套餐列表">
|
<Grid table-title="租户套餐列表">
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<Button
|
<TableAction
|
||||||
type="primary"
|
:actions="[
|
||||||
@click="onCreate"
|
{
|
||||||
v-access:code="['system:tenant-package:create']"
|
label: $t('ui.actionTitle.create', ['套餐']),
|
||||||
>
|
type: 'primary',
|
||||||
<Plus class="size-5" />
|
icon: ACTION_ICON.ADD,
|
||||||
{{ $t('ui.actionTitle.create', ['套餐']) }}
|
auth: ['system:tenant-package:create'],
|
||||||
</Button>
|
onClick: handleCreate,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #actions="{ row }">
|
<template #actions="{ row }">
|
||||||
<TableAction
|
<TableAction
|
||||||
@@ -109,19 +113,19 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
{
|
{
|
||||||
label: $t('common.edit'),
|
label: $t('common.edit'),
|
||||||
type: 'link',
|
type: 'link',
|
||||||
icon: 'ant-design:edit-outlined',
|
icon: ACTION_ICON.EDIT,
|
||||||
auth: ['system:role:update'],
|
auth: ['system:role:update'],
|
||||||
onClick: onEdit.bind(null, row),
|
onClick: handleEdit.bind(null, row),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: $t('common.delete'),
|
label: $t('common.delete'),
|
||||||
type: 'link',
|
type: 'link',
|
||||||
danger: true,
|
danger: true,
|
||||||
icon: 'ant-design:delete-outlined',
|
icon: ACTION_ICON.DELETE,
|
||||||
auth: ['system:role:delete'],
|
auth: ['system:role:delete'],
|
||||||
popConfirm: {
|
popConfirm: {
|
||||||
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||||
confirm: onDelete.bind(null, row),
|
confirm: handleDelete.bind(null, row),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ export function useGridColumns<T = SystemUserApi.User>(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
width: 160,
|
width: 180,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
slots: { default: 'actions' },
|
slots: { default: 'actions' },
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ import type { SystemUserApi } from '#/api/system/user';
|
|||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
|
||||||
import { confirm, Page, useVbenModal } from '@vben/common-ui';
|
import { confirm, Page, useVbenModal } from '@vben/common-ui';
|
||||||
import { Download, Plus, Upload } from '@vben/icons';
|
|
||||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||||
|
|
||||||
import { Button, message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||||
import {
|
import {
|
||||||
deleteUser,
|
deleteUser,
|
||||||
exportUser,
|
exportUser,
|
||||||
@@ -19,7 +18,6 @@ import {
|
|||||||
updateUserStatus,
|
updateUserStatus,
|
||||||
} from '#/api/system/user';
|
} from '#/api/system/user';
|
||||||
import { DocAlert } from '#/components/doc-alert';
|
import { DocAlert } from '#/components/doc-alert';
|
||||||
import { TableAction } from '#/components/table-action';
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
import { DICT_TYPE, getDictLabel } from '#/utils';
|
import { DICT_TYPE, getDictLabel } from '#/utils';
|
||||||
|
|
||||||
@@ -56,61 +54,64 @@ function onRefresh() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 导出表格 */
|
/** 导出表格 */
|
||||||
async function onExport() {
|
async function handleExport() {
|
||||||
const data = await exportUser(await gridApi.formApi.getValues());
|
const data = await exportUser(await gridApi.formApi.getValues());
|
||||||
downloadFileFromBlobPart({ fileName: '用户.xls', source: data });
|
downloadFileFromBlobPart({ fileName: '用户.xls', source: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 选择部门 */
|
/** 选择部门 */
|
||||||
const searchDeptId = ref<number | undefined>(undefined);
|
const searchDeptId = ref<number | undefined>(undefined);
|
||||||
async function onDeptSelect(dept: SystemDeptApi.Dept) {
|
|
||||||
|
async function handleDeptSelect(dept: SystemDeptApi.Dept) {
|
||||||
searchDeptId.value = dept.id;
|
searchDeptId.value = dept.id;
|
||||||
onRefresh();
|
onRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 创建用户 */
|
/** 创建用户 */
|
||||||
function onCreate() {
|
function handleCreate() {
|
||||||
formModalApi.setData(null).open();
|
formModalApi.setData(null).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 导入用户 */
|
/** 导入用户 */
|
||||||
function onImport() {
|
function handleImport() {
|
||||||
importModalApi.open();
|
importModalApi.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 编辑用户 */
|
/** 编辑用户 */
|
||||||
function onEdit(row: SystemUserApi.User) {
|
function handleEdit(row: SystemUserApi.User) {
|
||||||
formModalApi.setData(row).open();
|
formModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除用户 */
|
/** 删除用户 */
|
||||||
async function onDelete(row: SystemUserApi.User) {
|
async function handleDelete(row: SystemUserApi.User) {
|
||||||
const hideLoading = message.loading({
|
const hideLoading = message.loading({
|
||||||
content: $t('ui.actionMessage.deleting', [row.username]),
|
content: $t('ui.actionMessage.deleting', [row.username]),
|
||||||
duration: 0,
|
key: 'action_key_msg',
|
||||||
key: 'action_process_msg',
|
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await deleteUser(row.id as number);
|
await deleteUser(row.id as number);
|
||||||
message.success($t('ui.actionMessage.deleteSuccess', [row.username]));
|
message.success({
|
||||||
|
content: $t('ui.actionMessage.deleteSuccess', [row.username]),
|
||||||
|
key: 'action_key_msg',
|
||||||
|
});
|
||||||
onRefresh();
|
onRefresh();
|
||||||
} catch {
|
} finally {
|
||||||
hideLoading();
|
hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 重置密码 */
|
/** 重置密码 */
|
||||||
function onResetPassword(row: SystemUserApi.User) {
|
function handleResetPassword(row: SystemUserApi.User) {
|
||||||
resetPasswordModalApi.setData(row).open();
|
resetPasswordModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 分配角色 */
|
/** 分配角色 */
|
||||||
function onAssignRole(row: SystemUserApi.User) {
|
function handleAssignRole(row: SystemUserApi.User) {
|
||||||
assignRoleModalApi.setData(row).open();
|
assignRoleModalApi.setData(row).open();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 更新用户状态 */
|
/** 更新用户状态 */
|
||||||
async function onStatusChange(
|
async function handleStatusChange(
|
||||||
newStatus: number,
|
newStatus: number,
|
||||||
row: SystemUserApi.User,
|
row: SystemUserApi.User,
|
||||||
): Promise<boolean | undefined> {
|
): Promise<boolean | undefined> {
|
||||||
@@ -140,7 +141,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
},
|
},
|
||||||
gridOptions: {
|
gridOptions: {
|
||||||
columns: useGridColumns(onStatusChange),
|
columns: useGridColumns(handleStatusChange),
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
keepSource: true,
|
keepSource: true,
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
@@ -185,38 +186,37 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
<div class="flex h-full w-full">
|
<div class="flex h-full w-full">
|
||||||
<!-- 左侧部门树 -->
|
<!-- 左侧部门树 -->
|
||||||
<div class="h-full w-1/6 pr-4">
|
<div class="h-full w-1/6 pr-4">
|
||||||
<DeptTree @select="onDeptSelect" />
|
<DeptTree @select="handleDeptSelect" />
|
||||||
</div>
|
</div>
|
||||||
<!-- 右侧用户列表 -->
|
<!-- 右侧用户列表 -->
|
||||||
<div class="w-5/6">
|
<div class="w-5/6">
|
||||||
<Grid table-title="用户列表">
|
<Grid table-title="用户列表">
|
||||||
<template #toolbar-tools>
|
<template #toolbar-tools>
|
||||||
<Button
|
<TableAction
|
||||||
type="primary"
|
:actions="[
|
||||||
@click="onCreate"
|
{
|
||||||
v-access:code="['system:user:create']"
|
label: $t('ui.actionTitle.create', ['用户']),
|
||||||
>
|
type: 'primary',
|
||||||
<Plus class="size-5" />
|
icon: ACTION_ICON.ADD,
|
||||||
{{ $t('ui.actionTitle.create', ['用户']) }}
|
auth: ['system:user:create'],
|
||||||
</Button>
|
onClick: handleCreate,
|
||||||
<Button
|
},
|
||||||
type="primary"
|
{
|
||||||
class="ml-2"
|
label: $t('ui.actionTitle.export'),
|
||||||
@click="onExport"
|
type: 'primary',
|
||||||
v-access:code="['system:user:export']"
|
icon: ACTION_ICON.DOWNLOAD,
|
||||||
>
|
auth: ['system:user:export'],
|
||||||
<Download class="size-5" />
|
onClick: handleExport,
|
||||||
{{ $t('ui.actionTitle.export') }}
|
},
|
||||||
</Button>
|
{
|
||||||
<Button
|
label: $t('ui.actionTitle.import', ['用户']),
|
||||||
type="primary"
|
type: 'primary',
|
||||||
class="ml-2"
|
icon: ACTION_ICON.UPLOAD,
|
||||||
@click="onImport"
|
auth: ['system:user:import'],
|
||||||
v-access:code="['system:user:import']"
|
onClick: handleImport,
|
||||||
>
|
},
|
||||||
<Upload class="size-5" />
|
]"
|
||||||
{{ $t('ui.actionTitle.import', ['用户']) }}
|
/>
|
||||||
</Button>
|
|
||||||
</template>
|
</template>
|
||||||
<template #actions="{ row }">
|
<template #actions="{ row }">
|
||||||
<TableAction
|
<TableAction
|
||||||
@@ -224,34 +224,34 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
|||||||
{
|
{
|
||||||
label: $t('common.edit'),
|
label: $t('common.edit'),
|
||||||
type: 'link',
|
type: 'link',
|
||||||
icon: 'ant-design:edit-outlined',
|
icon: ACTION_ICON.EDIT,
|
||||||
auth: ['system:user:update'],
|
auth: ['system:user:update'],
|
||||||
onClick: onEdit.bind(null, row),
|
onClick: handleEdit.bind(null, row),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: $t('common.delete'),
|
label: $t('common.delete'),
|
||||||
type: 'link',
|
type: 'link',
|
||||||
danger: true,
|
danger: true,
|
||||||
icon: 'ant-design:delete-outlined',
|
icon: ACTION_ICON.DELETE,
|
||||||
auth: ['system:user:delete'],
|
auth: ['system:user:delete'],
|
||||||
popConfirm: {
|
popConfirm: {
|
||||||
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
title: $t('ui.actionMessage.deleteConfirm', [row.name]),
|
||||||
confirm: onDelete.bind(null, row),
|
confirm: handleDelete.bind(null, row),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
:drop-down-actions="[
|
:drop-down-actions="[
|
||||||
{
|
{
|
||||||
label: '数据权限',
|
label: '分配角色',
|
||||||
type: 'link',
|
type: 'link',
|
||||||
auth: ['system:permission:assign-user-role'],
|
auth: ['system:permission:assign-user-role'],
|
||||||
onClick: onAssignRole.bind(null, row),
|
onClick: handleAssignRole.bind(null, row),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '菜单权限',
|
label: '重置密码',
|
||||||
type: 'link',
|
type: 'link',
|
||||||
auth: ['system:user:update-password'],
|
auth: ['system:user:update-password'],
|
||||||
onClick: onResetPassword.bind(null, row),
|
onClick: handleResetPassword.bind(null, row),
|
||||||
},
|
},
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ function beforeUpload(file: FileType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 下载模版 */
|
/** 下载模版 */
|
||||||
async function onDownload() {
|
async function handleDownload() {
|
||||||
const data = await importUserTemplate();
|
const data = await importUserTemplate();
|
||||||
downloadFileFromBlobPart({ fileName: '用户导入模板.xls', source: data });
|
downloadFileFromBlobPart({ fileName: '用户导入模板.xls', source: data });
|
||||||
}
|
}
|
||||||
@@ -78,7 +78,7 @@ async function onDownload() {
|
|||||||
</Form>
|
</Form>
|
||||||
<template #prepend-footer>
|
<template #prepend-footer>
|
||||||
<div class="flex flex-auto items-center">
|
<div class="flex flex-auto items-center">
|
||||||
<Button @click="onDownload"> 下载导入模板 </Button>
|
<Button @click="handleDownload"> 下载导入模板 </Button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -186,6 +186,12 @@ const defaultPreferences: Preferences = {
|
|||||||
colorWeakMode: false,
|
colorWeakMode: false,
|
||||||
compact: false,
|
compact: false,
|
||||||
contentCompact: 'wide',
|
contentCompact: 'wide',
|
||||||
|
contentCompactWidth: 1200,
|
||||||
|
contentPadding: 0,
|
||||||
|
contentPaddingBottom: 0,
|
||||||
|
contentPaddingLeft: 0,
|
||||||
|
contentPaddingRight: 0,
|
||||||
|
contentPaddingTop: 0,
|
||||||
defaultAvatar:
|
defaultAvatar:
|
||||||
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
|
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
|
||||||
defaultHomePath: '/analytics',
|
defaultHomePath: '/analytics',
|
||||||
@@ -200,6 +206,7 @@ const defaultPreferences: Preferences = {
|
|||||||
name: 'Vben Admin',
|
name: 'Vben Admin',
|
||||||
preferencesButtonPosition: 'auto',
|
preferencesButtonPosition: 'auto',
|
||||||
watermark: false,
|
watermark: false,
|
||||||
|
zIndex: 200,
|
||||||
},
|
},
|
||||||
breadcrumb: {
|
breadcrumb: {
|
||||||
enable: true,
|
enable: true,
|
||||||
@@ -220,9 +227,11 @@ const defaultPreferences: Preferences = {
|
|||||||
footer: {
|
footer: {
|
||||||
enable: false,
|
enable: false,
|
||||||
fixed: false,
|
fixed: false,
|
||||||
|
height: 32,
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
enable: true,
|
enable: true,
|
||||||
|
height: 50,
|
||||||
hidden: false,
|
hidden: false,
|
||||||
menuAlign: 'start',
|
menuAlign: 'start',
|
||||||
mode: 'fixed',
|
mode: 'fixed',
|
||||||
@@ -248,11 +257,14 @@ const defaultPreferences: Preferences = {
|
|||||||
collapsed: false,
|
collapsed: false,
|
||||||
collapsedButton: true,
|
collapsedButton: true,
|
||||||
collapsedShowTitle: false,
|
collapsedShowTitle: false,
|
||||||
|
collapseWidth: 60,
|
||||||
enable: true,
|
enable: true,
|
||||||
expandOnHover: true,
|
expandOnHover: true,
|
||||||
extraCollapse: false,
|
extraCollapse: false,
|
||||||
|
extraCollapsedWidth: 60,
|
||||||
fixedButton: true,
|
fixedButton: true,
|
||||||
hidden: false,
|
hidden: false,
|
||||||
|
mixedWidth: 80,
|
||||||
width: 224,
|
width: 224,
|
||||||
},
|
},
|
||||||
tabbar: {
|
tabbar: {
|
||||||
@@ -319,6 +331,18 @@ interface AppPreferences {
|
|||||||
compact: boolean;
|
compact: boolean;
|
||||||
/** Whether to enable content compact mode */
|
/** Whether to enable content compact mode */
|
||||||
contentCompact: ContentCompactType;
|
contentCompact: ContentCompactType;
|
||||||
|
/** Content compact width */
|
||||||
|
contentCompactWidth: number;
|
||||||
|
/** Content padding */
|
||||||
|
contentPadding: number;
|
||||||
|
/** Content bottom padding */
|
||||||
|
contentPaddingBottom: number;
|
||||||
|
/** Content left padding */
|
||||||
|
contentPaddingLeft: number;
|
||||||
|
/** Content right padding */
|
||||||
|
contentPaddingRight: number;
|
||||||
|
/** Content top padding */
|
||||||
|
contentPaddingTop: number;
|
||||||
// /** Default application avatar */
|
// /** Default application avatar */
|
||||||
defaultAvatar: string;
|
defaultAvatar: string;
|
||||||
/** Default homepage path */
|
/** Default homepage path */
|
||||||
@@ -349,6 +373,8 @@ interface AppPreferences {
|
|||||||
* @zh_CN Whether to enable watermark
|
* @zh_CN Whether to enable watermark
|
||||||
*/
|
*/
|
||||||
watermark: boolean;
|
watermark: boolean;
|
||||||
|
/** z-index */
|
||||||
|
zIndex: number;
|
||||||
}
|
}
|
||||||
interface BreadcrumbPreferences {
|
interface BreadcrumbPreferences {
|
||||||
/** Whether breadcrumbs are enabled */
|
/** Whether breadcrumbs are enabled */
|
||||||
@@ -385,11 +411,15 @@ interface FooterPreferences {
|
|||||||
enable: boolean;
|
enable: boolean;
|
||||||
/** Whether the footer is fixed */
|
/** Whether the footer is fixed */
|
||||||
fixed: boolean;
|
fixed: boolean;
|
||||||
|
/** Footer height */
|
||||||
|
height: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HeaderPreferences {
|
interface HeaderPreferences {
|
||||||
/** Whether the header is enabled */
|
/** Whether the header is enabled */
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
|
/** Header height */
|
||||||
|
height: number;
|
||||||
/** Whether the header is hidden, css-hidden */
|
/** Whether the header is hidden, css-hidden */
|
||||||
hidden: boolean;
|
hidden: boolean;
|
||||||
/** Header menu alignment */
|
/** Header menu alignment */
|
||||||
@@ -422,16 +452,22 @@ interface SidebarPreferences {
|
|||||||
collapsedButton: boolean;
|
collapsedButton: boolean;
|
||||||
/** Whether to show title when sidebar is collapsed */
|
/** Whether to show title when sidebar is collapsed */
|
||||||
collapsedShowTitle: boolean;
|
collapsedShowTitle: boolean;
|
||||||
|
/** Sidebar collapse width */
|
||||||
|
collapseWidth: number;
|
||||||
/** Whether the sidebar is visible */
|
/** Whether the sidebar is visible */
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
/** Menu auto-expand state */
|
/** Menu auto-expand state */
|
||||||
expandOnHover: boolean;
|
expandOnHover: boolean;
|
||||||
/** Whether the sidebar extension area is collapsed */
|
/** Whether the sidebar extension area is collapsed */
|
||||||
extraCollapse: boolean;
|
extraCollapse: boolean;
|
||||||
|
/** Sidebar extension area collapse width */
|
||||||
|
extraCollapsedWidth: number;
|
||||||
/** Whether the sidebar fixed button is visible */
|
/** Whether the sidebar fixed button is visible */
|
||||||
fixedButton: boolean;
|
fixedButton: boolean;
|
||||||
/** Whether the sidebar is hidden - css */
|
/** Whether the sidebar is hidden - css */
|
||||||
hidden: boolean;
|
hidden: boolean;
|
||||||
|
/** Mixed sidebar width */
|
||||||
|
mixedWidth: number;
|
||||||
/** Sidebar width */
|
/** Sidebar width */
|
||||||
width: number;
|
width: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ server {
|
|||||||
|
|
||||||
使用 nginx 处理项目部署后的跨域问题
|
使用 nginx 处理项目部署后的跨域问题
|
||||||
|
|
||||||
1. 配置前端项目接口地址,在项目目录下的``.env.production`文件中配置:
|
1. 配置前端项目接口地址,在项目目录下的`.env.production`文件中配置:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
VITE_GLOB_API_URL=/api
|
VITE_GLOB_API_URL=/api
|
||||||
|
|||||||
@@ -339,6 +339,10 @@ interface RouteMeta {
|
|||||||
| 'success'
|
| 'success'
|
||||||
| 'warning'
|
| 'warning'
|
||||||
| string;
|
| string;
|
||||||
|
/**
|
||||||
|
* 路由的完整路径作为key(默认true)
|
||||||
|
*/
|
||||||
|
fullPathKey?: boolean;
|
||||||
/**
|
/**
|
||||||
* 当前路由的子级在菜单中不展现
|
* 当前路由的子级在菜单中不展现
|
||||||
* @default false
|
* @default false
|
||||||
@@ -502,6 +506,13 @@ interface RouteMeta {
|
|||||||
|
|
||||||
用于配置页面的徽标颜色。
|
用于配置页面的徽标颜色。
|
||||||
|
|
||||||
|
### fullPathKey
|
||||||
|
|
||||||
|
- 类型:`boolean`
|
||||||
|
- 默认值:`true`
|
||||||
|
|
||||||
|
是否将路由的完整路径作为tab key(默认true)
|
||||||
|
|
||||||
### activePath
|
### activePath
|
||||||
|
|
||||||
- 类型:`string`
|
- 类型:`string`
|
||||||
@@ -602,3 +613,32 @@ const { refresh } = useRefresh();
|
|||||||
refresh();
|
refresh();
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 标签页与路由控制
|
||||||
|
|
||||||
|
在某些场景下,需要单个路由打开多个标签页,或者修改路由的query不打开新的标签页
|
||||||
|
|
||||||
|
每个标签页Tab使用唯一的key标识,设置Tab key有三种方式,优先级由高到低:
|
||||||
|
|
||||||
|
- 使用路由query参数pageKey
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
// 跳转路由
|
||||||
|
const router = useRouter();
|
||||||
|
router.push({
|
||||||
|
path: 'path',
|
||||||
|
query: {
|
||||||
|
pageKey: 'key',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- 路由的完整路径作为key
|
||||||
|
|
||||||
|
`meta` 属性中的 `fullPathKey`不为false,则使用路由`fullPath`作为key
|
||||||
|
|
||||||
|
- 路由的path作为key
|
||||||
|
|
||||||
|
`meta` 属性中的 `fullPathKey`为false,则使用路由`path`作为key
|
||||||
|
|||||||
@@ -185,6 +185,12 @@ const defaultPreferences: Preferences = {
|
|||||||
colorWeakMode: false,
|
colorWeakMode: false,
|
||||||
compact: false,
|
compact: false,
|
||||||
contentCompact: 'wide',
|
contentCompact: 'wide',
|
||||||
|
contentCompactWidth: 1200,
|
||||||
|
contentPadding: 0,
|
||||||
|
contentPaddingBottom: 0,
|
||||||
|
contentPaddingLeft: 0,
|
||||||
|
contentPaddingRight: 0,
|
||||||
|
contentPaddingTop: 0,
|
||||||
defaultAvatar:
|
defaultAvatar:
|
||||||
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
|
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
|
||||||
defaultHomePath: '/analytics',
|
defaultHomePath: '/analytics',
|
||||||
@@ -199,6 +205,7 @@ const defaultPreferences: Preferences = {
|
|||||||
name: 'Vben Admin',
|
name: 'Vben Admin',
|
||||||
preferencesButtonPosition: 'auto',
|
preferencesButtonPosition: 'auto',
|
||||||
watermark: false,
|
watermark: false,
|
||||||
|
zIndex: 200,
|
||||||
},
|
},
|
||||||
breadcrumb: {
|
breadcrumb: {
|
||||||
enable: true,
|
enable: true,
|
||||||
@@ -219,9 +226,11 @@ const defaultPreferences: Preferences = {
|
|||||||
footer: {
|
footer: {
|
||||||
enable: false,
|
enable: false,
|
||||||
fixed: false,
|
fixed: false,
|
||||||
|
height: 32,
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
enable: true,
|
enable: true,
|
||||||
|
height: 50,
|
||||||
hidden: false,
|
hidden: false,
|
||||||
menuAlign: 'start',
|
menuAlign: 'start',
|
||||||
mode: 'fixed',
|
mode: 'fixed',
|
||||||
@@ -247,11 +256,14 @@ const defaultPreferences: Preferences = {
|
|||||||
collapsed: false,
|
collapsed: false,
|
||||||
collapsedButton: true,
|
collapsedButton: true,
|
||||||
collapsedShowTitle: false,
|
collapsedShowTitle: false,
|
||||||
|
collapseWidth: 60,
|
||||||
enable: true,
|
enable: true,
|
||||||
expandOnHover: true,
|
expandOnHover: true,
|
||||||
extraCollapse: false,
|
extraCollapse: false,
|
||||||
|
extraCollapsedWidth: 60,
|
||||||
fixedButton: true,
|
fixedButton: true,
|
||||||
hidden: false,
|
hidden: false,
|
||||||
|
mixedWidth: 80,
|
||||||
width: 224,
|
width: 224,
|
||||||
},
|
},
|
||||||
tabbar: {
|
tabbar: {
|
||||||
@@ -318,6 +330,18 @@ interface AppPreferences {
|
|||||||
compact: boolean;
|
compact: boolean;
|
||||||
/** 是否开启内容紧凑模式 */
|
/** 是否开启内容紧凑模式 */
|
||||||
contentCompact: ContentCompactType;
|
contentCompact: ContentCompactType;
|
||||||
|
/** 内容紧凑宽度 */
|
||||||
|
contentCompactWidth: number;
|
||||||
|
/** 内容内边距 */
|
||||||
|
contentPadding: number;
|
||||||
|
/** 内容底部内边距 */
|
||||||
|
contentPaddingBottom: number;
|
||||||
|
/** 内容左侧内边距 */
|
||||||
|
contentPaddingLeft: number;
|
||||||
|
/** 内容右侧内边距 */
|
||||||
|
contentPaddingRight: number;
|
||||||
|
/** 内容顶部内边距 */
|
||||||
|
contentPaddingTop: number;
|
||||||
// /** 应用默认头像 */
|
// /** 应用默认头像 */
|
||||||
defaultAvatar: string;
|
defaultAvatar: string;
|
||||||
/** 默认首页地址 */
|
/** 默认首页地址 */
|
||||||
@@ -348,6 +372,8 @@ interface AppPreferences {
|
|||||||
* @zh_CN 是否开启水印
|
* @zh_CN 是否开启水印
|
||||||
*/
|
*/
|
||||||
watermark: boolean;
|
watermark: boolean;
|
||||||
|
/** z-index */
|
||||||
|
zIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BreadcrumbPreferences {
|
interface BreadcrumbPreferences {
|
||||||
@@ -385,11 +411,15 @@ interface FooterPreferences {
|
|||||||
enable: boolean;
|
enable: boolean;
|
||||||
/** 底栏是否固定 */
|
/** 底栏是否固定 */
|
||||||
fixed: boolean;
|
fixed: boolean;
|
||||||
|
/** 底栏高度 */
|
||||||
|
height: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HeaderPreferences {
|
interface HeaderPreferences {
|
||||||
/** 顶栏是否启用 */
|
/** 顶栏是否启用 */
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
|
/** 顶栏高度 */
|
||||||
|
height: number;
|
||||||
/** 顶栏是否隐藏,css-隐藏 */
|
/** 顶栏是否隐藏,css-隐藏 */
|
||||||
hidden: boolean;
|
hidden: boolean;
|
||||||
/** 顶栏菜单位置 */
|
/** 顶栏菜单位置 */
|
||||||
@@ -423,16 +453,22 @@ interface SidebarPreferences {
|
|||||||
collapsedButton: boolean;
|
collapsedButton: boolean;
|
||||||
/** 侧边栏折叠时,是否显示title */
|
/** 侧边栏折叠时,是否显示title */
|
||||||
collapsedShowTitle: boolean;
|
collapsedShowTitle: boolean;
|
||||||
|
/** 侧边栏折叠宽度 */
|
||||||
|
collapseWidth: number;
|
||||||
/** 侧边栏是否可见 */
|
/** 侧边栏是否可见 */
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
/** 菜单自动展开状态 */
|
/** 菜单自动展开状态 */
|
||||||
expandOnHover: boolean;
|
expandOnHover: boolean;
|
||||||
/** 侧边栏扩展区域是否折叠 */
|
/** 侧边栏扩展区域是否折叠 */
|
||||||
extraCollapse: boolean;
|
extraCollapse: boolean;
|
||||||
|
/** 侧边栏扩展区域折叠宽度 */
|
||||||
|
extraCollapsedWidth: number;
|
||||||
/** 侧边栏固定按钮是否可见 */
|
/** 侧边栏固定按钮是否可见 */
|
||||||
fixedButton: boolean;
|
fixedButton: boolean;
|
||||||
/** 侧边栏是否隐藏 - css */
|
/** 侧边栏是否隐藏 - css */
|
||||||
hidden: boolean;
|
hidden: boolean;
|
||||||
|
/** 混合侧边栏宽度 */
|
||||||
|
mixedWidth: number;
|
||||||
/** 侧边栏宽度 */
|
/** 侧边栏宽度 */
|
||||||
width: number;
|
width: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"stub": "pnpm unbuild"
|
"stub": "pnpm unbuild --stub"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
import type { RouteLocationNormalized } from 'vue-router';
|
import type { RouteLocationNormalized } from 'vue-router';
|
||||||
|
|
||||||
export type TabDefinition = RouteLocationNormalized;
|
export interface TabDefinition extends RouteLocationNormalized {
|
||||||
|
/**
|
||||||
|
* 标签页的key
|
||||||
|
*/
|
||||||
|
key?: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ interface RouteMeta {
|
|||||||
| 'success'
|
| 'success'
|
||||||
| 'warning'
|
| 'warning'
|
||||||
| string;
|
| string;
|
||||||
|
/**
|
||||||
|
* 路由的完整路径作为key(默认true)
|
||||||
|
*/
|
||||||
|
fullPathKey?: boolean;
|
||||||
/**
|
/**
|
||||||
* 当前路由的子级在菜单中不展现
|
* 当前路由的子级在菜单中不展现
|
||||||
* @default false
|
* @default false
|
||||||
|
|||||||
@@ -10,6 +10,12 @@ exports[`defaultPreferences immutability test > should not modify the config obj
|
|||||||
"colorWeakMode": false,
|
"colorWeakMode": false,
|
||||||
"compact": false,
|
"compact": false,
|
||||||
"contentCompact": "wide",
|
"contentCompact": "wide",
|
||||||
|
"contentCompactWidth": 1200,
|
||||||
|
"contentPadding": 0,
|
||||||
|
"contentPaddingBottom": 0,
|
||||||
|
"contentPaddingLeft": 0,
|
||||||
|
"contentPaddingRight": 0,
|
||||||
|
"contentPaddingTop": 0,
|
||||||
"defaultAvatar": "https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp",
|
"defaultAvatar": "https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp",
|
||||||
"defaultHomePath": "/analytics",
|
"defaultHomePath": "/analytics",
|
||||||
"dynamicTitle": true,
|
"dynamicTitle": true,
|
||||||
@@ -23,6 +29,7 @@ exports[`defaultPreferences immutability test > should not modify the config obj
|
|||||||
"name": "Vben Admin",
|
"name": "Vben Admin",
|
||||||
"preferencesButtonPosition": "auto",
|
"preferencesButtonPosition": "auto",
|
||||||
"watermark": false,
|
"watermark": false,
|
||||||
|
"zIndex": 200,
|
||||||
},
|
},
|
||||||
"breadcrumb": {
|
"breadcrumb": {
|
||||||
"enable": true,
|
"enable": true,
|
||||||
@@ -43,9 +50,11 @@ exports[`defaultPreferences immutability test > should not modify the config obj
|
|||||||
"footer": {
|
"footer": {
|
||||||
"enable": false,
|
"enable": false,
|
||||||
"fixed": false,
|
"fixed": false,
|
||||||
|
"height": 32,
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"enable": true,
|
"enable": true,
|
||||||
|
"height": 50,
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"menuAlign": "start",
|
"menuAlign": "start",
|
||||||
"mode": "fixed",
|
"mode": "fixed",
|
||||||
@@ -68,14 +77,17 @@ exports[`defaultPreferences immutability test > should not modify the config obj
|
|||||||
},
|
},
|
||||||
"sidebar": {
|
"sidebar": {
|
||||||
"autoActivateChild": false,
|
"autoActivateChild": false,
|
||||||
|
"collapseWidth": 60,
|
||||||
"collapsed": false,
|
"collapsed": false,
|
||||||
"collapsedButton": true,
|
"collapsedButton": true,
|
||||||
"collapsedShowTitle": false,
|
"collapsedShowTitle": false,
|
||||||
"enable": true,
|
"enable": true,
|
||||||
"expandOnHover": true,
|
"expandOnHover": true,
|
||||||
"extraCollapse": false,
|
"extraCollapse": false,
|
||||||
|
"extraCollapsedWidth": 60,
|
||||||
"fixedButton": true,
|
"fixedButton": true,
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
|
"mixedWidth": 80,
|
||||||
"width": 224,
|
"width": 224,
|
||||||
},
|
},
|
||||||
"tabbar": {
|
"tabbar": {
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ const defaultPreferences: Preferences = {
|
|||||||
colorWeakMode: false,
|
colorWeakMode: false,
|
||||||
compact: false,
|
compact: false,
|
||||||
contentCompact: 'wide',
|
contentCompact: 'wide',
|
||||||
|
contentCompactWidth: 1200,
|
||||||
|
contentPadding: 0,
|
||||||
|
contentPaddingBottom: 0,
|
||||||
|
contentPaddingLeft: 0,
|
||||||
|
contentPaddingRight: 0,
|
||||||
|
contentPaddingTop: 0,
|
||||||
defaultAvatar:
|
defaultAvatar:
|
||||||
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
|
'https://unpkg.com/@vbenjs/static-source@0.1.7/source/avatar-v1.webp',
|
||||||
defaultHomePath: '/analytics',
|
defaultHomePath: '/analytics',
|
||||||
@@ -23,6 +29,7 @@ const defaultPreferences: Preferences = {
|
|||||||
name: 'Vben Admin',
|
name: 'Vben Admin',
|
||||||
preferencesButtonPosition: 'auto',
|
preferencesButtonPosition: 'auto',
|
||||||
watermark: false,
|
watermark: false,
|
||||||
|
zIndex: 200,
|
||||||
},
|
},
|
||||||
breadcrumb: {
|
breadcrumb: {
|
||||||
enable: true,
|
enable: true,
|
||||||
@@ -43,13 +50,16 @@ const defaultPreferences: Preferences = {
|
|||||||
footer: {
|
footer: {
|
||||||
enable: false,
|
enable: false,
|
||||||
fixed: false,
|
fixed: false,
|
||||||
|
height: 32,
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
enable: true,
|
enable: true,
|
||||||
|
height: 50,
|
||||||
hidden: false,
|
hidden: false,
|
||||||
menuAlign: 'start',
|
menuAlign: 'start',
|
||||||
mode: 'fixed',
|
mode: 'fixed',
|
||||||
},
|
},
|
||||||
|
|
||||||
logo: {
|
logo: {
|
||||||
enable: true,
|
enable: true,
|
||||||
source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
|
source: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
|
||||||
@@ -71,11 +81,14 @@ const defaultPreferences: Preferences = {
|
|||||||
collapsed: false,
|
collapsed: false,
|
||||||
collapsedButton: true,
|
collapsedButton: true,
|
||||||
collapsedShowTitle: false,
|
collapsedShowTitle: false,
|
||||||
|
collapseWidth: 60,
|
||||||
enable: true,
|
enable: true,
|
||||||
expandOnHover: true,
|
expandOnHover: true,
|
||||||
extraCollapse: false,
|
extraCollapse: false,
|
||||||
|
extraCollapsedWidth: 60,
|
||||||
fixedButton: true,
|
fixedButton: true,
|
||||||
hidden: false,
|
hidden: false,
|
||||||
|
mixedWidth: 80,
|
||||||
width: 224,
|
width: 224,
|
||||||
},
|
},
|
||||||
tabbar: {
|
tabbar: {
|
||||||
|
|||||||
@@ -33,6 +33,18 @@ interface AppPreferences {
|
|||||||
compact: boolean;
|
compact: boolean;
|
||||||
/** 是否开启内容紧凑模式 */
|
/** 是否开启内容紧凑模式 */
|
||||||
contentCompact: ContentCompactType;
|
contentCompact: ContentCompactType;
|
||||||
|
/** 内容紧凑宽度 */
|
||||||
|
contentCompactWidth: number;
|
||||||
|
/** 内容内边距 */
|
||||||
|
contentPadding: number;
|
||||||
|
/** 内容底部内边距 */
|
||||||
|
contentPaddingBottom: number;
|
||||||
|
/** 内容左侧内边距 */
|
||||||
|
contentPaddingLeft: number;
|
||||||
|
/** 内容右侧内边距 */
|
||||||
|
contentPaddingRight: number;
|
||||||
|
/** 内容顶部内边距 */
|
||||||
|
contentPaddingTop: number;
|
||||||
// /** 应用默认头像 */
|
// /** 应用默认头像 */
|
||||||
defaultAvatar: string;
|
defaultAvatar: string;
|
||||||
/** 默认首页地址 */
|
/** 默认首页地址 */
|
||||||
@@ -63,6 +75,8 @@ interface AppPreferences {
|
|||||||
* @zh_CN 是否开启水印
|
* @zh_CN 是否开启水印
|
||||||
*/
|
*/
|
||||||
watermark: boolean;
|
watermark: boolean;
|
||||||
|
/** z-index */
|
||||||
|
zIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BreadcrumbPreferences {
|
interface BreadcrumbPreferences {
|
||||||
@@ -100,11 +114,15 @@ interface FooterPreferences {
|
|||||||
enable: boolean;
|
enable: boolean;
|
||||||
/** 底栏是否固定 */
|
/** 底栏是否固定 */
|
||||||
fixed: boolean;
|
fixed: boolean;
|
||||||
|
/** 底栏高度 */
|
||||||
|
height: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HeaderPreferences {
|
interface HeaderPreferences {
|
||||||
/** 顶栏是否启用 */
|
/** 顶栏是否启用 */
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
|
/** 顶栏高度 */
|
||||||
|
height: number;
|
||||||
/** 顶栏是否隐藏,css-隐藏 */
|
/** 顶栏是否隐藏,css-隐藏 */
|
||||||
hidden: boolean;
|
hidden: boolean;
|
||||||
/** 顶栏菜单位置 */
|
/** 顶栏菜单位置 */
|
||||||
@@ -138,16 +156,22 @@ interface SidebarPreferences {
|
|||||||
collapsedButton: boolean;
|
collapsedButton: boolean;
|
||||||
/** 侧边栏折叠时,是否显示title */
|
/** 侧边栏折叠时,是否显示title */
|
||||||
collapsedShowTitle: boolean;
|
collapsedShowTitle: boolean;
|
||||||
|
/** 侧边栏折叠宽度 */
|
||||||
|
collapseWidth: number;
|
||||||
/** 侧边栏是否可见 */
|
/** 侧边栏是否可见 */
|
||||||
enable: boolean;
|
enable: boolean;
|
||||||
/** 菜单自动展开状态 */
|
/** 菜单自动展开状态 */
|
||||||
expandOnHover: boolean;
|
expandOnHover: boolean;
|
||||||
/** 侧边栏扩展区域是否折叠 */
|
/** 侧边栏扩展区域是否折叠 */
|
||||||
extraCollapse: boolean;
|
extraCollapse: boolean;
|
||||||
|
/** 侧边栏扩展区域折叠宽度 */
|
||||||
|
extraCollapsedWidth: number;
|
||||||
/** 侧边栏固定按钮是否可见 */
|
/** 侧边栏固定按钮是否可见 */
|
||||||
fixedButton: boolean;
|
fixedButton: boolean;
|
||||||
/** 侧边栏是否隐藏 - css */
|
/** 侧边栏是否隐藏 - css */
|
||||||
hidden: boolean;
|
hidden: boolean;
|
||||||
|
/** 混合侧边栏宽度 */
|
||||||
|
mixedWidth: number;
|
||||||
/** 侧边栏宽度 */
|
/** 侧边栏宽度 */
|
||||||
width: number;
|
width: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export interface VbenButtonGroupProps
|
|||||||
btnClass?: any;
|
btnClass?: any;
|
||||||
gap?: number;
|
gap?: number;
|
||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
options?: { label: CustomRenderType; value: ValueType }[];
|
options?: { [key: string]: any; label: CustomRenderType; value: ValueType }[];
|
||||||
showIcon?: boolean;
|
showIcon?: boolean;
|
||||||
size?: 'large' | 'middle' | 'small';
|
size?: 'large' | 'middle' | 'small';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ async function onBtnClick(value: ValueType) {
|
|||||||
<CircleCheckBig v-else-if="innerValue.includes(btn.value)" />
|
<CircleCheckBig v-else-if="innerValue.includes(btn.value)" />
|
||||||
<Circle v-else />
|
<Circle v-else />
|
||||||
</div>
|
</div>
|
||||||
<slot name="option" :label="btn.label" :value="btn.value">
|
<slot name="option" :label="btn.label" :value="btn.value" :data="btn">
|
||||||
<VbenRenderContent :content="btn.label" />
|
<VbenRenderContent :content="btn.label" />
|
||||||
</slot>
|
</slot>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -127,6 +127,9 @@ async function onBtnClick(value: ValueType) {
|
|||||||
</template>
|
</template>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.vben-check-button-group {
|
.vben-check-button-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
&:deep(.size-large) button {
|
&:deep(.size-large) button {
|
||||||
.icon-wrapper {
|
.icon-wrapper {
|
||||||
margin-right: 0.3rem;
|
margin-right: 0.3rem;
|
||||||
@@ -159,5 +162,16 @@ async function onBtnClick(value: ValueType) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.no-gap > :deep(button):nth-of-type(1) {
|
||||||
|
border-right-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.no-gap {
|
||||||
|
:deep(button + button) {
|
||||||
|
margin-right: -1px;
|
||||||
|
border-left-width: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -224,15 +224,20 @@ defineExpose({
|
|||||||
:class="
|
:class="
|
||||||
cn('cursor-pointer', getNodeClass?.(item), {
|
cn('cursor-pointer', getNodeClass?.(item), {
|
||||||
'data-[selected]:bg-accent': !multiple,
|
'data-[selected]:bg-accent': !multiple,
|
||||||
|
'cursor-not-allowed': disabled,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
v-bind="
|
||||||
|
Object.assign(item.bind, {
|
||||||
|
onfocus: disabled ? 'this.blur()' : undefined,
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
v-bind="item.bind"
|
|
||||||
@select="
|
@select="
|
||||||
(event) => {
|
(event) => {
|
||||||
if (event.detail.originalEvent.type === 'click') {
|
if (event.detail.originalEvent.type === 'click') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
onSelect(item, event.detail.isSelected);
|
!disabled && onSelect(item, event.detail.isSelected);
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
@toggle="
|
@toggle="
|
||||||
@@ -240,7 +245,7 @@ defineExpose({
|
|||||||
if (event.detail.originalEvent.type === 'click') {
|
if (event.detail.originalEvent.type === 'click') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
onToggle(item);
|
!disabled && onToggle(item);
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
class="tree-node focus:ring-grass8 my-0.5 flex items-center rounded px-2 py-1 outline-none focus:ring-2"
|
class="tree-node focus:ring-grass8 my-0.5 flex items-center rounded px-2 py-1 outline-none focus:ring-2"
|
||||||
@@ -262,10 +267,11 @@ defineExpose({
|
|||||||
<Checkbox
|
<Checkbox
|
||||||
v-if="multiple"
|
v-if="multiple"
|
||||||
:checked="isSelected"
|
:checked="isSelected"
|
||||||
|
:disabled="disabled"
|
||||||
:indeterminate="isIndeterminate"
|
:indeterminate="isIndeterminate"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
handleSelect();
|
!disabled && handleSelect();
|
||||||
// onSelect(item, !isSelected);
|
// onSelect(item, !isSelected);
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
@@ -276,7 +282,7 @@ defineExpose({
|
|||||||
(_event) => {
|
(_event) => {
|
||||||
// $event.stopPropagation();
|
// $event.stopPropagation();
|
||||||
// $event.preventDefault();
|
// $event.preventDefault();
|
||||||
handleSelect();
|
!disabled && handleSelect();
|
||||||
// onSelect(item, !isSelected);
|
// onSelect(item, !isSelected);
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
|
|||||||
@@ -40,14 +40,14 @@ const style = computed(() => {
|
|||||||
|
|
||||||
const tabsView = computed(() => {
|
const tabsView = computed(() => {
|
||||||
return props.tabs.map((tab) => {
|
return props.tabs.map((tab) => {
|
||||||
const { fullPath, meta, name, path } = tab || {};
|
const { fullPath, meta, name, path, key } = tab || {};
|
||||||
const { affixTab, icon, newTabTitle, tabClosable, title } = meta || {};
|
const { affixTab, icon, newTabTitle, tabClosable, title } = meta || {};
|
||||||
return {
|
return {
|
||||||
affixTab: !!affixTab,
|
affixTab: !!affixTab,
|
||||||
closable: Reflect.has(meta, 'tabClosable') ? !!tabClosable : true,
|
closable: Reflect.has(meta, 'tabClosable') ? !!tabClosable : true,
|
||||||
fullPath,
|
fullPath,
|
||||||
icon: icon as string,
|
icon: icon as string,
|
||||||
key: fullPath || path,
|
key,
|
||||||
meta,
|
meta,
|
||||||
name,
|
name,
|
||||||
path,
|
path,
|
||||||
|
|||||||
@@ -47,14 +47,14 @@ const typeWithClass = computed(() => {
|
|||||||
|
|
||||||
const tabsView = computed(() => {
|
const tabsView = computed(() => {
|
||||||
return props.tabs.map((tab) => {
|
return props.tabs.map((tab) => {
|
||||||
const { fullPath, meta, name, path } = tab || {};
|
const { fullPath, meta, name, path, key } = tab || {};
|
||||||
const { affixTab, icon, newTabTitle, tabClosable, title } = meta || {};
|
const { affixTab, icon, newTabTitle, tabClosable, title } = meta || {};
|
||||||
return {
|
return {
|
||||||
affixTab: !!affixTab,
|
affixTab: !!affixTab,
|
||||||
closable: Reflect.has(meta, 'tabClosable') ? !!tabClosable : true,
|
closable: Reflect.has(meta, 'tabClosable') ? !!tabClosable : true,
|
||||||
fullPath,
|
fullPath,
|
||||||
icon: icon as string,
|
icon: icon as string,
|
||||||
key: fullPath || path,
|
key,
|
||||||
meta,
|
meta,
|
||||||
name,
|
name,
|
||||||
path,
|
path,
|
||||||
|
|||||||
@@ -193,67 +193,107 @@ export function useElementPlusDesignTokens() {
|
|||||||
|
|
||||||
'--el-border-radius-base': getCssVariableValue('--radius', false),
|
'--el-border-radius-base': getCssVariableValue('--radius', false),
|
||||||
'--el-color-danger': getCssVariableValue('--destructive-500'),
|
'--el-color-danger': getCssVariableValue('--destructive-500'),
|
||||||
'--el-color-danger-dark-2': getCssVariableValue('--destructive'),
|
'--el-color-danger-dark-2': isDark.value
|
||||||
'--el-color-danger-light-3': getCssVariableValue('--destructive-400'),
|
? getCssVariableValue('--destructive-400')
|
||||||
'--el-color-danger-light-5': getCssVariableValue('--destructive-300'),
|
: getCssVariableValue('--destructive-600'),
|
||||||
'--el-color-danger-light-7': getCssVariableValue('--destructive-200'),
|
'--el-color-danger-light-3': isDark.value
|
||||||
|
? getCssVariableValue('--destructive-600')
|
||||||
|
: getCssVariableValue('--destructive-400'),
|
||||||
|
'--el-color-danger-light-5': isDark.value
|
||||||
|
? getCssVariableValue('--destructive-700')
|
||||||
|
: getCssVariableValue('--destructive-300'),
|
||||||
|
'--el-color-danger-light-7': isDark.value
|
||||||
|
? getCssVariableValue('--destructive-800')
|
||||||
|
: getCssVariableValue('--destructive-200'),
|
||||||
'--el-color-danger-light-8': isDark.value
|
'--el-color-danger-light-8': isDark.value
|
||||||
? border
|
? getCssVariableValue('--destructive-900')
|
||||||
: getCssVariableValue('--destructive-100'),
|
: getCssVariableValue('--destructive-100'),
|
||||||
'--el-color-danger-light-9': isDark.value
|
'--el-color-danger-light-9': isDark.value
|
||||||
? accent
|
? getCssVariableValue('--destructive-950')
|
||||||
: getCssVariableValue('--destructive-50'),
|
: getCssVariableValue('--destructive-50'),
|
||||||
|
|
||||||
'--el-color-error': getCssVariableValue('--destructive-500'),
|
'--el-color-error': getCssVariableValue('--destructive-500'),
|
||||||
'--el-color-error-dark-2': getCssVariableValue('--destructive'),
|
'--el-color-error-dark-2': isDark.value
|
||||||
'--el-color-error-light-3': getCssVariableValue('--destructive-400'),
|
? getCssVariableValue('--destructive-400')
|
||||||
'--el-color-error-light-5': getCssVariableValue('--destructive-300'),
|
: getCssVariableValue('--destructive-600'),
|
||||||
'--el-color-error-light-7': getCssVariableValue('--destructive-200'),
|
'--el-color-error-light-3': isDark.value
|
||||||
|
? getCssVariableValue('--destructive-600')
|
||||||
|
: getCssVariableValue('--destructive-400'),
|
||||||
|
'--el-color-error-light-5': isDark.value
|
||||||
|
? getCssVariableValue('--destructive-700')
|
||||||
|
: getCssVariableValue('--destructive-300'),
|
||||||
|
'--el-color-error-light-7': isDark.value
|
||||||
|
? getCssVariableValue('--destructive-800')
|
||||||
|
: getCssVariableValue('--destructive-200'),
|
||||||
'--el-color-error-light-8': isDark.value
|
'--el-color-error-light-8': isDark.value
|
||||||
? border
|
? getCssVariableValue('--destructive-900')
|
||||||
: getCssVariableValue('--destructive-100'),
|
: getCssVariableValue('--destructive-100'),
|
||||||
'--el-color-error-light-9': isDark.value
|
'--el-color-error-light-9': isDark.value
|
||||||
? accent
|
? getCssVariableValue('--destructive-950')
|
||||||
: getCssVariableValue('--destructive-50'),
|
: getCssVariableValue('--destructive-50'),
|
||||||
|
|
||||||
|
'--el-color-info-light-5': border,
|
||||||
'--el-color-info-light-8': border,
|
'--el-color-info-light-8': border,
|
||||||
'--el-color-info-light-9': getCssVariableValue('--info'), // getCssVariableValue('--secondary'),
|
'--el-color-info-light-9': getCssVariableValue('--info'), // getCssVariableValue('--secondary'),
|
||||||
|
|
||||||
'--el-color-primary': getCssVariableValue('--primary-500'),
|
'--el-color-primary': getCssVariableValue('--primary-500'),
|
||||||
'--el-color-primary-dark-2': getCssVariableValue('--primary'),
|
'--el-color-primary-dark-2': isDark.value
|
||||||
'--el-color-primary-light-3': getCssVariableValue('--primary-400'),
|
? getCssVariableValue('--primary-400')
|
||||||
'--el-color-primary-light-5': getCssVariableValue('--primary-300'),
|
: getCssVariableValue('--primary-600'),
|
||||||
|
'--el-color-primary-light-3': isDark.value
|
||||||
|
? getCssVariableValue('--primary-600')
|
||||||
|
: getCssVariableValue('--primary-400'),
|
||||||
|
'--el-color-primary-light-5': isDark.value
|
||||||
|
? getCssVariableValue('--primary-700')
|
||||||
|
: getCssVariableValue('--primary-300'),
|
||||||
'--el-color-primary-light-7': isDark.value
|
'--el-color-primary-light-7': isDark.value
|
||||||
? border
|
? getCssVariableValue('--primary-800')
|
||||||
: getCssVariableValue('--primary-200'),
|
: getCssVariableValue('--primary-200'),
|
||||||
'--el-color-primary-light-8': isDark.value
|
'--el-color-primary-light-8': isDark.value
|
||||||
? border
|
? getCssVariableValue('--primary-900')
|
||||||
: getCssVariableValue('--primary-100'),
|
: getCssVariableValue('--primary-100'),
|
||||||
'--el-color-primary-light-9': isDark.value
|
'--el-color-primary-light-9': isDark.value
|
||||||
? accent
|
? getCssVariableValue('--primary-950')
|
||||||
: getCssVariableValue('--primary-50'),
|
: getCssVariableValue('--primary-50'),
|
||||||
|
|
||||||
'--el-color-success': getCssVariableValue('--success-500'),
|
'--el-color-success': getCssVariableValue('--success-500'),
|
||||||
'--el-color-success-dark-2': getCssVariableValue('--success'),
|
'--el-color-success-dark-2': isDark.value
|
||||||
'--el-color-success-light-3': getCssVariableValue('--success-400'),
|
? getCssVariableValue('--success-400')
|
||||||
'--el-color-success-light-5': getCssVariableValue('--success-300'),
|
: getCssVariableValue('--success-600'),
|
||||||
'--el-color-success-light-7': getCssVariableValue('--success-200'),
|
'--el-color-success-light-3': isDark.value
|
||||||
|
? getCssVariableValue('--success-600')
|
||||||
|
: getCssVariableValue('--success-400'),
|
||||||
|
'--el-color-success-light-5': isDark.value
|
||||||
|
? getCssVariableValue('--success-700')
|
||||||
|
: getCssVariableValue('--success-300'),
|
||||||
|
'--el-color-success-light-7': isDark.value
|
||||||
|
? getCssVariableValue('--success-800')
|
||||||
|
: getCssVariableValue('--success-200'),
|
||||||
'--el-color-success-light-8': isDark.value
|
'--el-color-success-light-8': isDark.value
|
||||||
? border
|
? getCssVariableValue('--success-900')
|
||||||
: getCssVariableValue('--success-100'),
|
: getCssVariableValue('--success-100'),
|
||||||
'--el-color-success-light-9': isDark.value
|
'--el-color-success-light-9': isDark.value
|
||||||
? accent
|
? getCssVariableValue('--success-950')
|
||||||
: getCssVariableValue('--success-50'),
|
: getCssVariableValue('--success-50'),
|
||||||
|
|
||||||
'--el-color-warning': getCssVariableValue('--warning-500'),
|
'--el-color-warning': getCssVariableValue('--warning-500'),
|
||||||
'--el-color-warning-dark-2': getCssVariableValue('--warning'),
|
'--el-color-warning-dark-2': isDark.value
|
||||||
'--el-color-warning-light-3': getCssVariableValue('--warning-400'),
|
? getCssVariableValue('--warning-400')
|
||||||
'--el-color-warning-light-5': getCssVariableValue('--warning-300'),
|
: getCssVariableValue('--warning-600'),
|
||||||
'--el-color-warning-light-7': getCssVariableValue('--warning-200'),
|
'--el-color-warning-light-3': isDark.value
|
||||||
|
? getCssVariableValue('--warning-600')
|
||||||
|
: getCssVariableValue('--warning-400'),
|
||||||
|
'--el-color-warning-light-5': isDark.value
|
||||||
|
? getCssVariableValue('--warning-700')
|
||||||
|
: getCssVariableValue('--warning-300'),
|
||||||
|
'--el-color-warning-light-7': isDark.value
|
||||||
|
? getCssVariableValue('--warning-800')
|
||||||
|
: getCssVariableValue('--warning-200'),
|
||||||
'--el-color-warning-light-8': isDark.value
|
'--el-color-warning-light-8': isDark.value
|
||||||
? border
|
? getCssVariableValue('--warning-900')
|
||||||
: getCssVariableValue('--warning-100'),
|
: getCssVariableValue('--warning-100'),
|
||||||
'--el-color-warning-light-9': isDark.value
|
'--el-color-warning-light-9': isDark.value
|
||||||
? accent
|
? getCssVariableValue('--warning-950')
|
||||||
: getCssVariableValue('--warning-50'),
|
: getCssVariableValue('--warning-50'),
|
||||||
|
|
||||||
'--el-fill-color': getCssVariableValue('--accent'),
|
'--el-fill-color': getCssVariableValue('--accent'),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { ComputedRef } from 'vue';
|
||||||
import type { RouteLocationNormalized } from 'vue-router';
|
import type { RouteLocationNormalized } from 'vue-router';
|
||||||
|
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
@@ -53,7 +54,24 @@ export function useTabs() {
|
|||||||
await tabbarStore.closeTabByKey(key, router);
|
await tabbarStore.closeTabByKey(key, router);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setTabTitle(title: string) {
|
/**
|
||||||
|
* 设置当前标签页的标题
|
||||||
|
*
|
||||||
|
* @description 支持设置静态标题字符串或动态计算标题
|
||||||
|
* @description 动态标题会在每次渲染时重新计算,适用于多语言或状态相关的标题
|
||||||
|
*
|
||||||
|
* @param title - 标题内容
|
||||||
|
* - 静态标题: 直接传入字符串
|
||||||
|
* - 动态标题: 传入 ComputedRef
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // 静态标题
|
||||||
|
* setTabTitle('标签页')
|
||||||
|
*
|
||||||
|
* // 动态标题(多语言)
|
||||||
|
* setTabTitle(computed(() => t('page.title')))
|
||||||
|
*/
|
||||||
|
async function setTabTitle(title: ComputedRef<string> | string) {
|
||||||
tabbarStore.setUpdateTime();
|
tabbarStore.setUpdateTime();
|
||||||
await tabbarStore.setTabTitle(route, title);
|
await tabbarStore.setTabTitle(route, title);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const { authPanelCenter, authPanelLeft, authPanelRight, isDark } =
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
:class="[isDark]"
|
:class="[isDark ? 'dark' : '']"
|
||||||
class="flex min-h-full flex-1 select-none overflow-x-hidden"
|
class="flex min-h-full flex-1 select-none overflow-x-hidden"
|
||||||
>
|
>
|
||||||
<template v-if="toolbar">
|
<template v-if="toolbar">
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
export { default as AuthPageLayout } from './authentication.vue';
|
export { default as AuthPageLayout } from './authentication.vue';
|
||||||
|
export * from './types';
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { computed } from 'vue';
|
|||||||
import { RouterView } from 'vue-router';
|
import { RouterView } from 'vue-router';
|
||||||
|
|
||||||
import { preferences, usePreferences } from '@vben/preferences';
|
import { preferences, usePreferences } from '@vben/preferences';
|
||||||
import { storeToRefs, useTabbarStore } from '@vben/stores';
|
import { getTabKey, storeToRefs, useTabbarStore } from '@vben/stores';
|
||||||
|
|
||||||
import { IFrameRouterView } from '../../iframe';
|
import { IFrameRouterView } from '../../iframe';
|
||||||
|
|
||||||
@@ -115,13 +115,13 @@ function transformComponent(
|
|||||||
:is="transformComponent(Component, route)"
|
:is="transformComponent(Component, route)"
|
||||||
v-if="renderRouteView"
|
v-if="renderRouteView"
|
||||||
v-show="!route.meta.iframeSrc"
|
v-show="!route.meta.iframeSrc"
|
||||||
:key="route.fullPath"
|
:key="getTabKey(route)"
|
||||||
/>
|
/>
|
||||||
</KeepAlive>
|
</KeepAlive>
|
||||||
<component
|
<component
|
||||||
:is="Component"
|
:is="Component"
|
||||||
v-else-if="renderRouteView"
|
v-else-if="renderRouteView"
|
||||||
:key="route.fullPath"
|
:key="getTabKey(route)"
|
||||||
/>
|
/>
|
||||||
</Transition>
|
</Transition>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -134,13 +134,13 @@ function transformComponent(
|
|||||||
:is="transformComponent(Component, route)"
|
:is="transformComponent(Component, route)"
|
||||||
v-if="renderRouteView"
|
v-if="renderRouteView"
|
||||||
v-show="!route.meta.iframeSrc"
|
v-show="!route.meta.iframeSrc"
|
||||||
:key="route.fullPath"
|
:key="getTabKey(route)"
|
||||||
/>
|
/>
|
||||||
</KeepAlive>
|
</KeepAlive>
|
||||||
<component
|
<component
|
||||||
:is="Component"
|
:is="Component"
|
||||||
v-else-if="renderRouteView"
|
v-else-if="renderRouteView"
|
||||||
:key="route.fullPath"
|
:key="getTabKey(route)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</RouterView>
|
</RouterView>
|
||||||
|
|||||||
@@ -180,8 +180,16 @@ const headerSlots = computed(() => {
|
|||||||
<VbenAdminLayout
|
<VbenAdminLayout
|
||||||
v-model:sidebar-extra-visible="sidebarExtraVisible"
|
v-model:sidebar-extra-visible="sidebarExtraVisible"
|
||||||
:content-compact="preferences.app.contentCompact"
|
:content-compact="preferences.app.contentCompact"
|
||||||
|
:content-compact-width="preferences.app.contentCompactWidth"
|
||||||
|
:content-padding="preferences.app.contentPadding"
|
||||||
|
:content-padding-bottom="preferences.app.contentPaddingBottom"
|
||||||
|
:content-padding-left="preferences.app.contentPaddingLeft"
|
||||||
|
:content-padding-right="preferences.app.contentPaddingRight"
|
||||||
|
:content-padding-top="preferences.app.contentPaddingTop"
|
||||||
:footer-enable="preferences.footer.enable"
|
:footer-enable="preferences.footer.enable"
|
||||||
:footer-fixed="preferences.footer.fixed"
|
:footer-fixed="preferences.footer.fixed"
|
||||||
|
:footer-height="preferences.footer.height"
|
||||||
|
:header-height="preferences.header.height"
|
||||||
:header-hidden="preferences.header.hidden"
|
:header-hidden="preferences.header.hidden"
|
||||||
:header-mode="preferences.header.mode"
|
:header-mode="preferences.header.mode"
|
||||||
:header-theme="headerTheme"
|
:header-theme="headerTheme"
|
||||||
@@ -196,11 +204,15 @@ const headerSlots = computed(() => {
|
|||||||
:sidebar-fixed-button="preferences.sidebar.fixedButton"
|
:sidebar-fixed-button="preferences.sidebar.fixedButton"
|
||||||
:sidebar-expand-on-hover="preferences.sidebar.expandOnHover"
|
:sidebar-expand-on-hover="preferences.sidebar.expandOnHover"
|
||||||
:sidebar-extra-collapse="preferences.sidebar.extraCollapse"
|
:sidebar-extra-collapse="preferences.sidebar.extraCollapse"
|
||||||
|
:sidebar-extra-collapsed-width="preferences.sidebar.extraCollapsedWidth"
|
||||||
:sidebar-hidden="preferences.sidebar.hidden"
|
:sidebar-hidden="preferences.sidebar.hidden"
|
||||||
|
:sidebar-mixed-width="preferences.sidebar.mixedWidth"
|
||||||
:sidebar-theme="sidebarTheme"
|
:sidebar-theme="sidebarTheme"
|
||||||
:sidebar-width="preferences.sidebar.width"
|
:sidebar-width="preferences.sidebar.width"
|
||||||
|
:side-collapse-width="preferences.sidebar.collapseWidth"
|
||||||
:tabbar-enable="preferences.tabbar.enable"
|
:tabbar-enable="preferences.tabbar.enable"
|
||||||
:tabbar-height="preferences.tabbar.height"
|
:tabbar-height="preferences.tabbar.height"
|
||||||
|
:z-index="preferences.app.zIndex"
|
||||||
@side-mouse-leave="handleSideMouseLeave"
|
@side-mouse-leave="handleSideMouseLeave"
|
||||||
@toggle-sidebar="toggleSidebar"
|
@toggle-sidebar="toggleSidebar"
|
||||||
@update:sidebar-collapse="
|
@update:sidebar-collapse="
|
||||||
|
|||||||
@@ -140,7 +140,10 @@ function useMixedMenu() {
|
|||||||
watch(
|
watch(
|
||||||
() => route.path,
|
() => route.path,
|
||||||
(path) => {
|
(path) => {
|
||||||
const currentPath = (route?.meta?.activePath as string) ?? path;
|
const currentPath = route?.meta?.activePath ?? route?.meta?.link ?? path;
|
||||||
|
if (willOpenedByWindow(currentPath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
calcSideMenus(currentPath);
|
calcSideMenus(currentPath);
|
||||||
if (rootMenuPath.value)
|
if (rootMenuPath.value)
|
||||||
defaultSubMap.set(rootMenuPath.value, currentPath);
|
defaultSubMap.set(rootMenuPath.value, currentPath);
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const {
|
|||||||
} = useTabbar();
|
} = useTabbar();
|
||||||
|
|
||||||
const menus = computed(() => {
|
const menus = computed(() => {
|
||||||
const tab = tabbarStore.getTabByPath(currentActive.value);
|
const tab = tabbarStore.getTabByKey(currentActive.value);
|
||||||
const menus = createContextMenus(tab);
|
const menus = createContextMenus(tab);
|
||||||
return menus.map((item) => {
|
return menus.map((item) => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
X,
|
X,
|
||||||
} from '@vben/icons';
|
} from '@vben/icons';
|
||||||
import { $t, useI18n } from '@vben/locales';
|
import { $t, useI18n } from '@vben/locales';
|
||||||
import { useAccessStore, useTabbarStore } from '@vben/stores';
|
import { getTabKey, useAccessStore, useTabbarStore } from '@vben/stores';
|
||||||
import { filterTree } from '@vben/utils';
|
import { filterTree } from '@vben/utils';
|
||||||
|
|
||||||
export function useTabbar() {
|
export function useTabbar() {
|
||||||
@@ -44,8 +44,11 @@ export function useTabbar() {
|
|||||||
toggleTabPin,
|
toggleTabPin,
|
||||||
} = useTabs();
|
} = useTabs();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前路径对应的tab的key
|
||||||
|
*/
|
||||||
const currentActive = computed(() => {
|
const currentActive = computed(() => {
|
||||||
return route.fullPath;
|
return getTabKey(route);
|
||||||
});
|
});
|
||||||
|
|
||||||
const { locale } = useI18n();
|
const { locale } = useI18n();
|
||||||
@@ -73,7 +76,8 @@ export function useTabbar() {
|
|||||||
|
|
||||||
// 点击tab,跳转路由
|
// 点击tab,跳转路由
|
||||||
const handleClick = (key: string) => {
|
const handleClick = (key: string) => {
|
||||||
router.push(key);
|
const { fullPath, path } = tabbarStore.getTabByKey(key);
|
||||||
|
router.push(fullPath || path);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 关闭tab
|
// 关闭tab
|
||||||
@@ -100,7 +104,7 @@ export function useTabbar() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => route.path,
|
() => route.fullPath,
|
||||||
() => {
|
() => {
|
||||||
const meta = route.matched?.[route.matched.length - 1]?.meta;
|
const meta = route.matched?.[route.matched.length - 1]?.meta;
|
||||||
tabbarStore.addTab({
|
tabbarStore.addTab({
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"create": "Create",
|
"create": "Create",
|
||||||
|
"detail": "Detail",
|
||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
"showSearchPanel": "Show search panel",
|
"showSearchPanel": "Show search panel",
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
"deleteConfirm": "Are you sure to delete {0}?",
|
"deleteConfirm": "Are you sure to delete {0}?",
|
||||||
"deleting": "Deleting {0} ...",
|
"deleting": "Deleting {0} ...",
|
||||||
"deleteSuccess": "{0} deleted successfully",
|
"deleteSuccess": "{0} deleted successfully",
|
||||||
|
"deleteFailed": "{0} deleted failed",
|
||||||
"operationSuccess": "Operation succeeded",
|
"operationSuccess": "Operation succeeded",
|
||||||
"operationFailed": "Operation failed",
|
"operationFailed": "Operation failed",
|
||||||
"importSuccess": "Import succeeded",
|
"importSuccess": "Import succeeded",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"edit": "修改",
|
"edit": "修改",
|
||||||
"delete": "删除",
|
"delete": "删除",
|
||||||
"create": "新增",
|
"create": "新增",
|
||||||
|
"detail": "详情",
|
||||||
"yes": "是",
|
"yes": "是",
|
||||||
"no": "否",
|
"no": "否",
|
||||||
"showSearchPanel": "显示搜索面板",
|
"showSearchPanel": "显示搜索面板",
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
"deleteConfirm": "确定删除 {0} 吗?",
|
"deleteConfirm": "确定删除 {0} 吗?",
|
||||||
"deleting": "正在删除 {0} ...",
|
"deleting": "正在删除 {0} ...",
|
||||||
"deleteSuccess": "{0} 删除成功",
|
"deleteSuccess": "{0} 删除成功",
|
||||||
|
"deleteFailed": "{0} 删除失败",
|
||||||
"operationSuccess": "操作成功",
|
"operationSuccess": "操作成功",
|
||||||
"operationFailed": "操作失败",
|
"operationFailed": "操作失败",
|
||||||
"importSuccess": "导入成功",
|
"importSuccess": "导入成功",
|
||||||
|
|||||||
@@ -22,12 +22,13 @@ describe('useAccessStore', () => {
|
|||||||
const tab: any = {
|
const tab: any = {
|
||||||
fullPath: '/home',
|
fullPath: '/home',
|
||||||
meta: {},
|
meta: {},
|
||||||
|
key: '/home',
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
path: '/home',
|
path: '/home',
|
||||||
};
|
};
|
||||||
store.addTab(tab);
|
const addNewTab = store.addTab(tab);
|
||||||
expect(store.tabs.length).toBe(1);
|
expect(store.tabs.length).toBe(1);
|
||||||
expect(store.tabs[0]).toEqual(tab);
|
expect(store.tabs[0]).toEqual(addNewTab);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds a new tab if it does not exist', () => {
|
it('adds a new tab if it does not exist', () => {
|
||||||
@@ -38,20 +39,22 @@ describe('useAccessStore', () => {
|
|||||||
name: 'New',
|
name: 'New',
|
||||||
path: '/new',
|
path: '/new',
|
||||||
};
|
};
|
||||||
store.addTab(newTab);
|
const addNewTab = store.addTab(newTab);
|
||||||
expect(store.tabs).toContainEqual(newTab);
|
expect(store.tabs).toContainEqual(addNewTab);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updates an existing tab instead of adding a new one', () => {
|
it('updates an existing tab instead of adding a new one', () => {
|
||||||
const store = useTabbarStore();
|
const store = useTabbarStore();
|
||||||
const initialTab: any = {
|
const initialTab: any = {
|
||||||
fullPath: '/existing',
|
fullPath: '/existing',
|
||||||
meta: {},
|
meta: {
|
||||||
|
fullPathKey: false,
|
||||||
|
},
|
||||||
name: 'Existing',
|
name: 'Existing',
|
||||||
path: '/existing',
|
path: '/existing',
|
||||||
query: {},
|
query: {},
|
||||||
};
|
};
|
||||||
store.tabs.push(initialTab);
|
store.addTab(initialTab);
|
||||||
const updatedTab = { ...initialTab, query: { id: '1' } };
|
const updatedTab = { ...initialTab, query: { id: '1' } };
|
||||||
store.addTab(updatedTab);
|
store.addTab(updatedTab);
|
||||||
expect(store.tabs.length).toBe(1);
|
expect(store.tabs.length).toBe(1);
|
||||||
@@ -60,9 +63,12 @@ describe('useAccessStore', () => {
|
|||||||
|
|
||||||
it('closes all tabs', async () => {
|
it('closes all tabs', async () => {
|
||||||
const store = useTabbarStore();
|
const store = useTabbarStore();
|
||||||
store.tabs = [
|
store.addTab({
|
||||||
{ fullPath: '/home', meta: {}, name: 'Home', path: '/home' },
|
fullPath: '/home',
|
||||||
] as any;
|
meta: {},
|
||||||
|
name: 'Home',
|
||||||
|
path: '/home',
|
||||||
|
} as any);
|
||||||
router.replace = vi.fn();
|
router.replace = vi.fn();
|
||||||
|
|
||||||
await store.closeAllTabs(router);
|
await store.closeAllTabs(router);
|
||||||
@@ -157,7 +163,7 @@ describe('useAccessStore', () => {
|
|||||||
path: '/contact',
|
path: '/contact',
|
||||||
} as any);
|
} as any);
|
||||||
|
|
||||||
await store._bulkCloseByPaths(['/home', '/contact']);
|
await store._bulkCloseByKeys(['/home', '/contact']);
|
||||||
|
|
||||||
expect(store.tabs).toHaveLength(1);
|
expect(store.tabs).toHaveLength(1);
|
||||||
expect(store.tabs[0]?.name).toBe('About');
|
expect(store.tabs[0]?.name).toBe('About');
|
||||||
@@ -183,9 +189,8 @@ describe('useAccessStore', () => {
|
|||||||
name: 'Contact',
|
name: 'Contact',
|
||||||
path: '/contact',
|
path: '/contact',
|
||||||
};
|
};
|
||||||
store.addTab(targetTab);
|
const addTargetTab = store.addTab(targetTab);
|
||||||
|
await store.closeLeftTabs(addTargetTab);
|
||||||
await store.closeLeftTabs(targetTab);
|
|
||||||
|
|
||||||
expect(store.tabs).toHaveLength(1);
|
expect(store.tabs).toHaveLength(1);
|
||||||
expect(store.tabs[0]?.name).toBe('Contact');
|
expect(store.tabs[0]?.name).toBe('Contact');
|
||||||
@@ -205,7 +210,7 @@ describe('useAccessStore', () => {
|
|||||||
name: 'About',
|
name: 'About',
|
||||||
path: '/about',
|
path: '/about',
|
||||||
};
|
};
|
||||||
store.addTab(targetTab);
|
const addTargetTab = store.addTab(targetTab);
|
||||||
store.addTab({
|
store.addTab({
|
||||||
fullPath: '/contact',
|
fullPath: '/contact',
|
||||||
meta: {},
|
meta: {},
|
||||||
@@ -213,7 +218,7 @@ describe('useAccessStore', () => {
|
|||||||
path: '/contact',
|
path: '/contact',
|
||||||
} as any);
|
} as any);
|
||||||
|
|
||||||
await store.closeOtherTabs(targetTab);
|
await store.closeOtherTabs(addTargetTab);
|
||||||
|
|
||||||
expect(store.tabs).toHaveLength(1);
|
expect(store.tabs).toHaveLength(1);
|
||||||
expect(store.tabs[0]?.name).toBe('About');
|
expect(store.tabs[0]?.name).toBe('About');
|
||||||
@@ -227,7 +232,7 @@ describe('useAccessStore', () => {
|
|||||||
name: 'Home',
|
name: 'Home',
|
||||||
path: '/home',
|
path: '/home',
|
||||||
};
|
};
|
||||||
store.addTab(targetTab);
|
const addTargetTab = store.addTab(targetTab);
|
||||||
store.addTab({
|
store.addTab({
|
||||||
fullPath: '/about',
|
fullPath: '/about',
|
||||||
meta: {},
|
meta: {},
|
||||||
@@ -241,7 +246,7 @@ describe('useAccessStore', () => {
|
|||||||
path: '/contact',
|
path: '/contact',
|
||||||
} as any);
|
} as any);
|
||||||
|
|
||||||
await store.closeRightTabs(targetTab);
|
await store.closeRightTabs(addTargetTab);
|
||||||
|
|
||||||
expect(store.tabs).toHaveLength(1);
|
expect(store.tabs).toHaveLength(1);
|
||||||
expect(store.tabs[0]?.name).toBe('Home');
|
expect(store.tabs[0]?.name).toBe('Home');
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import type { Router, RouteRecordNormalized } from 'vue-router';
|
import type { ComputedRef } from 'vue';
|
||||||
|
import type {
|
||||||
|
RouteLocationNormalized,
|
||||||
|
Router,
|
||||||
|
RouteRecordNormalized,
|
||||||
|
} from 'vue-router';
|
||||||
|
|
||||||
import type { TabDefinition } from '@vben-core/typings';
|
import type { TabDefinition } from '@vben-core/typings';
|
||||||
|
|
||||||
@@ -52,23 +57,23 @@ export const useTabbarStore = defineStore('core-tabbar', {
|
|||||||
/**
|
/**
|
||||||
* Close tabs in bulk
|
* Close tabs in bulk
|
||||||
*/
|
*/
|
||||||
async _bulkCloseByPaths(paths: string[]) {
|
async _bulkCloseByKeys(keys: string[]) {
|
||||||
this.tabs = this.tabs.filter((item) => {
|
const keySet = new Set(keys);
|
||||||
return !paths.includes(getTabPath(item));
|
this.tabs = this.tabs.filter(
|
||||||
});
|
(item) => !keySet.has(getTabKeyFromTab(item)),
|
||||||
|
);
|
||||||
|
|
||||||
this.updateCacheTabs();
|
await this.updateCacheTabs();
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @zh_CN 关闭标签页
|
* @zh_CN 关闭标签页
|
||||||
* @param tab
|
* @param tab
|
||||||
*/
|
*/
|
||||||
_close(tab: TabDefinition) {
|
_close(tab: TabDefinition) {
|
||||||
const { fullPath } = tab;
|
|
||||||
if (isAffixTab(tab)) {
|
if (isAffixTab(tab)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const index = this.tabs.findIndex((item) => item.fullPath === fullPath);
|
const index = this.tabs.findIndex((item) => equalTab(item, tab));
|
||||||
index !== -1 && this.tabs.splice(index, 1);
|
index !== -1 && this.tabs.splice(index, 1);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@@ -101,14 +106,17 @@ export const useTabbarStore = defineStore('core-tabbar', {
|
|||||||
* @zh_CN 添加标签页
|
* @zh_CN 添加标签页
|
||||||
* @param routeTab
|
* @param routeTab
|
||||||
*/
|
*/
|
||||||
addTab(routeTab: TabDefinition) {
|
addTab(routeTab: TabDefinition): TabDefinition {
|
||||||
const tab = cloneTab(routeTab);
|
let tab = cloneTab(routeTab);
|
||||||
|
if (!tab.key) {
|
||||||
|
tab.key = getTabKey(routeTab);
|
||||||
|
}
|
||||||
if (!isTabShown(tab)) {
|
if (!isTabShown(tab)) {
|
||||||
return;
|
return tab;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabIndex = this.tabs.findIndex((tab) => {
|
const tabIndex = this.tabs.findIndex((item) => {
|
||||||
return getTabPath(tab) === getTabPath(routeTab);
|
return equalTab(item, tab);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (tabIndex === -1) {
|
if (tabIndex === -1) {
|
||||||
@@ -154,10 +162,11 @@ export const useTabbarStore = defineStore('core-tabbar', {
|
|||||||
mergedTab.meta.newTabTitle = curMeta.newTabTitle;
|
mergedTab.meta.newTabTitle = curMeta.newTabTitle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tab = mergedTab;
|
||||||
this.tabs.splice(tabIndex, 1, mergedTab);
|
this.tabs.splice(tabIndex, 1, mergedTab);
|
||||||
}
|
}
|
||||||
this.updateCacheTabs();
|
this.updateCacheTabs();
|
||||||
|
return tab;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @zh_CN 关闭所有标签页
|
* @zh_CN 关闭所有标签页
|
||||||
@@ -173,65 +182,63 @@ export const useTabbarStore = defineStore('core-tabbar', {
|
|||||||
* @param tab
|
* @param tab
|
||||||
*/
|
*/
|
||||||
async closeLeftTabs(tab: TabDefinition) {
|
async closeLeftTabs(tab: TabDefinition) {
|
||||||
const index = this.tabs.findIndex(
|
const index = this.tabs.findIndex((item) => equalTab(item, tab));
|
||||||
(item) => getTabPath(item) === getTabPath(tab),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (index < 1) {
|
if (index < 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const leftTabs = this.tabs.slice(0, index);
|
const leftTabs = this.tabs.slice(0, index);
|
||||||
const paths: string[] = [];
|
const keys: string[] = [];
|
||||||
|
|
||||||
for (const item of leftTabs) {
|
for (const item of leftTabs) {
|
||||||
if (!isAffixTab(item)) {
|
if (!isAffixTab(item)) {
|
||||||
paths.push(getTabPath(item));
|
keys.push(item.key as string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await this._bulkCloseByPaths(paths);
|
await this._bulkCloseByKeys(keys);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @zh_CN 关闭其他标签页
|
* @zh_CN 关闭其他标签页
|
||||||
* @param tab
|
* @param tab
|
||||||
*/
|
*/
|
||||||
async closeOtherTabs(tab: TabDefinition) {
|
async closeOtherTabs(tab: TabDefinition) {
|
||||||
const closePaths = this.tabs.map((item) => getTabPath(item));
|
const closeKeys = this.tabs.map((item) => getTabKeyFromTab(item));
|
||||||
|
|
||||||
const paths: string[] = [];
|
const keys: string[] = [];
|
||||||
|
|
||||||
for (const path of closePaths) {
|
for (const key of closeKeys) {
|
||||||
if (path !== tab.fullPath) {
|
if (key !== tab.key) {
|
||||||
const closeTab = this.tabs.find((item) => getTabPath(item) === path);
|
const closeTab = this.tabs.find(
|
||||||
|
(item) => getTabKeyFromTab(item) === key,
|
||||||
|
);
|
||||||
if (!closeTab) {
|
if (!closeTab) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!isAffixTab(closeTab)) {
|
if (!isAffixTab(closeTab)) {
|
||||||
paths.push(getTabPath(closeTab));
|
keys.push(closeTab.key as string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await this._bulkCloseByPaths(paths);
|
await this._bulkCloseByKeys(keys);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @zh_CN 关闭右侧标签页
|
* @zh_CN 关闭右侧标签页
|
||||||
* @param tab
|
* @param tab
|
||||||
*/
|
*/
|
||||||
async closeRightTabs(tab: TabDefinition) {
|
async closeRightTabs(tab: TabDefinition) {
|
||||||
const index = this.tabs.findIndex(
|
const index = this.tabs.findIndex((item) => equalTab(item, tab));
|
||||||
(item) => getTabPath(item) === getTabPath(tab),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (index !== -1 && index < this.tabs.length - 1) {
|
if (index !== -1 && index < this.tabs.length - 1) {
|
||||||
const rightTabs = this.tabs.slice(index + 1);
|
const rightTabs = this.tabs.slice(index + 1);
|
||||||
|
|
||||||
const paths: string[] = [];
|
const keys: string[] = [];
|
||||||
for (const item of rightTabs) {
|
for (const item of rightTabs) {
|
||||||
if (!isAffixTab(item)) {
|
if (!isAffixTab(item)) {
|
||||||
paths.push(getTabPath(item));
|
keys.push(item.key as string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await this._bulkCloseByPaths(paths);
|
await this._bulkCloseByKeys(keys);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -242,15 +249,14 @@ export const useTabbarStore = defineStore('core-tabbar', {
|
|||||||
*/
|
*/
|
||||||
async closeTab(tab: TabDefinition, router: Router) {
|
async closeTab(tab: TabDefinition, router: Router) {
|
||||||
const { currentRoute } = router;
|
const { currentRoute } = router;
|
||||||
|
|
||||||
// 关闭不是激活选项卡
|
// 关闭不是激活选项卡
|
||||||
if (getTabPath(currentRoute.value) !== getTabPath(tab)) {
|
if (getTabKey(currentRoute.value) !== getTabKeyFromTab(tab)) {
|
||||||
this._close(tab);
|
this._close(tab);
|
||||||
this.updateCacheTabs();
|
this.updateCacheTabs();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const index = this.getTabs.findIndex(
|
const index = this.getTabs.findIndex(
|
||||||
(item) => getTabPath(item) === getTabPath(currentRoute.value),
|
(item) => getTabKeyFromTab(item) === getTabKey(currentRoute.value),
|
||||||
);
|
);
|
||||||
|
|
||||||
const before = this.getTabs[index - 1];
|
const before = this.getTabs[index - 1];
|
||||||
@@ -277,7 +283,7 @@ export const useTabbarStore = defineStore('core-tabbar', {
|
|||||||
async closeTabByKey(key: string, router: Router) {
|
async closeTabByKey(key: string, router: Router) {
|
||||||
const originKey = decodeURIComponent(key);
|
const originKey = decodeURIComponent(key);
|
||||||
const index = this.tabs.findIndex(
|
const index = this.tabs.findIndex(
|
||||||
(item) => getTabPath(item) === originKey,
|
(item) => getTabKeyFromTab(item) === originKey,
|
||||||
);
|
);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
return;
|
return;
|
||||||
@@ -290,12 +296,12 @@ export const useTabbarStore = defineStore('core-tabbar', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据路径获取标签页
|
* 根据tab的key获取tab
|
||||||
* @param path
|
* @param key
|
||||||
*/
|
*/
|
||||||
getTabByPath(path: string) {
|
getTabByKey(key: string) {
|
||||||
return this.getTabs.find(
|
return this.getTabs.find(
|
||||||
(item) => getTabPath(item) === path,
|
(item) => getTabKeyFromTab(item) === key,
|
||||||
) as TabDefinition;
|
) as TabDefinition;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@@ -311,22 +317,19 @@ export const useTabbarStore = defineStore('core-tabbar', {
|
|||||||
* @param tab
|
* @param tab
|
||||||
*/
|
*/
|
||||||
async pinTab(tab: TabDefinition) {
|
async pinTab(tab: TabDefinition) {
|
||||||
const index = this.tabs.findIndex(
|
const index = this.tabs.findIndex((item) => equalTab(item, tab));
|
||||||
(item) => getTabPath(item) === getTabPath(tab),
|
if (index === -1) {
|
||||||
);
|
return;
|
||||||
if (index !== -1) {
|
|
||||||
const oldTab = this.tabs[index];
|
|
||||||
tab.meta.affixTab = true;
|
|
||||||
tab.meta.title = oldTab?.meta?.title as string;
|
|
||||||
// this.addTab(tab);
|
|
||||||
this.tabs.splice(index, 1, tab);
|
|
||||||
}
|
}
|
||||||
|
const oldTab = this.tabs[index];
|
||||||
|
tab.meta.affixTab = true;
|
||||||
|
tab.meta.title = oldTab?.meta?.title as string;
|
||||||
|
// this.addTab(tab);
|
||||||
|
this.tabs.splice(index, 1, tab);
|
||||||
// 过滤固定tabs,后面更改affixTabOrder的值的话可能会有问题,目前行464排序affixTabs没有设置值
|
// 过滤固定tabs,后面更改affixTabOrder的值的话可能会有问题,目前行464排序affixTabs没有设置值
|
||||||
const affixTabs = this.tabs.filter((tab) => isAffixTab(tab));
|
const affixTabs = this.tabs.filter((tab) => isAffixTab(tab));
|
||||||
// 获得固定tabs的index
|
// 获得固定tabs的index
|
||||||
const newIndex = affixTabs.findIndex(
|
const newIndex = affixTabs.findIndex((item) => equalTab(item, tab));
|
||||||
(item) => getTabPath(item) === getTabPath(tab),
|
|
||||||
);
|
|
||||||
// 交换位置重新排序
|
// 交换位置重新排序
|
||||||
await this.sortTabs(index, newIndex);
|
await this.sortTabs(index, newIndex);
|
||||||
},
|
},
|
||||||
@@ -371,9 +374,7 @@ export const useTabbarStore = defineStore('core-tabbar', {
|
|||||||
if (tab?.meta?.newTabTitle) {
|
if (tab?.meta?.newTabTitle) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const findTab = this.tabs.find(
|
const findTab = this.tabs.find((item) => equalTab(item, tab));
|
||||||
(item) => getTabPath(item) === getTabPath(tab),
|
|
||||||
);
|
|
||||||
if (findTab) {
|
if (findTab) {
|
||||||
findTab.meta.newTabTitle = undefined;
|
findTab.meta.newTabTitle = undefined;
|
||||||
await this.updateCacheTabs();
|
await this.updateCacheTabs();
|
||||||
@@ -401,13 +402,24 @@ export const useTabbarStore = defineStore('core-tabbar', {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @zh_CN 设置标签页标题
|
* @zh_CN 设置标签页标题
|
||||||
* @param tab
|
*
|
||||||
* @param title
|
* @zh_CN 支持设置静态标题字符串或计算属性作为动态标题
|
||||||
|
* @zh_CN 当标题为计算属性时,标题会随计算属性值变化而自动更新
|
||||||
|
* @zh_CN 适用于需要根据状态或多语言动态更新标题的场景
|
||||||
|
*
|
||||||
|
* @param {TabDefinition} tab - 标签页对象
|
||||||
|
* @param {ComputedRef<string> | string} title - 标题内容,支持静态字符串或计算属性
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // 设置静态标题
|
||||||
|
* setTabTitle(tab, '新标签页');
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // 设置动态标题
|
||||||
|
* setTabTitle(tab, computed(() => t('common.dashboard')));
|
||||||
*/
|
*/
|
||||||
async setTabTitle(tab: TabDefinition, title: string) {
|
async setTabTitle(tab: TabDefinition, title: ComputedRef<string> | string) {
|
||||||
const findTab = this.tabs.find(
|
const findTab = this.tabs.find((item) => equalTab(item, tab));
|
||||||
(item) => getTabPath(item) === getTabPath(tab),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (findTab) {
|
if (findTab) {
|
||||||
findTab.meta.newTabTitle = title;
|
findTab.meta.newTabTitle = title;
|
||||||
@@ -448,17 +460,15 @@ export const useTabbarStore = defineStore('core-tabbar', {
|
|||||||
* @param tab
|
* @param tab
|
||||||
*/
|
*/
|
||||||
async unpinTab(tab: TabDefinition) {
|
async unpinTab(tab: TabDefinition) {
|
||||||
const index = this.tabs.findIndex(
|
const index = this.tabs.findIndex((item) => equalTab(item, tab));
|
||||||
(item) => getTabPath(item) === getTabPath(tab),
|
if (index === -1) {
|
||||||
);
|
return;
|
||||||
|
|
||||||
if (index !== -1) {
|
|
||||||
const oldTab = this.tabs[index];
|
|
||||||
tab.meta.affixTab = false;
|
|
||||||
tab.meta.title = oldTab?.meta?.title as string;
|
|
||||||
// this.addTab(tab);
|
|
||||||
this.tabs.splice(index, 1, tab);
|
|
||||||
}
|
}
|
||||||
|
const oldTab = this.tabs[index];
|
||||||
|
tab.meta.affixTab = false;
|
||||||
|
tab.meta.title = oldTab?.meta?.title as string;
|
||||||
|
// this.addTab(tab);
|
||||||
|
this.tabs.splice(index, 1, tab);
|
||||||
// 过滤固定tabs,后面更改affixTabOrder的值的话可能会有问题,目前行464排序affixTabs没有设置值
|
// 过滤固定tabs,后面更改affixTabOrder的值的话可能会有问题,目前行464排序affixTabs没有设置值
|
||||||
const affixTabs = this.tabs.filter((tab) => isAffixTab(tab));
|
const affixTabs = this.tabs.filter((tab) => isAffixTab(tab));
|
||||||
// 获得固定tabs的index,使用固定tabs的下一个位置也就是活动tabs的第一个位置
|
// 获得固定tabs的index,使用固定tabs的下一个位置也就是活动tabs的第一个位置
|
||||||
@@ -591,11 +601,49 @@ function isTabShown(tab: TabDefinition) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @zh_CN 获取标签页路径
|
* 从route获取tab页的key
|
||||||
* @param tab
|
* @param tab
|
||||||
*/
|
*/
|
||||||
function getTabPath(tab: RouteRecordNormalized | TabDefinition) {
|
function getTabKey(tab: RouteLocationNormalized | RouteRecordNormalized) {
|
||||||
return decodeURIComponent((tab as TabDefinition).fullPath || tab.path);
|
const {
|
||||||
|
fullPath,
|
||||||
|
path,
|
||||||
|
meta: { fullPathKey } = {},
|
||||||
|
query = {},
|
||||||
|
} = tab as RouteLocationNormalized;
|
||||||
|
// pageKey可能是数组(查询参数重复时可能出现)
|
||||||
|
const pageKey = Array.isArray(query.pageKey)
|
||||||
|
? query.pageKey[0]
|
||||||
|
: query.pageKey;
|
||||||
|
let rawKey;
|
||||||
|
if (pageKey) {
|
||||||
|
rawKey = pageKey;
|
||||||
|
} else {
|
||||||
|
rawKey = fullPathKey === false ? path : (fullPath ?? path);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return decodeURIComponent(rawKey);
|
||||||
|
} catch {
|
||||||
|
return rawKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从tab获取tab页的key
|
||||||
|
* 如果tab没有key,那么就从route获取key
|
||||||
|
* @param tab
|
||||||
|
*/
|
||||||
|
function getTabKeyFromTab(tab: TabDefinition): string {
|
||||||
|
return tab.key ?? getTabKey(tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比较两个tab是否相等
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
*/
|
||||||
|
function equalTab(a: TabDefinition, b: TabDefinition) {
|
||||||
|
return getTabKeyFromTab(a) === getTabKeyFromTab(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
function routeToTab(route: RouteRecordNormalized) {
|
function routeToTab(route: RouteRecordNormalized) {
|
||||||
@@ -603,5 +651,8 @@ function routeToTab(route: RouteRecordNormalized) {
|
|||||||
meta: route.meta,
|
meta: route.meta,
|
||||||
name: route.name,
|
name: route.name,
|
||||||
path: route.path,
|
path: route.path,
|
||||||
|
key: getTabKey(route),
|
||||||
} as TabDefinition;
|
} as TabDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { getTabKey };
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const checkValue = ref(['a', 'b']);
|
|||||||
|
|
||||||
const options = [
|
const options = [
|
||||||
{ label: '选项1', value: 'a' },
|
{ label: '选项1', value: 'a' },
|
||||||
{ label: '选项2', value: 'b' },
|
{ label: '选项2', value: 'b', num: 999 },
|
||||||
{ label: '选项3', value: 'c' },
|
{ label: '选项3', value: 'c' },
|
||||||
{ label: '选项4', value: 'd' },
|
{ label: '选项4', value: 'd' },
|
||||||
{ label: '选项5', value: 'e' },
|
{ label: '选项5', value: 'e' },
|
||||||
@@ -168,10 +168,11 @@ function onBtnClick(value: any) {
|
|||||||
:options="options"
|
:options="options"
|
||||||
v-bind="compProps"
|
v-bind="compProps"
|
||||||
>
|
>
|
||||||
<template #option="{ label, value }">
|
<template #option="{ label, value, data }">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span>{{ label }}</span>
|
<span>{{ label }}</span>
|
||||||
<span class="ml-2 text-gray-400">{{ value }}</span>
|
<span class="ml-2 text-gray-400">{{ value }}</span>
|
||||||
|
<span v-if="data.num" class="white ml-2">{{ data.num }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</VbenCheckButtonGroup>
|
</VbenCheckButtonGroup>
|
||||||
|
|||||||
Reference in New Issue
Block a user