From 757eb720186825194daa1c17b8db49818fc95ed9 Mon Sep 17 00:00:00 2001 From: xingyu4j Date: Thu, 16 Oct 2025 16:35:02 +0800 Subject: [PATCH] feat: naive system init --- apps/web-naive/src/adapter/vxe-table.ts | 16 +- .../components/description/description.vue | 79 ++++ .../src/components/description/index.ts | 3 + .../src/components/description/typing.ts | 27 ++ .../components/description/use-description.ts | 71 ++++ .../src/components/table-action/icons.ts | 13 + .../src/components/table-action/index.ts | 4 + .../components/table-action/table-action.vue | 221 +++++++++++ .../src/components/table-action/typing.ts | 28 ++ apps/web-naive/src/utils/index.ts | 1 + apps/web-naive/src/utils/rangePickerProps.ts | 53 +++ apps/web-naive/src/views/system/area/data.ts | 48 +++ .../web-naive/src/views/system/area/index.vue | 79 ++++ .../src/views/system/area/modules/form.vue | 49 +++ apps/web-naive/src/views/system/dept/data.ts | 165 ++++++++ .../web-naive/src/views/system/dept/index.vue | 195 ++++++++++ .../src/views/system/dept/modules/form.vue | 83 ++++ apps/web-naive/src/views/system/dict/data.ts | 355 ++++++++++++++++++ .../web-naive/src/views/system/dict/index.vue | 33 ++ .../views/system/dict/modules/data-form.vue | 89 +++++ .../views/system/dict/modules/data-grid.vue | 208 ++++++++++ .../views/system/dict/modules/type-form.vue | 85 +++++ .../views/system/dict/modules/type-grid.vue | 189 ++++++++++ .../src/views/system/loginlog/data.ts | 150 ++++++++ .../src/views/system/loginlog/index.vue | 104 +++++ .../views/system/loginlog/modules/detail.vue | 53 +++ .../src/views/system/mail/account/data.ts | 185 +++++++++ .../src/views/system/mail/account/index.vue | 172 +++++++++ .../system/mail/account/modules/form.vue | 88 +++++ .../src/views/system/mail/log/data.ts | 262 +++++++++++++ .../src/views/system/mail/log/index.vue | 91 +++++ .../views/system/mail/log/modules/detail.vue | 53 +++ .../src/views/system/mail/template/data.ts | 267 +++++++++++++ .../src/views/system/mail/template/index.vue | 205 ++++++++++ .../system/mail/template/modules/form.vue | 88 +++++ .../mail/template/modules/send-form.vue | 108 ++++++ apps/web-naive/src/views/system/menu/data.ts | 324 ++++++++++++++++ .../web-naive/src/views/system/menu/index.vue | 181 +++++++++ .../src/views/system/menu/modules/form.vue | 83 ++++ .../web-naive/src/views/system/notice/data.ts | 136 +++++++ .../src/views/system/notice/index.vue | 189 ++++++++++ .../src/views/system/notice/modules/form.vue | 81 ++++ .../src/views/system/notify/message/data.ts | 242 ++++++++++++ .../src/views/system/notify/message/index.vue | 84 +++++ .../system/notify/message/modules/detail.vue | 53 +++ .../src/views/system/notify/my/data.ts | 142 +++++++ .../src/views/system/notify/my/index.vue | 190 ++++++++++ .../views/system/notify/my/modules/detail.vue | 53 +++ .../src/views/system/notify/template/data.ts | 291 ++++++++++++++ .../views/system/notify/template/index.vue | 209 +++++++++++ .../system/notify/template/modules/form.vue | 88 +++++ .../notify/template/modules/send-form.vue | 111 ++++++ .../src/views/system/oauth2/client/data.ts | 265 +++++++++++++ .../src/views/system/oauth2/client/index.vue | 176 +++++++++ .../system/oauth2/client/modules/form.vue | 88 +++++ .../src/views/system/oauth2/token/data.ts | 93 +++++ .../src/views/system/oauth2/token/index.vue | 145 +++++++ .../src/views/system/operatelog/data.ts | 195 ++++++++++ .../src/views/system/operatelog/index.vue | 104 +++++ .../system/operatelog/modules/detail.vue | 53 +++ apps/web-naive/src/views/system/post/data.ts | 148 ++++++++ .../web-naive/src/views/system/post/index.vue | 183 +++++++++ .../src/views/system/post/modules/form.vue | 81 ++++ apps/web-naive/src/views/system/role/data.ts | 258 +++++++++++++ .../web-naive/src/views/system/role/index.vue | 232 ++++++++++++ .../modules/assign-data-permission-form.vue | 166 ++++++++ .../system/role/modules/assign-menu-form.vue | 171 +++++++++ .../src/views/system/role/modules/form.vue | 81 ++++ .../src/views/system/sms/channel/data.ts | 196 ++++++++++ .../src/views/system/sms/channel/index.vue | 173 +++++++++ .../views/system/sms/channel/modules/form.vue | 87 +++++ .../src/views/system/sms/log/data.ts | 278 ++++++++++++++ .../src/views/system/sms/log/index.vue | 104 +++++ .../views/system/sms/log/modules/detail.vue | 53 +++ .../src/views/system/sms/template/data.ts | 277 ++++++++++++++ .../src/views/system/sms/template/index.vue | 209 +++++++++++ .../system/sms/template/modules/form.vue | 88 +++++ .../system/sms/template/modules/send-form.vue | 108 ++++++ .../src/views/system/social/client/data.ts | 211 +++++++++++ .../src/views/system/social/client/index.vue | 174 +++++++++ .../system/social/client/modules/form.vue | 88 +++++ .../src/views/system/social/user/data.ts | 164 ++++++++ .../src/views/system/social/user/index.vue | 78 ++++ .../system/social/user/modules/detail.vue | 58 +++ .../web-naive/src/views/system/tenant/data.ts | 247 ++++++++++++ .../src/views/system/tenant/index.vue | 185 +++++++++ .../src/views/system/tenant/modules/form.vue | 80 ++++ .../src/views/system/tenantPackage/data.ts | 130 +++++++ .../src/views/system/tenantPackage/index.vue | 172 +++++++++ .../system/tenantPackage/modules/form.vue | 163 ++++++++ apps/web-naive/src/views/system/user/data.ts | 347 +++++++++++++++++ .../web-naive/src/views/system/user/index.vue | 302 +++++++++++++++ .../system/user/modules/assign-role-form.vue | 77 ++++ .../views/system/user/modules/dept-tree.vue | 90 +++++ .../src/views/system/user/modules/form.vue | 81 ++++ .../views/system/user/modules/import-form.vue | 84 +++++ .../user/modules/reset-password-form.vue | 65 ++++ 97 files changed, 13178 insertions(+), 7 deletions(-) create mode 100644 apps/web-naive/src/components/description/description.vue create mode 100644 apps/web-naive/src/components/description/index.ts create mode 100644 apps/web-naive/src/components/description/typing.ts create mode 100644 apps/web-naive/src/components/description/use-description.ts create mode 100644 apps/web-naive/src/components/table-action/icons.ts create mode 100644 apps/web-naive/src/components/table-action/index.ts create mode 100644 apps/web-naive/src/components/table-action/table-action.vue create mode 100644 apps/web-naive/src/components/table-action/typing.ts create mode 100644 apps/web-naive/src/utils/index.ts create mode 100644 apps/web-naive/src/utils/rangePickerProps.ts create mode 100644 apps/web-naive/src/views/system/area/data.ts create mode 100644 apps/web-naive/src/views/system/area/index.vue create mode 100644 apps/web-naive/src/views/system/area/modules/form.vue create mode 100644 apps/web-naive/src/views/system/dept/data.ts create mode 100644 apps/web-naive/src/views/system/dept/index.vue create mode 100644 apps/web-naive/src/views/system/dept/modules/form.vue create mode 100644 apps/web-naive/src/views/system/dict/data.ts create mode 100644 apps/web-naive/src/views/system/dict/index.vue create mode 100644 apps/web-naive/src/views/system/dict/modules/data-form.vue create mode 100644 apps/web-naive/src/views/system/dict/modules/data-grid.vue create mode 100644 apps/web-naive/src/views/system/dict/modules/type-form.vue create mode 100644 apps/web-naive/src/views/system/dict/modules/type-grid.vue create mode 100644 apps/web-naive/src/views/system/loginlog/data.ts create mode 100644 apps/web-naive/src/views/system/loginlog/index.vue create mode 100644 apps/web-naive/src/views/system/loginlog/modules/detail.vue create mode 100644 apps/web-naive/src/views/system/mail/account/data.ts create mode 100644 apps/web-naive/src/views/system/mail/account/index.vue create mode 100644 apps/web-naive/src/views/system/mail/account/modules/form.vue create mode 100644 apps/web-naive/src/views/system/mail/log/data.ts create mode 100644 apps/web-naive/src/views/system/mail/log/index.vue create mode 100644 apps/web-naive/src/views/system/mail/log/modules/detail.vue create mode 100644 apps/web-naive/src/views/system/mail/template/data.ts create mode 100644 apps/web-naive/src/views/system/mail/template/index.vue create mode 100644 apps/web-naive/src/views/system/mail/template/modules/form.vue create mode 100644 apps/web-naive/src/views/system/mail/template/modules/send-form.vue create mode 100644 apps/web-naive/src/views/system/menu/data.ts create mode 100644 apps/web-naive/src/views/system/menu/index.vue create mode 100644 apps/web-naive/src/views/system/menu/modules/form.vue create mode 100644 apps/web-naive/src/views/system/notice/data.ts create mode 100644 apps/web-naive/src/views/system/notice/index.vue create mode 100644 apps/web-naive/src/views/system/notice/modules/form.vue create mode 100644 apps/web-naive/src/views/system/notify/message/data.ts create mode 100644 apps/web-naive/src/views/system/notify/message/index.vue create mode 100644 apps/web-naive/src/views/system/notify/message/modules/detail.vue create mode 100644 apps/web-naive/src/views/system/notify/my/data.ts create mode 100644 apps/web-naive/src/views/system/notify/my/index.vue create mode 100644 apps/web-naive/src/views/system/notify/my/modules/detail.vue create mode 100644 apps/web-naive/src/views/system/notify/template/data.ts create mode 100644 apps/web-naive/src/views/system/notify/template/index.vue create mode 100644 apps/web-naive/src/views/system/notify/template/modules/form.vue create mode 100644 apps/web-naive/src/views/system/notify/template/modules/send-form.vue create mode 100644 apps/web-naive/src/views/system/oauth2/client/data.ts create mode 100644 apps/web-naive/src/views/system/oauth2/client/index.vue create mode 100644 apps/web-naive/src/views/system/oauth2/client/modules/form.vue create mode 100644 apps/web-naive/src/views/system/oauth2/token/data.ts create mode 100644 apps/web-naive/src/views/system/oauth2/token/index.vue create mode 100644 apps/web-naive/src/views/system/operatelog/data.ts create mode 100644 apps/web-naive/src/views/system/operatelog/index.vue create mode 100644 apps/web-naive/src/views/system/operatelog/modules/detail.vue create mode 100644 apps/web-naive/src/views/system/post/data.ts create mode 100644 apps/web-naive/src/views/system/post/index.vue create mode 100644 apps/web-naive/src/views/system/post/modules/form.vue create mode 100644 apps/web-naive/src/views/system/role/data.ts create mode 100644 apps/web-naive/src/views/system/role/index.vue create mode 100644 apps/web-naive/src/views/system/role/modules/assign-data-permission-form.vue create mode 100644 apps/web-naive/src/views/system/role/modules/assign-menu-form.vue create mode 100644 apps/web-naive/src/views/system/role/modules/form.vue create mode 100644 apps/web-naive/src/views/system/sms/channel/data.ts create mode 100644 apps/web-naive/src/views/system/sms/channel/index.vue create mode 100644 apps/web-naive/src/views/system/sms/channel/modules/form.vue create mode 100644 apps/web-naive/src/views/system/sms/log/data.ts create mode 100644 apps/web-naive/src/views/system/sms/log/index.vue create mode 100644 apps/web-naive/src/views/system/sms/log/modules/detail.vue create mode 100644 apps/web-naive/src/views/system/sms/template/data.ts create mode 100644 apps/web-naive/src/views/system/sms/template/index.vue create mode 100644 apps/web-naive/src/views/system/sms/template/modules/form.vue create mode 100644 apps/web-naive/src/views/system/sms/template/modules/send-form.vue create mode 100644 apps/web-naive/src/views/system/social/client/data.ts create mode 100644 apps/web-naive/src/views/system/social/client/index.vue create mode 100644 apps/web-naive/src/views/system/social/client/modules/form.vue create mode 100644 apps/web-naive/src/views/system/social/user/data.ts create mode 100644 apps/web-naive/src/views/system/social/user/index.vue create mode 100644 apps/web-naive/src/views/system/social/user/modules/detail.vue create mode 100644 apps/web-naive/src/views/system/tenant/data.ts create mode 100644 apps/web-naive/src/views/system/tenant/index.vue create mode 100644 apps/web-naive/src/views/system/tenant/modules/form.vue create mode 100644 apps/web-naive/src/views/system/tenantPackage/data.ts create mode 100644 apps/web-naive/src/views/system/tenantPackage/index.vue create mode 100644 apps/web-naive/src/views/system/tenantPackage/modules/form.vue create mode 100644 apps/web-naive/src/views/system/user/data.ts create mode 100644 apps/web-naive/src/views/system/user/index.vue create mode 100644 apps/web-naive/src/views/system/user/modules/assign-role-form.vue create mode 100644 apps/web-naive/src/views/system/user/modules/dept-tree.vue create mode 100644 apps/web-naive/src/views/system/user/modules/form.vue create mode 100644 apps/web-naive/src/views/system/user/modules/import-form.vue create mode 100644 apps/web-naive/src/views/system/user/modules/reset-password-form.vue diff --git a/apps/web-naive/src/adapter/vxe-table.ts b/apps/web-naive/src/adapter/vxe-table.ts index 17222f2a0..d88e4d296 100644 --- a/apps/web-naive/src/adapter/vxe-table.ts +++ b/apps/web-naive/src/adapter/vxe-table.ts @@ -145,14 +145,12 @@ setupVbenVxeTable({ const loadingKey = `__loading_${column.field}`; const finallyProps = { inlinePrompt: true, - activeText: $t('common.enabled'), - inactiveText: $t('common.disabled'), - activeValue: 1, - inactiveValue: 0, + checkedValue: 0, + uncheckedValue: 1, ...props, - modelValue: row[column.field], + value: row[column.field], loading: row[loadingKey] ?? false, - 'onUpdate:modelValue': onChange, + 'onUpdate:value': onChange, }; async function onChange(newVal: any) { @@ -167,7 +165,10 @@ setupVbenVxeTable({ } } - return h(NSwitch, finallyProps); + return h(NSwitch, finallyProps, { + checked: $t('common.enabled'), + unchecked: $t('common.disabled'), + }); }, }); @@ -206,4 +207,5 @@ setupVbenVxeTable({ export { useVbenVxeGrid }; +export * from '#/components/table-action'; export type * from '@vben/plugins/vxe-table'; diff --git a/apps/web-naive/src/components/description/description.vue b/apps/web-naive/src/components/description/description.vue new file mode 100644 index 000000000..89e0578cd --- /dev/null +++ b/apps/web-naive/src/components/description/description.vue @@ -0,0 +1,79 @@ + diff --git a/apps/web-naive/src/components/description/index.ts b/apps/web-naive/src/components/description/index.ts new file mode 100644 index 000000000..a707c4865 --- /dev/null +++ b/apps/web-naive/src/components/description/index.ts @@ -0,0 +1,3 @@ +export { default as Description } from './description.vue'; +export * from './typing'; +export { useDescription } from './use-description'; diff --git a/apps/web-naive/src/components/description/typing.ts b/apps/web-naive/src/components/description/typing.ts new file mode 100644 index 000000000..7aadf72dd --- /dev/null +++ b/apps/web-naive/src/components/description/typing.ts @@ -0,0 +1,27 @@ +import type { DescriptionsProps } from 'naive-ui'; + +import type { CSSProperties, VNode } from 'vue'; + +// TODO @xingyu:【content】这个纠结下;1)vben2.0 是 render;https://doc.vvbin.cn/components/desc.html#usage 2) +// TODO @xingyu:vben2.0 还有 sapn【done】、labelMinWidth、contentMinWidth +// TODO @xingyu:【hidden】这个纠结下;1)vben2.0 是 show; +export interface DescriptionItemSchema { + label: string | VNode; // 内容的描述 + field?: string; // 对应 data 中的字段名 + content?: ((data: any) => string | VNode) | string | VNode; // 自定义需要展示的内容,比如说 dict-tag + span?: number; // 包含列的数量 + labelStyle?: CSSProperties; // 自定义标签样式 + contentStyle?: CSSProperties; // 自定义内容样式 + hidden?: ((data: any) => boolean) | boolean; // 是否显示 +} + +// TODO @xingyu:vben2.0 还有 title【done】、bordered【done】d、useCollapse、collapseOptions +// TODO @xingyu:from 5.0:bordered 默认为 true +// TODO @xingyu:from 5.0:column 默认为 lg: 3, md: 3, sm: 2, xl: 3, xs: 1, xxl: 4 +// TODO @xingyu:from 5.0:size 默认为 small;有 'default', 'middle', 'small', undefined +// TODO @xingyu:from 5.0:useCollapse 默认为 true +export interface DescriptionsOptions { + data?: Record; // 数据 + schema?: DescriptionItemSchema[]; // 描述项配置 + componentProps?: DescriptionsProps; // antd Descriptions 组件参数 +} diff --git a/apps/web-naive/src/components/description/use-description.ts b/apps/web-naive/src/components/description/use-description.ts new file mode 100644 index 000000000..7f99238bf --- /dev/null +++ b/apps/web-naive/src/components/description/use-description.ts @@ -0,0 +1,71 @@ +import type { DescriptionsOptions } from './typing'; + +import { defineComponent, h, isReactive, reactive, watch } from 'vue'; + +import Description from './description.vue'; + +/** 描述列表 api 定义 */ +class DescriptionApi { + private state = reactive>({}); + + constructor(options: DescriptionsOptions) { + this.state = { ...options }; + } + + getState(): DescriptionsOptions { + return this.state as DescriptionsOptions; + } + + // TODO @xingyu:【setState】纠结下:1)vben2.0 是 data https://doc.vvbin.cn/components/desc.html#usage; + setState(newState: Partial) { + this.state = { ...this.state, ...newState }; + } +} + +export type ExtendedDescriptionApi = DescriptionApi; + +export function useDescription(options: DescriptionsOptions) { + const IS_REACTIVE = isReactive(options); + const api = new DescriptionApi(options); + // 扩展 API + const extendedApi: ExtendedDescriptionApi = api as never; + const Desc = defineComponent({ + name: 'UseDescription', + inheritAttrs: false, + setup(_, { attrs, slots }) { + // 合并props和attrs到state + api.setState({ ...attrs }); + + return () => + h( + Description, + { + ...api.getState(), + ...attrs, + }, + slots, + ); + }, + }); + + // 响应式支持 + if (IS_REACTIVE) { + watch( + () => options.schema, + (newSchema) => { + api.setState({ schema: newSchema }); + }, + { immediate: true, deep: true }, + ); + + watch( + () => options.data, + (newData) => { + api.setState({ data: newData }); + }, + { immediate: true, deep: true }, + ); + } + + return [Desc, extendedApi] as const; +} diff --git a/apps/web-naive/src/components/table-action/icons.ts b/apps/web-naive/src/components/table-action/icons.ts new file mode 100644 index 000000000..88cda5978 --- /dev/null +++ b/apps/web-naive/src/components/table-action/icons.ts @@ -0,0 +1,13 @@ +export const ACTION_ICON = { + DOWNLOAD: 'lucide:download', + UPLOAD: 'lucide:upload', + ADD: 'lucide:plus', + EDIT: 'lucide:edit', + DELETE: 'lucide:trash-2', + REFRESH: 'lucide:refresh-cw', + SEARCH: 'lucide:search', + FILTER: 'lucide:filter', + MORE: 'lucide:ellipsis-vertical', + VIEW: 'lucide:eye', + COPY: 'lucide:copy', +}; diff --git a/apps/web-naive/src/components/table-action/index.ts b/apps/web-naive/src/components/table-action/index.ts new file mode 100644 index 000000000..672c0a533 --- /dev/null +++ b/apps/web-naive/src/components/table-action/index.ts @@ -0,0 +1,4 @@ +export * from './icons'; + +export { default as TableAction } from './table-action.vue'; +export * from './typing'; diff --git a/apps/web-naive/src/components/table-action/table-action.vue b/apps/web-naive/src/components/table-action/table-action.vue new file mode 100644 index 000000000..50fd7f87f --- /dev/null +++ b/apps/web-naive/src/components/table-action/table-action.vue @@ -0,0 +1,221 @@ + + + + diff --git a/apps/web-naive/src/components/table-action/typing.ts b/apps/web-naive/src/components/table-action/typing.ts new file mode 100644 index 000000000..ffe4761bc --- /dev/null +++ b/apps/web-naive/src/components/table-action/typing.ts @@ -0,0 +1,28 @@ +import type { ButtonProps } from 'naive-ui/es/button/src/Button'; +import type { TooltipProps } from 'naive-ui/es/tooltip/src/Tooltip'; + +export interface PopConfirm { + title: string; + okText?: string; + cancelText?: string; + confirm: () => void; + cancel?: () => void; + icon?: string; + disabled?: boolean; +} + +export interface ActionItem extends ButtonProps { + onClick?: () => void; + type?: ButtonProps['type']; + label?: string; + color?: 'error' | 'success' | 'warning'; + icon?: string; + popConfirm?: PopConfirm; + disabled?: boolean; + divider?: boolean; + // 权限编码控制是否显示 + auth?: string[]; + // 业务控制是否显示 + ifShow?: ((action: ActionItem) => boolean) | boolean; + tooltip?: string | TooltipProps; +} diff --git a/apps/web-naive/src/utils/index.ts b/apps/web-naive/src/utils/index.ts new file mode 100644 index 000000000..800284d14 --- /dev/null +++ b/apps/web-naive/src/utils/index.ts @@ -0,0 +1 @@ +export * from './rangePickerProps'; diff --git a/apps/web-naive/src/utils/rangePickerProps.ts b/apps/web-naive/src/utils/rangePickerProps.ts new file mode 100644 index 000000000..d7ecc1dd4 --- /dev/null +++ b/apps/web-naive/src/utils/rangePickerProps.ts @@ -0,0 +1,53 @@ +import dayjs from 'dayjs'; + +import { $t } from '#/locales'; + +/** 时间段选择器拓展 */ +export function getRangePickerDefaultProps() { + return { + startPlaceholder: $t('utils.rangePicker.beginTime'), + endPlaceholder: $t('utils.rangePicker.endTime'), + type: 'datetimerange', + // TODO + format: 'YYYY-MM-dd HH:mm:ss', + valueFormat: 'YYYY-MM-dd HH:mm:ss', + defaultTime: ['00:00:00', '23:59:59'], + shortcuts: { + [$t('utils.rangePicker.today')]: () => + [ + dayjs().startOf('day').toDate(), + dayjs().endOf('day').toDate(), + ] as const, + [$t('utils.rangePicker.last7Days')]: () => + [ + dayjs().subtract(7, 'day').startOf('day').toDate(), + dayjs().endOf('day').toDate(), + ] as const, + [$t('utils.rangePicker.last30Days')]: () => + [ + dayjs().subtract(30, 'day').startOf('day').toDate(), + dayjs().endOf('day').toDate(), + ] as const, + [$t('utils.rangePicker.yesterday')]: () => + [ + dayjs().subtract(1, 'day').startOf('day').toDate(), + dayjs().subtract(1, 'day').endOf('day').toDate(), + ] as const, + [$t('utils.rangePicker.thisWeek')]: () => + [ + dayjs().startOf('week').toDate(), + dayjs().endOf('day').toDate(), + ] as const, + [$t('utils.rangePicker.thisMonth')]: () => + [ + dayjs().startOf('month').toDate(), + dayjs().endOf('day').toDate(), + ] as const, + [$t('utils.rangePicker.lastWeek')]: () => + [ + dayjs().subtract(1, 'week').startOf('day').toDate(), + dayjs().endOf('day').toDate(), + ] as const, + }, + }; +} diff --git a/apps/web-naive/src/views/system/area/data.ts b/apps/web-naive/src/views/system/area/data.ts new file mode 100644 index 000000000..e6cc06c99 --- /dev/null +++ b/apps/web-naive/src/views/system/area/data.ts @@ -0,0 +1,48 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemAreaApi } from '#/api/system/area'; + +import { z } from '#/adapter/form'; + +/** 查询 IP 的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'ip', + label: 'IP 地址', + component: 'Input', + componentProps: { + placeholder: '请输入 IP 地址', + }, + rules: z.string().ip({ message: '请输入正确的 IP 地址' }), + }, + { + fieldName: 'result', + label: '地址', + component: 'Input', + componentProps: { + placeholder: '展示查询 IP 结果', + readonly: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '地区编码', + minWidth: 120, + align: 'left', + fixed: 'left', + treeNode: true, + }, + { + field: 'name', + title: '地区名称', + minWidth: 200, + }, + ]; +} diff --git a/apps/web-naive/src/views/system/area/index.vue b/apps/web-naive/src/views/system/area/index.vue new file mode 100644 index 000000000..a07141905 --- /dev/null +++ b/apps/web-naive/src/views/system/area/index.vue @@ -0,0 +1,79 @@ + + + diff --git a/apps/web-naive/src/views/system/area/modules/form.vue b/apps/web-naive/src/views/system/area/modules/form.vue new file mode 100644 index 000000000..b8fa1565c --- /dev/null +++ b/apps/web-naive/src/views/system/area/modules/form.vue @@ -0,0 +1,49 @@ + + + diff --git a/apps/web-naive/src/views/system/dept/data.ts b/apps/web-naive/src/views/system/dept/data.ts new file mode 100644 index 000000000..2357d958b --- /dev/null +++ b/apps/web-naive/src/views/system/dept/data.ts @@ -0,0 +1,165 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemDeptApi } from '#/api/system/dept'; +import type { SystemUserApi } from '#/api/system/user'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { handleTree } from '@vben/utils'; + +import { z } from '#/adapter/form'; +import { getDeptList } from '#/api/system/dept'; +import { getSimpleUserList } from '#/api/system/user'; + +let userList: SystemUserApi.User[] = []; +async function getUserData() { + userList = await getSimpleUserList(); +} + +getUserData(); + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'parentId', + label: '上级部门', + component: 'ApiTreeSelect', + componentProps: { + clearable: true, + api: async () => { + const data = await getDeptList(); + data.unshift({ + id: 0, + name: '顶级部门', + }); + return handleTree(data); + }, + labelField: 'name', + valueField: 'id', + childrenField: 'children', + placeholder: '请选择上级部门', + treeDefaultExpandAll: true, + }, + rules: 'selectRequired', + }, + { + fieldName: 'name', + label: '部门名称', + component: 'Input', + componentProps: { + placeholder: '请输入部门名称', + }, + rules: 'required', + }, + { + fieldName: 'sort', + label: '显示顺序', + component: 'InputNumber', + componentProps: { + min: 0, + placeholder: '请输入显示顺序', + }, + rules: 'required', + }, + { + fieldName: 'leaderUserId', + label: '负责人', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + placeholder: '请选择负责人', + clearable: true, + }, + rules: z.number().optional(), + }, + { + fieldName: 'phone', + label: '联系电话', + component: 'Input', + componentProps: { + maxLength: 11, + placeholder: '请输入联系电话', + }, + rules: 'mobileRequired', + }, + { + fieldName: 'email', + label: '邮箱', + component: 'Input', + componentProps: { + placeholder: '请输入邮箱', + }, + rules: z.string().email('邮箱格式不正确').or(z.literal('')).optional(), + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'name', + title: '部门名称', + minWidth: 150, + align: 'left', + fixed: 'left', + treeNode: true, + }, + { + field: 'leaderUserId', + title: '负责人', + minWidth: 150, + formatter: ({ cellValue }) => + userList.find((user) => user.id === cellValue)?.nickname || '-', + }, + { + field: 'sort', + title: '显示顺序', + minWidth: 100, + }, + { + field: 'status', + title: '部门状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 260, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-naive/src/views/system/dept/index.vue b/apps/web-naive/src/views/system/dept/index.vue new file mode 100644 index 000000000..43d090192 --- /dev/null +++ b/apps/web-naive/src/views/system/dept/index.vue @@ -0,0 +1,195 @@ + + + diff --git a/apps/web-naive/src/views/system/dept/modules/form.vue b/apps/web-naive/src/views/system/dept/modules/form.vue new file mode 100644 index 000000000..fdcfc1b49 --- /dev/null +++ b/apps/web-naive/src/views/system/dept/modules/form.vue @@ -0,0 +1,83 @@ + + + diff --git a/apps/web-naive/src/views/system/dict/data.ts b/apps/web-naive/src/views/system/dict/data.ts new file mode 100644 index 000000000..3cc948443 --- /dev/null +++ b/apps/web-naive/src/views/system/dict/data.ts @@ -0,0 +1,355 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getSimpleDictTypeList } from '#/api/system/dict/type'; + +// ============================== 字典类型 ============================== + +/** 类型新增/修改的表单 */ +export function useTypeFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '字典名称', + component: 'Input', + componentProps: { + placeholder: '请输入字典名称', + }, + rules: 'required', + }, + { + fieldName: 'type', + label: '字典类型', + component: 'Input', + componentProps: (values) => { + return { + placeholder: '请输入字典类型', + disabled: !!values.id, + }; + }, + rules: 'required', + dependencies: { + triggerFields: [''], + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + type: 'textarea', + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 类型列表的搜索表单 */ +export function useTypeGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '字典名称', + component: 'Input', + componentProps: { + placeholder: '请输入字典名称', + clearable: true, + }, + }, + { + fieldName: 'type', + label: '字典类型', + component: 'Input', + componentProps: { + placeholder: '请输入字典类型', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择状态', + clearable: true, + }, + }, + ]; +} + +/** 类型列表的字段 */ +export function useTypeGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '字典编号', + minWidth: 100, + }, + { + field: 'name', + title: '字典名称', + minWidth: 200, + }, + { + field: 'type', + title: '字典类型', + minWidth: 220, + }, + { + field: 'status', + title: '状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'remark', + title: '备注', + minWidth: 180, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + minWidth: 140, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +// ============================== 字典数据 ============================== + +// TODO @芋艿:后续针对 antd,增加 +/** + * 颜色选项 + */ +const colorOptions = [ + { value: '', label: '无' }, + { value: 'processing', label: '主要' }, + { value: 'success', label: '成功' }, + { value: 'default', label: '默认' }, + { value: 'warning', label: '警告' }, + { value: 'error', label: '危险' }, + { value: 'pink', label: 'pink' }, + { value: 'red', label: 'red' }, + { value: 'orange', label: 'orange' }, + { value: 'green', label: 'green' }, + { value: 'cyan', label: 'cyan' }, + { value: 'blue', label: 'blue' }, + { value: 'purple', label: 'purple' }, +]; + +/** 数据新增/修改的表单 */ +export function useDataFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'dictType', + label: '字典类型', + component: 'ApiSelect', + componentProps: (values) => { + return { + api: getSimpleDictTypeList, + placeholder: '请输入字典类型', + labelField: 'name', + valueField: 'type', + disabled: !!values.id, + }; + }, + rules: 'required', + dependencies: { + triggerFields: [''], + }, + }, + { + fieldName: 'label', + label: '数据标签', + component: 'Input', + componentProps: { + placeholder: '请输入数据标签', + }, + rules: 'required', + }, + { + fieldName: 'value', + label: '数据键值', + component: 'Input', + componentProps: { + placeholder: '请输入数据键值', + }, + rules: 'required', + }, + { + fieldName: 'sort', + label: '显示排序', + component: 'InputNumber', + componentProps: { + placeholder: '请输入显示排序', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择状态', + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'colorType', + label: '颜色类型', + component: 'Select', + componentProps: { + options: colorOptions, + placeholder: '请选择颜色类型', + }, + }, + { + fieldName: 'cssClass', + label: 'CSS Class', + component: 'Input', + componentProps: { + placeholder: '请输入 CSS Class', + }, + help: '输入 hex 模式的颜色, 例如 #108ee9', + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + type: 'textarea', + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 字典数据列表搜索表单 */ +export function useDataGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'label', + label: '字典标签', + component: 'Input', + componentProps: { + placeholder: '请输入字典标签', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择状态', + clearable: true, + }, + }, + ]; +} + +/** 字典数据表格列 */ +export function useDataGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '字典编码', + minWidth: 100, + }, + { + field: 'label', + title: '字典标签', + minWidth: 180, + }, + { + field: 'value', + title: '字典键值', + minWidth: 100, + }, + { + field: 'sort', + title: '字典排序', + minWidth: 100, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'colorType', + title: '颜色类型', + minWidth: 120, + slots: { default: 'colorType' }, + }, + { + field: 'cssClass', + title: 'CSS Class', + minWidth: 120, + slots: { default: 'cssClass' }, + }, + { + title: '创建时间', + field: 'createTime', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + minWidth: 140, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-naive/src/views/system/dict/index.vue b/apps/web-naive/src/views/system/dict/index.vue new file mode 100644 index 000000000..8d824256d --- /dev/null +++ b/apps/web-naive/src/views/system/dict/index.vue @@ -0,0 +1,33 @@ + + + diff --git a/apps/web-naive/src/views/system/dict/modules/data-form.vue b/apps/web-naive/src/views/system/dict/modules/data-form.vue new file mode 100644 index 000000000..a74fb3421 --- /dev/null +++ b/apps/web-naive/src/views/system/dict/modules/data-form.vue @@ -0,0 +1,89 @@ + + + diff --git a/apps/web-naive/src/views/system/dict/modules/data-grid.vue b/apps/web-naive/src/views/system/dict/modules/data-grid.vue new file mode 100644 index 000000000..f36df5b2e --- /dev/null +++ b/apps/web-naive/src/views/system/dict/modules/data-grid.vue @@ -0,0 +1,208 @@ + + + diff --git a/apps/web-naive/src/views/system/dict/modules/type-form.vue b/apps/web-naive/src/views/system/dict/modules/type-form.vue new file mode 100644 index 000000000..ea4d23455 --- /dev/null +++ b/apps/web-naive/src/views/system/dict/modules/type-form.vue @@ -0,0 +1,85 @@ + + + diff --git a/apps/web-naive/src/views/system/dict/modules/type-grid.vue b/apps/web-naive/src/views/system/dict/modules/type-grid.vue new file mode 100644 index 000000000..c60fea4f7 --- /dev/null +++ b/apps/web-naive/src/views/system/dict/modules/type-grid.vue @@ -0,0 +1,189 @@ + + + diff --git a/apps/web-naive/src/views/system/loginlog/data.ts b/apps/web-naive/src/views/system/loginlog/data.ts new file mode 100644 index 000000000..03704e311 --- /dev/null +++ b/apps/web-naive/src/views/system/loginlog/data.ts @@ -0,0 +1,150 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemLoginLogApi } from '#/api/system/login-log'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { formatDateTime } from '@vben/utils'; + +import { DictTag } from '#/components/dict-tag'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'username', + label: '用户名称', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入用户名称', + }, + }, + { + fieldName: 'userIp', + label: '登录地址', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入登录地址', + }, + }, + { + fieldName: 'createTime', + label: '登录时间', + component: 'DatePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '日志编号', + minWidth: 100, + }, + { + field: 'logType', + title: '操作类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_LOGIN_TYPE }, + }, + }, + { + field: 'username', + title: '用户名称', + minWidth: 180, + }, + { + field: 'userIp', + title: '登录地址', + minWidth: 180, + }, + { + field: 'userAgent', + title: '浏览器', + minWidth: 200, + }, + { + field: 'result', + title: '登录结果', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_LOGIN_RESULT }, + }, + }, + { + field: 'createTime', + title: '登录日期', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 120, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'id', + label: '日志编号', + }, + { + field: 'logType', + label: '操作类型', + content: (data: SystemLoginLogApi.LoginLog) => { + return h(DictTag, { + type: DICT_TYPE.SYSTEM_LOGIN_TYPE, + value: data?.logType, + }); + }, + }, + { + field: 'username', + label: '用户名称', + }, + { + field: 'userIp', + label: '登录地址', + }, + { + field: 'userAgent', + label: '浏览器', + }, + { + field: 'result', + label: '登录结果', + content: (data: SystemLoginLogApi.LoginLog) => { + return h(DictTag, { + type: DICT_TYPE.SYSTEM_LOGIN_RESULT, + value: data?.result, + }); + }, + }, + { + field: 'createTime', + label: '登录日期', + content: (data: SystemLoginLogApi.LoginLog) => { + return formatDateTime(data?.createTime || '') as string; + }, + }, + ]; +} diff --git a/apps/web-naive/src/views/system/loginlog/index.vue b/apps/web-naive/src/views/system/loginlog/index.vue new file mode 100644 index 000000000..c7ef33a2a --- /dev/null +++ b/apps/web-naive/src/views/system/loginlog/index.vue @@ -0,0 +1,104 @@ + + + diff --git a/apps/web-naive/src/views/system/loginlog/modules/detail.vue b/apps/web-naive/src/views/system/loginlog/modules/detail.vue new file mode 100644 index 000000000..9d3ceed35 --- /dev/null +++ b/apps/web-naive/src/views/system/loginlog/modules/detail.vue @@ -0,0 +1,53 @@ + + + diff --git a/apps/web-naive/src/views/system/mail/account/data.ts b/apps/web-naive/src/views/system/mail/account/data.ts new file mode 100644 index 000000000..08dd38d9a --- /dev/null +++ b/apps/web-naive/src/views/system/mail/account/data.ts @@ -0,0 +1,185 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'mail', + label: '邮箱', + component: 'Input', + componentProps: { + placeholder: '请输入邮箱', + }, + rules: 'required', + }, + { + fieldName: 'username', + label: '用户名', + component: 'Input', + componentProps: { + placeholder: '请输入用户名', + }, + rules: 'required', + }, + { + fieldName: 'password', + label: '密码', + component: 'InputPassword', + componentProps: { + placeholder: '请输入密码', + }, + rules: 'required', + }, + { + fieldName: 'host', + label: 'SMTP 服务器域名', + component: 'Input', + componentProps: { + placeholder: '请输入 SMTP 服务器域名', + }, + rules: 'required', + }, + { + fieldName: 'port', + label: 'SMTP 服务器端口', + component: 'InputNumber', + componentProps: { + placeholder: '请输入 SMTP 服务器端口', + min: 0, + max: 65_535, + }, + rules: 'required', + }, + { + fieldName: 'sslEnable', + label: '是否开启 SSL', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.boolean().default(true), + }, + { + fieldName: 'starttlsEnable', + label: '是否开启 STARTTLS', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.boolean().default(false), + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + type: 'textarea', + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'mail', + label: '邮箱', + component: 'Input', + componentProps: { + placeholder: '请输入邮箱', + clearable: true, + }, + }, + { + fieldName: 'username', + label: '用户名', + component: 'Input', + componentProps: { + placeholder: '请输入用户名', + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'mail', + title: '邮箱', + minWidth: 160, + }, + { + field: 'username', + title: '用户名', + minWidth: 160, + }, + { + field: 'host', + title: 'SMTP 服务器域名', + minWidth: 150, + }, + { + field: 'port', + title: 'SMTP 服务器端口', + minWidth: 130, + }, + { + field: 'sslEnable', + title: '是否开启 SSL', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + }, + { + field: 'starttlsEnable', + title: '是否开启 STARTTLS', + minWidth: 145, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-naive/src/views/system/mail/account/index.vue b/apps/web-naive/src/views/system/mail/account/index.vue new file mode 100644 index 000000000..0c3e86b34 --- /dev/null +++ b/apps/web-naive/src/views/system/mail/account/index.vue @@ -0,0 +1,172 @@ + + diff --git a/apps/web-naive/src/views/system/mail/account/modules/form.vue b/apps/web-naive/src/views/system/mail/account/modules/form.vue new file mode 100644 index 000000000..59046a0ef --- /dev/null +++ b/apps/web-naive/src/views/system/mail/account/modules/form.vue @@ -0,0 +1,88 @@ + + + diff --git a/apps/web-naive/src/views/system/mail/log/data.ts b/apps/web-naive/src/views/system/mail/log/data.ts new file mode 100644 index 000000000..348098211 --- /dev/null +++ b/apps/web-naive/src/views/system/mail/log/data.ts @@ -0,0 +1,262 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemMailLogApi } from '#/api/system/mail/log'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { formatDateTime } from '@vben/utils'; + +import { getSimpleMailAccountList } from '#/api/system/mail/account'; +import { DictTag } from '#/components/dict-tag'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'sendTime', + label: '发送时间', + component: 'DatePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + { + fieldName: 'userId', + label: '用户编号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入用户编号', + }, + }, + { + fieldName: 'userType', + label: '用户类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.USER_TYPE, 'number'), + clearable: true, + placeholder: '请选择用户类型', + }, + }, + { + fieldName: 'sendStatus', + label: '发送状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_MAIL_SEND_STATUS, 'number'), + clearable: true, + placeholder: '请选择发送状态', + }, + }, + { + fieldName: 'accountId', + label: '邮箱账号', + component: 'ApiSelect', + componentProps: { + api: async () => await getSimpleMailAccountList(), + labelField: 'mail', + valueField: 'id', + clearable: true, + placeholder: '请选择邮箱账号', + }, + }, + { + fieldName: 'templateId', + label: '模板编号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入模板编号', + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'sendTime', + title: '发送时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'userType', + title: '接收用户', + minWidth: 150, + slots: { default: 'userInfo' }, + }, + { + field: 'toMails', + title: '接收信息', + minWidth: 300, + formatter: ({ row }) => { + const lines: string[] = []; + if (row.toMails && row.toMails.length > 0) { + lines.push(`收件:${row.toMails.join('、')}`); + } + if (row.ccMails && row.ccMails.length > 0) { + lines.push(`抄送:${row.ccMails.join('、')}`); + } + if (row.bccMails && row.bccMails.length > 0) { + lines.push(`密送:${row.bccMails.join('、')}`); + } + return lines.join('\n'); + }, + }, + { + field: 'templateTitle', + title: '邮件标题', + minWidth: 120, + }, + { + field: 'templateContent', + title: '邮件内容', + minWidth: 300, + }, + { + field: 'fromMail', + title: '发送邮箱', + minWidth: 120, + }, + { + field: 'sendStatus', + title: '发送状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_MAIL_SEND_STATUS }, + }, + }, + { + field: 'templateCode', + title: '模板编码', + minWidth: 120, + }, + { + title: '操作', + width: 80, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'id', + label: '编号', + }, + { + field: 'createTime', + label: '创建时间', + content: (data: SystemMailLogApi.MailLog) => { + return formatDateTime(data?.createTime || '') as string; + }, + }, + { + field: 'fromMail', + label: '发送邮箱', + }, + { + field: 'userId', + label: '接收用户', + content: (data: SystemMailLogApi.MailLog) => { + if (data?.userType && data?.userId) { + return h('div', [ + h(DictTag, { + type: DICT_TYPE.USER_TYPE, + value: data.userType, + }), + ` (${data.userId})`, + ]); + } + return '无'; + }, + }, + { + field: 'toMails', + label: '接收信息', + content: (data: SystemMailLogApi.MailLog) => { + const lines: string[] = []; + if (data?.toMails && data.toMails.length > 0) { + lines.push(`收件:${data.toMails.join('、')}`); + } + if (data?.ccMails && data.ccMails.length > 0) { + lines.push(`抄送:${data.ccMails.join('、')}`); + } + if (data?.bccMails && data.bccMails.length > 0) { + lines.push(`密送:${data.bccMails.join('、')}`); + } + return h( + 'div', + { + style: { whiteSpace: 'pre-line' }, + }, + lines.join('\n'), + ); + }, + }, + { + field: 'templateId', + label: '模板编号', + }, + { + field: 'templateCode', + label: '模板编码', + }, + { + field: 'templateTitle', + label: '邮件标题', + }, + { + field: 'templateContent', + label: '邮件内容', + span: 2, + content: (data: SystemMailLogApi.MailLog) => { + return h('div', { + innerHTML: data?.templateContent || '', + }); + }, + }, + { + field: 'sendStatus', + label: '发送状态', + content: (data: SystemMailLogApi.MailLog) => { + return h(DictTag, { + type: DICT_TYPE.SYSTEM_MAIL_SEND_STATUS, + value: data?.sendStatus, + }); + }, + }, + { + field: 'sendTime', + label: '发送时间', + content: (data: SystemMailLogApi.MailLog) => { + return formatDateTime(data?.sendTime || '') as string; + }, + }, + { + field: 'sendMessageId', + label: '发送消息编号', + }, + { + field: 'sendException', + label: '发送异常', + }, + ]; +} diff --git a/apps/web-naive/src/views/system/mail/log/index.vue b/apps/web-naive/src/views/system/mail/log/index.vue new file mode 100644 index 000000000..0b898a121 --- /dev/null +++ b/apps/web-naive/src/views/system/mail/log/index.vue @@ -0,0 +1,91 @@ + + diff --git a/apps/web-naive/src/views/system/mail/log/modules/detail.vue b/apps/web-naive/src/views/system/mail/log/modules/detail.vue new file mode 100644 index 000000000..8775e691d --- /dev/null +++ b/apps/web-naive/src/views/system/mail/log/modules/detail.vue @@ -0,0 +1,53 @@ + + + diff --git a/apps/web-naive/src/views/system/mail/template/data.ts b/apps/web-naive/src/views/system/mail/template/data.ts new file mode 100644 index 000000000..66b447139 --- /dev/null +++ b/apps/web-naive/src/views/system/mail/template/data.ts @@ -0,0 +1,267 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getSimpleMailAccountList } from '#/api/system/mail/account'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '模板名称', + component: 'Input', + componentProps: { + placeholder: '请输入模板名称', + }, + rules: 'required', + }, + { + fieldName: 'code', + label: '模板编码', + component: 'Input', + componentProps: { + placeholder: '请输入模板编码', + }, + rules: 'required', + }, + { + fieldName: 'accountId', + label: '邮箱账号', + component: 'ApiSelect', + componentProps: { + api: async () => await getSimpleMailAccountList(), + labelField: 'mail', + valueField: 'id', + placeholder: '请选择邮箱账号', + }, + rules: 'required', + }, + { + fieldName: 'nickname', + label: '发送人名称', + component: 'Input', + componentProps: { + placeholder: '请输入发送人名称', + }, + }, + { + fieldName: 'title', + label: '模板标题', + component: 'Input', + componentProps: { + placeholder: '请输入模板标题', + }, + rules: 'required', + }, + { + fieldName: 'content', + label: '模板内容', + component: 'RichTextarea', + rules: 'required', + }, + { + fieldName: 'status', + label: '开启状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + type: 'textarea', + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 发送邮件表单 */ +export function useSendMailFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'templateParams', + label: '模板参数', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'content', + label: '模板内容', + component: 'RichTextarea', + componentProps: { + options: { + readonly: true, + }, + }, + }, + { + fieldName: 'toMails', + label: '收件邮箱', + component: 'Select', + componentProps: { + mode: 'tags', + clearable: true, + placeholder: '请输入收件邮箱,按 Enter 添加', + }, + }, + { + fieldName: 'ccMails', + label: '抄送邮箱', + component: 'Select', + componentProps: { + mode: 'tags', + clearable: true, + placeholder: '请输入抄送邮箱,按 Enter 添加', + }, + }, + { + fieldName: 'bccMails', + label: '密送邮箱', + component: 'Select', + componentProps: { + mode: 'tags', + clearable: true, + placeholder: '请输入密送邮箱,按 Enter 添加', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'status', + label: '开启状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + clearable: true, + placeholder: '请选择开启状态', + }, + }, + { + fieldName: 'code', + label: '模板编码', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入模板编码', + }, + }, + { + fieldName: 'name', + label: '模板名称', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入模板名称', + }, + }, + { + fieldName: 'accountId', + label: '邮箱账号', + component: 'ApiSelect', + componentProps: { + api: async () => await getSimpleMailAccountList(), + labelField: 'mail', + valueField: 'id', + clearable: true, + placeholder: '请选择邮箱账号', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'DatePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + getAccountMail?: (accountId: number) => string | undefined, +): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'code', + title: '模板编码', + minWidth: 120, + }, + { + field: 'name', + title: '模板名称', + minWidth: 120, + }, + { + field: 'title', + title: '模板标题', + minWidth: 120, + }, + { + field: 'accountId', + title: '邮箱账号', + minWidth: 120, + formatter: ({ cellValue }) => getAccountMail?.(cellValue) || '-', + }, + { + field: 'nickname', + title: '发送人名称', + minWidth: 120, + }, + { + field: 'status', + title: '开启状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-naive/src/views/system/mail/template/index.vue b/apps/web-naive/src/views/system/mail/template/index.vue new file mode 100644 index 000000000..bffcf750d --- /dev/null +++ b/apps/web-naive/src/views/system/mail/template/index.vue @@ -0,0 +1,205 @@ + + diff --git a/apps/web-naive/src/views/system/mail/template/modules/form.vue b/apps/web-naive/src/views/system/mail/template/modules/form.vue new file mode 100644 index 000000000..d50fdd9c9 --- /dev/null +++ b/apps/web-naive/src/views/system/mail/template/modules/form.vue @@ -0,0 +1,88 @@ + + + diff --git a/apps/web-naive/src/views/system/mail/template/modules/send-form.vue b/apps/web-naive/src/views/system/mail/template/modules/send-form.vue new file mode 100644 index 000000000..eb6f0ca25 --- /dev/null +++ b/apps/web-naive/src/views/system/mail/template/modules/send-form.vue @@ -0,0 +1,108 @@ + + + diff --git a/apps/web-naive/src/views/system/menu/data.ts b/apps/web-naive/src/views/system/menu/data.ts new file mode 100644 index 000000000..1c624980d --- /dev/null +++ b/apps/web-naive/src/views/system/menu/data.ts @@ -0,0 +1,324 @@ +import type { Recordable } from '@vben/types'; + +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemMenuApi } from '#/api/system/menu'; + +import { h } from 'vue'; + +import { + CommonStatusEnum, + DICT_TYPE, + SystemMenuTypeEnum, +} from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { IconifyIcon } from '@vben/icons'; +import { handleTree, isHttpUrl } from '@vben/utils'; + +import { z } from '#/adapter/form'; +import { getMenuList } from '#/api/system/menu'; +import { $t } from '#/locales'; +import { componentKeys } from '#/router/routes'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'parentId', + label: '上级菜单', + component: 'ApiTreeSelect', + componentProps: { + clearable: true, + api: async () => { + const data = await getMenuList(); + data.unshift({ + id: 0, + name: '顶级部门', + } as SystemMenuApi.Menu); + return handleTree(data); + }, + labelField: 'name', + valueField: 'id', + childrenField: 'children', + placeholder: '请选择上级菜单', + filterTreeNode(input: string, node: Recordable) { + if (!input || input.length === 0) { + return true; + } + const name: string = node.label ?? ''; + if (!name) return false; + return name.includes(input) || $t(name).includes(input); + }, + showSearch: true, + treeDefaultExpandedKeys: [0], + }, + rules: 'selectRequired', + renderComponentContent() { + return { + title({ label, icon }: { icon: string; label: string }) { + const components = []; + if (!label) return ''; + if (icon) { + components.push(h(IconifyIcon, { class: 'size-4', icon })); + } + components.push(h('span', { class: '' }, $t(label || ''))); + return h('div', { class: 'flex items-center gap-1' }, components); + }, + }; + }, + }, + { + fieldName: 'name', + label: '菜单名称', + component: 'Input', + componentProps: { + placeholder: '请输入菜单名称', + }, + rules: 'required', + }, + { + fieldName: 'type', + label: '菜单类型', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_MENU_TYPE, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.number().default(SystemMenuTypeEnum.DIR), + }, + { + fieldName: 'icon', + label: '菜单图标', + component: 'IconPicker', + componentProps: { + placeholder: '请选择菜单图标', + prefix: 'carbon', + }, + rules: 'required', + dependencies: { + triggerFields: ['type'], + show: (values) => { + return [SystemMenuTypeEnum.DIR, SystemMenuTypeEnum.MENU].includes( + values.type, + ); + }, + }, + }, + { + fieldName: 'path', + label: '路由地址', + component: 'Input', + componentProps: { + placeholder: '请输入路由地址', + }, + rules: z.string(), + help: '访问的路由地址,如:`user`。如需外网地址时,则以 `http(s)://` 开头', + dependencies: { + triggerFields: ['type', 'parentId'], + show: (values) => { + return [SystemMenuTypeEnum.DIR, SystemMenuTypeEnum.MENU].includes( + values.type, + ); + }, + rules: (values) => { + const schema = z.string().min(1, '路由地址不能为空'); + if (isHttpUrl(values.path)) { + return schema; + } + if (values.parentId === 0) { + return schema.refine( + (path) => path.charAt(0) === '/', + '路径必须以 / 开头', + ); + } + return schema.refine( + (path) => path.charAt(0) !== '/', + '路径不能以 / 开头', + ); + }, + }, + }, + { + fieldName: 'component', + label: '组件地址', + component: 'Input', + componentProps: { + placeholder: '请输入组件地址', + }, + dependencies: { + triggerFields: ['type'], + show: (values) => { + return [SystemMenuTypeEnum.MENU].includes(values.type); + }, + }, + }, + { + fieldName: 'componentName', + label: '组件名称', + component: 'AutoComplete', + componentProps: { + clearable: true, + filterOption(input: string, option: { value: string }) { + return option.value.toLowerCase().includes(input.toLowerCase()); + }, + placeholder: '请选择组件名称', + options: componentKeys.map((v) => ({ value: v })), + }, + dependencies: { + triggerFields: ['type'], + show: (values) => { + return [SystemMenuTypeEnum.MENU].includes(values.type); + }, + }, + }, + { + fieldName: 'permission', + label: '权限标识', + component: 'Input', + componentProps: { + placeholder: '请输入菜单描述', + }, + dependencies: { + show: (values) => { + return [SystemMenuTypeEnum.BUTTON, SystemMenuTypeEnum.MENU].includes( + values.type, + ); + }, + triggerFields: ['type'], + }, + }, + { + fieldName: 'sort', + label: '显示顺序', + component: 'InputNumber', + componentProps: { + min: 0, + placeholder: '请输入显示顺序', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '菜单状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'alwaysShow', + label: '总是显示', + component: 'RadioGroup', + componentProps: { + options: [ + { label: '总是', value: true }, + { label: '不是', value: false }, + ], + buttonStyle: 'solid', + optionType: 'button', + }, + rules: 'required', + defaultValue: true, + help: '选择不是时,当该菜单只有一个子菜单时,不展示自己,直接展示子菜单', + dependencies: { + triggerFields: ['type'], + show: (values) => { + return [SystemMenuTypeEnum.MENU].includes(values.type); + }, + }, + }, + { + fieldName: 'keepAlive', + label: '缓存状态', + component: 'RadioGroup', + componentProps: { + options: [ + { label: '缓存', value: true }, + { label: '不缓存', value: false }, + ], + buttonStyle: 'solid', + optionType: 'button', + }, + rules: 'required', + defaultValue: true, + help: '选择缓存时,则会被 `keep-alive` 缓存,必须填写「组件名称」字段', + dependencies: { + triggerFields: ['type'], + show: (values) => { + return [SystemMenuTypeEnum.MENU].includes(values.type); + }, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'name', + title: '菜单名称', + minWidth: 250, + align: 'left', + fixed: 'left', + slots: { default: 'name' }, + treeNode: true, + }, + { + field: 'type', + title: '菜单类型', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_MENU_TYPE }, + }, + }, + { + field: 'sort', + title: '显示排序', + minWidth: 100, + }, + { + field: 'permission', + title: '权限标识', + minWidth: 200, + }, + { + field: 'path', + title: '组件路径', + minWidth: 200, + }, + { + field: 'componentName', + title: '组件名称', + minWidth: 200, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + title: '操作', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-naive/src/views/system/menu/index.vue b/apps/web-naive/src/views/system/menu/index.vue new file mode 100644 index 000000000..50b581c85 --- /dev/null +++ b/apps/web-naive/src/views/system/menu/index.vue @@ -0,0 +1,181 @@ + + + diff --git a/apps/web-naive/src/views/system/menu/modules/form.vue b/apps/web-naive/src/views/system/menu/modules/form.vue new file mode 100644 index 000000000..6df8a28e4 --- /dev/null +++ b/apps/web-naive/src/views/system/menu/modules/form.vue @@ -0,0 +1,83 @@ + + + diff --git a/apps/web-naive/src/views/system/notice/data.ts b/apps/web-naive/src/views/system/notice/data.ts new file mode 100644 index 000000000..3ea6dcc3e --- /dev/null +++ b/apps/web-naive/src/views/system/notice/data.ts @@ -0,0 +1,136 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'title', + label: '公告标题', + component: 'Input', + rules: 'required', + }, + { + fieldName: 'type', + label: '公告类型', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_NOTICE_TYPE, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: 'required', + }, + { + fieldName: 'content', + label: '公告内容', + component: 'RichTextarea', + rules: 'required', + }, + { + fieldName: 'status', + label: '公告状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + type: 'textarea', + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'title', + label: '公告标题', + component: 'Input', + componentProps: { + placeholder: '请输入公告标题', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '公告状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择公告状态', + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '公告编号', + minWidth: 100, + }, + { + field: 'title', + title: '公告标题', + minWidth: 200, + }, + { + field: 'type', + title: '公告类型', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_NOTICE_TYPE }, + }, + }, + { + field: 'status', + title: '公告状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-naive/src/views/system/notice/index.vue b/apps/web-naive/src/views/system/notice/index.vue new file mode 100644 index 000000000..09120d6f0 --- /dev/null +++ b/apps/web-naive/src/views/system/notice/index.vue @@ -0,0 +1,189 @@ + + + diff --git a/apps/web-naive/src/views/system/notice/modules/form.vue b/apps/web-naive/src/views/system/notice/modules/form.vue new file mode 100644 index 000000000..5d4a58be0 --- /dev/null +++ b/apps/web-naive/src/views/system/notice/modules/form.vue @@ -0,0 +1,81 @@ + + + diff --git a/apps/web-naive/src/views/system/notify/message/data.ts b/apps/web-naive/src/views/system/notify/message/data.ts new file mode 100644 index 000000000..951671dd9 --- /dev/null +++ b/apps/web-naive/src/views/system/notify/message/data.ts @@ -0,0 +1,242 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemNotifyMessageApi } from '#/api/system/notify/message'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { formatDateTime } from '@vben/utils'; + +import { DictTag } from '#/components/dict-tag'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'userId', + label: '用户编号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入用户编号', + }, + }, + { + fieldName: 'userType', + label: '用户类型', + component: 'Select', + componentProps: { + clearable: true, + options: getDictOptions(DICT_TYPE.USER_TYPE, 'number'), + placeholder: '请选择用户类型', + }, + }, + { + fieldName: 'templateCode', + label: '模板编码', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入模板编码', + }, + }, + { + fieldName: 'templateType', + label: '模版类型', + component: 'Select', + componentProps: { + options: getDictOptions( + DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE, + 'number', + ), + clearable: true, + placeholder: '请选择模版类型', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'DatePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'userType', + title: '用户类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.USER_TYPE }, + }, + }, + { + field: 'userId', + title: '用户编号', + minWidth: 100, + }, + { + field: 'templateCode', + title: '模板编码', + minWidth: 120, + }, + { + field: 'templateNickname', + title: '发送人名称', + minWidth: 180, + }, + { + field: 'templateContent', + title: '模版内容', + minWidth: 200, + }, + { + field: 'templateParams', + title: '模版参数', + minWidth: 180, + formatter: ({ cellValue }) => { + try { + return JSON.stringify(cellValue); + } catch { + return ''; + } + }, + }, + { + field: 'templateType', + title: '模版类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE }, + }, + }, + { + field: 'readStatus', + title: '是否已读', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + }, + { + field: 'readTime', + title: '阅读时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 80, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'id', + label: '编号', + }, + { + field: 'userType', + label: '用户类型', + content: (data: SystemNotifyMessageApi.NotifyMessage) => { + return h(DictTag, { + type: DICT_TYPE.USER_TYPE, + value: data?.userType, + }); + }, + }, + { + field: 'userId', + label: '用户编号', + }, + { + field: 'templateId', + label: '模版编号', + }, + { + field: 'templateCode', + label: '模板编码', + }, + { + field: 'templateNickname', + label: '发送人名称', + }, + { + field: 'templateContent', + label: '模版内容', + }, + { + field: 'templateParams', + label: '模版参数', + content: (data: SystemNotifyMessageApi.NotifyMessage) => { + try { + return JSON.stringify(data?.templateParams); + } catch { + return ''; + } + }, + }, + { + field: 'templateType', + label: '模版类型', + content: (data: SystemNotifyMessageApi.NotifyMessage) => { + return h(DictTag, { + type: DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE, + value: data?.templateType, + }); + }, + }, + { + field: 'readStatus', + label: '是否已读', + content: (data: SystemNotifyMessageApi.NotifyMessage) => { + return h(DictTag, { + type: DICT_TYPE.INFRA_BOOLEAN_STRING, + value: data?.readStatus, + }); + }, + }, + { + field: 'readTime', + label: '阅读时间', + content: (data: SystemNotifyMessageApi.NotifyMessage) => { + return formatDateTime(data?.readTime || '') as string; + }, + }, + { + field: 'createTime', + label: '创建时间', + content: (data: SystemNotifyMessageApi.NotifyMessage) => { + return formatDateTime(data?.createTime || '') as string; + }, + }, + ]; +} diff --git a/apps/web-naive/src/views/system/notify/message/index.vue b/apps/web-naive/src/views/system/notify/message/index.vue new file mode 100644 index 000000000..f41ccfd92 --- /dev/null +++ b/apps/web-naive/src/views/system/notify/message/index.vue @@ -0,0 +1,84 @@ + + + diff --git a/apps/web-naive/src/views/system/notify/message/modules/detail.vue b/apps/web-naive/src/views/system/notify/message/modules/detail.vue new file mode 100644 index 000000000..6e4c26eab --- /dev/null +++ b/apps/web-naive/src/views/system/notify/message/modules/detail.vue @@ -0,0 +1,53 @@ + + + diff --git a/apps/web-naive/src/views/system/notify/my/data.ts b/apps/web-naive/src/views/system/notify/my/data.ts new file mode 100644 index 000000000..119f9097e --- /dev/null +++ b/apps/web-naive/src/views/system/notify/my/data.ts @@ -0,0 +1,142 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemNotifyMessageApi } from '#/api/system/notify/message'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { formatDateTime } from '@vben/utils'; + +import { DictTag } from '#/components/dict-tag'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'readStatus', + label: '是否已读', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'), + clearable: true, + placeholder: '请选择是否已读', + }, + }, + { + fieldName: 'createTime', + label: '发送时间', + component: 'DatePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + title: '', + width: 40, + type: 'checkbox', + }, + { + field: 'templateNickname', + title: '发送人', + minWidth: 180, + }, + { + field: 'createTime', + title: '发送时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'templateType', + title: '类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE }, + }, + }, + { + field: 'templateContent', + title: '消息内容', + minWidth: 300, + }, + { + field: 'readStatus', + title: '是否已读', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, + }, + }, + { + field: 'readTime', + title: '阅读时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'templateNickname', + label: '发送人', + }, + { + field: 'createTime', + label: '发送时间', + content: (data: SystemNotifyMessageApi.NotifyMessage) => { + return formatDateTime(data?.createTime || '') as string; + }, + }, + { + field: 'templateType', + label: '消息类型', + content: (data: SystemNotifyMessageApi.NotifyMessage) => { + return h(DictTag, { + type: DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE, + value: data?.templateType, + }); + }, + }, + { + field: 'readStatus', + label: '是否已读', + content: (data: SystemNotifyMessageApi.NotifyMessage) => { + return h(DictTag, { + type: DICT_TYPE.INFRA_BOOLEAN_STRING, + value: data?.readStatus, + }); + }, + }, + { + field: 'readTime', + label: '阅读时间', + content: (data: SystemNotifyMessageApi.NotifyMessage) => { + return formatDateTime(data?.readTime || '') as string; + }, + }, + { + field: 'templateContent', + label: '消息内容', + }, + ]; +} diff --git a/apps/web-naive/src/views/system/notify/my/index.vue b/apps/web-naive/src/views/system/notify/my/index.vue new file mode 100644 index 000000000..1d8616628 --- /dev/null +++ b/apps/web-naive/src/views/system/notify/my/index.vue @@ -0,0 +1,190 @@ + + diff --git a/apps/web-naive/src/views/system/notify/my/modules/detail.vue b/apps/web-naive/src/views/system/notify/my/modules/detail.vue new file mode 100644 index 000000000..6e4c26eab --- /dev/null +++ b/apps/web-naive/src/views/system/notify/my/modules/detail.vue @@ -0,0 +1,53 @@ + + + diff --git a/apps/web-naive/src/views/system/notify/template/data.ts b/apps/web-naive/src/views/system/notify/template/data.ts new file mode 100644 index 000000000..011bbe992 --- /dev/null +++ b/apps/web-naive/src/views/system/notify/template/data.ts @@ -0,0 +1,291 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE, UserTypeEnum } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '模板名称', + component: 'Input', + componentProps: { + placeholder: '请输入模板名称', + }, + rules: 'required', + }, + { + fieldName: 'code', + label: '模板编码', + component: 'Input', + componentProps: { + placeholder: '请输入模板编码', + }, + rules: 'required', + }, + { + fieldName: 'nickname', + label: '发送人名称', + component: 'Input', + componentProps: { + placeholder: '请输入发送人名称', + }, + rules: 'required', + }, + { + fieldName: 'content', + label: '模板内容', + component: 'Input', + componentProps: { + type: 'textarea', + placeholder: '请输入模板内容', + }, + rules: 'required', + }, + { + fieldName: 'type', + label: '模板类型', + component: 'Select', + componentProps: { + options: getDictOptions( + DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE, + 'number', + ), + placeholder: '请选择模板类型', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + type: 'textarea', + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '模板名称', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入模板名称', + }, + }, + { + fieldName: 'code', + label: '模板编码', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入模板编码', + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + clearable: true, + placeholder: '请选择状态', + }, + }, + { + fieldName: 'type', + label: '模板类型', + component: 'Select', + componentProps: { + options: getDictOptions( + DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE, + 'number', + ), + clearable: true, + placeholder: '请选择模板类型', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'DatePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 发送站内信表单 */ +export function useSendNotifyFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'content', + label: '模板内容', + component: 'Input', + componentProps: { + type: 'textarea', + disabled: true, + }, + }, + { + fieldName: 'templateCode', + label: '模板编码', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'userType', + label: '用户类型', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.USER_TYPE, 'number'), + }, + rules: z.number().default(UserTypeEnum.MEMBER), + }, + { + fieldName: 'userId', + label: '接收人 ID', + component: 'Input', + componentProps: { + placeholder: '请输入用户编号', + }, + dependencies: { + show(values) { + return values.userType === UserTypeEnum.MEMBER; + }, + triggerFields: ['userType'], + }, + rules: 'required', + }, + { + fieldName: 'userId', + label: '接收人', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + labelField: 'nickname', + valueField: 'id', + placeholder: '请选择接收人', + }, + dependencies: { + show(values) { + return values.userType === UserTypeEnum.ADMIN; + }, + triggerFields: ['userType'], + }, + rules: 'required', + }, + { + fieldName: 'templateParams', + label: '模板参数', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'name', + title: '模板名称', + minWidth: 120, + }, + { + field: 'code', + title: '模板编码', + minWidth: 120, + }, + { + field: 'nickname', + title: '发送人名称', + minWidth: 120, + }, + { + field: 'content', + title: '模板内容', + minWidth: 200, + }, + { + field: 'type', + title: '模板类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE }, + }, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'remark', + title: '备注', + minWidth: 120, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-naive/src/views/system/notify/template/index.vue b/apps/web-naive/src/views/system/notify/template/index.vue new file mode 100644 index 000000000..ab0037993 --- /dev/null +++ b/apps/web-naive/src/views/system/notify/template/index.vue @@ -0,0 +1,209 @@ + + + diff --git a/apps/web-naive/src/views/system/notify/template/modules/form.vue b/apps/web-naive/src/views/system/notify/template/modules/form.vue new file mode 100644 index 000000000..2d7557227 --- /dev/null +++ b/apps/web-naive/src/views/system/notify/template/modules/form.vue @@ -0,0 +1,88 @@ + + + diff --git a/apps/web-naive/src/views/system/notify/template/modules/send-form.vue b/apps/web-naive/src/views/system/notify/template/modules/send-form.vue new file mode 100644 index 000000000..1bc7bd5fb --- /dev/null +++ b/apps/web-naive/src/views/system/notify/template/modules/send-form.vue @@ -0,0 +1,111 @@ + + + diff --git a/apps/web-naive/src/views/system/oauth2/client/data.ts b/apps/web-naive/src/views/system/oauth2/client/data.ts new file mode 100644 index 000000000..cbc7c8108 --- /dev/null +++ b/apps/web-naive/src/views/system/oauth2/client/data.ts @@ -0,0 +1,265 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'clientId', + label: '客户端编号', + component: 'Input', + componentProps: { + placeholder: '请输入客户端编号', + }, + rules: 'required', + }, + { + fieldName: 'secret', + label: '客户端密钥', + component: 'Input', + componentProps: { + placeholder: '请输入客户端密钥', + }, + rules: 'required', + }, + { + fieldName: 'name', + label: '应用名', + component: 'Input', + componentProps: { + placeholder: '请输入应用名', + }, + rules: 'required', + }, + { + fieldName: 'logo', + label: '应用图标', + component: 'ImageUpload', + rules: 'required', + }, + { + fieldName: 'description', + label: '应用描述', + component: 'Input', + componentProps: { + type: 'textarea', + placeholder: '请输入应用描述', + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'accessTokenValiditySeconds', + label: '访问令牌的有效期', + component: 'InputNumber', + componentProps: { + placeholder: '请输入访问令牌的有效期,单位:秒', + min: 0, + }, + rules: 'required', + }, + { + fieldName: 'refreshTokenValiditySeconds', + label: '刷新令牌的有效期', + component: 'InputNumber', + componentProps: { + placeholder: '请输入刷新令牌的有效期,单位:秒', + min: 0, + }, + rules: 'required', + }, + { + fieldName: 'authorizedGrantTypes', + label: '授权类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_OAUTH2_GRANT_TYPE), + mode: 'multiple', + placeholder: '请输入授权类型', + }, + rules: 'required', + }, + { + fieldName: 'scopes', + label: '授权范围', + component: 'Select', + componentProps: { + placeholder: '请输入授权范围', + mode: 'tags', + clearable: true, + }, + }, + { + fieldName: 'autoApproveScopes', + label: '自动授权范围', + component: 'Select', + componentProps: { + placeholder: '请输入自动授权范围', + mode: 'multiple', + }, + dependencies: { + triggerFields: ['scopes'], + componentProps: (values) => ({ + options: values.scopes + ? values.scopes.map((scope: string) => ({ + label: scope, + value: scope, + })) + : [], + }), + }, + }, + { + fieldName: 'redirectUris', + label: '可重定向的 URI 地址', + component: 'Select', + componentProps: { + placeholder: '请输入可重定向的 URI 地址', + mode: 'tags', + }, + rules: 'required', + }, + { + fieldName: 'authorities', + label: '权限', + component: 'Select', + componentProps: { + placeholder: '请输入权限', + mode: 'tags', + }, + }, + { + fieldName: 'resourceIds', + label: '资源', + component: 'Select', + componentProps: { + mode: 'tags', + placeholder: '请输入资源', + }, + }, + { + fieldName: 'additionalInformation', + label: '附加信息', + component: 'Input', + componentProps: { + type: 'textarea', + placeholder: '请输入附加信息,JSON 格式数据', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '应用名', + component: 'Input', + componentProps: { + placeholder: '请输入应用名', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + clearable: true, + placeholder: '请输入状态', + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'clientId', + title: '客户端编号', + minWidth: 120, + }, + { + field: 'secret', + title: '客户端密钥', + minWidth: 120, + }, + { + field: 'name', + title: '应用名', + minWidth: 120, + }, + { + field: 'logo', + title: '应用图标', + minWidth: 100, + cellRender: { + name: 'CellImage', + }, + }, + { + field: 'status', + title: '状态', + minWidth: 80, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'accessTokenValiditySeconds', + title: '访问令牌的有效期', + minWidth: 150, + formatter: ({ cellValue }) => `${cellValue} 秒`, + }, + { + field: 'refreshTokenValiditySeconds', + title: '刷新令牌的有效期', + minWidth: 150, + formatter: ({ cellValue }) => `${cellValue} 秒`, + }, + { + field: 'authorizedGrantTypes', + title: '授权类型', + minWidth: 100, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-naive/src/views/system/oauth2/client/index.vue b/apps/web-naive/src/views/system/oauth2/client/index.vue new file mode 100644 index 000000000..ddbe5e329 --- /dev/null +++ b/apps/web-naive/src/views/system/oauth2/client/index.vue @@ -0,0 +1,176 @@ + + + diff --git a/apps/web-naive/src/views/system/oauth2/client/modules/form.vue b/apps/web-naive/src/views/system/oauth2/client/modules/form.vue new file mode 100644 index 000000000..090cbc6d0 --- /dev/null +++ b/apps/web-naive/src/views/system/oauth2/client/modules/form.vue @@ -0,0 +1,88 @@ + + + diff --git a/apps/web-naive/src/views/system/oauth2/token/data.ts b/apps/web-naive/src/views/system/oauth2/token/data.ts new file mode 100644 index 000000000..51a9d59c8 --- /dev/null +++ b/apps/web-naive/src/views/system/oauth2/token/data.ts @@ -0,0 +1,93 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'userId', + label: '用户编号', + component: 'Input', + componentProps: { + placeholder: '请输入用户编号', + clearable: true, + }, + }, + { + fieldName: 'userType', + label: '用户类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.USER_TYPE, 'number'), + placeholder: '请选择用户类型', + clearable: true, + }, + }, + { + fieldName: 'clientId', + label: '客户端编号', + component: 'Input', + componentProps: { + placeholder: '请输入客户端编号', + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'accessToken', + title: '访问令牌', + minWidth: 300, + }, + { + field: 'refreshToken', + title: '刷新令牌', + minWidth: 300, + }, + { + field: 'userId', + title: '用户编号', + minWidth: 100, + }, + { + field: 'userType', + title: '用户类型', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.USER_TYPE }, + }, + }, + { + field: 'clientId', + title: '客户端编号', + minWidth: 120, + }, + { + field: 'expiresTime', + title: '过期时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 80, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-naive/src/views/system/oauth2/token/index.vue b/apps/web-naive/src/views/system/oauth2/token/index.vue new file mode 100644 index 000000000..3358311e5 --- /dev/null +++ b/apps/web-naive/src/views/system/oauth2/token/index.vue @@ -0,0 +1,145 @@ + + + diff --git a/apps/web-naive/src/views/system/operatelog/data.ts b/apps/web-naive/src/views/system/operatelog/data.ts new file mode 100644 index 000000000..e14f50815 --- /dev/null +++ b/apps/web-naive/src/views/system/operatelog/data.ts @@ -0,0 +1,195 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemOperateLogApi } from '#/api/system/operate-log'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { formatDateTime } from '@vben/utils'; + +import { getSimpleUserList } from '#/api/system/user'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'userId', + label: '操作人', + component: 'ApiSelect', + componentProps: { + api: getSimpleUserList, + fieldNames: { + label: 'nickname', + value: 'id', + }, + clearable: true, + placeholder: '请选择操作人员', + }, + }, + { + fieldName: 'type', + label: '操作模块', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入操作模块', + }, + }, + { + fieldName: 'subType', + label: '操作名', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入操作名', + }, + }, + { + fieldName: 'action', + label: '操作内容', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入操作内容', + }, + }, + { + fieldName: 'createTime', + label: '操作时间', + component: 'DatePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + { + fieldName: 'bizId', + label: '业务编号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入业务编号', + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '日志编号', + minWidth: 100, + }, + { + field: 'userName', + title: '操作人', + minWidth: 120, + }, + { + field: 'type', + title: '操作模块', + minWidth: 120, + }, + { + field: 'subType', + title: '操作名', + minWidth: 160, + }, + { + field: 'action', + title: '操作内容', + minWidth: 200, + }, + { + field: 'createTime', + title: '操作时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'bizId', + title: '业务编号', + minWidth: 120, + }, + { + field: 'userIp', + title: '操作 IP', + minWidth: 120, + }, + { + title: '操作', + width: 80, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'id', + label: '日志编号', + }, + { + field: 'traceId', + label: '链路追踪', + hidden: (data: SystemOperateLogApi.OperateLog) => !data?.traceId, + }, + { + field: 'userId', + label: '操作人编号', + }, + { + field: 'userName', + label: '操作人名字', + }, + { + field: 'userIp', + label: '操作人 IP', + }, + { + field: 'userAgent', + label: '操作人 UA', + }, + { + field: 'type', + label: '操作模块', + }, + { + field: 'subType', + label: '操作名', + }, + { + field: 'action', + label: '操作内容', + }, + { + field: 'extra', + label: '操作拓展参数', + hidden: (data: SystemOperateLogApi.OperateLog) => !data?.extra, + }, + { + label: '请求 URL', + content: (data: SystemOperateLogApi.OperateLog) => { + if (data?.requestMethod && data?.requestUrl) { + return `${data.requestMethod} ${data.requestUrl}`; + } + return ''; + }, + }, + { + field: 'createTime', + label: '操作时间', + content: (data: SystemOperateLogApi.OperateLog) => { + return formatDateTime(data?.createTime || '') as string; + }, + }, + { + field: 'bizId', + label: '业务编号', + }, + ]; +} diff --git a/apps/web-naive/src/views/system/operatelog/index.vue b/apps/web-naive/src/views/system/operatelog/index.vue new file mode 100644 index 000000000..d4ccd08d5 --- /dev/null +++ b/apps/web-naive/src/views/system/operatelog/index.vue @@ -0,0 +1,104 @@ + + + diff --git a/apps/web-naive/src/views/system/operatelog/modules/detail.vue b/apps/web-naive/src/views/system/operatelog/modules/detail.vue new file mode 100644 index 000000000..daa439f00 --- /dev/null +++ b/apps/web-naive/src/views/system/operatelog/modules/detail.vue @@ -0,0 +1,53 @@ + + + diff --git a/apps/web-naive/src/views/system/post/data.ts b/apps/web-naive/src/views/system/post/data.ts new file mode 100644 index 000000000..5098cb080 --- /dev/null +++ b/apps/web-naive/src/views/system/post/data.ts @@ -0,0 +1,148 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'Input', + fieldName: 'name', + label: '岗位名称', + rules: 'required', + }, + { + component: 'Input', + fieldName: 'code', + label: '岗位编码', + rules: 'required', + }, + { + fieldName: 'sort', + label: '显示顺序', + component: 'InputNumber', + componentProps: { + min: 0, + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '岗位状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'remark', + label: '岗位备注', + component: 'Input', + componentProps: { + type: 'textarea', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '岗位名称', + component: 'Input', + componentProps: { + placeholder: '请输入岗位名称', + clearable: true, + }, + }, + { + fieldName: 'code', + label: '岗位编码', + component: 'Input', + componentProps: { + placeholder: '请输入岗位编码', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '岗位状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择岗位状态', + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '岗位编号', + minWidth: 200, + }, + { + field: 'name', + title: '岗位名称', + minWidth: 200, + }, + { + field: 'code', + title: '岗位编码', + minWidth: 200, + }, + { + field: 'sort', + title: '显示顺序', + minWidth: 100, + }, + { + field: 'remark', + title: '岗位备注', + minWidth: 200, + }, + { + field: 'status', + title: '岗位状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-naive/src/views/system/post/index.vue b/apps/web-naive/src/views/system/post/index.vue new file mode 100644 index 000000000..7173b5084 --- /dev/null +++ b/apps/web-naive/src/views/system/post/index.vue @@ -0,0 +1,183 @@ + + + diff --git a/apps/web-naive/src/views/system/post/modules/form.vue b/apps/web-naive/src/views/system/post/modules/form.vue new file mode 100644 index 000000000..d55975a0c --- /dev/null +++ b/apps/web-naive/src/views/system/post/modules/form.vue @@ -0,0 +1,81 @@ + + + diff --git a/apps/web-naive/src/views/system/role/data.ts b/apps/web-naive/src/views/system/role/data.ts new file mode 100644 index 000000000..42e9f22ca --- /dev/null +++ b/apps/web-naive/src/views/system/role/data.ts @@ -0,0 +1,258 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { + CommonStatusEnum, + DICT_TYPE, + SystemDataScopeEnum, +} from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '角色名称', + component: 'Input', + rules: 'required', + }, + { + fieldName: 'code', + label: '角色标识', + component: 'Input', + rules: 'required', + }, + { + fieldName: 'sort', + label: '显示顺序', + component: 'InputNumber', + componentProps: { + min: 0, + placeholder: '请输入显示顺序', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '角色状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'remark', + label: '角色备注', + component: 'Input', + componentProps: { + type: 'textarea', + }, + }, + ]; +} + +/** 分配数据权限的表单 */ +export function useAssignDataPermissionFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '角色名称', + component: 'Input', + componentProps: { + disabled: true, + }, + }, + { + component: 'Input', + fieldName: 'code', + label: '角色标识', + componentProps: { + disabled: true, + }, + }, + { + component: 'Select', + fieldName: 'dataScope', + label: '权限范围', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE, 'number'), + }, + }, + { + fieldName: 'dataScopeDeptIds', + label: '部门范围', + component: 'Input', + formItemClass: 'items-start', + dependencies: { + triggerFields: ['dataScope'], + show: (values) => { + return values.dataScope === SystemDataScopeEnum.DEPT_CUSTOM; + }, + }, + }, + ]; +} + +/** 分配菜单的表单 */ +export function useAssignMenuFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '角色名称', + component: 'Input', + componentProps: { + disabled: true, + }, + }, + { + fieldName: 'code', + label: '角色标识', + component: 'Input', + componentProps: { + disabled: true, + }, + }, + { + fieldName: 'menuIds', + label: '菜单权限', + component: 'Input', + formItemClass: 'items-start', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '角色名称', + component: 'Input', + componentProps: { + placeholder: '请输入角色名称', + clearable: true, + }, + }, + { + fieldName: 'code', + label: '角色标识', + component: 'Input', + componentProps: { + placeholder: '请输入角色标识', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '角色状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择角色状态', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'DatePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '角色编号', + minWidth: 100, + }, + { + field: 'name', + title: '角色名称', + minWidth: 200, + }, + { + field: 'type', + title: '角色类型', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_ROLE_TYPE }, + }, + }, + { + field: 'code', + title: '角色标识', + minWidth: 200, + }, + { + field: 'sort', + title: '显示顺序', + minWidth: 100, + }, + { + field: 'remark', + title: '角色备注', + minWidth: 100, + }, + { + field: 'status', + title: '角色状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 240, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-naive/src/views/system/role/index.vue b/apps/web-naive/src/views/system/role/index.vue new file mode 100644 index 000000000..12b8de84b --- /dev/null +++ b/apps/web-naive/src/views/system/role/index.vue @@ -0,0 +1,232 @@ + + + diff --git a/apps/web-naive/src/views/system/role/modules/assign-data-permission-form.vue b/apps/web-naive/src/views/system/role/modules/assign-data-permission-form.vue new file mode 100644 index 000000000..e264aee3e --- /dev/null +++ b/apps/web-naive/src/views/system/role/modules/assign-data-permission-form.vue @@ -0,0 +1,166 @@ + + + diff --git a/apps/web-naive/src/views/system/role/modules/assign-menu-form.vue b/apps/web-naive/src/views/system/role/modules/assign-menu-form.vue new file mode 100644 index 000000000..bf29ece72 --- /dev/null +++ b/apps/web-naive/src/views/system/role/modules/assign-menu-form.vue @@ -0,0 +1,171 @@ + + + diff --git a/apps/web-naive/src/views/system/role/modules/form.vue b/apps/web-naive/src/views/system/role/modules/form.vue new file mode 100644 index 000000000..9aaf614f1 --- /dev/null +++ b/apps/web-naive/src/views/system/role/modules/form.vue @@ -0,0 +1,81 @@ + + + diff --git a/apps/web-naive/src/views/system/sms/channel/data.ts b/apps/web-naive/src/views/system/sms/channel/data.ts new file mode 100644 index 000000000..7f04ef629 --- /dev/null +++ b/apps/web-naive/src/views/system/sms/channel/data.ts @@ -0,0 +1,196 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'signature', + label: '短信签名', + component: 'Input', + componentProps: { + placeholder: '请输入短信签名', + }, + rules: 'required', + }, + { + fieldName: 'code', + label: '渠道编码', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, 'string'), + placeholder: '请选择短信渠道', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '启用状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + type: 'textarea', + placeholder: '请输入备注', + }, + }, + { + fieldName: 'apiKey', + label: '短信 API 的账号', + component: 'Input', + componentProps: { + placeholder: '请输入短信 API 的账号', + }, + rules: 'required', + }, + { + fieldName: 'apiSecret', + label: '短信 API 的密钥', + component: 'Input', + componentProps: { + placeholder: '请输入短信 API 的密钥', + }, + }, + { + fieldName: 'callbackUrl', + label: '短信发送回调 URL', + component: 'Input', + componentProps: { + placeholder: '请输入短信发送回调 URL', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'signature', + label: '短信签名', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入短信签名', + }, + }, + { + fieldName: 'code', + label: '渠道编码', + component: 'Select', + componentProps: { + clearable: true, + options: getDictOptions(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, 'string'), + placeholder: '请选择短信渠道', + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + clearable: true, + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'DatePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'signature', + title: '短信签名', + minWidth: 120, + }, + { + field: 'code', + title: '渠道编码', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE }, + }, + }, + { + field: 'status', + title: '启用状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'apiKey', + title: '短信 API 的账号', + minWidth: 180, + }, + { + field: 'apiSecret', + title: '短信 API 的密钥', + minWidth: 180, + }, + { + field: 'callbackUrl', + title: '短信发送回调 URL', + minWidth: 180, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'remark', + title: '备注', + minWidth: 120, + }, + { + title: '操作', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-naive/src/views/system/sms/channel/index.vue b/apps/web-naive/src/views/system/sms/channel/index.vue new file mode 100644 index 000000000..1eec59a3c --- /dev/null +++ b/apps/web-naive/src/views/system/sms/channel/index.vue @@ -0,0 +1,173 @@ + + + diff --git a/apps/web-naive/src/views/system/sms/channel/modules/form.vue b/apps/web-naive/src/views/system/sms/channel/modules/form.vue new file mode 100644 index 000000000..6eb3eb10c --- /dev/null +++ b/apps/web-naive/src/views/system/sms/channel/modules/form.vue @@ -0,0 +1,87 @@ + + + diff --git a/apps/web-naive/src/views/system/sms/log/data.ts b/apps/web-naive/src/views/system/sms/log/data.ts new file mode 100644 index 000000000..4644bd7cf --- /dev/null +++ b/apps/web-naive/src/views/system/sms/log/data.ts @@ -0,0 +1,278 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemSmsLogApi } from '#/api/system/sms/log'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { formatDateTime } from '@vben/utils'; + +import { getSimpleSmsChannelList } from '#/api/system/sms/channel'; +import { DictTag } from '#/components/dict-tag'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'mobile', + label: '手机号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入手机号', + }, + }, + { + fieldName: 'channelId', + label: '短信渠道', + component: 'ApiSelect', + componentProps: { + api: async () => await getSimpleSmsChannelList(), + labelField: 'signature', + valueField: 'id', + clearable: true, + placeholder: '请选择短信渠道', + }, + }, + { + fieldName: 'templateId', + label: '模板编号', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入模板编号', + }, + }, + { + fieldName: 'sendStatus', + label: '发送状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_SMS_SEND_STATUS, 'number'), + clearable: true, + placeholder: '请选择发送状态', + }, + }, + { + fieldName: 'sendTime', + label: '发送时间', + component: 'DatePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + { + fieldName: 'receiveStatus', + label: '接收状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS, 'number'), + clearable: true, + placeholder: '请选择接收状态', + }, + }, + { + fieldName: 'receiveTime', + label: '接收时间', + component: 'DatePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'mobile', + title: '手机号', + minWidth: 120, + }, + { + field: 'templateContent', + title: '短信内容', + minWidth: 300, + }, + { + field: 'sendStatus', + title: '发送状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_SMS_SEND_STATUS }, + }, + }, + { + field: 'sendTime', + title: '发送时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'receiveStatus', + title: '接收状态', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS }, + }, + }, + { + field: 'receiveTime', + title: '接收时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'channelCode', + title: '短信渠道', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE }, + }, + }, + { + field: 'templateId', + title: '模板编号', + minWidth: 100, + }, + { + field: 'templateType', + title: '短信类型', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 80, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'createTime', + label: '创建时间', + content: (data: SystemSmsLogApi.SmsLog) => { + return formatDateTime(data?.createTime || '') as string; + }, + }, + { + field: 'mobile', + label: '手机号', + }, + { + field: 'channelCode', + label: '短信渠道', + }, + { + field: 'templateId', + label: '模板编号', + }, + { + field: 'templateType', + label: '模板类型', + content: (data: SystemSmsLogApi.SmsLog) => { + return h(DictTag, { + type: DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE, + value: data?.templateType, + }); + }, + }, + { + field: 'templateContent', + label: '短信内容', + }, + { + field: 'sendStatus', + label: '发送状态', + content: (data: SystemSmsLogApi.SmsLog) => { + return h(DictTag, { + type: DICT_TYPE.SYSTEM_SMS_SEND_STATUS, + value: data?.sendStatus, + }); + }, + }, + { + field: 'sendTime', + label: '发送时间', + content: (data: SystemSmsLogApi.SmsLog) => { + return formatDateTime(data?.sendTime || '') as string; + }, + }, + { + field: 'apiSendCode', + label: 'API 发送编码', + }, + { + field: 'apiSendMsg', + label: 'API 发送消息', + }, + { + field: 'receiveStatus', + label: '接收状态', + content: (data: SystemSmsLogApi.SmsLog) => { + return h(DictTag, { + type: DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS, + value: data?.receiveStatus, + }); + }, + }, + { + field: 'receiveTime', + label: '接收时间', + content: (data: SystemSmsLogApi.SmsLog) => { + return formatDateTime(data?.receiveTime || '') as string; + }, + }, + { + field: 'apiReceiveCode', + label: 'API 接收编码', + }, + { + field: 'apiReceiveMsg', + label: 'API 接收消息', + span: 2, + }, + { + field: 'apiRequestId', + label: 'API 请求 ID', + }, + { + field: 'apiSerialNo', + label: 'API 序列号', + }, + ]; +} diff --git a/apps/web-naive/src/views/system/sms/log/index.vue b/apps/web-naive/src/views/system/sms/log/index.vue new file mode 100644 index 000000000..96cd4b268 --- /dev/null +++ b/apps/web-naive/src/views/system/sms/log/index.vue @@ -0,0 +1,104 @@ + + + diff --git a/apps/web-naive/src/views/system/sms/log/modules/detail.vue b/apps/web-naive/src/views/system/sms/log/modules/detail.vue new file mode 100644 index 000000000..fcc8bfc94 --- /dev/null +++ b/apps/web-naive/src/views/system/sms/log/modules/detail.vue @@ -0,0 +1,53 @@ + + + diff --git a/apps/web-naive/src/views/system/sms/template/data.ts b/apps/web-naive/src/views/system/sms/template/data.ts new file mode 100644 index 000000000..062468169 --- /dev/null +++ b/apps/web-naive/src/views/system/sms/template/data.ts @@ -0,0 +1,277 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getSimpleSmsChannelList } from '#/api/system/sms/channel'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'type', + label: '短信类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE, 'number'), + placeholder: '请选择短信类型', + }, + rules: 'required', + }, + { + fieldName: 'name', + label: '模板名称', + component: 'Input', + componentProps: { + placeholder: '请输入模板名称', + }, + rules: 'required', + }, + { + fieldName: 'code', + label: '模板编码', + component: 'Input', + componentProps: { + placeholder: '请输入模板编码', + }, + rules: 'required', + }, + { + fieldName: 'channelId', + label: '短信渠道', + component: 'ApiSelect', + componentProps: { + api: async () => await getSimpleSmsChannelList(), + labelField: 'signature', + valueField: 'id', + placeholder: '请选择短信渠道', + }, + rules: 'required', + }, + { + fieldName: 'status', + label: '开启状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'content', + label: '模板内容', + component: 'Input', + componentProps: { + type: 'textarea', + placeholder: '请输入模板内容', + rows: 4, + }, + rules: 'required', + }, + { + fieldName: 'apiTemplateId', + label: '短信 API 的模板编号', + component: 'Input', + componentProps: { + placeholder: '请输入短信 API 的模板编号', + }, + rules: 'required', + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + type: 'textarea', + placeholder: '请输入备注', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'type', + label: '短信类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE, 'number'), + clearable: true, + placeholder: '请选择短信类型', + }, + }, + { + fieldName: 'status', + label: '开启状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + clearable: true, + placeholder: '请选择开启状态', + }, + }, + { + fieldName: 'code', + label: '模板编码', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入模板编码', + }, + }, + { + fieldName: 'name', + label: '模板名称', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入模板名称', + }, + }, + { + fieldName: 'channelId', + label: '短信渠道', + component: 'ApiSelect', + componentProps: { + api: async () => await getSimpleSmsChannelList(), + labelField: 'signature', + valueField: 'id', + clearable: true, + placeholder: '请选择短信渠道', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'DatePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 发送短信表单 */ +export function useSendSmsFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'content', + label: '模板内容', + component: 'Input', + componentProps: { + type: 'textarea', + disabled: true, + }, + }, + { + fieldName: 'mobile', + label: '手机号码', + component: 'Input', + componentProps: { + placeholder: '请输入手机号码', + }, + rules: 'required', + }, + { + fieldName: 'templateParams', + label: '模板参数', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'type', + title: '短信类型', + minWidth: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE }, + }, + }, + { + field: 'name', + title: '模板名称', + minWidth: 120, + }, + { + field: 'code', + title: '模板编码', + minWidth: 120, + }, + { + field: 'content', + title: '模板内容', + minWidth: 200, + }, + { + field: 'status', + title: '开启状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'apiTemplateId', + title: '短信 API 的模板编号', + minWidth: 180, + }, + { + field: 'channelCode', + title: '短信渠道', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'remark', + title: '备注', + minWidth: 120, + }, + { + title: '操作', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-naive/src/views/system/sms/template/index.vue b/apps/web-naive/src/views/system/sms/template/index.vue new file mode 100644 index 000000000..ced74e682 --- /dev/null +++ b/apps/web-naive/src/views/system/sms/template/index.vue @@ -0,0 +1,209 @@ + + + diff --git a/apps/web-naive/src/views/system/sms/template/modules/form.vue b/apps/web-naive/src/views/system/sms/template/modules/form.vue new file mode 100644 index 000000000..f7e5ac7f3 --- /dev/null +++ b/apps/web-naive/src/views/system/sms/template/modules/form.vue @@ -0,0 +1,88 @@ + + + diff --git a/apps/web-naive/src/views/system/sms/template/modules/send-form.vue b/apps/web-naive/src/views/system/sms/template/modules/send-form.vue new file mode 100644 index 000000000..4ac65e3c4 --- /dev/null +++ b/apps/web-naive/src/views/system/sms/template/modules/send-form.vue @@ -0,0 +1,108 @@ + + + diff --git a/apps/web-naive/src/views/system/social/client/data.ts b/apps/web-naive/src/views/system/social/client/data.ts new file mode 100644 index 000000000..8ddb5c242 --- /dev/null +++ b/apps/web-naive/src/views/system/social/client/data.ts @@ -0,0 +1,211 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { + CommonStatusEnum, + DICT_TYPE, + SystemUserSocialTypeEnum, +} from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '应用名', + component: 'Input', + componentProps: { + placeholder: '请输入应用名', + }, + rules: 'required', + }, + { + fieldName: 'socialType', + label: '社交平台', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_SOCIAL_TYPE, 'number'), + }, + rules: 'required', + }, + { + fieldName: 'userType', + label: '用户类型', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.USER_TYPE, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: 'required', + }, + { + fieldName: 'clientId', + label: '客户端编号', + component: 'Input', + componentProps: { + placeholder: '请输入客户端编号,对应各平台的 appKey', + }, + rules: 'required', + }, + { + fieldName: 'clientSecret', + label: '客户端密钥', + component: 'Input', + componentProps: { + placeholder: '请输入客户端密钥,对应各平台的 appSecret', + }, + rules: 'required', + }, + { + fieldName: 'agentId', + label: 'agentId', + component: 'Input', + componentProps: { + placeholder: '授权方的网页应用 ID,有则填', + }, + dependencies: { + triggerFields: ['socialType'], + show: (values) => + values.socialType === SystemUserSocialTypeEnum.WECHAT_ENTERPRISE.type, + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '应用名', + component: 'Input', + componentProps: { + placeholder: '请输入应用名', + clearable: true, + }, + }, + { + fieldName: 'socialType', + label: '社交平台', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_SOCIAL_TYPE, 'number'), + placeholder: '请选择社交平台', + clearable: true, + }, + }, + { + fieldName: 'userType', + label: '用户类型', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.USER_TYPE, 'number'), + placeholder: '请选择用户类型', + clearable: true, + }, + }, + { + fieldName: 'clientId', + label: '客户端编号', + component: 'Input', + componentProps: { + placeholder: '请输入客户端编号', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + placeholder: '请选择状态', + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '编号', + minWidth: 100, + }, + { + field: 'name', + title: '应用名', + minWidth: 120, + }, + { + field: 'socialType', + title: '社交平台', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_SOCIAL_TYPE }, + }, + }, + { + field: 'userType', + title: '用户类型', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.USER_TYPE }, + }, + }, + { + field: 'clientId', + title: '客户端编号', + minWidth: 180, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-naive/src/views/system/social/client/index.vue b/apps/web-naive/src/views/system/social/client/index.vue new file mode 100644 index 000000000..73d337a63 --- /dev/null +++ b/apps/web-naive/src/views/system/social/client/index.vue @@ -0,0 +1,174 @@ + + + diff --git a/apps/web-naive/src/views/system/social/client/modules/form.vue b/apps/web-naive/src/views/system/social/client/modules/form.vue new file mode 100644 index 000000000..d605d863d --- /dev/null +++ b/apps/web-naive/src/views/system/social/client/modules/form.vue @@ -0,0 +1,88 @@ + + + diff --git a/apps/web-naive/src/views/system/social/user/data.ts b/apps/web-naive/src/views/system/social/user/data.ts new file mode 100644 index 000000000..da5cba3d7 --- /dev/null +++ b/apps/web-naive/src/views/system/social/user/data.ts @@ -0,0 +1,164 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemSocialUserApi } from '#/api/system/social/user'; +import type { DescriptionItemSchema } from '#/components/description'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { DictTag } from '#/components/dict-tag'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'type', + label: '社交平台', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_SOCIAL_TYPE, 'number'), + placeholder: '请选择社交平台', + clearable: true, + }, + }, + { + fieldName: 'nickname', + label: '用户昵称', + component: 'Input', + componentProps: { + placeholder: '请输入用户昵称', + clearable: true, + }, + }, + { + fieldName: 'openid', + label: '社交 openid', + component: 'Input', + componentProps: { + placeholder: '请输入社交 openid', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'DatePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'type', + title: '社交平台', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.SYSTEM_SOCIAL_TYPE }, + }, + }, + { + field: 'openid', + title: '社交 openid', + minWidth: 180, + }, + { + field: 'nickname', + title: '用户昵称', + minWidth: 120, + }, + { + field: 'avatar', + title: '用户头像', + minWidth: 100, + cellRender: { + name: 'CellImage', + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'updateTime', + title: '更新时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 120, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} + +/** 详情页的字段 */ +export function useDetailSchema(): DescriptionItemSchema[] { + return [ + { + field: 'type', + label: '社交平台', + content: (data: SystemSocialUserApi.SocialUser) => { + return h(DictTag, { + type: DICT_TYPE.SYSTEM_SOCIAL_TYPE, + value: data?.type, + }); + }, + }, + { + field: 'nickname', + label: '用户昵称', + }, + { + field: 'avatar', + label: '用户头像', + // TODO @芋艿:使用 antd 的 Image 组件 + content: (data: SystemSocialUserApi.SocialUser) => { + if (data?.avatar) { + return h('img', { + src: data.avatar, + style: 'width: 30px; height: 30px; cursor: pointer;', + onClick: () => { + // 可以添加图片预览功能 + window.open(data.avatar, '_blank'); + }, + }); + } + return '无'; + }, + }, + { + field: 'token', + label: '社交 token', + }, + { + field: 'rawTokenInfo', + label: '原始 Token 数据', + }, + { + field: 'rawUserInfo', + label: '原始 User 数据', + }, + { + field: 'code', + label: '最后一次的认证 code', + }, + { + field: 'state', + label: '最后一次的认证 state', + }, + ]; +} diff --git a/apps/web-naive/src/views/system/social/user/index.vue b/apps/web-naive/src/views/system/social/user/index.vue new file mode 100644 index 000000000..c114d6d44 --- /dev/null +++ b/apps/web-naive/src/views/system/social/user/index.vue @@ -0,0 +1,78 @@ + + + diff --git a/apps/web-naive/src/views/system/social/user/modules/detail.vue b/apps/web-naive/src/views/system/social/user/modules/detail.vue new file mode 100644 index 000000000..6234ae279 --- /dev/null +++ b/apps/web-naive/src/views/system/social/user/modules/detail.vue @@ -0,0 +1,58 @@ + + + diff --git a/apps/web-naive/src/views/system/tenant/data.ts b/apps/web-naive/src/views/system/tenant/data.ts new file mode 100644 index 000000000..6bca0e524 --- /dev/null +++ b/apps/web-naive/src/views/system/tenant/data.ts @@ -0,0 +1,247 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemTenantPackageApi } from '#/api/system/tenant-package'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getTenantPackageList } from '#/api/system/tenant-package'; +import { getRangePickerDefaultProps } from '#/utils'; + +// TODO @xingyu:这个不用 ref 么? +let tenantPackageList: SystemTenantPackageApi.TenantPackage[] = []; + +async function getTenantPackageData() { + tenantPackageList = await getTenantPackageList(); +} + +getTenantPackageData(); + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '租户名称', + component: 'Input', + rules: 'required', + }, + { + fieldName: 'packageId', + label: '租户套餐', + component: 'ApiSelect', + componentProps: { + api: () => getTenantPackageList(), + labelField: 'name', + valueField: 'id', + placeholder: '请选择租户套餐', + }, + rules: 'required', + }, + { + fieldName: 'contactName', + label: '联系人', + component: 'Input', + rules: 'required', + }, + { + fieldName: 'contactMobile', + label: '联系手机', + component: 'Input', + rules: 'mobile', + }, + { + label: '用户名称', + fieldName: 'username', + component: 'Input', + rules: 'required', + dependencies: { + triggerFields: ['id'], + show: (values) => !values.id, + }, + }, + { + label: '用户密码', + fieldName: 'password', + component: 'InputPassword', + rules: 'required', + dependencies: { + triggerFields: ['id'], + show: (values) => !values.id, + }, + }, + { + label: '账号额度', + fieldName: 'accountCount', + component: 'InputNumber', + rules: 'required', + }, + { + label: '过期时间', + fieldName: 'expireTime', + component: 'DatePicker', + componentProps: { + format: 'YYYY-MM-DD', + valueFormat: 'x', + placeholder: '请选择过期时间', + }, + rules: 'required', + }, + { + label: '绑定域名', + fieldName: 'websites', + component: 'Select', + componentProps: { + placeholder: '请输入绑定域名', + mode: 'tags', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '租户状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '租户名', + component: 'Input', + componentProps: { + placeholder: '请输入租户名', + clearable: true, + }, + }, + { + fieldName: 'contactName', + label: '联系人', + component: 'Input', + componentProps: { + placeholder: '请输入联系人', + clearable: true, + }, + }, + { + fieldName: 'contactMobile', + label: '联系手机', + component: 'Input', + componentProps: { + placeholder: '请输入联系手机', + clearable: true, + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + placeholder: '请选择状态', + clearable: true, + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'DatePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '租户编号', + minWidth: 100, + }, + { + field: 'name', + title: '租户名', + minWidth: 180, + }, + { + field: 'packageId', + title: '租户套餐', + minWidth: 180, + formatter: ({ cellValue }) => { + return cellValue === 0 + ? '系统租户' + : tenantPackageList.find((pkg) => pkg.id === cellValue)?.name || '-'; + }, + }, + { + field: 'contactName', + title: '联系人', + minWidth: 100, + }, + { + field: 'contactMobile', + title: '联系手机', + minWidth: 180, + }, + { + field: 'accountCount', + title: '账号额度', + minWidth: 100, + }, + { + field: 'expireTime', + title: '过期时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + field: 'websites', + title: '绑定域名', + minWidth: 180, + }, + { + field: 'status', + title: '租户状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 130, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-naive/src/views/system/tenant/index.vue b/apps/web-naive/src/views/system/tenant/index.vue new file mode 100644 index 000000000..d325063c6 --- /dev/null +++ b/apps/web-naive/src/views/system/tenant/index.vue @@ -0,0 +1,185 @@ + + diff --git a/apps/web-naive/src/views/system/tenant/modules/form.vue b/apps/web-naive/src/views/system/tenant/modules/form.vue new file mode 100644 index 000000000..f122901cd --- /dev/null +++ b/apps/web-naive/src/views/system/tenant/modules/form.vue @@ -0,0 +1,80 @@ + + diff --git a/apps/web-naive/src/views/system/tenantPackage/data.ts b/apps/web-naive/src/views/system/tenantPackage/data.ts new file mode 100644 index 000000000..221c781eb --- /dev/null +++ b/apps/web-naive/src/views/system/tenantPackage/data.ts @@ -0,0 +1,130 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; + +import { z } from '#/adapter/form'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'id', + component: 'Input', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'name', + label: '套餐名称', + component: 'Input', + rules: 'required', + }, + { + fieldName: 'menuIds', + label: '菜单权限', + component: 'Input', + formItemClass: 'items-start', + }, + { + fieldName: 'status', + label: '状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + type: 'textarea', + }, + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '套餐名称', + component: 'Input', + componentProps: { + clearable: true, + placeholder: '请输入套餐名称', + }, + }, + { + fieldName: 'status', + label: '状态', + component: 'Select', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + clearable: true, + placeholder: '请选择状态', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'DatePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns(): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '套餐编号', + minWidth: 100, + }, + { + field: 'name', + title: '套餐名称', + minWidth: 180, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.COMMON_STATUS }, + }, + }, + { + field: 'remark', + title: '备注', + minWidth: 200, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 220, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-naive/src/views/system/tenantPackage/index.vue b/apps/web-naive/src/views/system/tenantPackage/index.vue new file mode 100644 index 000000000..9b2470d9c --- /dev/null +++ b/apps/web-naive/src/views/system/tenantPackage/index.vue @@ -0,0 +1,172 @@ + + + diff --git a/apps/web-naive/src/views/system/tenantPackage/modules/form.vue b/apps/web-naive/src/views/system/tenantPackage/modules/form.vue new file mode 100644 index 000000000..6f3b363c0 --- /dev/null +++ b/apps/web-naive/src/views/system/tenantPackage/modules/form.vue @@ -0,0 +1,163 @@ + + + diff --git a/apps/web-naive/src/views/system/user/data.ts b/apps/web-naive/src/views/system/user/data.ts new file mode 100644 index 000000000..d1795e20a --- /dev/null +++ b/apps/web-naive/src/views/system/user/data.ts @@ -0,0 +1,347 @@ +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { SystemUserApi } from '#/api/system/user'; + +import { CommonStatusEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { $t } from '@vben/locales'; +import { handleTree } from '@vben/utils'; + +import { z } from '#/adapter/form'; +import { getDeptList } from '#/api/system/dept'; +import { getSimplePostList } from '#/api/system/post'; +import { getSimpleRoleList } from '#/api/system/role'; +import { getRangePickerDefaultProps } from '#/utils'; + +/** 新增/修改的表单 */ +export function useFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'username', + label: '用户名称', + component: 'Input', + rules: 'required', + }, + { + label: '用户密码', + fieldName: 'password', + component: 'InputPassword', + rules: 'required', + dependencies: { + triggerFields: ['id'], + show: (values) => !values.id, + }, + }, + { + fieldName: 'nickname', + label: '用户昵称', + component: 'Input', + rules: 'required', + }, + { + fieldName: 'deptId', + label: '归属部门', + component: 'ApiTreeSelect', + componentProps: { + api: async () => { + const data = await getDeptList(); + return handleTree(data); + }, + labelField: 'name', + valueField: 'id', + childrenField: 'children', + placeholder: '请选择归属部门', + treeDefaultExpandAll: true, + }, + }, + { + fieldName: 'postIds', + label: '岗位', + component: 'ApiSelect', + componentProps: { + api: getSimplePostList, + labelField: 'name', + valueField: 'id', + mode: 'multiple', + placeholder: '请选择岗位', + }, + }, + { + fieldName: 'email', + label: '邮箱', + component: 'Input', + rules: z.string().email('邮箱格式不正确').or(z.literal('')).optional(), + componentProps: { + placeholder: '请输入邮箱', + }, + }, + { + fieldName: 'mobile', + label: '手机号码', + component: 'Input', + componentProps: { + placeholder: '请输入手机号码', + }, + }, + { + fieldName: 'sex', + label: '用户性别', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.number().default(1), + }, + { + fieldName: 'status', + label: '用户状态', + component: 'RadioGroup', + componentProps: { + options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), + buttonStyle: 'solid', + optionType: 'button', + }, + rules: z.number().default(CommonStatusEnum.ENABLE), + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + type: 'textarea', + }, + }, + ]; +} + +/** 重置密码的表单 */ +export function useResetPasswordFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + component: 'VbenInputPassword', + componentProps: { + passwordStrength: true, + placeholder: '请输入新密码', + }, + dependencies: { + rules(values) { + return z + .string({ message: '请输入新密码' }) + .min(5, '密码长度不能少于 5 个字符') + .max(20, '密码长度不能超过 20 个字符') + .refine( + (value) => value !== values.oldPassword, + '新旧密码不能相同', + ); + }, + triggerFields: ['newPassword', 'oldPassword'], + }, + fieldName: 'newPassword', + label: '新密码', + rules: 'required', + }, + { + component: 'VbenInputPassword', + componentProps: { + passwordStrength: true, + placeholder: $t('authentication.confirmPassword'), + }, + dependencies: { + rules(values) { + return z + .string({ message: '请输入确认密码' }) + .min(5, '密码长度不能少于 5 个字符') + .max(20, '密码长度不能超过 20 个字符') + .refine( + (value) => value === values.newPassword, + '新密码和确认密码不一致', + ); + }, + triggerFields: ['newPassword', 'confirmPassword'], + }, + fieldName: 'confirmPassword', + label: '确认密码', + rules: 'required', + }, + ]; +} + +/** 分配角色的表单 */ +export function useAssignRoleFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'username', + label: '用户名称', + component: 'Input', + componentProps: { + disabled: true, + }, + }, + { + fieldName: 'nickname', + label: '用户昵称', + component: 'Input', + componentProps: { + disabled: true, + }, + }, + { + fieldName: 'roleIds', + label: '角色', + component: 'ApiSelect', + componentProps: { + api: getSimpleRoleList, + labelField: 'name', + valueField: 'id', + mode: 'multiple', + placeholder: '请选择角色', + }, + }, + ]; +} + +/** 用户导入的表单 */ +export function useImportFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'file', + label: '用户数据', + component: 'Upload', + rules: 'required', + help: '仅允许导入 xls、xlsx 格式文件', + }, + { + fieldName: 'updateSupport', + label: '是否覆盖', + component: 'Switch', + componentProps: { + checkedChildren: '是', + unCheckedChildren: '否', + }, + rules: z.boolean().default(false), + help: '是否更新已经存在的用户数据', + }, + ]; +} + +/** 列表的搜索表单 */ +export function useGridFormSchema(): VbenFormSchema[] { + return [ + { + fieldName: 'username', + label: '用户名称', + component: 'Input', + componentProps: { + placeholder: '请输入用户名称', + clearable: true, + }, + }, + { + fieldName: 'mobile', + label: '手机号码', + component: 'Input', + componentProps: { + placeholder: '请输入手机号码', + clearable: true, + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'DatePicker', + componentProps: { + ...getRangePickerDefaultProps(), + clearable: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + onStatusChange?: ( + newStatus: number, + row: SystemUserApi.User, + ) => PromiseLike, +): VxeTableGridOptions['columns'] { + return [ + { type: 'checkbox', width: 40 }, + { + field: 'id', + title: '用户编号', + minWidth: 100, + }, + { + field: 'username', + title: '用户名称', + minWidth: 120, + }, + { + field: 'nickname', + title: '用户昵称', + minWidth: 120, + }, + { + field: 'deptName', + title: '部门', + minWidth: 120, + }, + { + field: 'mobile', + title: '手机号码', + minWidth: 120, + }, + { + field: 'status', + title: '状态', + minWidth: 100, + align: 'center', + cellRender: { + attrs: { beforeChange: onStatusChange }, + name: 'CellSwitch', + props: { + checkedValue: CommonStatusEnum.ENABLE, + unCheckedValue: CommonStatusEnum.DISABLE, + }, + }, + }, + { + field: 'createTime', + title: '创建时间', + minWidth: 180, + formatter: 'formatDateTime', + }, + { + title: '操作', + width: 180, + fixed: 'right', + slots: { default: 'actions' }, + }, + ]; +} diff --git a/apps/web-naive/src/views/system/user/index.vue b/apps/web-naive/src/views/system/user/index.vue new file mode 100644 index 000000000..0dd926595 --- /dev/null +++ b/apps/web-naive/src/views/system/user/index.vue @@ -0,0 +1,302 @@ + + + diff --git a/apps/web-naive/src/views/system/user/modules/assign-role-form.vue b/apps/web-naive/src/views/system/user/modules/assign-role-form.vue new file mode 100644 index 000000000..aa2f303fe --- /dev/null +++ b/apps/web-naive/src/views/system/user/modules/assign-role-form.vue @@ -0,0 +1,77 @@ + + + diff --git a/apps/web-naive/src/views/system/user/modules/dept-tree.vue b/apps/web-naive/src/views/system/user/modules/dept-tree.vue new file mode 100644 index 000000000..7542da87d --- /dev/null +++ b/apps/web-naive/src/views/system/user/modules/dept-tree.vue @@ -0,0 +1,90 @@ + + + diff --git a/apps/web-naive/src/views/system/user/modules/form.vue b/apps/web-naive/src/views/system/user/modules/form.vue new file mode 100644 index 000000000..c5479df8f --- /dev/null +++ b/apps/web-naive/src/views/system/user/modules/form.vue @@ -0,0 +1,81 @@ + + + diff --git a/apps/web-naive/src/views/system/user/modules/import-form.vue b/apps/web-naive/src/views/system/user/modules/import-form.vue new file mode 100644 index 000000000..e41e3c560 --- /dev/null +++ b/apps/web-naive/src/views/system/user/modules/import-form.vue @@ -0,0 +1,84 @@ + + + diff --git a/apps/web-naive/src/views/system/user/modules/reset-password-form.vue b/apps/web-naive/src/views/system/user/modules/reset-password-form.vue new file mode 100644 index 000000000..1f04b5af7 --- /dev/null +++ b/apps/web-naive/src/views/system/user/modules/reset-password-form.vue @@ -0,0 +1,65 @@ + + +