diff --git a/apps/web-antd/src/components/cropper/index.ts b/apps/web-antd/src/components/cropper/index.ts index a553b57d5..43fd89ff3 100644 --- a/apps/web-antd/src/components/cropper/index.ts +++ b/apps/web-antd/src/components/cropper/index.ts @@ -1,3 +1,3 @@ export { default as CropperAvatar } from './cropper-avatar.vue'; export { default as CropperImage } from './cropper.vue'; -export type { Cropper } from './typing'; +export type { CropperType } from './typing'; diff --git a/apps/web-antd/src/locales/langs/en-US/demos.json b/apps/web-antd/src/locales/langs/en-US/demos.json deleted file mode 100644 index 071564349..000000000 --- a/apps/web-antd/src/locales/langs/en-US/demos.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "title": "Demos", - "antd": "Ant Design Vue", - "vben": { - "title": "Project", - "about": "About", - "document": "Document", - "antdv": "Ant Design Vue Version", - "naive-ui": "Naive UI Version", - "element-plus": "Element Plus Version" - } -} diff --git a/apps/web-antd/src/locales/langs/zh-CN/demos.json b/apps/web-antd/src/locales/langs/zh-CN/demos.json deleted file mode 100644 index 93ee722f5..000000000 --- a/apps/web-antd/src/locales/langs/zh-CN/demos.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "title": "演示", - "antd": "Ant Design Vue", - "vben": { - "title": "项目", - "about": "关于", - "document": "文档", - "antdv": "Ant Design Vue 版本", - "naive-ui": "Naive UI 版本", - "element-plus": "Element Plus 版本" - } -} diff --git a/apps/web-naive/.env b/apps/web-naive/.env index 213b52ce9..8e294a48d 100644 --- a/apps/web-naive/.env +++ b/apps/web-naive/.env @@ -1,8 +1,26 @@ # 应用标题 -VITE_APP_TITLE=Vben Admin Naive +VITE_APP_TITLE=芋道管理系统 # 应用命名空间,用于缓存、store等功能的前缀,确保隔离 -VITE_APP_NAMESPACE=vben-web-naive +VITE_APP_NAMESPACE=yudao-vben-naive # 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密 VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key + +# 是否开启模拟数据 +VITE_NITRO_MOCK=false + +# 租户开关 +VITE_APP_TENANT_ENABLE=true + +# 验证码的开关 +VITE_APP_CAPTCHA_ENABLE=false + +# 文档地址的开关 +VITE_APP_DOCALERT_ENABLE=true + +# 百度统计 +VITE_APP_BAIDU_CODE = e98f2eab6ceb8688bc6d8fc5332ff093 + +# GoView域名 +VITE_GOVIEW_URL='http://127.0.0.1:3000' diff --git a/apps/web-naive/.env.development b/apps/web-naive/.env.development index 11c5254ae..0cc341046 100644 --- a/apps/web-naive/.env.development +++ b/apps/web-naive/.env.development @@ -3,14 +3,19 @@ VITE_PORT=5888 VITE_BASE=/ +# 请求路径 +VITE_BASE_URL=http://127.0.0.1:48080 # 接口地址 -VITE_GLOB_API_URL=/api - -# 是否开启 Nitro Mock服务,true 为开启,false 为关闭 -VITE_NITRO_MOCK=true - +VITE_GLOB_API_URL=/admin-api +# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务 +VITE_UPLOAD_TYPE=server # 是否打开 devtools,true 为打开,false 为关闭 VITE_DEVTOOLS=false # 是否注入全局loading VITE_INJECT_APP_LOADING=true + +# 默认登录用户名 +VITE_APP_DEFAULT_USERNAME=admin +# 默认登录密码 +VITE_APP_DEFAULT_PASSWORD=admin123 diff --git a/apps/web-naive/.env.production b/apps/web-naive/.env.production index 5375847a6..910fd64cc 100644 --- a/apps/web-naive/.env.production +++ b/apps/web-naive/.env.production @@ -1,7 +1,11 @@ VITE_BASE=/ +# 请求路径 +VITE_BASE_URL=http://127.0.0.1:48080 # 接口地址 -VITE_GLOB_API_URL=https://mock-napi.vben.pro/api +VITE_GLOB_API_URL=http://127.0.0.1:48080/admin-api +# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务 +VITE_UPLOAD_TYPE=server # 是否开启压缩,可以设置为 none, brotli, gzip VITE_COMPRESS=none diff --git a/apps/web-naive/package.json b/apps/web-naive/package.json index ed95930c2..35167c759 100644 --- a/apps/web-naive/package.json +++ b/apps/web-naive/package.json @@ -41,9 +41,15 @@ "@vben/types": "workspace:*", "@vben/utils": "workspace:*", "@vueuse/core": "catalog:", + "cropperjs": "catalog:", + "crypto-js": "catalog:", + "dayjs": "catalog:", "naive-ui": "catalog:", "pinia": "catalog:", "vue": "catalog:", "vue-router": "catalog:" + }, + "devDependencies": { + "@types/crypto-js": "catalog:" } } diff --git a/apps/web-naive/src/adapter/form.ts b/apps/web-naive/src/adapter/form.ts index 2f2ed2abe..cc3435f01 100644 --- a/apps/web-naive/src/adapter/form.ts +++ b/apps/web-naive/src/adapter/form.ts @@ -8,6 +8,9 @@ import type { ComponentType } from './component'; import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; import { $t } from '@vben/locales'; +/** 手机号正则表达式(中国) */ +const MOBILE_REGEX = /(?:0|86|\+86)?1[3-9]\d{9}/; + setupVbenForm({ config: { // naive-ui组件的空值为null,不能是undefined,否则重置表单时不生效 @@ -32,6 +35,25 @@ setupVbenForm({ } return true; }, + // 手机号非必填 + mobile: (value, _params, ctx) => { + if (value === undefined || value === null || value.length === 0) { + return true; + } else if (!MOBILE_REGEX.test(value)) { + return $t('ui.formRules.phone', [ctx.label]); + } + return true; + }, + // 手机号必填 + mobileRequired: (value, _params, ctx) => { + if (value === undefined || value === null || value.length === 0) { + return $t('ui.formRules.required', [ctx.label]); + } + if (!MOBILE_REGEX.test(value)) { + return $t('ui.formRules.phone', [ctx.label]); + } + return true; + }, }, }); diff --git a/apps/web-naive/src/adapter/vxe-table.ts b/apps/web-naive/src/adapter/vxe-table.ts index 081cfb29e..bd543f964 100644 --- a/apps/web-naive/src/adapter/vxe-table.ts +++ b/apps/web-naive/src/adapter/vxe-table.ts @@ -1,8 +1,16 @@ +import type { Recordable } from '@vben/types'; + import { h } from 'vue'; +import { IconifyIcon } from '@vben/icons'; +import { $te } from '@vben/locales'; import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table'; +import { isFunction, isString } from '@vben/utils'; -import { NButton, NImage } from 'naive-ui'; +import { NButton, NImage, NPopconfirm, NSwitch } from 'naive-ui'; + +import { DictTag } from '#/components/dict-tag'; +import { $t } from '#/locales'; import { useVbenForm } from './form'; @@ -20,16 +28,32 @@ setupVbenVxeTable({ // 全局禁用vxe-table的表单配置,使用formOptions enabled: false, }, + toolbarConfig: { + import: false, // 是否导入 + export: false, // 是否导出 + refresh: true, // 是否刷新 + print: false, // 是否打印 + zoom: true, // 是否缩放 + custom: true, // 是否自定义配置 + }, + customConfig: { + mode: 'modal', + }, proxyConfig: { autoLoad: true, response: { - result: 'items', + result: 'list', total: 'total', - list: 'items', }, showActiveMsg: true, showResponseMsg: false, }, + pagerConfig: { + enabled: true, + }, + sortConfig: { + multiple: true, + }, round: true, showOverflow: true, size: 'small', @@ -56,12 +80,211 @@ setupVbenVxeTable({ }, }); + // 表格配置项可以用 cellRender: { name: 'CellDict', props:{dictType: ''} }, + vxeUI.renderer.add('CellDict', { + renderTableDefault(renderOpts, params) { + const { props } = renderOpts; + const { column, row } = params; + if (!props) { + return ''; + } + // 使用 DictTag 组件替代原来的实现 + return h(DictTag, { + type: props.type, + value: row[column.field]?.toString(), + }); + }, + }); + + // 表格配置项可以用 cellRender: { name: 'CellSwitch', props: { beforeChange: () => {} } }, + // add by 芋艿:from https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/adapter/vxe-table.ts#L97-L123 + vxeUI.renderer.add('CellSwitch', { + renderTableDefault({ attrs, props }, { column, row }) { + const loadingKey = `__loading_${column.field}`; + const finallyProps = { + checkedChildren: $t('common.enabled'), + checkedValue: 1, + unCheckedChildren: $t('common.disabled'), + unCheckedValue: 0, + ...props, + checked: row[column.field], + loading: row[loadingKey] ?? false, + 'onUpdate:checked': onChange, + }; + async function onChange(newVal: any) { + row[loadingKey] = true; + try { + const result = await attrs?.beforeChange?.(newVal, row); + if (result !== false) { + row[column.field] = newVal; + } + } finally { + row[loadingKey] = false; + } + } + return h(NSwitch, finallyProps); + }, + }); + + // 注册表格的操作按钮渲染器 cellRender: { name: 'CellOperation', options: ['edit', 'delete'] } + // add by 芋艿:from https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/adapter/vxe-table.ts#L125-L255 + vxeUI.renderer.add('CellOperation', { + renderTableDefault({ attrs, options, props }, { column, row }) { + const defaultProps = { + text: true, + type: 'primary', + ...props, + }; + let align = 'end'; + switch (column.align) { + case 'center': { + align = 'center'; + break; + } + case 'left': { + align = 'start'; + break; + } + default: { + align = 'end'; + break; + } + } + const presets: Recordable> = { + delete: { + type: 'error', + concent: $t('common.delete'), + }, + edit: { + concent: $t('common.edit'), + }, + }; + const operations: Array> = ( + options || ['edit', 'delete'] + ) + .map((opt) => { + if (isString(opt)) { + return presets[opt] + ? { code: opt, ...presets[opt], ...defaultProps } + : { + code: opt, + concent: $te(`common.${opt}`) ? $t(`common.${opt}`) : opt, + ...defaultProps, + }; + } else { + return { ...defaultProps, ...presets[opt.code], ...opt }; + } + }) + .map((opt) => { + const optBtn: Recordable = {}; + Object.keys(opt).forEach((key) => { + optBtn[key] = isFunction(opt[key]) ? opt[key](row) : opt[key]; + }); + return optBtn; + }) + .filter((opt) => opt.show !== false); + + function renderBtn(opt: Recordable, listen = true) { + return h( + NButton, + { + ...props, + ...opt, + icon: undefined, + onClick: listen + ? () => + attrs?.onClick?.({ + code: opt.code, + row, + }) + : undefined, + }, + { + default: () => { + const content = []; + if (opt.icon) { + content.push( + h(IconifyIcon, { class: 'size-5', icon: opt.icon }), + ); + } + content.push(opt.concent); + return content; + }, + }, + ); + } + + function renderConfirm(opt: Recordable) { + return h( + NPopconfirm, + { + ...props, + ...opt, + icon: undefined, + onPositiveClick: () => { + attrs?.onClick?.({ + code: opt.code, + row, + }); + }, + }, + { + trigger: () => renderBtn({ ...opt }, false), + default: () => + h( + 'div', + { class: 'truncate' }, + $t('ui.actionMessage.deleteConfirm', [ + row[attrs?.nameField || 'name'], + ]), + ), + }, + ); + } + + const btns = operations.map((opt) => + opt.code === 'delete' ? renderConfirm(opt) : renderBtn(opt), + ); + return h( + 'div', + { + class: 'flex table-operations ml-2', + style: { justifyContent: align }, + }, + btns, + ); + }, + }); + // 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化 // vxeUI.formats.add + // add by 星语:数量格式化,例如说:金额 + vxeUI.formats.add('formatAmount', { + cellFormatMethod({ cellValue }, digits = 2) { + if (cellValue === null || cellValue === undefined) { + return ''; + } + if (isString(cellValue)) { + cellValue = Number.parseFloat(cellValue); + } + // 如果非 number,则直接返回空串 + if (Number.isNaN(cellValue)) { + return ''; + } + return cellValue.toFixed(digits); + }, + }); }, useVbenForm, }); export { useVbenVxeGrid }; - +// add by 芋艿:from https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/adapter/vxe-table.ts#L264-L270 +export type OnActionClickParams> = { + code: string; + row: T; +}; +export type OnActionClickFn> = ( + params: OnActionClickParams, +) => void; export type * from '@vben/plugins/vxe-table'; diff --git a/apps/web-naive/src/api/core/auth.ts b/apps/web-naive/src/api/core/auth.ts index 71d9f9943..ccb6da340 100644 --- a/apps/web-naive/src/api/core/auth.ts +++ b/apps/web-naive/src/api/core/auth.ts @@ -1,3 +1,5 @@ +import type { AuthPermissionInfo } from '@vben/types'; + import { baseRequestClient, requestClient } from '#/api/request'; export namespace AuthApi { @@ -5,47 +7,151 @@ export namespace AuthApi { export interface LoginParams { password?: string; username?: string; + captchaVerification?: string; + // 绑定社交登录时,需要传递如下参数 + socialType?: number; + socialCode?: string; + socialState?: string; } /** 登录接口返回值 */ export interface LoginResult { accessToken: string; + refreshToken: string; + userId: number; + expiresTime: number; } - export interface RefreshTokenResult { - data: string; - status: number; + /** 租户信息返回值 */ + export interface TenantResult { + id: number; + name: string; + } + + /** 手机验证码获取接口参数 */ + export interface SmsCodeParams { + mobile: string; + scene: number; + } + + /** 手机验证码登录接口参数 */ + export interface SmsLoginParams { + mobile: string; + code: string; + } + + /** 注册接口参数 */ + export interface RegisterParams { + username: string; + password: string; + captchaVerification: string; + } + + /** 重置密码接口参数 */ + export interface ResetPasswordParams { + password: string; + mobile: string; + code: string; + } + + /** 社交快捷登录接口参数 */ + export interface SocialLoginParams { + type: number; + code: string; + state: string; } } -/** - * 登录 - */ +/** 登录 */ export async function loginApi(data: AuthApi.LoginParams) { - return requestClient.post('/auth/login', data); + return requestClient.post('/system/auth/login', data); } -/** - * 刷新accessToken - */ -export async function refreshTokenApi() { - return baseRequestClient.post('/auth/refresh', { - withCredentials: true, +/** 刷新 accessToken */ +export async function refreshTokenApi(refreshToken: string) { + return baseRequestClient.post( + `/system/auth/refresh-token?refreshToken=${refreshToken}`, + ); +} + +/** 退出登录 */ +export async function logoutApi(accessToken: string) { + return baseRequestClient.post( + '/system/auth/logout', + {}, + { + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }, + ); +} + +/** 获取权限信息 */ +export async function getAuthPermissionInfoApi() { + return requestClient.get( + '/system/auth/get-permission-info', + ); +} + +/** 获取租户列表 */ +export async function getTenantSimpleList() { + return requestClient.get( + `/system/tenant/simple-list`, + ); +} + +/** 使用租户域名,获得租户信息 */ +export async function getTenantByWebsite(website: string) { + return requestClient.get( + `/system/tenant/get-by-website?website=${website}`, + ); +} + +/** 获取验证码 */ +export async function getCaptcha(data: any) { + return baseRequestClient.post('/system/captcha/get', data); +} + +/** 校验验证码 */ +export async function checkCaptcha(data: any) { + return baseRequestClient.post('/system/captcha/check', data); +} + +/** 获取登录验证码 */ +export async function sendSmsCode(data: AuthApi.SmsCodeParams) { + return requestClient.post('/system/auth/send-sms-code', data); +} + +/** 短信验证码登录 */ +export async function smsLogin(data: AuthApi.SmsLoginParams) { + return requestClient.post('/system/auth/sms-login', data); +} + +/** 注册 */ +export async function register(data: AuthApi.RegisterParams) { + return requestClient.post('/system/auth/register', data); +} + +/** 通过短信重置密码 */ +export async function smsResetPassword(data: AuthApi.ResetPasswordParams) { + return requestClient.post('/system/auth/reset-password', data); +} + +/** 社交授权的跳转 */ +export async function socialAuthRedirect(type: number, redirectUri: string) { + return requestClient.get('/system/auth/social-auth-redirect', { + params: { + type, + redirectUri, + }, }); } -/** - * 退出登录 - */ -export async function logoutApi() { - return baseRequestClient.post('/auth/logout', { - withCredentials: true, - }); -} - -/** - * 获取用户权限码 - */ -export async function getAccessCodesApi() { - return requestClient.get('/auth/codes'); +/** 社交快捷登录 */ +export async function socialLogin(data: AuthApi.SocialLoginParams) { + return requestClient.post( + '/system/auth/social-login', + data, + ); } diff --git a/apps/web-naive/src/api/core/index.ts b/apps/web-naive/src/api/core/index.ts index 28a5aef47..269586ee8 100644 --- a/apps/web-naive/src/api/core/index.ts +++ b/apps/web-naive/src/api/core/index.ts @@ -1,3 +1 @@ export * from './auth'; -export * from './menu'; -export * from './user'; diff --git a/apps/web-naive/src/api/core/menu.ts b/apps/web-naive/src/api/core/menu.ts deleted file mode 100644 index 9ef60b11c..000000000 --- a/apps/web-naive/src/api/core/menu.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { RouteRecordStringComponent } from '@vben/types'; - -import { requestClient } from '#/api/request'; - -/** - * 获取用户所有菜单 - */ -export async function getAllMenusApi() { - return requestClient.get('/menu/all'); -} diff --git a/apps/web-naive/src/api/core/user.ts b/apps/web-naive/src/api/core/user.ts deleted file mode 100644 index 7e28ea848..000000000 --- a/apps/web-naive/src/api/core/user.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { UserInfo } from '@vben/types'; - -import { requestClient } from '#/api/request'; - -/** - * 获取用户信息 - */ -export async function getUserInfoApi() { - return requestClient.get('/user/info'); -} diff --git a/apps/web-naive/src/api/infra/api-access-log/index.ts b/apps/web-naive/src/api/infra/api-access-log/index.ts new file mode 100644 index 000000000..656e38084 --- /dev/null +++ b/apps/web-naive/src/api/infra/api-access-log/index.ts @@ -0,0 +1,44 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace InfraApiAccessLogApi { + /** API 访问日志信息 */ + export interface ApiAccessLog { + id: number; + traceId: string; + userId: number; + userType: number; + applicationName: string; + requestMethod: string; + requestParams: string; + responseBody: string; + requestUrl: string; + userIp: string; + userAgent: string; + operateModule: string; + operateName: string; + operateType: number; + beginTime: string; + endTime: string; + duration: number; + resultCode: number; + resultMsg: string; + createTime: string; + } +} + +/** 查询 API 访问日志列表 */ +export function getApiAccessLogPage(params: PageParam) { + return requestClient.get>( + '/infra/api-access-log/page', + { params }, + ); +} + +/** 导出 API 访问日志 */ +export function exportApiAccessLog(params: any) { + return requestClient.download('/infra/api-access-log/export-excel', { + params, + }); +} diff --git a/apps/web-naive/src/api/infra/api-error-log/index.ts b/apps/web-naive/src/api/infra/api-error-log/index.ts new file mode 100644 index 000000000..863f73e9b --- /dev/null +++ b/apps/web-naive/src/api/infra/api-error-log/index.ts @@ -0,0 +1,55 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace InfraApiErrorLogApi { + /** API 错误日志信息 */ + export interface ApiErrorLog { + id: number; + traceId: string; + userId: number; + userType: number; + applicationName: string; + requestMethod: string; + requestParams: string; + requestUrl: string; + userIp: string; + userAgent: string; + exceptionTime: string; + exceptionName: string; + exceptionMessage: string; + exceptionRootCauseMessage: string; + exceptionStackTrace: string; + exceptionClassName: string; + exceptionFileName: string; + exceptionMethodName: string; + exceptionLineNumber: number; + processUserId: number; + processStatus: number; + processTime: string; + resultCode: number; + createTime: string; + } +} + +/** 查询 API 错误日志列表 */ +export function getApiErrorLogPage(params: PageParam) { + return requestClient.get>( + '/infra/api-error-log/page', + { params }, + ); +} + +/** 更新 API 错误日志的处理状态 */ +export function updateApiErrorLogStatus(id: number, processStatus: number) { + return requestClient.put( + `/infra/api-error-log/update-status?id=${id}&processStatus=${processStatus}`, + ); +} + +/** 导出 API 错误日志 */ +export function exportApiErrorLog(params: any) { + return requestClient.download('/infra/api-error-log/export-excel', { + params, + }); +} diff --git a/apps/web-naive/src/api/infra/codegen/index.ts b/apps/web-naive/src/api/infra/codegen/index.ts new file mode 100644 index 000000000..d8fea0453 --- /dev/null +++ b/apps/web-naive/src/api/infra/codegen/index.ts @@ -0,0 +1,157 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace InfraCodegenApi { + /** 代码生成表定义 */ + export interface CodegenTable { + id: number; + tableId: number; + isParentMenuIdValid: boolean; + dataSourceConfigId: number; + scene: number; + tableName: string; + tableComment: string; + remark: string; + moduleName: string; + businessName: string; + className: string; + classComment: string; + author: string; + createTime: Date; + updateTime: Date; + templateType: number; + parentMenuId: number; + } + + /** 代码生成字段定义 */ + export interface CodegenColumn { + id: number; + tableId: number; + columnName: string; + dataType: string; + columnComment: string; + nullable: number; + primaryKey: number; + ordinalPosition: number; + javaType: string; + javaField: string; + dictType: string; + example: string; + createOperation: number; + updateOperation: number; + listOperation: number; + listOperationCondition: string; + listOperationResult: number; + htmlType: string; + } + + /** 数据库表定义 */ + export interface DatabaseTable { + name: string; + comment: string; + } + + /** 代码生成详情 */ + export interface CodegenDetail { + table: CodegenTable; + columns: CodegenColumn[]; + } + + /** 代码预览 */ + export interface CodegenPreview { + filePath: string; + code: string; + } + + /** 更新代码生成请求 */ + export interface CodegenUpdateReqVO { + table: any | CodegenTable; + columns: CodegenColumn[]; + } + + /** 创建代码生成请求 */ + export interface CodegenCreateListReqVO { + dataSourceConfigId?: number; + tableNames: string[]; + } +} + +/** 查询列表代码生成表定义 */ +export function getCodegenTableList(dataSourceConfigId: number) { + return requestClient.get( + '/infra/codegen/table/list?', + { + params: { dataSourceConfigId }, + }, + ); +} + +/** 查询列表代码生成表定义 */ +export function getCodegenTablePage(params: PageParam) { + return requestClient.get>( + '/infra/codegen/table/page', + { params }, + ); +} + +/** 查询详情代码生成表定义 */ +export function getCodegenTable(tableId: number) { + return requestClient.get( + '/infra/codegen/detail', + { + params: { tableId }, + }, + ); +} + +/** 修改代码生成表定义 */ +export function updateCodegenTable(data: InfraCodegenApi.CodegenUpdateReqVO) { + return requestClient.put('/infra/codegen/update', data); +} + +/** 基于数据库的表结构,同步数据库的表和字段定义 */ +export function syncCodegenFromDB(tableId: number) { + return requestClient.put('/infra/codegen/sync-from-db', { + params: { tableId }, + }); +} + +/** 预览生成代码 */ +export function previewCodegen(tableId: number) { + return requestClient.get( + '/infra/codegen/preview', + { + params: { tableId }, + }, + ); +} + +/** 下载生成代码 */ +export function downloadCodegen(tableId: number) { + return requestClient.download('/infra/codegen/download', { + params: { tableId }, + }); +} + +/** 获得表定义 */ +export function getSchemaTableList(params: any) { + return requestClient.get( + '/infra/codegen/db/table/list', + { params }, + ); +} + +/** 基于数据库的表结构,创建代码生成器的表定义 */ +export function createCodegenList( + data: InfraCodegenApi.CodegenCreateListReqVO, +) { + return requestClient.post('/infra/codegen/create-list', data); +} + +/** 删除代码生成表定义 */ +export function deleteCodegenTable(tableId: number) { + return requestClient.delete('/infra/codegen/delete', { + params: { tableId }, + }); +} diff --git a/apps/web-naive/src/api/infra/config/index.ts b/apps/web-naive/src/api/infra/config/index.ts new file mode 100644 index 000000000..3911e01c7 --- /dev/null +++ b/apps/web-naive/src/api/infra/config/index.ts @@ -0,0 +1,62 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace InfraConfigApi { + /** 参数配置信息 */ + export interface Config { + id?: number; + category: string; + name: string; + key: string; + value: string; + type: number; + visible: boolean; + remark: string; + createTime?: Date; + } +} + +/** 查询参数列表 */ +export function getConfigPage(params: PageParam) { + return requestClient.get>( + '/infra/config/page', + { + params, + }, + ); +} + +/** 查询参数详情 */ +export function getConfig(id: number) { + return requestClient.get(`/infra/config/get?id=${id}`); +} + +/** 根据参数键名查询参数值 */ +export function getConfigKey(configKey: string) { + return requestClient.get( + `/infra/config/get-value-by-key?key=${configKey}`, + ); +} + +/** 新增参数 */ +export function createConfig(data: InfraConfigApi.Config) { + return requestClient.post('/infra/config/create', data); +} + +/** 修改参数 */ +export function updateConfig(data: InfraConfigApi.Config) { + return requestClient.put('/infra/config/update', data); +} + +/** 删除参数 */ +export function deleteConfig(id: number) { + return requestClient.delete(`/infra/config/delete?id=${id}`); +} + +/** 导出参数 */ +export function exportConfig(params: any) { + return requestClient.download('/infra/config/export', { + params, + }); +} diff --git a/apps/web-naive/src/api/infra/data-source-config/index.ts b/apps/web-naive/src/api/infra/data-source-config/index.ts new file mode 100644 index 000000000..88641f654 --- /dev/null +++ b/apps/web-naive/src/api/infra/data-source-config/index.ts @@ -0,0 +1,46 @@ +import { requestClient } from '#/api/request'; + +export namespace InfraDataSourceConfigApi { + /** 数据源配置信息 */ + export interface DataSourceConfig { + id?: number; + name: string; + url: string; + username: string; + password: string; + createTime?: Date; + } +} + +/** 查询数据源配置列表 */ +export function getDataSourceConfigList() { + return requestClient.get( + '/infra/data-source-config/list', + ); +} + +/** 查询数据源配置详情 */ +export function getDataSourceConfig(id: number) { + return requestClient.get( + `/infra/data-source-config/get?id=${id}`, + ); +} + +/** 新增数据源配置 */ +export function createDataSourceConfig( + data: InfraDataSourceConfigApi.DataSourceConfig, +) { + return requestClient.post('/infra/data-source-config/create', data); +} + +/** 修改数据源配置 */ +export function updateDataSourceConfig( + data: InfraDataSourceConfigApi.DataSourceConfig, +) { + return requestClient.put('/infra/data-source-config/update', data); +} + +/** 删除数据源配置 */ +export function deleteDataSourceConfig(id: number) { + return requestClient.delete(`/infra/data-source-config/delete?id=${id}`); +} diff --git a/apps/web-naive/src/api/infra/demo/demo01/index.ts b/apps/web-naive/src/api/infra/demo/demo01/index.ts new file mode 100644 index 000000000..5a940a61d --- /dev/null +++ b/apps/web-naive/src/api/infra/demo/demo01/index.ts @@ -0,0 +1,52 @@ +import type { Dayjs } from 'dayjs'; + +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace Demo01ContactApi { + /** 示例联系人信息 */ + export interface Demo01Contact { + id: number; // 编号 + name?: string; // 名字 + sex?: boolean; // 性别 + birthday?: Dayjs | string; // 出生年 + description?: string; // 简介 + avatar: string; // 头像 + } +} + +/** 查询示例联系人分页 */ +export function getDemo01ContactPage(params: PageParam) { + return requestClient.get>( + '/infra/demo01-contact/page', + { params }, + ); +} + +/** 查询示例联系人详情 */ +export function getDemo01Contact(id: number) { + return requestClient.get( + `/infra/demo01-contact/get?id=${id}`, + ); +} + +/** 新增示例联系人 */ +export function createDemo01Contact(data: Demo01ContactApi.Demo01Contact) { + return requestClient.post('/infra/demo01-contact/create', data); +} + +/** 修改示例联系人 */ +export function updateDemo01Contact(data: Demo01ContactApi.Demo01Contact) { + return requestClient.put('/infra/demo01-contact/update', data); +} + +/** 删除示例联系人 */ +export function deleteDemo01Contact(id: number) { + return requestClient.delete(`/infra/demo01-contact/delete?id=${id}`); +} + +/** 导出示例联系人 */ +export function exportDemo01Contact(params: any) { + return requestClient.download('/infra/demo01-contact/export-excel', params); +} diff --git a/apps/web-naive/src/api/infra/demo/demo02/index.ts b/apps/web-naive/src/api/infra/demo/demo02/index.ts new file mode 100644 index 000000000..45fcb148d --- /dev/null +++ b/apps/web-naive/src/api/infra/demo/demo02/index.ts @@ -0,0 +1,46 @@ +import { requestClient } from '#/api/request'; + +export namespace Demo02CategoryApi { + /** 示例分类信息 */ + export interface Demo02Category { + id: number; // 编号 + name?: string; // 名字 + parentId?: number; // 父级编号 + children?: Demo02Category[]; + } +} + +/** 查询示例分类列表 */ +export function getDemo02CategoryList(params: any) { + return requestClient.get( + '/infra/demo02-category/list', + { params }, + ); +} + +/** 查询示例分类详情 */ +export function getDemo02Category(id: number) { + return requestClient.get( + `/infra/demo02-category/get?id=${id}`, + ); +} + +/** 新增示例分类 */ +export function createDemo02Category(data: Demo02CategoryApi.Demo02Category) { + return requestClient.post('/infra/demo02-category/create', data); +} + +/** 修改示例分类 */ +export function updateDemo02Category(data: Demo02CategoryApi.Demo02Category) { + return requestClient.put('/infra/demo02-category/update', data); +} + +/** 删除示例分类 */ +export function deleteDemo02Category(id: number) { + return requestClient.delete(`/infra/demo02-category/delete?id=${id}`); +} + +/** 导出示例分类 */ +export function exportDemo02Category(params: any) { + return requestClient.download('/infra/demo02-category/export-excel', params); +} diff --git a/apps/web-naive/src/api/infra/demo/demo03/erp/index.ts b/apps/web-naive/src/api/infra/demo/demo03/erp/index.ts new file mode 100644 index 000000000..f9704bf14 --- /dev/null +++ b/apps/web-naive/src/api/infra/demo/demo03/erp/index.ts @@ -0,0 +1,137 @@ +import type { Dayjs } from 'dayjs'; + +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace Demo03StudentApi { + /** 学生课程信息 */ + export interface Demo03Course { + id: number; // 编号 + studentId?: number; // 学生编号 + name?: string; // 名字 + score?: number; // 分数 + } + + /** 学生班级信息 */ + export interface Demo03Grade { + id: number; // 编号 + studentId?: number; // 学生编号 + name?: string; // 名字 + teacher?: string; // 班主任 + } + + /** 学生信息 */ + export interface Demo03Student { + id: number; // 编号 + name?: string; // 名字 + sex?: number; // 性别 + birthday?: Dayjs | string; // 出生日期 + description?: string; // 简介 + } +} + +/** 查询学生分页 */ +export function getDemo03StudentPage(params: PageParam) { + return requestClient.get>( + '/infra/demo03-student/page', + { params }, + ); +} + +/** 查询学生详情 */ +export function getDemo03Student(id: number) { + return requestClient.get( + `/infra/demo03-student/get?id=${id}`, + ); +} + +/** 新增学生 */ +export function createDemo03Student(data: Demo03StudentApi.Demo03Student) { + return requestClient.post('/infra/demo03-student/create', data); +} + +/** 修改学生 */ +export function updateDemo03Student(data: Demo03StudentApi.Demo03Student) { + return requestClient.put('/infra/demo03-student/update', data); +} + +/** 删除学生 */ +export function deleteDemo03Student(id: number) { + return requestClient.delete(`/infra/demo03-student/delete?id=${id}`); +} + +/** 导出学生 */ +export function exportDemo03Student(params: any) { + return requestClient.download('/infra/demo03-student/export-excel', params); +} + +// ==================== 子表(学生课程) ==================== + +/** 获得学生课程分页 */ +export function getDemo03CoursePage(params: PageParam) { + return requestClient.get>( + `/infra/demo03-student/demo03-course/page`, + { + params, + }, + ); +} +/** 新增学生课程 */ +export function createDemo03Course(data: Demo03StudentApi.Demo03Course) { + return requestClient.post(`/infra/demo03-student/demo03-course/create`, data); +} + +/** 修改学生课程 */ +export function updateDemo03Course(data: Demo03StudentApi.Demo03Course) { + return requestClient.put(`/infra/demo03-student/demo03-course/update`, data); +} + +/** 删除学生课程 */ +export function deleteDemo03Course(id: number) { + return requestClient.delete( + `/infra/demo03-student/demo03-course/delete?id=${id}`, + ); +} + +/** 获得学生课程 */ +export function getDemo03Course(id: number) { + return requestClient.get( + `/infra/demo03-student/demo03-course/get?id=${id}`, + ); +} + +// ==================== 子表(学生班级) ==================== + +/** 获得学生班级分页 */ +export function getDemo03GradePage(params: PageParam) { + return requestClient.get>( + `/infra/demo03-student/demo03-grade/page`, + { + params, + }, + ); +} +/** 新增学生班级 */ +export function createDemo03Grade(data: Demo03StudentApi.Demo03Grade) { + return requestClient.post(`/infra/demo03-student/demo03-grade/create`, data); +} + +/** 修改学生班级 */ +export function updateDemo03Grade(data: Demo03StudentApi.Demo03Grade) { + return requestClient.put(`/infra/demo03-student/demo03-grade/update`, data); +} + +/** 删除学生班级 */ +export function deleteDemo03Grade(id: number) { + return requestClient.delete( + `/infra/demo03-student/demo03-grade/delete?id=${id}`, + ); +} + +/** 获得学生班级 */ +export function getDemo03Grade(id: number) { + return requestClient.get( + `/infra/demo03-student/demo03-grade/get?id=${id}`, + ); +} diff --git a/apps/web-naive/src/api/infra/demo/demo03/inner/index.ts b/apps/web-naive/src/api/infra/demo/demo03/inner/index.ts new file mode 100644 index 000000000..a83cf4215 --- /dev/null +++ b/apps/web-naive/src/api/infra/demo/demo03/inner/index.ts @@ -0,0 +1,85 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace Demo03StudentApi { + /** 学生课程信息 */ + export interface Demo03Course { + id: number; // 编号 + studentId?: number; // 学生编号 + name?: string; // 名字 + score?: number; // 分数 + } + + /** 学生班级信息 */ + export interface Demo03Grade { + id: number; // 编号 + studentId?: number; // 学生编号 + name?: string; // 名字 + teacher?: string; // 班主任 + } + + /** 学生信息 */ + export interface Demo03Student { + id: number; // 编号 + name?: string; // 名字 + sex?: number; // 性别 + birthday?: Date; // 出生日期 + description?: string; // 简介 + demo03courses?: Demo03Course[]; + demo03grade?: Demo03Grade; + } +} + +/** 查询学生分页 */ +export function getDemo03StudentPage(params: PageParam) { + return requestClient.get>( + '/infra/demo03-student/page', + { params }, + ); +} + +/** 查询学生详情 */ +export function getDemo03Student(id: number) { + return requestClient.get( + `/infra/demo03-student/get?id=${id}`, + ); +} + +/** 新增学生 */ +export function createDemo03Student(data: Demo03StudentApi.Demo03Student) { + return requestClient.post('/infra/demo03-student/create', data); +} + +/** 修改学生 */ +export function updateDemo03Student(data: Demo03StudentApi.Demo03Student) { + return requestClient.put('/infra/demo03-student/update', data); +} + +/** 删除学生 */ +export function deleteDemo03Student(id: number) { + return requestClient.delete(`/infra/demo03-student/delete?id=${id}`); +} + +/** 导出学生 */ +export function exportDemo03Student(params: any) { + return requestClient.download('/infra/demo03-student/export-excel', params); +} + +// ==================== 子表(学生课程) ==================== + +/** 获得学生课程列表 */ +export function getDemo03CourseListByStudentId(studentId: number) { + return requestClient.get( + `/infra/demo03-student/demo03-course/list-by-student-id?studentId=${studentId}`, + ); +} + +// ==================== 子表(学生班级) ==================== + +/** 获得学生班级 */ +export function getDemo03GradeByStudentId(studentId: number) { + return requestClient.get( + `/infra/demo03-student/demo03-grade/get-by-student-id?studentId=${studentId}`, + ); +} diff --git a/apps/web-naive/src/api/infra/demo/demo03/normal/index.ts b/apps/web-naive/src/api/infra/demo/demo03/normal/index.ts new file mode 100644 index 000000000..a04a919e9 --- /dev/null +++ b/apps/web-naive/src/api/infra/demo/demo03/normal/index.ts @@ -0,0 +1,87 @@ +import type { Dayjs } from 'dayjs'; + +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace Demo03StudentApi { + /** 学生课程信息 */ + export interface Demo03Course { + id: number; // 编号 + studentId?: number; // 学生编号 + name?: string; // 名字 + score?: number; // 分数 + } + + /** 学生班级信息 */ + export interface Demo03Grade { + id: number; // 编号 + studentId?: number; // 学生编号 + name?: string; // 名字 + teacher?: string; // 班主任 + } + + /** 学生信息 */ + export interface Demo03Student { + id: number; // 编号 + name?: string; // 名字 + sex?: number; // 性别 + birthday?: Dayjs | string; // 出生日期 + description?: string; // 简介 + demo03courses?: Demo03Course[]; + demo03grade?: Demo03Grade; + } +} + +/** 查询学生分页 */ +export function getDemo03StudentPage(params: PageParam) { + return requestClient.get>( + '/infra/demo03-student/page', + { params }, + ); +} + +/** 查询学生详情 */ +export function getDemo03Student(id: number) { + return requestClient.get( + `/infra/demo03-student/get?id=${id}`, + ); +} + +/** 新增学生 */ +export function createDemo03Student(data: Demo03StudentApi.Demo03Student) { + return requestClient.post('/infra/demo03-student/create', data); +} + +/** 修改学生 */ +export function updateDemo03Student(data: Demo03StudentApi.Demo03Student) { + return requestClient.put('/infra/demo03-student/update', data); +} + +/** 删除学生 */ +export function deleteDemo03Student(id: number) { + return requestClient.delete(`/infra/demo03-student/delete?id=${id}`); +} + +/** 导出学生 */ +export function exportDemo03Student(params: any) { + return requestClient.download('/infra/demo03-student/export-excel', params); +} + +// ==================== 子表(学生课程) ==================== + +/** 获得学生课程列表 */ +export function getDemo03CourseListByStudentId(studentId: number) { + return requestClient.get( + `/infra/demo03-student/demo03-course/list-by-student-id?studentId=${studentId}`, + ); +} + +// ==================== 子表(学生班级) ==================== + +/** 获得学生班级 */ +export function getDemo03GradeByStudentId(studentId: number) { + return requestClient.get( + `/infra/demo03-student/demo03-grade/get-by-student-id?studentId=${studentId}`, + ); +} diff --git a/apps/web-naive/src/api/infra/file-config/index.ts b/apps/web-naive/src/api/infra/file-config/index.ts new file mode 100644 index 000000000..a16cf2bc0 --- /dev/null +++ b/apps/web-naive/src/api/infra/file-config/index.ts @@ -0,0 +1,75 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace InfraFileConfigApi { + /** 文件客户端配置 */ + export interface FileClientConfig { + basePath: string; + host?: string; + port?: number; + username?: string; + password?: string; + mode?: string; + endpoint?: string; + bucket?: string; + accessKey?: string; + accessSecret?: string; + pathStyle?: boolean; + domain: string; + } + + /** 文件配置信息 */ + export interface FileConfig { + id?: number; + name: string; + storage?: number; + master: boolean; + visible: boolean; + config: FileClientConfig; + remark: string; + createTime?: Date; + } +} + +/** 查询文件配置列表 */ +export function getFileConfigPage(params: PageParam) { + return requestClient.get>( + '/infra/file-config/page', + { + params, + }, + ); +} + +/** 查询文件配置详情 */ +export function getFileConfig(id: number) { + return requestClient.get( + `/infra/file-config/get?id=${id}`, + ); +} + +/** 更新文件配置为主配置 */ +export function updateFileConfigMaster(id: number) { + return requestClient.put(`/infra/file-config/update-master?id=${id}`); +} + +/** 新增文件配置 */ +export function createFileConfig(data: InfraFileConfigApi.FileConfig) { + return requestClient.post('/infra/file-config/create', data); +} + +/** 修改文件配置 */ +export function updateFileConfig(data: InfraFileConfigApi.FileConfig) { + return requestClient.put('/infra/file-config/update', data); +} + +/** 删除文件配置 */ +export function deleteFileConfig(id: number) { + return requestClient.delete(`/infra/file-config/delete?id=${id}`); +} + +/** 测试文件配置 */ +export function testFileConfig(id: number) { + return requestClient.get(`/infra/file-config/test?id=${id}`); +} diff --git a/apps/web-naive/src/api/infra/file/index.ts b/apps/web-naive/src/api/infra/file/index.ts new file mode 100644 index 000000000..a399db67d --- /dev/null +++ b/apps/web-naive/src/api/infra/file/index.ts @@ -0,0 +1,73 @@ +import type { AxiosRequestConfig, PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +/** Axios 上传进度事件 */ +export type AxiosProgressEvent = AxiosRequestConfig['onUploadProgress']; + +export namespace InfraFileApi { + /** 文件信息 */ + export interface File { + id?: number; + configId?: number; + path: string; + name?: string; + url?: string; + size?: number; + type?: string; + createTime?: Date; + } + + /** 文件预签名地址 */ + export interface FilePresignedUrlRespVO { + configId: number; // 文件配置编号 + uploadUrl: string; // 文件上传 URL + url: string; // 文件 URL + path: string; // 文件路径 + } + + /** 上传文件 */ + export interface FileUploadReqVO { + file: globalThis.File; + directory?: string; + } +} + +/** 查询文件列表 */ +export function getFilePage(params: PageParam) { + return requestClient.get>('/infra/file/page', { + params, + }); +} + +/** 删除文件 */ +export function deleteFile(id: number) { + return requestClient.delete(`/infra/file/delete?id=${id}`); +} + +/** 获取文件预签名地址 */ +export function getFilePresignedUrl(name: string, directory?: string) { + return requestClient.get( + '/infra/file/presigned-url', + { + params: { name, directory }, + }, + ); +} + +/** 创建文件 */ +export function createFile(data: InfraFileApi.File) { + return requestClient.post('/infra/file/create', data); +} + +/** 上传文件 */ +export function uploadFile( + data: InfraFileApi.FileUploadReqVO, + onUploadProgress?: AxiosProgressEvent, +) { + // 特殊:由于 upload 内部封装,即使 directory 为 undefined,也会传递给后端 + if (!data.directory) { + delete data.directory; + } + return requestClient.upload('/infra/file/upload', data, { onUploadProgress }); +} diff --git a/apps/web-naive/src/api/infra/job-log/index.ts b/apps/web-naive/src/api/infra/job-log/index.ts new file mode 100644 index 000000000..e115b9df7 --- /dev/null +++ b/apps/web-naive/src/api/infra/job-log/index.ts @@ -0,0 +1,41 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace InfraJobLogApi { + /** 任务日志信息 */ + export interface JobLog { + id?: number; + jobId: number; + handlerName: string; + handlerParam: string; + cronExpression: string; + executeIndex: string; + beginTime: Date; + endTime: Date; + duration: string; + status: number; + createTime?: string; + result: string; + } +} + +/** 查询任务日志列表 */ +export function getJobLogPage(params: PageParam) { + return requestClient.get>( + '/infra/job-log/page', + { params }, + ); +} + +/** 查询任务日志详情 */ +export function getJobLog(id: number) { + return requestClient.get( + `/infra/job-log/get?id=${id}`, + ); +} + +/** 导出定时任务日志 */ +export function exportJobLog(params: any) { + return requestClient.download('/infra/job-log/export-excel', { params }); +} diff --git a/apps/web-naive/src/api/infra/job/index.ts b/apps/web-naive/src/api/infra/job/index.ts new file mode 100644 index 000000000..fcec25c83 --- /dev/null +++ b/apps/web-naive/src/api/infra/job/index.ts @@ -0,0 +1,70 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace InfraJobApi { + /** 任务信息 */ + export interface Job { + id?: number; + name: string; + status: number; + handlerName: string; + handlerParam: string; + cronExpression: string; + retryCount: number; + retryInterval: number; + monitorTimeout: number; + createTime?: Date; + } +} + +/** 查询任务列表 */ +export function getJobPage(params: PageParam) { + return requestClient.get>('/infra/job/page', { + params, + }); +} + +/** 查询任务详情 */ +export function getJob(id: number) { + return requestClient.get(`/infra/job/get?id=${id}`); +} + +/** 新增任务 */ +export function createJob(data: InfraJobApi.Job) { + return requestClient.post('/infra/job/create', data); +} + +/** 修改定时任务调度 */ +export function updateJob(data: InfraJobApi.Job) { + return requestClient.put('/infra/job/update', data); +} + +/** 删除定时任务调度 */ +export function deleteJob(id: number) { + return requestClient.delete(`/infra/job/delete?id=${id}`); +} + +/** 导出定时任务调度 */ +export function exportJob(params: any) { + return requestClient.download('/infra/job/export-excel', { params }); +} + +/** 任务状态修改 */ +export function updateJobStatus(id: number, status: number) { + const params = { + id, + status, + }; + return requestClient.put('/infra/job/update-status', { params }); +} + +/** 定时任务立即执行一次 */ +export function runJob(id: number) { + return requestClient.put(`/infra/job/trigger?id=${id}`); +} + +/** 获得定时任务的下 n 次执行时间 */ +export function getJobNextTimes(id: number) { + return requestClient.get(`/infra/job/get_next_times?id=${id}`); +} diff --git a/apps/web-naive/src/api/infra/redis/index.ts b/apps/web-naive/src/api/infra/redis/index.ts new file mode 100644 index 000000000..cb8179da5 --- /dev/null +++ b/apps/web-naive/src/api/infra/redis/index.ts @@ -0,0 +1,190 @@ +import { requestClient } from '#/api/request'; + +export namespace InfraRedisApi { + /** Redis 信息 */ + export interface RedisInfo { + io_threaded_reads_processed: string; + tracking_clients: string; + uptime_in_seconds: string; + cluster_connections: string; + current_cow_size: string; + maxmemory_human: string; + aof_last_cow_size: string; + master_replid2: string; + mem_replication_backlog: string; + aof_rewrite_scheduled: string; + total_net_input_bytes: string; + rss_overhead_ratio: string; + hz: string; + current_cow_size_age: string; + redis_build_id: string; + errorstat_BUSYGROUP: string; + aof_last_bgrewrite_status: string; + multiplexing_api: string; + client_recent_max_output_buffer: string; + allocator_resident: string; + mem_fragmentation_bytes: string; + aof_current_size: string; + repl_backlog_first_byte_offset: string; + tracking_total_prefixes: string; + redis_mode: string; + redis_git_dirty: string; + aof_delayed_fsync: string; + allocator_rss_bytes: string; + repl_backlog_histlen: string; + io_threads_active: string; + rss_overhead_bytes: string; + total_system_memory: string; + loading: string; + evicted_keys: string; + maxclients: string; + cluster_enabled: string; + redis_version: string; + repl_backlog_active: string; + mem_aof_buffer: string; + allocator_frag_bytes: string; + io_threaded_writes_processed: string; + instantaneous_ops_per_sec: string; + used_memory_human: string; + total_error_replies: string; + role: string; + maxmemory: string; + used_memory_lua: string; + rdb_current_bgsave_time_sec: string; + used_memory_startup: string; + used_cpu_sys_main_thread: string; + lazyfree_pending_objects: string; + aof_pending_bio_fsync: string; + used_memory_dataset_perc: string; + allocator_frag_ratio: string; + arch_bits: string; + used_cpu_user_main_thread: string; + mem_clients_normal: string; + expired_time_cap_reached_count: string; + unexpected_error_replies: string; + mem_fragmentation_ratio: string; + aof_last_rewrite_time_sec: string; + master_replid: string; + aof_rewrite_in_progress: string; + lru_clock: string; + maxmemory_policy: string; + run_id: string; + latest_fork_usec: string; + tracking_total_items: string; + total_commands_processed: string; + expired_keys: string; + errorstat_ERR: string; + used_memory: string; + module_fork_in_progress: string; + errorstat_WRONGPASS: string; + aof_buffer_length: string; + dump_payload_sanitizations: string; + mem_clients_slaves: string; + keyspace_misses: string; + server_time_usec: string; + executable: string; + lazyfreed_objects: string; + db0: string; + used_memory_peak_human: string; + keyspace_hits: string; + rdb_last_cow_size: string; + aof_pending_rewrite: string; + used_memory_overhead: string; + active_defrag_hits: string; + tcp_port: string; + uptime_in_days: string; + used_memory_peak_perc: string; + current_save_keys_processed: string; + blocked_clients: string; + total_reads_processed: string; + expire_cycle_cpu_milliseconds: string; + sync_partial_err: string; + used_memory_scripts_human: string; + aof_current_rewrite_time_sec: string; + aof_enabled: string; + process_supervised: string; + master_repl_offset: string; + used_memory_dataset: string; + used_cpu_user: string; + rdb_last_bgsave_status: string; + tracking_total_keys: string; + atomicvar_api: string; + allocator_rss_ratio: string; + client_recent_max_input_buffer: string; + clients_in_timeout_table: string; + aof_last_write_status: string; + mem_allocator: string; + used_memory_scripts: string; + used_memory_peak: string; + process_id: string; + master_failover_state: string; + errorstat_NOAUTH: string; + used_cpu_sys: string; + repl_backlog_size: string; + connected_slaves: string; + current_save_keys_total: string; + gcc_version: string; + total_system_memory_human: string; + sync_full: string; + connected_clients: string; + module_fork_last_cow_size: string; + total_writes_processed: string; + allocator_active: string; + total_net_output_bytes: string; + pubsub_channels: string; + current_fork_perc: string; + active_defrag_key_hits: string; + rdb_changes_since_last_save: string; + instantaneous_input_kbps: string; + used_memory_rss_human: string; + configured_hz: string; + expired_stale_perc: string; + active_defrag_misses: string; + used_cpu_sys_children: string; + number_of_cached_scripts: string; + sync_partial_ok: string; + used_memory_lua_human: string; + rdb_last_save_time: string; + pubsub_patterns: string; + slave_expires_tracked_keys: string; + redis_git_sha1: string; + used_memory_rss: string; + rdb_last_bgsave_time_sec: string; + os: string; + mem_not_counted_for_evict: string; + active_defrag_running: string; + rejected_connections: string; + aof_rewrite_buffer_length: string; + total_forks: string; + active_defrag_key_misses: string; + allocator_allocated: string; + aof_base_size: string; + instantaneous_output_kbps: string; + second_repl_offset: string; + rdb_bgsave_in_progress: string; + used_cpu_user_children: string; + total_connections_received: string; + migrate_cached_sockets: string; + } + + /** Redis 命令统计 */ + export interface RedisCommandStats { + command: string; + calls: number; + usec: number; + } + + /** Redis 监控信息 */ + export interface RedisMonitorInfo { + info: RedisInfo; + dbSize: number; + commandStats: RedisCommandStats[]; + } +} + +/** 获取 Redis 监控信息 */ +export function getRedisMonitorInfo() { + return requestClient.get( + '/infra/redis/get-monitor-info', + ); +} diff --git a/apps/web-naive/src/api/request.ts b/apps/web-naive/src/api/request.ts index f8fbacc0b..737344bb9 100644 --- a/apps/web-naive/src/api/request.ts +++ b/apps/web-naive/src/api/request.ts @@ -3,7 +3,7 @@ */ import type { RequestClientOptions } from '@vben/request'; -import { useAppConfig } from '@vben/hooks'; +import { isTenantEnable, useAppConfig } from '@vben/hooks'; import { preferences } from '@vben/preferences'; import { authenticateResponseInterceptor, @@ -19,6 +19,7 @@ import { useAuthStore } from '#/store'; import { refreshTokenApi } from './core'; const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); +const tenantEnable = isTenantEnable(); function createRequestClient(baseURL: string, options?: RequestClientOptions) { const client = new RequestClient({ @@ -49,8 +50,16 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) { */ async function doRefreshToken() { const accessStore = useAccessStore(); - const resp = await refreshTokenApi(); - const newToken = resp.data; + const refreshToken = accessStore.refreshToken as string; + if (!refreshToken) { + throw new Error('Refresh token is null!'); + } + const resp = await refreshTokenApi(refreshToken); + const newToken = resp?.data?.data?.accessToken; + // add by 芋艿:这里一定要抛出 resp.data,从而触发 authenticateResponseInterceptor 中,刷新令牌失败!!! + if (!newToken) { + throw resp.data; + } accessStore.setAccessToken(newToken); return newToken; } @@ -66,6 +75,14 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) { config.headers.Authorization = formatToken(accessStore.accessToken); config.headers['Accept-Language'] = preferences.app.locale; + // 添加租户编号 + config.headers['tenant-id'] = tenantEnable + ? accessStore.tenantId + : undefined; + // 只有登录时,才设置 visit-tenant-id 访问租户 + config.headers['visit-tenant-id'] = tenantEnable + ? accessStore.visitTenantId + : undefined; return config; }, }); @@ -96,7 +113,12 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) { // 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg // 当前mock接口返回的错误字段是 error 或者 message const responseData = error?.response?.data ?? {}; - const errorMessage = responseData?.error ?? responseData?.message ?? ''; + const errorMessage = + responseData?.error ?? responseData?.message ?? responseData.msg ?? ''; + // add by 芋艿:特殊:避免 401 “账号未登录”,重复提示。因为,此时会跳转到登录界面,只需提示一次!!! + if (error?.data?.code === 401) { + return; + } // 如果没有错误信息,则会根据状态码进行提示 message.error(errorMessage || msg); }), @@ -110,3 +132,17 @@ export const requestClient = createRequestClient(apiURL, { }); export const baseRequestClient = new RequestClient({ baseURL: apiURL }); +baseRequestClient.addRequestInterceptor({ + fulfilled: (config) => { + const accessStore = useAccessStore(); + // 添加租户编号 + config.headers['tenant-id'] = tenantEnable + ? accessStore.tenantId + : undefined; + // 只有登录时,才设置 visit-tenant-id 访问租户 + config.headers['visit-tenant-id'] = tenantEnable + ? accessStore.visitTenantId + : undefined; + return config; + }, +}); diff --git a/apps/web-naive/src/api/system/area/index.ts b/apps/web-naive/src/api/system/area/index.ts new file mode 100644 index 000000000..8d3361da5 --- /dev/null +++ b/apps/web-naive/src/api/system/area/index.ts @@ -0,0 +1,24 @@ +import { requestClient } from '#/api/request'; + +export namespace SystemAreaApi { + /** 地区信息 */ + export interface Area { + id?: number; + name: string; + code: string; + parentId?: number; + sort?: number; + status?: number; + createTime?: Date; + } +} + +/** 获得地区树 */ +export function getAreaTree() { + return requestClient.get('/system/area/tree'); +} + +/** 获得 IP 对应的地区名 */ +export function getAreaByIp(ip: string) { + return requestClient.get(`/system/area/get-by-ip?ip=${ip}`); +} diff --git a/apps/web-naive/src/api/system/dept/index.ts b/apps/web-naive/src/api/system/dept/index.ts new file mode 100644 index 000000000..d2677a793 --- /dev/null +++ b/apps/web-naive/src/api/system/dept/index.ts @@ -0,0 +1,47 @@ +import { requestClient } from '#/api/request'; + +export namespace SystemDeptApi { + /** 部门信息 */ + export interface Dept { + id?: number; + name: string; + parentId?: number; + status: number; + sort: number; + leaderUserId: number; + phone: string; + email: string; + createTime: Date; + children?: Dept[]; + } +} + +/** 查询部门(精简)列表 */ +export async function getSimpleDeptList() { + return requestClient.get('/system/dept/simple-list'); +} + +/** 查询部门列表 */ +export async function getDeptList() { + return requestClient.get('/system/dept/list'); +} + +/** 查询部门详情 */ +export async function getDept(id: number) { + return requestClient.get(`/system/dept/get?id=${id}`); +} + +/** 新增部门 */ +export async function createDept(data: SystemDeptApi.Dept) { + return requestClient.post('/system/dept/create', data); +} + +/** 修改部门 */ +export async function updateDept(data: SystemDeptApi.Dept) { + return requestClient.put('/system/dept/update', data); +} + +/** 删除部门 */ +export async function deleteDept(id: number) { + return requestClient.delete(`/system/dept/delete?id=${id}`); +} diff --git a/apps/web-naive/src/api/system/dict/data/index.ts b/apps/web-naive/src/api/system/dict/data/index.ts new file mode 100644 index 000000000..a64330cda --- /dev/null +++ b/apps/web-naive/src/api/system/dict/data/index.ts @@ -0,0 +1,54 @@ +import type { PageParam } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace SystemDictDataApi { + /** 字典数据 */ + export type DictData = { + colorType: string; + createTime: Date; + cssClass: string; + dictType: string; + id?: number; + label: string; + remark: string; + sort?: number; + status: number; + value: string; + }; +} + +// 查询字典数据(精简)列表 +export function getSimpleDictDataList() { + return requestClient.get('/system/dict-data/simple-list'); +} + +// 查询字典数据列表 +export function getDictDataPage(params: PageParam) { + return requestClient.get('/system/dict-data/page', { params }); +} + +// 查询字典数据详情 +export function getDictData(id: number) { + return requestClient.get(`/system/dict-data/get?id=${id}`); +} + +// 新增字典数据 +export function createDictData(data: SystemDictDataApi.DictData) { + return requestClient.post('/system/dict-data/create', data); +} + +// 修改字典数据 +export function updateDictData(data: SystemDictDataApi.DictData) { + return requestClient.put('/system/dict-data/update', data); +} + +// 删除字典数据 +export function deleteDictData(id: number) { + return requestClient.delete(`/system/dict-data/delete?id=${id}`); +} + +// 导出字典类型数据 +export function exportDictData(params: any) { + return requestClient.download('/system/dict-data/export', { params }); +} diff --git a/apps/web-naive/src/api/system/dict/type/index.ts b/apps/web-naive/src/api/system/dict/type/index.ts new file mode 100644 index 000000000..612fe1052 --- /dev/null +++ b/apps/web-naive/src/api/system/dict/type/index.ts @@ -0,0 +1,48 @@ +import { requestClient } from '#/api/request'; + +export namespace SystemDictTypeApi { + /** 字典类型 */ + export type DictType = { + createTime: Date; + id?: number; + name: string; + remark: string; + status: number; + type: string; + }; +} + +// 查询字典(精简)列表 +export function getSimpleDictTypeList() { + return requestClient.get('/system/dict-type/list-all-simple'); +} + +// 查询字典列表 +export function getDictTypePage(params: any) { + return requestClient.get('/system/dict-type/page', { params }); +} + +// 查询字典详情 +export function getDictType(id: number) { + return requestClient.get(`/system/dict-type/get?id=${id}`); +} + +// 新增字典 +export function createDictType(data: SystemDictTypeApi.DictType) { + return requestClient.post('/system/dict-type/create', data); +} + +// 修改字典 +export function updateDictType(data: SystemDictTypeApi.DictType) { + return requestClient.put('/system/dict-type/update', data); +} + +// 删除字典 +export function deleteDictType(id: number) { + return requestClient.delete(`/system/dict-type/delete?id=${id}`); +} + +// 导出字典类型 +export function exportDictType(params: any) { + return requestClient.download('/system/dict-type/export', { params }); +} diff --git a/apps/web-naive/src/api/system/login-log/index.ts b/apps/web-naive/src/api/system/login-log/index.ts new file mode 100644 index 000000000..4be20d9ed --- /dev/null +++ b/apps/web-naive/src/api/system/login-log/index.ts @@ -0,0 +1,33 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace SystemLoginLogApi { + /** 登录日志信息 */ + export interface LoginLog { + id: number; + logType: number; + traceId: number; + userId: number; + userType: number; + username: string; + result: number; + status: number; + userIp: string; + userAgent: string; + createTime: string; + } +} + +/** 查询登录日志列表 */ +export function getLoginLogPage(params: PageParam) { + return requestClient.get>( + '/system/login-log/page', + { params }, + ); +} + +/** 导出登录日志 */ +export function exportLoginLog(params: any) { + return requestClient.download('/system/login-log/export-excel', { params }); +} diff --git a/apps/web-naive/src/api/system/mail/account/index.ts b/apps/web-naive/src/api/system/mail/account/index.ts new file mode 100644 index 000000000..8a43a3326 --- /dev/null +++ b/apps/web-naive/src/api/system/mail/account/index.ts @@ -0,0 +1,57 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace SystemMailAccountApi { + /** 邮箱账号 */ + export interface MailAccount { + id: number; + mail: string; + username: string; + password: string; + host: string; + port: number; + sslEnable: boolean; + starttlsEnable: boolean; + status: number; + createTime: Date; + remark: string; + } +} + +/** 查询邮箱账号列表 */ +export function getMailAccountPage(params: PageParam) { + return requestClient.get>( + '/system/mail-account/page', + { params }, + ); +} + +/** 查询邮箱账号详情 */ +export function getMailAccount(id: number) { + return requestClient.get( + `/system/mail-account/get?id=${id}`, + ); +} + +/** 新增邮箱账号 */ +export function createMailAccount(data: SystemMailAccountApi.MailAccount) { + return requestClient.post('/system/mail-account/create', data); +} + +/** 修改邮箱账号 */ +export function updateMailAccount(data: SystemMailAccountApi.MailAccount) { + return requestClient.put('/system/mail-account/update', data); +} + +/** 删除邮箱账号 */ +export function deleteMailAccount(id: number) { + return requestClient.delete(`/system/mail-account/delete?id=${id}`); +} + +/** 获得邮箱账号精简列表 */ +export function getSimpleMailAccountList() { + return requestClient.get( + '/system/mail-account/simple-list', + ); +} diff --git a/apps/web-naive/src/api/system/mail/log/index.ts b/apps/web-naive/src/api/system/mail/log/index.ts new file mode 100644 index 000000000..52c9947c5 --- /dev/null +++ b/apps/web-naive/src/api/system/mail/log/index.ts @@ -0,0 +1,46 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace SystemMailLogApi { + /** 邮件日志 */ + export interface MailLog { + id: number; + userId: number; + userType: number; + toMail: string; + accountId: number; + fromMail: string; + templateId: number; + templateCode: string; + templateNickname: string; + templateTitle: string; + templateContent: string; + templateParams: string; + sendStatus: number; + sendTime: string; + sendMessageId: string; + sendException: string; + createTime: string; + } +} + +/** 查询邮件日志列表 */ +export function getMailLogPage(params: PageParam) { + return requestClient.get>( + '/system/mail-log/page', + { params }, + ); +} + +/** 查询邮件日志详情 */ +export function getMailLog(id: number) { + return requestClient.get( + `/system/mail-log/get?id=${id}`, + ); +} + +/** 重新发送邮件 */ +export function resendMail(id: number) { + return requestClient.put(`/system/mail-log/resend?id=${id}`); +} diff --git a/apps/web-naive/src/api/system/mail/template/index.ts b/apps/web-naive/src/api/system/mail/template/index.ts new file mode 100644 index 000000000..34b4a09d0 --- /dev/null +++ b/apps/web-naive/src/api/system/mail/template/index.ts @@ -0,0 +1,62 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace SystemMailTemplateApi { + /** 邮件模版信息 */ + export interface MailTemplate { + id: number; + name: string; + code: string; + accountId: number; + nickname: string; + title: string; + content: string; + params: string[]; + status: number; + remark: string; + createTime: Date; + } + + /** 邮件发送信息 */ + export interface MailSendReqVO { + mail: string; + templateCode: string; + templateParams: Record; + } +} + +/** 查询邮件模版列表 */ +export function getMailTemplatePage(params: PageParam) { + return requestClient.get>( + '/system/mail-template/page', + { params }, + ); +} + +/** 查询邮件模版详情 */ +export function getMailTemplate(id: number) { + return requestClient.get( + `/system/mail-template/get?id=${id}`, + ); +} + +/** 新增邮件模版 */ +export function createMailTemplate(data: SystemMailTemplateApi.MailTemplate) { + return requestClient.post('/system/mail-template/create', data); +} + +/** 修改邮件模版 */ +export function updateMailTemplate(data: SystemMailTemplateApi.MailTemplate) { + return requestClient.put('/system/mail-template/update', data); +} + +/** 删除邮件模版 */ +export function deleteMailTemplate(id: number) { + return requestClient.delete(`/system/mail-template/delete?id=${id}`); +} + +/** 发送邮件 */ +export function sendMail(data: SystemMailTemplateApi.MailSendReqVO) { + return requestClient.post('/system/mail-template/send-mail', data); +} diff --git a/apps/web-naive/src/api/system/menu/index.ts b/apps/web-naive/src/api/system/menu/index.ts new file mode 100644 index 000000000..5d23e5230 --- /dev/null +++ b/apps/web-naive/src/api/system/menu/index.ts @@ -0,0 +1,54 @@ +import { requestClient } from '#/api/request'; + +export namespace SystemMenuApi { + /** 菜单信息 */ + export interface Menu { + id: number; + name: string; + permission: string; + type: number; + sort: number; + parentId: number; + path: string; + icon: string; + component: string; + componentName?: string; + status: number; + visible: boolean; + keepAlive: boolean; + alwaysShow?: boolean; + createTime: Date; + } +} + +/** 查询菜单(精简)列表 */ +export async function getSimpleMenusList() { + return requestClient.get('/system/menu/simple-list'); +} + +/** 查询菜单列表 */ +export async function getMenuList(params?: Record) { + return requestClient.get('/system/menu/list', { + params, + }); +} + +/** 获取菜单详情 */ +export async function getMenu(id: number) { + return requestClient.get(`/system/menu/get?id=${id}`); +} + +/** 新增菜单 */ +export async function createMenu(data: SystemMenuApi.Menu) { + return requestClient.post('/system/menu/create', data); +} + +/** 修改菜单 */ +export async function updateMenu(data: SystemMenuApi.Menu) { + return requestClient.put('/system/menu/update', data); +} + +/** 删除菜单 */ +export async function deleteMenu(id: number) { + return requestClient.delete(`/system/menu/delete?id=${id}`); +} diff --git a/apps/web-naive/src/api/system/notice/index.ts b/apps/web-naive/src/api/system/notice/index.ts new file mode 100644 index 000000000..dac9ec708 --- /dev/null +++ b/apps/web-naive/src/api/system/notice/index.ts @@ -0,0 +1,52 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace SystemNoticeApi { + /** 公告信息 */ + export interface Notice { + id?: number; + title: string; + type: number; + content: string; + status: number; + remark: string; + creator?: string; + createTime?: Date; + } +} + +/** 查询公告列表 */ +export function getNoticePage(params: PageParam) { + return requestClient.get>( + '/system/notice/page', + { params }, + ); +} + +/** 查询公告详情 */ +export function getNotice(id: number) { + return requestClient.get( + `/system/notice/get?id=${id}`, + ); +} + +/** 新增公告 */ +export function createNotice(data: SystemNoticeApi.Notice) { + return requestClient.post('/system/notice/create', data); +} + +/** 修改公告 */ +export function updateNotice(data: SystemNoticeApi.Notice) { + return requestClient.put('/system/notice/update', data); +} + +/** 删除公告 */ +export function deleteNotice(id: number) { + return requestClient.delete(`/system/notice/delete?id=${id}`); +} + +/** 推送公告 */ +export function pushNotice(id: number) { + return requestClient.post(`/system/notice/push?id=${id}`); +} diff --git a/apps/web-naive/src/api/system/notify/message/index.ts b/apps/web-naive/src/api/system/notify/message/index.ts new file mode 100644 index 000000000..a8a4e8968 --- /dev/null +++ b/apps/web-naive/src/api/system/notify/message/index.ts @@ -0,0 +1,65 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace SystemNotifyMessageApi { + /** 站内信消息信息 */ + export interface NotifyMessage { + id: number; + userId: number; + userType: number; + templateId: number; + templateCode: string; + templateNickname: string; + templateContent: string; + templateType: number; + templateParams: string; + readStatus: boolean; + readTime: Date; + createTime: Date; + } +} + +/** 查询站内信消息列表 */ +export function getNotifyMessagePage(params: PageParam) { + return requestClient.get>( + '/system/notify-message/page', + { params }, + ); +} + +/** 获得我的站内信分页 */ +export function getMyNotifyMessagePage(params: PageParam) { + return requestClient.get>( + '/system/notify-message/my-page', + { params }, + ); +} + +/** 批量标记已读 */ +export function updateNotifyMessageRead(ids: number[]) { + return requestClient.put( + '/system/notify-message/update-read', + {}, + { + params: { ids }, + }, + ); +} + +/** 标记所有站内信为已读 */ +export function updateAllNotifyMessageRead() { + return requestClient.put('/system/notify-message/update-all-read'); +} + +/** 获取当前用户的最新站内信列表 */ +export function getUnreadNotifyMessageList() { + return requestClient.get( + '/system/notify-message/get-unread-list', + ); +} + +/** 获得当前用户的未读站内信数量 */ +export function getUnreadNotifyMessageCount() { + return requestClient.get('/system/notify-message/get-unread-count'); +} diff --git a/apps/web-naive/src/api/system/notify/template/index.ts b/apps/web-naive/src/api/system/notify/template/index.ts new file mode 100644 index 000000000..5f2e3de29 --- /dev/null +++ b/apps/web-naive/src/api/system/notify/template/index.ts @@ -0,0 +1,72 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace SystemNotifyTemplateApi { + /** 站内信模板信息 */ + export interface NotifyTemplate { + id?: number; + name: string; + nickname: string; + code: string; + content: string; + type?: number; + params: string[]; + status: number; + remark: string; + } + + /** 发送站内信请求 */ + export interface NotifySendReqVO { + userId: number; + userType: number; + templateCode: string; + templateParams: Record; + } +} + +/** 查询站内信模板列表 */ +export function getNotifyTemplatePage(params: PageParam) { + return requestClient.get>( + '/system/notify-template/page', + { params }, + ); +} + +/** 查询站内信模板详情 */ +export function getNotifyTemplate(id: number) { + return requestClient.get( + `/system/notify-template/get?id=${id}`, + ); +} + +/** 新增站内信模板 */ +export function createNotifyTemplate( + data: SystemNotifyTemplateApi.NotifyTemplate, +) { + return requestClient.post('/system/notify-template/create', data); +} + +/** 修改站内信模板 */ +export function updateNotifyTemplate( + data: SystemNotifyTemplateApi.NotifyTemplate, +) { + return requestClient.put('/system/notify-template/update', data); +} + +/** 删除站内信模板 */ +export function deleteNotifyTemplate(id: number) { + return requestClient.delete(`/system/notify-template/delete?id=${id}`); +} + +/** 导出站内信模板 */ +export function exportNotifyTemplate(params: any) { + return requestClient.download('/system/notify-template/export-excel', { + params, + }); +} + +/** 发送站内信 */ +export function sendNotify(data: SystemNotifyTemplateApi.NotifySendReqVO) { + return requestClient.post('/system/notify-template/send-notify', data); +} diff --git a/apps/web-naive/src/api/system/oauth2/client/index.ts b/apps/web-naive/src/api/system/oauth2/client/index.ts new file mode 100644 index 000000000..7c1db7747 --- /dev/null +++ b/apps/web-naive/src/api/system/oauth2/client/index.ts @@ -0,0 +1,57 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace SystemOAuth2ClientApi { + /** OAuth2.0 客户端信息 */ + export interface OAuth2Client { + id?: number; + clientId: string; + secret: string; + name: string; + logo: string; + description: string; + status: number; + accessTokenValiditySeconds: number; + refreshTokenValiditySeconds: number; + redirectUris: string[]; + autoApprove: boolean; + authorizedGrantTypes: string[]; + scopes: string[]; + authorities: string[]; + resourceIds: string[]; + additionalInformation: string; + isAdditionalInformationJson: boolean; + createTime?: Date; + } +} + +/** 查询 OAuth2.0 客户端列表 */ +export function getOAuth2ClientPage(params: PageParam) { + return requestClient.get>( + '/system/oauth2-client/page', + { params }, + ); +} + +/** 查询 OAuth2.0 客户端详情 */ +export function getOAuth2Client(id: number) { + return requestClient.get( + `/system/oauth2-client/get?id=${id}`, + ); +} + +/** 新增 OAuth2.0 客户端 */ +export function createOAuth2Client(data: SystemOAuth2ClientApi.OAuth2Client) { + return requestClient.post('/system/oauth2-client/create', data); +} + +/** 修改 OAuth2.0 客户端 */ +export function updateOAuth2Client(data: SystemOAuth2ClientApi.OAuth2Client) { + return requestClient.put('/system/oauth2-client/update', data); +} + +/** 删除 OAuth2.0 客户端 */ +export function deleteOAuth2Client(id: number) { + return requestClient.delete(`/system/oauth2-client/delete?id=${id}`); +} diff --git a/apps/web-naive/src/api/system/oauth2/open/index.ts b/apps/web-naive/src/api/system/oauth2/open/index.ts new file mode 100644 index 000000000..16d9c7e62 --- /dev/null +++ b/apps/web-naive/src/api/system/oauth2/open/index.ts @@ -0,0 +1,58 @@ +import { requestClient } from '#/api/request'; + +/** OAuth2.0 授权信息响应 */ +export namespace SystemOAuth2ClientApi { + /** 授权信息 */ + export interface AuthorizeInfoRespVO { + client: { + logo: string; + name: string; + }; + scopes: { + key: string; + value: boolean; + }[]; + } +} + +/** 获得授权信息 */ +export function getAuthorize(clientId: string) { + return requestClient.get( + `/system/oauth2/authorize?clientId=${clientId}`, + ); +} + +/** 发起授权 */ +export function authorize( + responseType: string, + clientId: string, + redirectUri: string, + state: string, + autoApprove: boolean, + checkedScopes: string[], + uncheckedScopes: string[], +) { + // 构建 scopes + const scopes: Record = {}; + for (const scope of checkedScopes) { + scopes[scope] = true; + } + for (const scope of uncheckedScopes) { + scopes[scope] = false; + } + + // 发起请求 + return requestClient.post('/system/oauth2/authorize', null, { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + params: { + response_type: responseType, + client_id: clientId, + redirect_uri: redirectUri, + state, + auto_approve: autoApprove, + scope: JSON.stringify(scopes), + }, + }); +} diff --git a/apps/web-naive/src/api/system/oauth2/token/index.ts b/apps/web-naive/src/api/system/oauth2/token/index.ts new file mode 100644 index 000000000..bd3697915 --- /dev/null +++ b/apps/web-naive/src/api/system/oauth2/token/index.ts @@ -0,0 +1,34 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace SystemOAuth2TokenApi { + /** OAuth2.0 令牌信息 */ + export interface OAuth2Token { + id?: number; + accessToken: string; + refreshToken: string; + userId: number; + userType: number; + clientId: string; + createTime?: Date; + expiresTime?: Date; + } +} + +/** 查询 OAuth2.0 令牌列表 */ +export function getOAuth2TokenPage(params: PageParam) { + return requestClient.get>( + '/system/oauth2-token/page', + { + params, + }, + ); +} + +/** 删除 OAuth2.0 令牌 */ +export function deleteOAuth2Token(accessToken: string) { + return requestClient.delete( + `/system/oauth2-token/delete?accessToken=${accessToken}`, + ); +} diff --git a/apps/web-naive/src/api/system/operate-log/index.ts b/apps/web-naive/src/api/system/operate-log/index.ts new file mode 100644 index 000000000..8b84a260e --- /dev/null +++ b/apps/web-naive/src/api/system/operate-log/index.ts @@ -0,0 +1,39 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace SystemOperateLogApi { + /** 操作日志信息 */ + export interface OperateLog { + id: number; + traceId: string; + userType: number; + userId: number; + userName: string; + type: string; + subType: string; + bizId: number; + action: string; + extra: string; + requestMethod: string; + requestUrl: string; + userIp: string; + userAgent: string; + creator: string; + creatorName: string; + createTime: string; + } +} + +/** 查询操作日志列表 */ +export function getOperateLogPage(params: PageParam) { + return requestClient.get>( + '/system/operate-log/page', + { params }, + ); +} + +/** 导出操作日志 */ +export function exportOperateLog(params: any) { + return requestClient.download('/system/operate-log/export-excel', { params }); +} diff --git a/apps/web-naive/src/api/system/permission/index.ts b/apps/web-naive/src/api/system/permission/index.ts new file mode 100644 index 000000000..9039d9a05 --- /dev/null +++ b/apps/web-naive/src/api/system/permission/index.ts @@ -0,0 +1,57 @@ +import { requestClient } from '#/api/request'; + +export namespace SystemPermissionApi { + /** 分配用户角色请求 */ + export interface AssignUserRoleReqVO { + userId: number; + roleIds: number[]; + } + + /** 分配角色菜单请求 */ + export interface AssignRoleMenuReqVO { + roleId: number; + menuIds: number[]; + } + + /** 分配角色数据权限请求 */ + export interface AssignRoleDataScopeReqVO { + roleId: number; + dataScope: number; + dataScopeDeptIds: number[]; + } +} + +/** 查询角色拥有的菜单权限 */ +export async function getRoleMenuList(roleId: number) { + return requestClient.get( + `/system/permission/list-role-menus?roleId=${roleId}`, + ); +} + +/** 赋予角色菜单权限 */ +export async function assignRoleMenu( + data: SystemPermissionApi.AssignRoleMenuReqVO, +) { + return requestClient.post('/system/permission/assign-role-menu', data); +} + +/** 赋予角色数据权限 */ +export async function assignRoleDataScope( + data: SystemPermissionApi.AssignRoleDataScopeReqVO, +) { + return requestClient.post('/system/permission/assign-role-data-scope', data); +} + +/** 查询用户拥有的角色数组 */ +export async function getUserRoleList(userId: number) { + return requestClient.get( + `/system/permission/list-user-roles?userId=${userId}`, + ); +} + +/** 赋予用户角色 */ +export async function assignUserRole( + data: SystemPermissionApi.AssignUserRoleReqVO, +) { + return requestClient.post('/system/permission/assign-user-role', data); +} diff --git a/apps/web-naive/src/api/system/post/index.ts b/apps/web-naive/src/api/system/post/index.ts new file mode 100644 index 000000000..a82f58155 --- /dev/null +++ b/apps/web-naive/src/api/system/post/index.ts @@ -0,0 +1,58 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace SystemPostApi { + /** 岗位信息 */ + export interface Post { + id?: number; + name: string; + code: string; + sort: number; + status: number; + remark: string; + createTime?: Date; + } +} + +/** 查询岗位列表 */ +export function getPostPage(params: PageParam) { + return requestClient.get>( + '/system/post/page', + { + params, + }, + ); +} + +/** 获取岗位精简信息列表 */ +export function getSimplePostList() { + return requestClient.get('/system/post/simple-list'); +} + +/** 查询岗位详情 */ +export function getPost(id: number) { + return requestClient.get(`/system/post/get?id=${id}`); +} + +/** 新增岗位 */ +export function createPost(data: SystemPostApi.Post) { + return requestClient.post('/system/post/create', data); +} + +/** 修改岗位 */ +export function updatePost(data: SystemPostApi.Post) { + return requestClient.put('/system/post/update', data); +} + +/** 删除岗位 */ +export function deletePost(id: number) { + return requestClient.delete(`/system/post/delete?id=${id}`); +} + +/** 导出岗位 */ +export function exportPost(params: any) { + return requestClient.download('/system/post/export', { + params, + }); +} diff --git a/apps/web-naive/src/api/system/role/index.ts b/apps/web-naive/src/api/system/role/index.ts new file mode 100644 index 000000000..07824c43e --- /dev/null +++ b/apps/web-naive/src/api/system/role/index.ts @@ -0,0 +1,58 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace SystemRoleApi { + /** 角色信息 */ + export interface Role { + id?: number; + name: string; + code: string; + sort: number; + status: number; + type: number; + dataScope: number; + dataScopeDeptIds: number[]; + createTime?: Date; + } +} + +/** 查询角色列表 */ +export function getRolePage(params: PageParam) { + return requestClient.get>( + '/system/role/page', + { params }, + ); +} + +/** 查询角色(精简)列表 */ +export function getSimpleRoleList() { + return requestClient.get('/system/role/simple-list'); +} + +/** 查询角色详情 */ +export function getRole(id: number) { + return requestClient.get(`/system/role/get?id=${id}`); +} + +/** 新增角色 */ +export function createRole(data: SystemRoleApi.Role) { + return requestClient.post('/system/role/create', data); +} + +/** 修改角色 */ +export function updateRole(data: SystemRoleApi.Role) { + return requestClient.put('/system/role/update', data); +} + +/** 删除角色 */ +export function deleteRole(id: number) { + return requestClient.delete(`/system/role/delete?id=${id}`); +} + +/** 导出角色 */ +export function exportRole(params: any) { + return requestClient.download('/system/role/export-excel', { + params, + }); +} diff --git a/apps/web-naive/src/api/system/sms/channel/index.ts b/apps/web-naive/src/api/system/sms/channel/index.ts new file mode 100644 index 000000000..56890bea5 --- /dev/null +++ b/apps/web-naive/src/api/system/sms/channel/index.ts @@ -0,0 +1,60 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace SystemSmsChannelApi { + /** 短信渠道信息 */ + export interface SmsChannel { + id?: number; + code: string; + status: number; + signature: string; + remark: string; + apiKey: string; + apiSecret: string; + callbackUrl: string; + createTime?: Date; + } +} + +/** 查询短信渠道列表 */ +export function getSmsChannelPage(params: PageParam) { + return requestClient.get>( + '/system/sms-channel/page', + { params }, + ); +} + +/** 获得短信渠道精简列表 */ +export function getSimpleSmsChannelList() { + return requestClient.get( + '/system/sms-channel/simple-list', + ); +} + +/** 查询短信渠道详情 */ +export function getSmsChannel(id: number) { + return requestClient.get( + `/system/sms-channel/get?id=${id}`, + ); +} + +/** 新增短信渠道 */ +export function createSmsChannel(data: SystemSmsChannelApi.SmsChannel) { + return requestClient.post('/system/sms-channel/create', data); +} + +/** 修改短信渠道 */ +export function updateSmsChannel(data: SystemSmsChannelApi.SmsChannel) { + return requestClient.put('/system/sms-channel/update', data); +} + +/** 删除短信渠道 */ +export function deleteSmsChannel(id: number) { + return requestClient.delete(`/system/sms-channel/delete?id=${id}`); +} + +/** 导出短信渠道 */ +export function exportSmsChannel(params: any) { + return requestClient.download('/system/sms-channel/export', { params }); +} diff --git a/apps/web-naive/src/api/system/sms/log/index.ts b/apps/web-naive/src/api/system/sms/log/index.ts new file mode 100644 index 000000000..8344f9932 --- /dev/null +++ b/apps/web-naive/src/api/system/sms/log/index.ts @@ -0,0 +1,45 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace SystemSmsLogApi { + /** 短信日志信息 */ + export interface SmsLog { + id?: number; + channelId?: number; + channelCode: string; + templateId?: number; + templateCode: string; + templateType?: number; + templateContent: string; + templateParams?: Record; + apiTemplateId: string; + mobile: string; + userId?: number; + userType?: number; + sendStatus?: number; + sendTime?: string; + apiSendCode: string; + apiSendMsg: string; + apiRequestId: string; + apiSerialNo: string; + receiveStatus?: number; + receiveTime?: string; + apiReceiveCode: string; + apiReceiveMsg: string; + createTime: string; + } +} + +/** 查询短信日志列表 */ +export function getSmsLogPage(params: PageParam) { + return requestClient.get>( + '/system/sms-log/page', + { params }, + ); +} + +/** 导出短信日志 */ +export function exportSmsLog(params: any) { + return requestClient.download('/system/sms-log/export-excel', { params }); +} diff --git a/apps/web-naive/src/api/system/sms/template/index.ts b/apps/web-naive/src/api/system/sms/template/index.ts new file mode 100644 index 000000000..63660bdf9 --- /dev/null +++ b/apps/web-naive/src/api/system/sms/template/index.ts @@ -0,0 +1,70 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace SystemSmsTemplateApi { + /** 短信模板信息 */ + export interface SmsTemplate { + id?: number; + type?: number; + status: number; + code: string; + name: string; + content: string; + remark: string; + apiTemplateId: string; + channelId?: number; + channelCode?: string; + params?: string[]; + createTime?: Date; + } + + /** 发送短信请求 */ + export interface SmsSendReqVO { + mobile: string; + templateCode: string; + templateParams: Record; + } +} + +/** 查询短信模板列表 */ +export function getSmsTemplatePage(params: PageParam) { + return requestClient.get>( + '/system/sms-template/page', + { params }, + ); +} + +/** 查询短信模板详情 */ +export function getSmsTemplate(id: number) { + return requestClient.get( + `/system/sms-template/get?id=${id}`, + ); +} + +/** 新增短信模板 */ +export function createSmsTemplate(data: SystemSmsTemplateApi.SmsTemplate) { + return requestClient.post('/system/sms-template/create', data); +} + +/** 修改短信模板 */ +export function updateSmsTemplate(data: SystemSmsTemplateApi.SmsTemplate) { + return requestClient.put('/system/sms-template/update', data); +} + +/** 删除短信模板 */ +export function deleteSmsTemplate(id: number) { + return requestClient.delete(`/system/sms-template/delete?id=${id}`); +} + +/** 导出短信模板 */ +export function exportSmsTemplate(params: any) { + return requestClient.download('/system/sms-template/export-excel', { + params, + }); +} + +/** 发送短信 */ +export function sendSms(data: SystemSmsTemplateApi.SmsSendReqVO) { + return requestClient.post('/system/sms-template/send-sms', data); +} diff --git a/apps/web-naive/src/api/system/social/client/index.ts b/apps/web-naive/src/api/system/social/client/index.ts new file mode 100644 index 000000000..d4da4dd48 --- /dev/null +++ b/apps/web-naive/src/api/system/social/client/index.ts @@ -0,0 +1,48 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace SystemSocialClientApi { + /** 社交客户端信息 */ + export interface SocialClient { + id?: number; + name: string; + socialType: number; + userType: number; + clientId: string; + clientSecret: string; + agentId?: string; + status: number; + createTime?: Date; + } +} + +/** 查询社交客户端列表 */ +export function getSocialClientPage(params: PageParam) { + return requestClient.get>( + '/system/social-client/page', + { params }, + ); +} + +/** 查询社交客户端详情 */ +export function getSocialClient(id: number) { + return requestClient.get( + `/system/social-client/get?id=${id}`, + ); +} + +/** 新增社交客户端 */ +export function createSocialClient(data: SystemSocialClientApi.SocialClient) { + return requestClient.post('/system/social-client/create', data); +} + +/** 修改社交客户端 */ +export function updateSocialClient(data: SystemSocialClientApi.SocialClient) { + return requestClient.put('/system/social-client/update', data); +} + +/** 删除社交客户端 */ +export function deleteSocialClient(id: number) { + return requestClient.delete(`/system/social-client/delete?id=${id}`); +} diff --git a/apps/web-naive/src/api/system/social/user/index.ts b/apps/web-naive/src/api/system/social/user/index.ts new file mode 100644 index 000000000..b91f15064 --- /dev/null +++ b/apps/web-naive/src/api/system/social/user/index.ts @@ -0,0 +1,66 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace SystemSocialUserApi { + /** 社交用户信息 */ + export interface SocialUser { + id?: number; + type: number; + openid: string; + token: string; + rawTokenInfo: string; + nickname: string; + avatar: string; + rawUserInfo: string; + code: string; + state: string; + createTime?: Date; + updateTime?: Date; + } + + /** 社交绑定请求 */ + export interface SocialUserBindReqVO { + type: number; + code: string; + state: string; + } + + /** 取消社交绑定请求 */ + export interface SocialUserUnbindReqVO { + type: number; + openid: string; + } +} + +/** 查询社交用户列表 */ +export function getSocialUserPage(params: PageParam) { + return requestClient.get>( + '/system/social-user/page', + { params }, + ); +} + +/** 查询社交用户详情 */ +export function getSocialUser(id: number) { + return requestClient.get( + `/system/social-user/get?id=${id}`, + ); +} + +/** 社交绑定,使用 code 授权码 */ +export function socialBind(data: SystemSocialUserApi.SocialUserBindReqVO) { + return requestClient.post('/system/social-user/bind', data); +} + +/** 取消社交绑定 */ +export function socialUnbind(data: SystemSocialUserApi.SocialUserUnbindReqVO) { + return requestClient.delete('/system/social-user/unbind', { data }); +} + +/** 获得绑定社交用户列表 */ +export function getBindSocialUserList() { + return requestClient.get( + '/system/social-user/get-bind-list', + ); +} diff --git a/apps/web-naive/src/api/system/tenant-package/index.ts b/apps/web-naive/src/api/system/tenant-package/index.ts new file mode 100644 index 000000000..5066cea98 --- /dev/null +++ b/apps/web-naive/src/api/system/tenant-package/index.ts @@ -0,0 +1,57 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace SystemTenantPackageApi { + /** 租户套餐信息 */ + export interface TenantPackage { + id: number; + name: string; + status: number; + remark: string; + creator: string; + updater: string; + updateTime: string; + menuIds: number[]; + createTime: Date; + } +} + +/** 租户套餐列表 */ +export function getTenantPackagePage(params: PageParam) { + return requestClient.get>( + '/system/tenant-package/page', + { params }, + ); +} + +/** 查询租户套餐详情 */ +export function getTenantPackage(id: number) { + return requestClient.get(`/system/tenant-package/get?id=${id}`); +} + +/** 新增租户套餐 */ +export function createTenantPackage( + data: SystemTenantPackageApi.TenantPackage, +) { + return requestClient.post('/system/tenant-package/create', data); +} + +/** 修改租户套餐 */ +export function updateTenantPackage( + data: SystemTenantPackageApi.TenantPackage, +) { + return requestClient.put('/system/tenant-package/update', data); +} + +/** 删除租户套餐 */ +export function deleteTenantPackage(id: number) { + return requestClient.delete(`/system/tenant-package/delete?id=${id}`); +} + +/** 获取租户套餐精简信息列表 */ +export function getTenantPackageList() { + return requestClient.get( + '/system/tenant-package/get-simple-list', + ); +} diff --git a/apps/web-naive/src/api/system/tenant/index.ts b/apps/web-naive/src/api/system/tenant/index.ts new file mode 100644 index 000000000..3bed9249c --- /dev/null +++ b/apps/web-naive/src/api/system/tenant/index.ts @@ -0,0 +1,69 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace SystemTenantApi { + /** 租户信息 */ + export interface Tenant { + id?: number; + name: string; + packageId: number; + contactName: string; + contactMobile: string; + accountCount: number; + expireTime: Date; + website: string; + status: number; + } +} + +/** 租户列表 */ +export function getTenantPage(params: PageParam) { + return requestClient.get>( + '/system/tenant/page', + { params }, + ); +} + +/** 获取租户精简信息列表 */ +export function getSimpleTenantList() { + return requestClient.get( + '/system/tenant/simple-list', + ); +} + +/** 查询租户详情 */ +export function getTenant(id: number) { + return requestClient.get( + `/system/tenant/get?id=${id}`, + ); +} + +/** 获取租户精简信息列表 */ +export function getTenantList() { + return requestClient.get( + '/system/tenant/simple-list', + ); +} + +/** 新增租户 */ +export function createTenant(data: SystemTenantApi.Tenant) { + return requestClient.post('/system/tenant/create', data); +} + +/** 修改租户 */ +export function updateTenant(data: SystemTenantApi.Tenant) { + return requestClient.put('/system/tenant/update', data); +} + +/** 删除租户 */ +export function deleteTenant(id: number) { + return requestClient.delete(`/system/tenant/delete?id=${id}`); +} + +/** 导出租户 */ +export function exportTenant(params: any) { + return requestClient.download('/system/tenant/export-excel', { + params, + }); +} diff --git a/apps/web-naive/src/api/system/user/index.ts b/apps/web-naive/src/api/system/user/index.ts new file mode 100644 index 000000000..52d8cdd2e --- /dev/null +++ b/apps/web-naive/src/api/system/user/index.ts @@ -0,0 +1,83 @@ +import type { PageParam, PageResult } from '@vben/request'; + +import { requestClient } from '#/api/request'; + +export namespace SystemUserApi { + /** 用户信息 */ + export interface User { + id?: number; + username: string; + nickname: string; + deptId: number; + postIds: string[]; + email: string; + mobile: string; + sex: number; + avatar: string; + loginIp: string; + status: number; + remark: string; + createTime?: Date; + } +} + +/** 查询用户管理列表 */ +export function getUserPage(params: PageParam) { + return requestClient.get>( + '/system/user/page', + { params }, + ); +} + +/** 查询用户详情 */ +export function getUser(id: number) { + return requestClient.get(`/system/user/get?id=${id}`); +} + +/** 新增用户 */ +export function createUser(data: SystemUserApi.User) { + return requestClient.post('/system/user/create', data); +} + +/** 修改用户 */ +export function updateUser(data: SystemUserApi.User) { + return requestClient.put('/system/user/update', data); +} + +/** 删除用户 */ +export function deleteUser(id: number) { + return requestClient.delete(`/system/user/delete?id=${id}`); +} + +/** 导出用户 */ +export function exportUser(params: any) { + return requestClient.download('/system/user/export', params); +} + +/** 下载用户导入模板 */ +export function importUserTemplate() { + return requestClient.download('/system/user/get-import-template'); +} + +/** 导入用户 */ +export function importUser(file: File, updateSupport: boolean) { + return requestClient.upload('/system/user/import', { + file, + updateSupport, + }); +} + +/** 用户密码重置 */ +export function resetUserPassword(id: number, password: string) { + return requestClient.put('/system/user/update-password', { id, password }); +} + +/** 用户状态修改 */ +export function updateUserStatus(id: number, status: number) { + return requestClient.put('/system/user/update-status', { id, status }); +} + +/** 获取用户精简信息列表 */ +export function getSimpleUserList() { + return requestClient.get('/system/user/simple-list'); +} diff --git a/apps/web-naive/src/api/system/user/profile/index.ts b/apps/web-naive/src/api/system/user/profile/index.ts new file mode 100644 index 000000000..97898e71a --- /dev/null +++ b/apps/web-naive/src/api/system/user/profile/index.ts @@ -0,0 +1,56 @@ +import { requestClient } from '#/api/request'; + +export namespace SystemUserProfileApi { + /** 用户个人中心信息 */ + export interface UserProfileRespVO { + id: number; + username: string; + nickname: string; + email?: string; + mobile?: string; + sex?: number; + avatar?: string; + loginIp: string; + loginDate: string; + createTime: string; + roles: any[]; + dept: any; + posts: any[]; + } + + /** 更新密码请求 */ + export interface UpdatePasswordReqVO { + oldPassword: string; + newPassword: string; + } + + /** 更新个人信息请求 */ + export interface UpdateProfileReqVO { + nickname?: string; + email?: string; + mobile?: string; + sex?: number; + avatar?: string; + } +} + +/** 获取登录用户信息 */ +export function getUserProfile() { + return requestClient.get( + '/system/user/profile/get', + ); +} + +/** 修改用户个人信息 */ +export function updateUserProfile( + data: SystemUserProfileApi.UpdateProfileReqVO, +) { + return requestClient.put('/system/user/profile/update', data); +} + +/** 修改用户个人密码 */ +export function updateUserPassword( + data: SystemUserProfileApi.UpdatePasswordReqVO, +) { + return requestClient.put('/system/user/profile/update-password', data); +} diff --git a/apps/web-naive/src/components/cropper/cropper-avatar.vue b/apps/web-naive/src/components/cropper/cropper-avatar.vue new file mode 100644 index 000000000..de4ed8c6e --- /dev/null +++ b/apps/web-naive/src/components/cropper/cropper-avatar.vue @@ -0,0 +1,159 @@ + + + + + diff --git a/apps/web-naive/src/components/cropper/cropper-modal.vue b/apps/web-naive/src/components/cropper/cropper-modal.vue new file mode 100644 index 000000000..2f2922cf2 --- /dev/null +++ b/apps/web-naive/src/components/cropper/cropper-modal.vue @@ -0,0 +1,370 @@ + + + + + diff --git a/apps/web-naive/src/components/cropper/cropper.vue b/apps/web-naive/src/components/cropper/cropper.vue new file mode 100644 index 000000000..c2d62755e --- /dev/null +++ b/apps/web-naive/src/components/cropper/cropper.vue @@ -0,0 +1,173 @@ + + + + + diff --git a/apps/web-naive/src/components/cropper/index.ts b/apps/web-naive/src/components/cropper/index.ts new file mode 100644 index 000000000..43fd89ff3 --- /dev/null +++ b/apps/web-naive/src/components/cropper/index.ts @@ -0,0 +1,3 @@ +export { default as CropperAvatar } from './cropper-avatar.vue'; +export { default as CropperImage } from './cropper.vue'; +export type { CropperType } from './typing'; diff --git a/apps/web-naive/src/components/cropper/typing.ts b/apps/web-naive/src/components/cropper/typing.ts new file mode 100644 index 000000000..6683c34a3 --- /dev/null +++ b/apps/web-naive/src/components/cropper/typing.ts @@ -0,0 +1,68 @@ +import type Cropper from 'cropperjs'; +import type { ButtonProps } from 'naive-ui'; + +import type { CSSProperties } from 'vue'; + +export interface apiFunParams { + file: Blob; + filename: string; + name: string; +} + +export interface CropendResult { + imgBase64: string; + imgInfo: Cropper.Data; +} + +export interface CropperProps { + src?: string; + alt?: string; + circled?: boolean; + realTimePreview?: boolean; + height?: number | string; + crossorigin?: '' | 'anonymous' | 'use-credentials' | undefined; + imageStyle?: CSSProperties; + options?: Cropper.Options; +} + +export interface CropperAvatarProps { + width?: number | string; + value?: string; + showBtn?: boolean; + btnProps?: ButtonProps; + btnText?: string; + uploadApi?: (params: apiFunParams) => Promise; + size?: number; +} + +export interface CropperModalProps { + circled?: boolean; + uploadApi?: (params: apiFunParams) => Promise; + src?: string; + size?: number; +} + +export const defaultOptions: Cropper.Options = { + aspectRatio: 1, + zoomable: true, + zoomOnTouch: true, + zoomOnWheel: true, + cropBoxMovable: true, + cropBoxResizable: true, + toggleDragModeOnDblclick: true, + autoCrop: true, + background: true, + highlight: true, + center: true, + responsive: true, + restore: true, + checkCrossOrigin: true, + checkOrientation: true, + scalable: true, + modal: true, + guides: true, + movable: true, + rotatable: true, +}; + +export type { Cropper as CropperType }; diff --git a/apps/web-naive/src/components/dict-tag/dict-tag.vue b/apps/web-naive/src/components/dict-tag/dict-tag.vue new file mode 100644 index 000000000..aad917044 --- /dev/null +++ b/apps/web-naive/src/components/dict-tag/dict-tag.vue @@ -0,0 +1,73 @@ + + + diff --git a/apps/web-naive/src/components/dict-tag/index.ts b/apps/web-naive/src/components/dict-tag/index.ts new file mode 100644 index 000000000..881265a39 --- /dev/null +++ b/apps/web-naive/src/components/dict-tag/index.ts @@ -0,0 +1 @@ +export { default as DictTag } from './dict-tag.vue'; diff --git a/apps/web-naive/src/components/doc-alert/doc-alert.vue b/apps/web-naive/src/components/doc-alert/doc-alert.vue new file mode 100644 index 000000000..7aba339e9 --- /dev/null +++ b/apps/web-naive/src/components/doc-alert/doc-alert.vue @@ -0,0 +1,32 @@ + + + diff --git a/apps/web-naive/src/components/doc-alert/index.ts b/apps/web-naive/src/components/doc-alert/index.ts new file mode 100644 index 000000000..213351536 --- /dev/null +++ b/apps/web-naive/src/components/doc-alert/index.ts @@ -0,0 +1 @@ +export { default as DocAlert } from './doc-alert.vue'; diff --git a/apps/web-naive/src/components/iframe/iframe.vue b/apps/web-naive/src/components/iframe/iframe.vue new file mode 100644 index 000000000..de70d89a9 --- /dev/null +++ b/apps/web-naive/src/components/iframe/iframe.vue @@ -0,0 +1,38 @@ + + + diff --git a/apps/web-naive/src/components/iframe/index.ts b/apps/web-naive/src/components/iframe/index.ts new file mode 100644 index 000000000..d437bc0d3 --- /dev/null +++ b/apps/web-naive/src/components/iframe/index.ts @@ -0,0 +1 @@ +export { default as IFrame } from './iframe.vue'; diff --git a/apps/web-naive/src/components/table-toolbar/index.ts b/apps/web-naive/src/components/table-toolbar/index.ts new file mode 100644 index 000000000..720e3224b --- /dev/null +++ b/apps/web-naive/src/components/table-toolbar/index.ts @@ -0,0 +1 @@ +export { default as TableToolbar } from './table-toolbar.vue'; diff --git a/apps/web-naive/src/components/table-toolbar/table-toolbar.vue b/apps/web-naive/src/components/table-toolbar/table-toolbar.vue new file mode 100644 index 000000000..eb453bdfd --- /dev/null +++ b/apps/web-naive/src/components/table-toolbar/table-toolbar.vue @@ -0,0 +1,60 @@ + + + + diff --git a/apps/web-naive/src/components/upload/file-upload.vue b/apps/web-naive/src/components/upload/file-upload.vue new file mode 100644 index 000000000..abbf637e9 --- /dev/null +++ b/apps/web-naive/src/components/upload/file-upload.vue @@ -0,0 +1,221 @@ + + + diff --git a/apps/web-naive/src/components/upload/helper.ts b/apps/web-naive/src/components/upload/helper.ts new file mode 100644 index 000000000..a7a67639d --- /dev/null +++ b/apps/web-naive/src/components/upload/helper.ts @@ -0,0 +1,20 @@ +export function checkFileType(file: File, accepts: string[]) { + if (!accepts || accepts.length === 0) { + return true; + } + const newTypes = accepts.join('|'); + const reg = new RegExp(`${String.raw`\.(` + newTypes})$`, 'i'); + return reg.test(file.name); +} + +/** + * 默认图片类型 + */ +export const defaultImageAccepts = ['jpg', 'jpeg', 'png', 'gif', 'webp']; + +export function checkImgType( + file: File, + accepts: string[] = defaultImageAccepts, +) { + return checkFileType(file, accepts); +} diff --git a/apps/web-naive/src/components/upload/image-upload.vue b/apps/web-naive/src/components/upload/image-upload.vue new file mode 100644 index 000000000..a26be8a15 --- /dev/null +++ b/apps/web-naive/src/components/upload/image-upload.vue @@ -0,0 +1,276 @@ + + + + + diff --git a/apps/web-naive/src/components/upload/index.ts b/apps/web-naive/src/components/upload/index.ts new file mode 100644 index 000000000..a66b2fca6 --- /dev/null +++ b/apps/web-naive/src/components/upload/index.ts @@ -0,0 +1,2 @@ +export { default as FileUpload } from './file-upload.vue'; +export { default as ImageUpload } from './image-upload.vue'; diff --git a/apps/web-naive/src/components/upload/typing.ts b/apps/web-naive/src/components/upload/typing.ts new file mode 100644 index 000000000..a6b54d479 --- /dev/null +++ b/apps/web-naive/src/components/upload/typing.ts @@ -0,0 +1,8 @@ +export enum UploadResultStatus { + DONE = 'done', + ERROR = 'error', + SUCCESS = 'success', + UPLOADING = 'uploading', +} + +export type UploadListType = 'picture' | 'picture-card' | 'text'; diff --git a/apps/web-naive/src/components/upload/use-upload.ts b/apps/web-naive/src/components/upload/use-upload.ts new file mode 100644 index 000000000..d47672bd4 --- /dev/null +++ b/apps/web-naive/src/components/upload/use-upload.ts @@ -0,0 +1,166 @@ +import type { Ref } from 'vue'; + +import type { AxiosProgressEvent, InfraFileApi } from '#/api/infra/file'; + +import { computed, unref } from 'vue'; + +import { useAppConfig } from '@vben/hooks'; +import { $t } from '@vben/locales'; + +// import CryptoJS from 'crypto-js'; +import { createFile, getFilePresignedUrl, uploadFile } from '#/api/infra/file'; +import { baseRequestClient } from '#/api/request'; + +const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); + +/** + * 上传类型 + */ +enum UPLOAD_TYPE { + // 客户端直接上传(只支持S3服务) + CLIENT = 'client', + // 客户端发送到后端上传 + SERVER = 'server', +} + +export function useUploadType({ + acceptRef, + helpTextRef, + maxNumberRef, + maxSizeRef, +}: { + acceptRef: Ref; + helpTextRef: Ref; + maxNumberRef: Ref; + maxSizeRef: Ref; +}) { + // 文件类型限制 + const getAccept = computed(() => { + const accept = unref(acceptRef); + if (accept && accept.length > 0) { + return accept; + } + return []; + }); + const getStringAccept = computed(() => { + return unref(getAccept) + .map((item) => { + return item.indexOf('/') > 0 || item.startsWith('.') + ? item + : `.${item}`; + }) + .join(','); + }); + + // 支持jpg、jpeg、png格式,不超过2M,最多可选择10张图片,。 + const getHelpText = computed(() => { + const helpText = unref(helpTextRef); + if (helpText) { + return helpText; + } + const helpTexts: string[] = []; + + const accept = unref(acceptRef); + if (accept.length > 0) { + helpTexts.push($t('ui.upload.accept', [accept.join(',')])); + } + + const maxSize = unref(maxSizeRef); + if (maxSize) { + helpTexts.push($t('ui.upload.maxSize', [maxSize])); + } + + const maxNumber = unref(maxNumberRef); + if (maxNumber && maxNumber !== Infinity) { + helpTexts.push($t('ui.upload.maxNumber', [maxNumber])); + } + return helpTexts.join(','); + }); + return { getAccept, getStringAccept, getHelpText }; +} + +// TODO @芋艿:目前保持和 admin-vue3 一致,后续可能重构 +export function useUpload(directory?: string) { + // 后端上传地址 + const uploadUrl = getUploadUrl(); + // 是否使用前端直连上传 + const isClientUpload = + UPLOAD_TYPE.CLIENT === import.meta.env.VITE_UPLOAD_TYPE; + // 重写ElUpload上传方法 + const httpRequest = async ( + file: File, + onUploadProgress?: AxiosProgressEvent, + ) => { + // 模式一:前端上传 + if (isClientUpload) { + // 1.1 生成文件名称 + const fileName = await generateFileName(file); + // 1.2 获取文件预签名地址 + const presignedInfo = await getFilePresignedUrl(fileName, directory); + // 1.3 上传文件 + return baseRequestClient + .put(presignedInfo.uploadUrl, file, { + headers: { + 'Content-Type': file.type, + }, + }) + .then(() => { + // 1.4. 记录文件信息到后端(异步) + createFile0(presignedInfo, file); + // 通知成功,数据格式保持与后端上传的返回结果一致 + return { url: presignedInfo.url }; + }); + } else { + // 模式二:后端上传 + return uploadFile({ file, directory }, onUploadProgress); + } + }; + + return { + uploadUrl, + httpRequest, + }; +} + +/** + * 获得上传 URL + */ +export function getUploadUrl(): string { + return `${apiURL}/infra/file/upload`; +} + +/** + * 创建文件信息 + * + * @param vo 文件预签名信息 + * @param file 文件 + */ +function createFile0(vo: InfraFileApi.FilePresignedUrlRespVO, file: File) { + const fileVO = { + configId: vo.configId, + url: vo.url, + path: vo.path, + name: file.name, + type: file.type, + size: file.size, + }; + createFile(fileVO); + return fileVO; +} + +/** + * 生成文件名称(使用算法SHA256) + * + * @param file 要上传的文件 + */ +async function generateFileName(file: File) { + // // 读取文件内容 + // const data = await file.arrayBuffer(); + // const wordArray = CryptoJS.lib.WordArray.create(data); + // // 计算SHA256 + // const sha256 = CryptoJS.SHA256(wordArray).toString(); + // // 拼接后缀 + // const ext = file.name.slice(Math.max(0, file.name.lastIndexOf('.'))); + // return `${sha256}${ext}`; + return file.name; +} diff --git a/apps/web-naive/src/layouts/basic.vue b/apps/web-naive/src/layouts/basic.vue index 691893841..2777f4a3a 100644 --- a/apps/web-naive/src/layouts/basic.vue +++ b/apps/web-naive/src/layouts/basic.vue @@ -1,12 +1,17 @@ + diff --git a/apps/web-naive/src/layouts/components/tenant-dropdown.vue b/apps/web-naive/src/layouts/components/tenant-dropdown.vue new file mode 100644 index 000000000..b56dde480 --- /dev/null +++ b/apps/web-naive/src/layouts/components/tenant-dropdown.vue @@ -0,0 +1,62 @@ + + diff --git a/apps/web-naive/src/locales/langs/en-US/demos.json b/apps/web-naive/src/locales/langs/en-US/demos.json deleted file mode 100644 index 839fc2e68..000000000 --- a/apps/web-naive/src/locales/langs/en-US/demos.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "title": "Demos", - "naive": "Naive UI", - "table": "Table", - "form": "Form", - "vben": { - "title": "Project", - "about": "About", - "document": "Document", - "antdv": "Ant Design Vue Version", - "naive-ui": "Naive UI Version", - "element-plus": "Element Plus Version" - } -} diff --git a/apps/web-naive/src/locales/langs/en-US/page.json b/apps/web-naive/src/locales/langs/en-US/page.json index 618a258c0..e889bfc10 100644 --- a/apps/web-naive/src/locales/langs/en-US/page.json +++ b/apps/web-naive/src/locales/langs/en-US/page.json @@ -10,5 +10,23 @@ "title": "Dashboard", "analytics": "Analytics", "workspace": "Workspace" + }, + "action": { + "action": "Action", + "add": "Add", + "edit": "Edit", + "delete": "Delete", + "save": "Save", + "import": "Import", + "export": "Export", + "submit": "Submit", + "cancel": "Cancel", + "confirm": "Confirm", + "reset": "Reset", + "search": "Search" + }, + "tenant": { + "placeholder": "Please select tenant", + "success": "Switch tenant success" } } diff --git a/apps/web-naive/src/locales/langs/en-US/utils.json b/apps/web-naive/src/locales/langs/en-US/utils.json new file mode 100644 index 000000000..b9206eff9 --- /dev/null +++ b/apps/web-naive/src/locales/langs/en-US/utils.json @@ -0,0 +1,14 @@ +{ + "rangePicker": { + "today": "Today", + "last7Days": "Last 7 Days", + "last30Days": "Last 30 Days", + "yesterday": "Yesterday", + "thisWeek": "This Week", + "thisMonth": "This Month", + "lastWeek": "Last Week", + "lastMonth": "Last Month", + "beginTime": "Begin Time", + "endTime": "End Time" + } +} diff --git a/apps/web-naive/src/locales/langs/zh-CN/demos.json b/apps/web-naive/src/locales/langs/zh-CN/demos.json deleted file mode 100644 index e0d7e616d..000000000 --- a/apps/web-naive/src/locales/langs/zh-CN/demos.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "title": "演示", - "naive": "Naive UI", - "table": "Table", - "form": "表单", - "vben": { - "title": "项目", - "about": "关于", - "document": "文档", - "antdv": "Ant Design Vue 版本", - "naive-ui": "Naive UI 版本", - "element-plus": "Element Plus 版本" - } -} diff --git a/apps/web-naive/src/locales/langs/zh-CN/page.json b/apps/web-naive/src/locales/langs/zh-CN/page.json index 4cb67081c..a7781960b 100644 --- a/apps/web-naive/src/locales/langs/zh-CN/page.json +++ b/apps/web-naive/src/locales/langs/zh-CN/page.json @@ -10,5 +10,23 @@ "title": "概览", "analytics": "分析页", "workspace": "工作台" + }, + "action": { + "action": "操作", + "add": "新增", + "edit": "编辑", + "delete": "删除", + "save": "保存", + "import": "导入", + "export": "导出", + "submit": "提交", + "cancel": "取消", + "confirm": "确认", + "reset": "重置", + "search": "搜索" + }, + "tenant": { + "placeholder": "请选择租户", + "success": "切换租户成功" } } diff --git a/apps/web-naive/src/locales/langs/zh-CN/utils.json b/apps/web-naive/src/locales/langs/zh-CN/utils.json new file mode 100644 index 000000000..d26f1f2cf --- /dev/null +++ b/apps/web-naive/src/locales/langs/zh-CN/utils.json @@ -0,0 +1,14 @@ +{ + "rangePicker": { + "today": "今天", + "last7Days": "最近 7 天", + "last30Days": "最近 30 天", + "yesterday": "昨天", + "thisWeek": "本周", + "thisMonth": "本月", + "lastWeek": "上周", + "lastMonth": "上月", + "beginTime": "开始时间", + "endTime": "结束时间" + } +} diff --git a/apps/web-naive/src/preferences.ts b/apps/web-naive/src/preferences.ts index b2e9ace43..73eb135cb 100644 --- a/apps/web-naive/src/preferences.ts +++ b/apps/web-naive/src/preferences.ts @@ -8,6 +8,18 @@ import { defineOverridesPreferences } from '@vben/preferences'; export const overridesPreferences = defineOverridesPreferences({ // overrides app: { + /** 后端路由模式 */ + accessMode: 'backend', name: import.meta.env.VITE_APP_TITLE, + enableRefreshToken: true, + }, + footer: { + /** 默认关闭 footer 页脚,因为有一定遮挡 */ + enable: false, + fixed: false, + }, + copyright: { + companyName: import.meta.env.VITE_APP_TITLE, + companySiteLink: 'https://gitee.com/yudaocode/yudao-ui-admin-vben', }, }); diff --git a/apps/web-naive/src/router/access.ts b/apps/web-naive/src/router/access.ts index 7a80bac09..c4b161a5d 100644 --- a/apps/web-naive/src/router/access.ts +++ b/apps/web-naive/src/router/access.ts @@ -1,20 +1,21 @@ import type { + AppRouteRecordRaw, ComponentRecordType, GenerateMenuAndRoutesOptions, } from '@vben/types'; import { generateAccessible } from '@vben/access'; import { preferences } from '@vben/preferences'; +import { useAccessStore } from '@vben/stores'; +import { convertServerMenuToRouteRecordStringComponent } from '@vben/utils'; -import { message } from '#/adapter/naive'; -import { getAllMenusApi } from '#/api'; import { BasicLayout, IFrameView } from '#/layouts'; -import { $t } from '#/locales'; const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue'); async function generateAccess(options: GenerateMenuAndRoutesOptions) { const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue'); + const accessStore = useAccessStore(); const layoutMap: ComponentRecordType = { BasicLayout, @@ -24,10 +25,10 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) { return await generateAccessible(preferences.app.accessMode, { ...options, fetchMenuListAsync: async () => { - message.loading(`${$t('common.loadingMenu')}...`, { - duration: 1.5, - }); - return await getAllMenusApi(); + // 由于 yudao 通过 accessStore 读取,所以不在进行 message.loading 提示 + // 补充说明:accessStore.accessMenus 一开始是 AppRouteRecordRaw 类型(后端加载),后面被赋值成 MenuRecordRaw 类型(前端转换) + const accessMenus = accessStore.accessMenus as AppRouteRecordRaw[]; + return convertServerMenuToRouteRecordStringComponent(accessMenus); }, // 可以指定没有权限跳转403页面 forbiddenComponent, diff --git a/apps/web-naive/src/router/guard.ts b/apps/web-naive/src/router/guard.ts index 4810f13a9..13a537f44 100644 --- a/apps/web-naive/src/router/guard.ts +++ b/apps/web-naive/src/router/guard.ts @@ -1,12 +1,15 @@ import type { Router } from 'vue-router'; import { LOGIN_PATH } from '@vben/constants'; +import { $t } from '@vben/locales'; import { preferences } from '@vben/preferences'; import { useAccessStore, useUserStore } from '@vben/stores'; import { startProgress, stopProgress } from '@vben/utils'; +import { message } from '#/adapter/naive'; +import { getSimpleDictDataList } from '#/api/system/dict/data'; import { accessRoutes, coreRouteNames } from '#/router/routes'; -import { useAuthStore } from '#/store'; +import { useAuthStore, useDictStore } from '#/store'; import { generateAccess } from './access'; @@ -49,6 +52,7 @@ function setupAccessGuard(router: Router) { const accessStore = useAccessStore(); const userStore = useUserStore(); const authStore = useAuthStore(); + const dictStore = useDictStore(); // 基本路由,这些路由不需要进入权限拦截 if (coreRouteNames.includes(to.name as string)) { @@ -89,10 +93,26 @@ function setupAccessGuard(router: Router) { if (accessStore.isAccessChecked) { return true; } + + // 加载字典数据(不阻塞加载) + dictStore.setDictCacheByApi(getSimpleDictDataList); + // 生成路由表 // 当前登录用户拥有的角色标识列表 - const userInfo = userStore.userInfo || (await authStore.fetchUserInfo()); - const userRoles = userInfo.roles ?? []; + let userInfo = userStore.userInfo; + if (!userInfo) { + // add by 芋艿:由于 yudao 是 fetchUserInfo 统一加载用户 + 权限信息,所以将 fetchMenuListAsync + const loading = message.loading(`${$t('common.loadingMenu')}...`); + try { + const authPermissionInfo = await authStore.fetchUserInfo(); + if (authPermissionInfo) { + userInfo = authPermissionInfo.user; + } + } finally { + loading.destroy(); + } + } + const userRoles = userStore.userRoles ?? []; // 生成菜单和路由 const { accessibleMenus, accessibleRoutes } = await generateAccess({ @@ -106,9 +126,10 @@ function setupAccessGuard(router: Router) { accessStore.setAccessMenus(accessibleMenus); accessStore.setAccessRoutes(accessibleRoutes); accessStore.setIsAccessChecked(true); + userStore.setUserRoles(userRoles); const redirectPath = (from.query.redirect ?? (to.path === preferences.app.defaultHomePath - ? userInfo.homePath || preferences.app.defaultHomePath + ? userInfo?.homePath || preferences.app.defaultHomePath : to.fullPath)) as string; return { diff --git a/apps/web-naive/src/router/index.ts b/apps/web-naive/src/router/index.ts index 484023034..5acec55ea 100644 --- a/apps/web-naive/src/router/index.ts +++ b/apps/web-naive/src/router/index.ts @@ -8,6 +8,7 @@ import { resetStaticRoutes } from '@vben/utils'; import { createRouterGuard } from './guard'; import { routes } from './routes'; +import { setupBaiduTongJi } from './tongji'; /** * @zh_CN 创建vue-router实例 @@ -33,5 +34,7 @@ const resetRoutes = () => resetStaticRoutes(router, routes); // 创建路由守卫 createRouterGuard(router); +// 设置百度统计 +setupBaiduTongJi(router); export { resetRoutes, router }; diff --git a/apps/web-naive/src/router/routes/core.ts b/apps/web-naive/src/router/routes/core.ts index 949b0b65a..22fd0017c 100644 --- a/apps/web-naive/src/router/routes/core.ts +++ b/apps/web-naive/src/router/routes/core.ts @@ -90,6 +90,23 @@ const coreRoutes: RouteRecordRaw[] = [ title: $t('page.auth.register'), }, }, + { + name: 'SocialLogin', + path: 'social-login', + component: () => + import('#/views/_core/authentication/social-login.vue'), + meta: { + title: $t('page.auth.login'), + }, + }, + { + name: 'SSOLogin', + path: 'sso-login', + component: () => import('#/views/_core/authentication/sso-login.vue'), + meta: { + title: $t('page.auth.login'), + }, + }, ], }, ]; diff --git a/apps/web-naive/src/router/routes/index.ts b/apps/web-naive/src/router/routes/index.ts index e6fb14402..738f9d3d9 100644 --- a/apps/web-naive/src/router/routes/index.ts +++ b/apps/web-naive/src/router/routes/index.ts @@ -34,4 +34,14 @@ const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name); /** 有权限校验的路由列表,包含动态路由和静态路由 */ const accessRoutes = [...dynamicRoutes, ...staticRoutes]; -export { accessRoutes, coreRouteNames, routes }; + +// add by 芋艿:from https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/router/routes/index.ts#L38-L45 +const componentKeys: string[] = Object.keys( + import.meta.glob('../../views/**/*.vue'), +) + .filter((item) => !item.includes('/modules/')) + .map((v) => { + const path = v.replace('../../views/', '/'); + return path.endsWith('.vue') ? path.slice(0, -4) : path; + }); +export { accessRoutes, componentKeys, coreRouteNames, routes }; diff --git a/apps/web-naive/src/router/routes/modules/dashboard.ts b/apps/web-naive/src/router/routes/modules/dashboard.ts index 5254dc65d..2cccc8115 100644 --- a/apps/web-naive/src/router/routes/modules/dashboard.ts +++ b/apps/web-naive/src/router/routes/modules/dashboard.ts @@ -12,6 +12,15 @@ const routes: RouteRecordRaw[] = [ name: 'Dashboard', path: '/dashboard', children: [ + { + name: 'Workspace', + path: '/workspace', + component: () => import('#/views/dashboard/workspace/index.vue'), + meta: { + icon: 'carbon:workspace', + title: $t('page.dashboard.workspace'), + }, + }, { name: 'Analytics', path: '/analytics', @@ -22,17 +31,18 @@ const routes: RouteRecordRaw[] = [ title: $t('page.dashboard.analytics'), }, }, - { - name: 'Workspace', - path: '/workspace', - component: () => import('#/views/dashboard/workspace/index.vue'), - meta: { - icon: 'carbon:workspace', - title: $t('page.dashboard.workspace'), - }, - }, ], }, + { + name: 'Profile', + path: '/profile', + component: () => import('#/views/_core/profile/index.vue'), + meta: { + icon: 'ant-design:profile-outlined', + title: $t('ui.widgets.profile'), + hideInMenu: true, + }, + }, ]; export default routes; diff --git a/apps/web-naive/src/router/routes/modules/demos.ts b/apps/web-naive/src/router/routes/modules/demos.ts deleted file mode 100644 index 5e49ffa01..000000000 --- a/apps/web-naive/src/router/routes/modules/demos.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { RouteRecordRaw } from 'vue-router'; - -import { $t } from '#/locales'; - -const routes: RouteRecordRaw[] = [ - { - meta: { - icon: 'ic:baseline-view-in-ar', - keepAlive: true, - order: 1000, - title: $t('demos.title'), - }, - name: 'Demos', - path: '/demos', - children: [ - { - meta: { - title: $t('demos.naive'), - }, - name: 'NaiveDemos', - path: '/demos/naive', - component: () => import('#/views/demos/naive/index.vue'), - }, - { - meta: { - title: $t('demos.table'), - }, - name: 'Table', - path: '/demos/table', - component: () => import('#/views/demos/table/index.vue'), - }, - { - meta: { - title: $t('demos.form'), - }, - name: 'Form', - path: '/demos/form', - component: () => import('#/views/demos/form/basic.vue'), - }, - ], - }, -]; - -export default routes; diff --git a/apps/web-naive/src/router/routes/modules/infra.ts b/apps/web-naive/src/router/routes/modules/infra.ts new file mode 100644 index 000000000..cc6d96def --- /dev/null +++ b/apps/web-naive/src/router/routes/modules/infra.ts @@ -0,0 +1,39 @@ +import type { RouteRecordRaw } from 'vue-router'; + +const routes: RouteRecordRaw[] = [ + { + path: '/infra/job/job-log', + component: () => import('#/views/infra/job/logger/index.vue'), + name: 'InfraJobLog', + meta: { + title: '调度日志', + icon: 'ant-design:history-outlined', + activePath: '/infra/job', + keepAlive: false, + hideInMenu: true, + }, + }, + { + path: '/codegen', + name: 'CodegenEdit', + meta: { + title: '代码生成', + icon: 'ic:baseline-view-in-ar', + keepAlive: true, + hideInMenu: true, + }, + children: [ + { + path: '/codegen/edit', + name: 'InfraCodegenEdit', + component: () => import('#/views/infra/codegen/edit/index.vue'), + meta: { + title: '修改生成配置', + activeMenu: '/infra/codegen', + }, + }, + ], + }, +]; + +export default routes; diff --git a/apps/web-naive/src/router/routes/modules/system.ts b/apps/web-naive/src/router/routes/modules/system.ts new file mode 100644 index 000000000..47e6b1682 --- /dev/null +++ b/apps/web-naive/src/router/routes/modules/system.ts @@ -0,0 +1,16 @@ +import type { RouteRecordRaw } from 'vue-router'; + +const routes: RouteRecordRaw[] = [ + { + path: '/system/notify-message', + component: () => import('#/views/system/notify/my/index.vue'), + name: 'MyNotifyMessage', + meta: { + title: '我的站内信', + icon: 'ant-design:message-filled', + hideInMenu: true, + }, + }, +]; + +export default routes; diff --git a/apps/web-naive/src/router/routes/modules/vben.ts b/apps/web-naive/src/router/routes/modules/vben.ts index 169de855b..96f980d10 100644 --- a/apps/web-naive/src/router/routes/modules/vben.ts +++ b/apps/web-naive/src/router/routes/modules/vben.ts @@ -1,82 +1,81 @@ import type { RouteRecordRaw } from 'vue-router'; -import { - VBEN_ANT_PREVIEW_URL, - VBEN_DOC_URL, - VBEN_ELE_PREVIEW_URL, - VBEN_GITHUB_URL, - VBEN_LOGO_URL, -} from '@vben/constants'; -import { SvgAntdvLogoIcon } from '@vben/icons'; - -import { IFrameView } from '#/layouts'; -import { $t } from '#/locales'; +// import { +// VBEN_DOC_URL, +// VBEN_ELE_PREVIEW_URL, +// VBEN_GITHUB_URL, +// VBEN_LOGO_URL, +// VBEN_NAIVE_PREVIEW_URL, +// } from '@vben/constants'; +// +// import { IFrameView } from '#/layouts'; +// import { $t } from '#/locales'; const routes: RouteRecordRaw[] = [ - { - meta: { - badgeType: 'dot', - icon: VBEN_LOGO_URL, - order: 9998, - title: $t('demos.vben.title'), - }, - name: 'VbenProject', - path: '/vben-admin', - children: [ - { - name: 'VbenDocument', - path: '/vben-admin/document', - component: IFrameView, - meta: { - icon: 'lucide:book-open-text', - link: VBEN_DOC_URL, - title: $t('demos.vben.document'), - }, - }, - { - name: 'VbenGithub', - path: '/vben-admin/github', - component: IFrameView, - meta: { - icon: 'mdi:github', - link: VBEN_GITHUB_URL, - title: 'Github', - }, - }, - { - name: 'VbenAntd', - path: '/vben-admin/antd', - component: IFrameView, - meta: { - badgeType: 'dot', - icon: SvgAntdvLogoIcon, - link: VBEN_ANT_PREVIEW_URL, - title: $t('demos.vben.antdv'), - }, - }, - { - name: 'VbenElementPlus', - path: '/vben-admin/ele', - component: IFrameView, - meta: { - badgeType: 'dot', - icon: 'logos:element', - link: VBEN_ELE_PREVIEW_URL, - title: $t('demos.vben.element-plus'), - }, - }, - ], - }, - { - name: 'VbenAbout', - path: '/vben-admin/about', - component: () => import('#/views/_core/about/index.vue'), - meta: { - icon: 'lucide:copyright', - title: $t('demos.vben.about'), - order: 9999, - }, - }, + // { + // meta: { + // badgeType: 'dot', + // icon: VBEN_LOGO_URL, + // order: 9998, + // title: $t('demos.vben.title'), + // }, + // name: 'VbenProject', + // path: '/vben-admin', + // children: [ + // { + // name: 'VbenDocument', + // path: '/vben-admin/document', + // component: IFrameView, + // meta: { + // icon: 'lucide:book-open-text', + // link: VBEN_DOC_URL, + // title: $t('demos.vben.document'), + // }, + // }, + // { + // name: 'VbenGithub', + // path: '/vben-admin/github', + // component: IFrameView, + // meta: { + // icon: 'mdi:github', + // link: VBEN_GITHUB_URL, + // title: 'Github', + // }, + // }, + // { + // name: 'VbenNaive', + // path: '/vben-admin/naive', + // component: IFrameView, + // meta: { + // badgeType: 'dot', + // icon: 'logos:naiveui', + // link: VBEN_NAIVE_PREVIEW_URL, + // title: $t('demos.vben.naive-ui'), + // }, + // }, + // { + // name: 'VbenElementPlus', + // path: '/vben-admin/ele', + // component: IFrameView, + // meta: { + // badgeType: 'dot', + // icon: 'logos:element', + // link: VBEN_ELE_PREVIEW_URL, + // title: $t('demos.vben.element-plus'), + // }, + // }, + // ], + // }, + // { + // name: 'VbenAbout', + // path: '/vben-admin/about', + // component: () => import('#/views/_core/about/index.vue'), + // meta: { + // icon: 'lucide:copyright', + // title: $t('demos.vben.about'), + // order: 9999, + // }, + // }, ]; -export default routes; +export default routes; // update by 芋艿:不展示 diff --git a/apps/web-naive/src/router/tongji.ts b/apps/web-naive/src/router/tongji.ts new file mode 100644 index 000000000..0aa3715a4 --- /dev/null +++ b/apps/web-naive/src/router/tongji.ts @@ -0,0 +1,30 @@ +import type { Router } from 'vue-router'; + +declare global { + interface Window { + _hmt: any[]; + } +} + +const HM_ID = import.meta.env.VITE_APP_BAIDU_CODE; + +/** + * 设置百度统计 + * @param router + */ +function setupBaiduTongJi(router: Router) { + // 如果没有配置百度统计的 ID,则不进行设置 + if (!HM_ID) { + return; + } + + // _hmt:用于 router push + window._hmt = window._hmt || []; + + router.afterEach((to) => { + // 添加到 _hmt 中 + window._hmt.push(['_trackPageview', to.fullPath]); + }); +} + +export { setupBaiduTongJi }; diff --git a/apps/web-naive/src/store/auth.ts b/apps/web-naive/src/store/auth.ts index 0ff050b3b..abad56210 100644 --- a/apps/web-naive/src/store/auth.ts +++ b/apps/web-naive/src/store/auth.ts @@ -1,4 +1,6 @@ -import type { Recordable, UserInfo } from '@vben/types'; +import type { AuthPermissionInfo, Recordable, UserInfo } from '@vben/types'; + +import type { AuthApi } from '#/api'; import { ref } from 'vue'; import { useRouter } from 'vue-router'; @@ -10,7 +12,14 @@ import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores'; import { defineStore } from 'pinia'; import { notification } from '#/adapter/naive'; -import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api'; +import { + getAuthPermissionInfoApi, + loginApi, + logoutApi, + register, + smsLogin, + socialLogin, +} from '#/api'; import { $t } from '#/locales'; export const useAuthStore = defineStore('auth', () => { @@ -23,9 +32,12 @@ export const useAuthStore = defineStore('auth', () => { /** * 异步处理登录操作 * Asynchronously handle the login process + * @param type 登录类型 * @param params 登录表单数据 + * @param onSuccess 登录成功后的回调函数 */ async function authLogin( + type: 'mobile' | 'register' | 'social' | 'username', params: Recordable, onSuccess?: () => Promise | void, ) { @@ -33,23 +45,30 @@ export const useAuthStore = defineStore('auth', () => { let userInfo: null | UserInfo = null; try { loginLoading.value = true; - const { accessToken } = await loginApi(params); + const { accessToken, refreshToken } = + type === 'mobile' + ? await smsLogin(params as AuthApi.SmsLoginParams) + : type === 'register' + ? await register(params as AuthApi.RegisterParams) + : // eslint-disable-next-line unicorn/no-nested-ternary + type === 'social' + ? await socialLogin(params as AuthApi.SocialLoginParams) + : await loginApi(params); // 如果成功获取到 accessToken if (accessToken) { - // 将 accessToken 存储到 accessStore 中 accessStore.setAccessToken(accessToken); + accessStore.setRefreshToken(refreshToken); - // 获取用户信息并存储到 accessStore 中 - const [fetchUserInfoResult, accessCodes] = await Promise.all([ - fetchUserInfo(), - getAccessCodesApi(), - ]); + // 获取用户信息并存储到 userStore、accessStore 中 + // TODO @芋艿:清理掉 accessCodes 相关的逻辑 + // const [fetchUserInfoResult, accessCodes] = await Promise.all([ + // fetchUserInfo(), + // // getAccessCodesApi(), + // ]); + const fetchUserInfoResult = await fetchUserInfo(); - userInfo = fetchUserInfoResult; - - userStore.setUserInfo(userInfo); - accessStore.setAccessCodes(accessCodes); + userInfo = fetchUserInfoResult.user; if (accessStore.loginExpired) { accessStore.setLoginExpired(false); @@ -61,10 +80,10 @@ export const useAuthStore = defineStore('auth', () => { ); } - if (userInfo?.realName) { + if (userInfo?.nickname) { notification.success({ content: $t('authentication.loginSuccess'), - description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`, + description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.nickname}`, duration: 3000, }); } @@ -80,7 +99,10 @@ export const useAuthStore = defineStore('auth', () => { async function logout(redirect: boolean = true) { try { - await logoutApi(); + const accessToken = accessStore.accessToken as string; + if (accessToken) { + await logoutApi(accessToken); + } } catch { // 不做任何处理 } @@ -99,10 +121,16 @@ export const useAuthStore = defineStore('auth', () => { } async function fetchUserInfo() { - let userInfo: null | UserInfo = null; - userInfo = await getUserInfoApi(); - userStore.setUserInfo(userInfo); - return userInfo; + // 加载 + let authPermissionInfo: AuthPermissionInfo | null = null; + authPermissionInfo = await getAuthPermissionInfoApi(); + // userStore + userStore.setUserInfo(authPermissionInfo.user); + userStore.setUserRoles(authPermissionInfo.roles); + // accessStore + accessStore.setAccessMenus(authPermissionInfo.menus); + accessStore.setAccessCodes(authPermissionInfo.permissions); + return authPermissionInfo; } function $reset() { diff --git a/apps/web-naive/src/store/dict.ts b/apps/web-naive/src/store/dict.ts new file mode 100644 index 000000000..37f636d2e --- /dev/null +++ b/apps/web-naive/src/store/dict.ts @@ -0,0 +1,74 @@ +import { acceptHMRUpdate, defineStore } from 'pinia'; + +export interface DictItem { + colorType?: string; + cssClass?: string; + label: string; + value: string; +} + +export type Dict = Record; + +interface DictState { + dictCache: Dict; +} + +// TODO @芋艿:可以共享么? +export const useDictStore = defineStore('dict', { + actions: { + getDictData(dictType: string, value: any) { + const dict = this.dictCache[dictType]; + if (!dict) { + return undefined; + } + return ( + dict.find((d) => d.value === value || d.value === value.toString()) ?? + undefined + ); + }, + getDictOptions(dictType: string) { + const dictOptions = this.dictCache[dictType]; + if (!dictOptions) { + return []; + } + return dictOptions; + }, + setDictCache(dicts: Dict) { + this.dictCache = dicts; + }, + setDictCacheByApi( + api: (params: Record) => Promise[]>, + params: Record = {}, + labelField: string = 'label', + valueField: string = 'value', + ) { + api(params).then((dicts) => { + const dictCacheData: Dict = {}; + dicts.forEach((dict) => { + dictCacheData[dict.dictType] = dicts + .filter((d) => d.dictType === dict.dictType) + .map((d) => ({ + colorType: d.colorType, + cssClass: d.cssClass, + label: d[labelField], + value: d[valueField], + })); + }); + this.setDictCache(dictCacheData); + }); + }, + }, + persist: { + // 持久化 + pick: ['dictCache'], + }, + state: (): DictState => ({ + dictCache: {}, + }), +}); + +// 解决热更新问题 +const hot = import.meta.hot; +if (hot) { + hot.accept(acceptHMRUpdate(useDictStore, hot)); +} diff --git a/apps/web-naive/src/store/index.ts b/apps/web-naive/src/store/index.ts index 269586ee8..b6a7763b1 100644 --- a/apps/web-naive/src/store/index.ts +++ b/apps/web-naive/src/store/index.ts @@ -1 +1,2 @@ export * from './auth'; +export * from './dict'; diff --git a/apps/web-naive/src/utils/constants.ts b/apps/web-naive/src/utils/constants.ts new file mode 100644 index 000000000..78da9f947 --- /dev/null +++ b/apps/web-naive/src/utils/constants.ts @@ -0,0 +1,636 @@ +// todo @芋艿:要不要共享 +/** + * Created by 芋道源码 + * + * 枚举类 + */ + +// ========== COMMON 模块 ========== +// 全局通用状态枚举 +export const CommonStatusEnum = { + ENABLE: 0, // 开启 + DISABLE: 1, // 禁用 +}; + +// 全局用户类型枚举 +export const UserTypeEnum = { + MEMBER: 1, // 会员 + ADMIN: 2, // 管理员 +}; + +// ========== SYSTEM 模块 ========== +/** + * 菜单的类型枚举 + */ +export const SystemMenuTypeEnum = { + DIR: 1, // 目录 + MENU: 2, // 菜单 + BUTTON: 3, // 按钮 +}; + +/** + * 角色的类型枚举 + */ +export const SystemRoleTypeEnum = { + SYSTEM: 1, // 内置角色 + CUSTOM: 2, // 自定义角色 +}; + +/** + * 数据权限的范围枚举 + */ +export const SystemDataScopeEnum = { + ALL: 1, // 全部数据权限 + DEPT_CUSTOM: 2, // 指定部门数据权限 + DEPT_ONLY: 3, // 部门数据权限 + DEPT_AND_CHILD: 4, // 部门及以下数据权限 + DEPT_SELF: 5, // 仅本人数据权限 +}; + +/** + * 用户的社交平台的类型枚举 + */ +export const SystemUserSocialTypeEnum = { + DINGTALK: { + title: '钉钉', + type: 20, + source: 'dingtalk', + img: 'https://s1.ax1x.com/2022/05/22/OzMDRs.png', + }, + WECHAT_ENTERPRISE: { + title: '企业微信', + type: 30, + source: 'wechat_enterprise', + img: 'https://s1.ax1x.com/2022/05/22/OzMrzn.png', + }, +}; + +// ========== INFRA 模块 ========== +/** + * 代码生成模板类型 + */ +export const InfraCodegenTemplateTypeEnum = { + CRUD: 1, // 基础 CRUD + TREE: 2, // 树形 CRUD + SUB: 15, // 主子表 CRUD +}; + +/** + * 任务状态的枚举 + */ +export const InfraJobStatusEnum = { + INIT: 0, // 初始化中 + NORMAL: 1, // 运行中 + STOP: 2, // 暂停运行 +}; + +/** + * API 异常数据的处理状态 + */ +export const InfraApiErrorLogProcessStatusEnum = { + INIT: 0, // 未处理 + DONE: 1, // 已处理 + IGNORE: 2, // 已忽略 +}; + +// ========== PAY 模块 ========== +/** + * 支付渠道枚举 + */ +export const PayChannelEnum = { + WX_PUB: { + code: 'wx_pub', + name: '微信 JSAPI 支付', + }, + WX_LITE: { + code: 'wx_lite', + name: '微信小程序支付', + }, + WX_APP: { + code: 'wx_app', + name: '微信 APP 支付', + }, + WX_NATIVE: { + code: 'wx_native', + name: '微信 Native 支付', + }, + WX_WAP: { + code: 'wx_wap', + name: '微信 WAP 网站支付', + }, + WX_BAR: { + code: 'wx_bar', + name: '微信条码支付', + }, + ALIPAY_PC: { + code: 'alipay_pc', + name: '支付宝 PC 网站支付', + }, + ALIPAY_WAP: { + code: 'alipay_wap', + name: '支付宝 WAP 网站支付', + }, + ALIPAY_APP: { + code: 'alipay_app', + name: '支付宝 APP 支付', + }, + ALIPAY_QR: { + code: 'alipay_qr', + name: '支付宝扫码支付', + }, + ALIPAY_BAR: { + code: 'alipay_bar', + name: '支付宝条码支付', + }, + WALLET: { + code: 'wallet', + name: '钱包支付', + }, + MOCK: { + code: 'mock', + name: '模拟支付', + }, +}; + +/** + * 支付的展示模式每局 + */ +export const PayDisplayModeEnum = { + URL: { + mode: 'url', + }, + IFRAME: { + mode: 'iframe', + }, + FORM: { + mode: 'form', + }, + QR_CODE: { + mode: 'qr_code', + }, + APP: { + mode: 'app', + }, +}; + +/** + * 支付类型枚举 + */ +export const PayType = { + WECHAT: 'WECHAT', + ALIPAY: 'ALIPAY', + MOCK: 'MOCK', +}; + +/** + * 支付订单状态枚举 + */ +export const PayOrderStatusEnum = { + WAITING: { + status: 0, + name: '未支付', + }, + SUCCESS: { + status: 10, + name: '已支付', + }, + CLOSED: { + status: 20, + name: '未支付', + }, +}; + +// ========== MALL - 商品模块 ========== +/** + * 商品 SPU 状态 + */ +export const ProductSpuStatusEnum = { + RECYCLE: { + status: -1, + name: '回收站', + }, + DISABLE: { + status: 0, + name: '下架', + }, + ENABLE: { + status: 1, + name: '上架', + }, +}; + +// ========== MALL - 营销模块 ========== +/** + * 优惠劵模板的有限期类型的枚举 + */ +export const CouponTemplateValidityTypeEnum = { + DATE: { + type: 1, + name: '固定日期可用', + }, + TERM: { + type: 2, + name: '领取之后可用', + }, +}; + +/** + * 优惠劵模板的领取方式的枚举 + */ +export const CouponTemplateTakeTypeEnum = { + USER: { + type: 1, + name: '直接领取', + }, + ADMIN: { + type: 2, + name: '指定发放', + }, + REGISTER: { + type: 3, + name: '新人券', + }, +}; + +/** + * 营销的商品范围枚举 + */ +export const PromotionProductScopeEnum = { + ALL: { + scope: 1, + name: '通用劵', + }, + SPU: { + scope: 2, + name: '商品劵', + }, + CATEGORY: { + scope: 3, + name: '品类劵', + }, +}; + +/** + * 营销的条件类型枚举 + */ +export const PromotionConditionTypeEnum = { + PRICE: { + type: 10, + name: '满 N 元', + }, + COUNT: { + type: 20, + name: '满 N 件', + }, +}; + +/** + * 优惠类型枚举 + */ +export const PromotionDiscountTypeEnum = { + PRICE: { + type: 1, + name: '满减', + }, + PERCENT: { + type: 2, + name: '折扣', + }, +}; + +// ========== MALL - 交易模块 ========== +/** + * 分销关系绑定模式枚举 + */ +export const BrokerageBindModeEnum = { + ANYTIME: { + mode: 1, + name: '首次绑定', + }, + REGISTER: { + mode: 2, + name: '注册绑定', + }, + OVERRIDE: { + mode: 3, + name: '覆盖绑定', + }, +}; +/** + * 分佣模式枚举 + */ +export const BrokerageEnabledConditionEnum = { + ALL: { + condition: 1, + name: '人人分销', + }, + ADMIN: { + condition: 2, + name: '指定分销', + }, +}; +/** + * 佣金记录业务类型枚举 + */ +export const BrokerageRecordBizTypeEnum = { + ORDER: { + type: 1, + name: '获得推广佣金', + }, + WITHDRAW: { + type: 2, + name: '提现申请', + }, +}; +/** + * 佣金提现状态枚举 + */ +export const BrokerageWithdrawStatusEnum = { + AUDITING: { + status: 0, + name: '审核中', + }, + AUDIT_SUCCESS: { + status: 10, + name: '审核通过', + }, + AUDIT_FAIL: { + status: 20, + name: '审核不通过', + }, + WITHDRAW_SUCCESS: { + status: 11, + name: '提现成功', + }, + WITHDRAW_FAIL: { + status: 21, + name: '提现失败', + }, +}; +/** + * 佣金提现类型枚举 + */ +export const BrokerageWithdrawTypeEnum = { + WALLET: { + type: 1, + name: '钱包', + }, + BANK: { + type: 2, + name: '银行卡', + }, + WECHAT: { + type: 3, + name: '微信', + }, + ALIPAY: { + type: 4, + name: '支付宝', + }, +}; + +/** + * 配送方式枚举 + */ +export const DeliveryTypeEnum = { + EXPRESS: { + type: 1, + name: '快递发货', + }, + PICK_UP: { + type: 2, + name: '到店自提', + }, +}; +/** + * 交易订单 - 状态 + */ +export const TradeOrderStatusEnum = { + UNPAID: { + status: 0, + name: '待支付', + }, + UNDELIVERED: { + status: 10, + name: '待发货', + }, + DELIVERED: { + status: 20, + name: '已发货', + }, + COMPLETED: { + status: 30, + name: '已完成', + }, + CANCELED: { + status: 40, + name: '已取消', + }, +}; + +// ========== ERP - 企业资源计划 ========== + +export const ErpBizType = { + PURCHASE_ORDER: 10, + PURCHASE_IN: 11, + PURCHASE_RETURN: 12, + SALE_ORDER: 20, + SALE_OUT: 21, + SALE_RETURN: 22, +}; + +// ========== BPM 模块 ========== + +export const BpmModelType = { + BPMN: 10, // BPMN 设计器 + SIMPLE: 20, // 简易设计器 +}; + +export const BpmModelFormType = { + NORMAL: 10, // 流程表单 + CUSTOM: 20, // 业务表单 +}; + +export const BpmProcessInstanceStatus = { + NOT_START: -1, // 未开始 + RUNNING: 1, // 审批中 + APPROVE: 2, // 审批通过 + REJECT: 3, // 审批不通过 + CANCEL: 4, // 已取消 +}; + +export const BpmAutoApproveType = { + NONE: 0, // 不自动通过 + APPROVE_ALL: 1, // 仅审批一次,后续重复的审批节点均自动通过 + APPROVE_SEQUENT: 2, // 仅针对连续审批的节点自动通过 +}; + +// 候选人策略枚举 ( 用于审批节点。抄送节点 ) +export enum CandidateStrategyEnum { + /** + * 审批人自选 + */ + APPROVE_USER_SELECT = 34, + /** + * 部门的负责人 + */ + DEPT_LEADER = 21, + /** + * 部门成员 + */ + DEPT_MEMBER = 20, + /** + * 流程表达式 + */ + EXPRESSION = 60, + /** + * 表单内部门负责人 + */ + FORM_DEPT_LEADER = 51, + /** + * 表单内用户字段 + */ + FORM_USER = 50, + /** + * 连续多级部门的负责人 + */ + MULTI_LEVEL_DEPT_LEADER = 23, + /** + * 指定岗位 + */ + POST = 22, + /** + * 指定角色 + */ + ROLE = 10, + /** + * 发起人自己 + */ + START_USER = 36, + /** + * 发起人部门负责人 + */ + START_USER_DEPT_LEADER = 37, + /** + * 发起人连续多级部门的负责人 + */ + START_USER_MULTI_LEVEL_DEPT_LEADER = 38, + /** + * 发起人自选 + */ + START_USER_SELECT = 35, + /** + * 指定用户 + */ + USER = 30, + /** + * 指定用户组 + */ + USER_GROUP = 40, +} + +/** + * 节点类型 + */ +export enum NodeTypeEnum { + /** + * 子流程节点 + */ + CHILD_PROCESS_NODE = 20, + /** + * 条件分支节点 (对应排他网关) + */ + CONDITION_BRANCH_NODE = 51, + /** + * 条件节点 + */ + CONDITION_NODE = 50, + + /** + * 抄送人节点 + */ + COPY_TASK_NODE = 12, + + /** + * 延迟器节点 + */ + DELAY_TIMER_NODE = 14, + + /** + * 结束节点 + */ + END_EVENT_NODE = 1, + + /** + * 包容分支节点 (对应包容网关) + */ + INCLUSIVE_BRANCH_NODE = 53, + + /** + * 并行分支节点 (对应并行网关) + */ + PARALLEL_BRANCH_NODE = 52, + + /** + * 路由分支节点 + */ + ROUTER_BRANCH_NODE = 54, + /** + * 发起人节点 + */ + START_USER_NODE = 10, + /** + * 办理人节点 + */ + TRANSACTOR_NODE = 13, + + /** + * 触发器节点 + */ + TRIGGER_NODE = 15, + /** + * 审批人节点 + */ + USER_TASK_NODE = 11, +} + +/** + * 任务状态枚举 + */ +export enum TaskStatusEnum { + /** + * 审批通过 + */ + APPROVE = 2, + + /** + * 审批通过中 + */ + APPROVING = 7, + /** + * 已取消 + */ + CANCEL = 4, + /** + * 未开始 + */ + NOT_START = -1, + + /** + * 审批不通过 + */ + REJECT = 3, + + /** + * 已退回 + */ + RETURN = 5, + /** + * 审批中 + */ + RUNNING = 1, + /** + * 待审批 + */ + WAIT = 0, +} diff --git a/apps/web-naive/src/utils/dict.ts b/apps/web-naive/src/utils/dict.ts new file mode 100644 index 000000000..5aaf456e5 --- /dev/null +++ b/apps/web-naive/src/utils/dict.ts @@ -0,0 +1,279 @@ +import type { SelectOption } from 'naive-ui/es/select'; +// TODO @芋艿:后续再优化 +// TODO @芋艿:可以共享么? + +import { isObject } from '@vben/utils'; + +import { useDictStore } from '#/store'; + +// TODO @dhb52:top-level 调用 导致:"getActivePinia()" was called but there was no active Pinia +// 先临时移入到方法中 +// const dictStore = useDictStore(); + +// TODO @dhb: antd 组件的 color 类型 +type ColorType = 'error' | 'info' | 'success' | 'warning'; + +export interface DictDataType { + dictType: string; + label: string; + value: boolean | number | string; + colorType: ColorType; + cssClass: string; +} + +export interface NumberDictDataType extends DictDataType { + value: number; +} + +export interface StringDictDataType extends DictDataType { + value: string; +} + +/** + * 获取字典标签 + * + * @param dictType 字典类型 + * @param value 字典值 + * @returns 字典标签 + */ +function getDictLabel(dictType: string, value: any) { + const dictStore = useDictStore(); + const dictObj = dictStore.getDictData(dictType, value); + return isObject(dictObj) ? dictObj.label : ''; +} + +/** + * 获取字典对象 + * + * @param dictType 字典类型 + * @param value 字典值 + * @returns 字典对象 + */ +function getDictObj(dictType: string, value: any) { + const dictStore = useDictStore(); + const dictObj = dictStore.getDictData(dictType, value); + return isObject(dictObj) ? dictObj : null; +} + +/** + * 获取字典数组 用于select radio 等 + * + * @param dictType 字典类型 + * @param valueType 字典值类型,默认 string 类型 + * @returns 字典数组 + */ +// TODO @puhui999:貌似可以定义一个类型?不使用 any[] +function getDictOptions( + dictType: string, + valueType: 'boolean' | 'number' | 'string' = 'string', +): any[] { + const dictStore = useDictStore(); + const dictOpts = dictStore.getDictOptions(dictType); + const dictOptions: SelectOption[] = []; + if (dictOpts.length > 0) { + let dictValue: boolean | number | string = ''; + dictOpts.forEach((d) => { + switch (valueType) { + case 'boolean': { + dictValue = `${d.value}` === 'true'; + break; + } + case 'number': { + dictValue = Number.parseInt(`${d.value}`); + break; + } + case 'string': { + dictValue = `${d.value}`; + break; + } + // No default + } + dictOptions.push({ + value: dictValue, + label: d.label, + }); + }); + } + return dictOptions.length > 0 ? dictOptions : []; +} + +// TODO @dhb52:下面的一系列方法,看看能不能复用 getDictOptions 方法 +export const getIntDictOptions = (dictType: string): NumberDictDataType[] => { + // 获得通用的 DictDataType 列表 + const dictOptions = getDictOptions(dictType) as DictDataType[]; + // 转换成 number 类型的 NumberDictDataType 类型 + // why 需要特殊转换:避免 IDEA 在 v-for="dict in getIntDictOptions(...)" 时,el-option 的 key 会告警 + const dictOption: NumberDictDataType[] = []; + dictOptions.forEach((dict: DictDataType) => { + dictOption.push({ + ...dict, + value: Number.parseInt(`${dict.value}`), + }); + }); + return dictOption; +}; + +// TODO @dhb52:下面的一系列方法,看看能不能复用 getDictOptions 方法 +export const getStrDictOptions = (dictType: string) => { + // 获得通用的 DictDataType 列表 + const dictOptions = getDictOptions(dictType) as DictDataType[]; + // 转换成 string 类型的 StringDictDataType 类型 + // why 需要特殊转换:避免 IDEA 在 v-for="dict in getStrDictOptions(...)" 时,el-option 的 key 会告警 + const dictOption: StringDictDataType[] = []; + dictOptions.forEach((dict: DictDataType) => { + dictOption.push({ + ...dict, + value: `${dict.value}`, + }); + }); + return dictOption; +}; + +// TODO @dhb52:下面的一系列方法,看看能不能复用 getDictOptions 方法 +export const getBoolDictOptions = (dictType: string) => { + const dictOption: DictDataType[] = []; + const dictOptions = getDictOptions(dictType) as DictDataType[]; + dictOptions.forEach((dict: DictDataType) => { + dictOption.push({ + ...dict, + value: `${dict.value}` === 'true', + }); + }); + return dictOption; +}; + +enum DICT_TYPE { + AI_GENERATE_MODE = 'ai_generate_mode', // AI 生成模式 + AI_IMAGE_STATUS = 'ai_image_status', // AI 图片状态 + AI_MUSIC_STATUS = 'ai_music_status', // AI 音乐状态 + // ========== AI - 人工智能模块 ========== + AI_PLATFORM = 'ai_platform', // AI 平台 + + AI_WRITE_FORMAT = 'ai_write_format', // AI 写作格式 + AI_WRITE_LANGUAGE = 'ai_write_language', // AI 写作语言 + AI_WRITE_LENGTH = 'ai_write_length', // AI 写作长度 + AI_WRITE_TONE = 'ai_write_tone', // AI 写作语气 + AI_WRITE_TYPE = 'ai_write_type', // AI 写作类型 + BPM_MODEL_FORM_TYPE = 'bpm_model_form_type', + // ========== BPM 模块 ========== + BPM_MODEL_TYPE = 'bpm_model_type', + BPM_OA_LEAVE_TYPE = 'bpm_oa_leave_type', + BPM_PROCESS_INSTANCE_STATUS = 'bpm_process_instance_status', + BPM_PROCESS_LISTENER_TYPE = 'bpm_process_listener_type', + BPM_PROCESS_LISTENER_VALUE_TYPE = 'bpm_process_listener_value_type', + BPM_TASK_CANDIDATE_STRATEGY = 'bpm_task_candidate_strategy', + BPM_TASK_STATUS = 'bpm_task_status', + BROKERAGE_BANK_NAME = 'brokerage_bank_name', // 佣金提现银行 + BROKERAGE_BIND_MODE = 'brokerage_bind_mode', // 分销关系绑定模式 + + BROKERAGE_ENABLED_CONDITION = 'brokerage_enabled_condition', // 分佣模式 + BROKERAGE_RECORD_BIZ_TYPE = 'brokerage_record_biz_type', // 佣金业务类型 + BROKERAGE_RECORD_STATUS = 'brokerage_record_status', // 佣金状态 + BROKERAGE_WITHDRAW_STATUS = 'brokerage_withdraw_status', // 佣金提现状态 + BROKERAGE_WITHDRAW_TYPE = 'brokerage_withdraw_type', // 佣金提现类型 + COMMON_STATUS = 'common_status', + // ========== CRM - 客户管理模块 ========== + CRM_AUDIT_STATUS = 'crm_audit_status', // CRM 审批状态 + CRM_BIZ_TYPE = 'crm_biz_type', // CRM 业务类型 + CRM_BUSINESS_END_STATUS_TYPE = 'crm_business_end_status_type', // CRM 商机结束状态类型 + CRM_CUSTOMER_INDUSTRY = 'crm_customer_industry', // CRM 客户所属行业 + + CRM_CUSTOMER_LEVEL = 'crm_customer_level', // CRM 客户级别 + CRM_CUSTOMER_SOURCE = 'crm_customer_source', // CRM 客户来源 + CRM_FOLLOW_UP_TYPE = 'crm_follow_up_type', // CRM 跟进方式 + CRM_PERMISSION_LEVEL = 'crm_permission_level', // CRM 数据权限的级别 + CRM_PRODUCT_STATUS = 'crm_product_status', // CRM 商品状态 + CRM_PRODUCT_UNIT = 'crm_product_unit', // CRM 产品单位 + CRM_RECEIVABLE_RETURN_TYPE = 'crm_receivable_return_type', // CRM 回款的还款方式 + DATE_INTERVAL = 'date_interval', // 数据间隔 + + // ========== ERP - 企业资源计划模块 ========== + ERP_AUDIT_STATUS = 'erp_audit_status', // ERP 审批状态 + ERP_STOCK_RECORD_BIZ_TYPE = 'erp_stock_record_biz_type', // 库存明细的业务类型 + // ========== MALL - 交易模块 ========== + EXPRESS_CHARGE_MODE = 'trade_delivery_express_charge_mode', // 快递的计费方式 + INFRA_API_ERROR_LOG_PROCESS_STATUS = 'infra_api_error_log_process_status', + // ========== INFRA 模块 ========== + INFRA_BOOLEAN_STRING = 'infra_boolean_string', + INFRA_CODEGEN_FRONT_TYPE = 'infra_codegen_front_type', + INFRA_CODEGEN_SCENE = 'infra_codegen_scene', + + INFRA_CODEGEN_TEMPLATE_TYPE = 'infra_codegen_template_type', + INFRA_CONFIG_TYPE = 'infra_config_type', + + INFRA_FILE_STORAGE = 'infra_file_storage', + INFRA_JOB_LOG_STATUS = 'infra_job_log_status', + + INFRA_JOB_STATUS = 'infra_job_status', + + INFRA_OPERATE_TYPE = 'infra_operate_type', + IOT_DATA_FORMAT = 'iot_data_format', // IOT 数据格式 + IOT_DATA_TYPE = 'iot_data_type', // IOT 数据类型 + IOT_DEVICE_STATUS = 'iot_device_status', // IOT 设备状态 + // ========== IOT - 物联网模块 ========== + IOT_NET_TYPE = 'iot_net_type', // IOT 联网方式 + IOT_PRODUCT_DEVICE_TYPE = 'iot_product_device_type', // IOT 产品设备类型 + IOT_PRODUCT_FUNCTION_TYPE = 'iot_product_function_type', // IOT 产品功能类型 + IOT_PRODUCT_STATUS = 'iot_product_status', // IOT 产品状态 + IOT_PROTOCOL_TYPE = 'iot_protocol_type', // IOT 接入网关协议 + IOT_RW_TYPE = 'iot_rw_type', // IOT 读写类型 + IOT_UNIT_TYPE = 'iot_unit_type', // IOT 单位类型 + IOT_VALIDATE_TYPE = 'iot_validate_type', // IOT 数据校验级别 + MEMBER_EXPERIENCE_BIZ_TYPE = 'member_experience_biz_type', // 会员经验业务类型 + // ========== Member 会员模块 ========== + MEMBER_POINT_BIZ_TYPE = 'member_point_biz_type', // 积分的业务类型 + // ========== MP 模块 ========== + MP_AUTO_REPLY_REQUEST_MATCH = 'mp_auto_reply_request_match', // 自动回复请求匹配类型 + + MP_MESSAGE_TYPE = 'mp_message_type', // 消息类型 + // ========== PAY 模块 ========== + PAY_CHANNEL_CODE = 'pay_channel_code', // 支付渠道编码类型 + PAY_NOTIFY_STATUS = 'pay_notify_status', // 商户支付回调状态 + PAY_NOTIFY_TYPE = 'pay_notify_type', // 商户支付回调状态 + PAY_ORDER_STATUS = 'pay_order_status', // 商户支付订单状态 + PAY_REFUND_STATUS = 'pay_refund_status', // 退款订单状态 + PAY_TRANSFER_STATUS = 'pay_transfer_status', // 转账订单状态 + PAY_TRANSFER_TYPE = 'pay_transfer_type', // 转账订单状态 + // ========== MALL - 商品模块 ========== + PRODUCT_SPU_STATUS = 'product_spu_status', // 商品状态 + + PROMOTION_BANNER_POSITION = 'promotion_banner_position', // banner 定位 + PROMOTION_BARGAIN_RECORD_STATUS = 'promotion_bargain_record_status', // 砍价记录的状态 + PROMOTION_COMBINATION_RECORD_STATUS = 'promotion_combination_record_status', // 拼团记录的状态 + PROMOTION_CONDITION_TYPE = 'promotion_condition_type', // 营销的条件类型枚举 + PROMOTION_COUPON_STATUS = 'promotion_coupon_status', // 优惠劵的状态 + PROMOTION_COUPON_TAKE_TYPE = 'promotion_coupon_take_type', // 优惠劵的领取方式 + PROMOTION_COUPON_TEMPLATE_VALIDITY_TYPE = 'promotion_coupon_template_validity_type', // 优惠劵模板的有限期类型 + // ========== MALL - 营销模块 ========== + PROMOTION_DISCOUNT_TYPE = 'promotion_discount_type', // 优惠类型 + PROMOTION_PRODUCT_SCOPE = 'promotion_product_scope', // 营销的商品范围 + SYSTEM_DATA_SCOPE = 'system_data_scope', + SYSTEM_LOGIN_RESULT = 'system_login_result', + + SYSTEM_LOGIN_TYPE = 'system_login_type', + SYSTEM_MAIL_SEND_STATUS = 'system_mail_send_status', + + SYSTEM_MENU_TYPE = 'system_menu_type', + SYSTEM_NOTICE_TYPE = 'system_notice_type', + SYSTEM_NOTIFY_TEMPLATE_TYPE = 'system_notify_template_type', + SYSTEM_OAUTH2_GRANT_TYPE = 'system_oauth2_grant_type', + SYSTEM_ROLE_TYPE = 'system_role_type', + SYSTEM_SMS_CHANNEL_CODE = 'system_sms_channel_code', + SYSTEM_SMS_RECEIVE_STATUS = 'system_sms_receive_status', + SYSTEM_SMS_SEND_STATUS = 'system_sms_send_status', + SYSTEM_SMS_TEMPLATE_TYPE = 'system_sms_template_type', + + SYSTEM_SOCIAL_TYPE = 'system_social_type', + // ========== SYSTEM 模块 ========== + SYSTEM_USER_SEX = 'system_user_sex', + TERMINAL = 'terminal', // 终端 + TRADE_AFTER_SALE_STATUS = 'trade_after_sale_status', // 售后 - 状态 + TRADE_AFTER_SALE_TYPE = 'trade_after_sale_type', // 售后 - 类型 + TRADE_AFTER_SALE_WAY = 'trade_after_sale_way', // 售后 - 方式 + TRADE_DELIVERY_TYPE = 'trade_delivery_type', // 配送方式 + TRADE_ORDER_ITEM_AFTER_SALE_STATUS = 'trade_order_item_after_sale_status', // 订单项 - 售后状态 + TRADE_ORDER_STATUS = 'trade_order_status', // 订单 - 状态 + TRADE_ORDER_TYPE = 'trade_order_type', // 订单 - 类型 + USER_TYPE = 'user_type', +} +export { DICT_TYPE, getDictLabel, getDictObj, getDictOptions }; diff --git a/apps/web-naive/src/utils/formCreate.ts b/apps/web-naive/src/utils/formCreate.ts new file mode 100644 index 000000000..d7f944c0f --- /dev/null +++ b/apps/web-naive/src/utils/formCreate.ts @@ -0,0 +1,64 @@ +/** + * 针对 https://github.com/xaboy/form-create-designer 封装的工具类 + */ + +import { isRef } from 'vue'; + +// 编码表单 Conf +export const encodeConf = (designerRef: object) => { + // @ts-ignore designerRef.value is dynamically added by form-create-designer + return JSON.stringify(designerRef.value.getOption()); +}; + +// 编码表单 Fields +export const encodeFields = (designerRef: object) => { + // @ts-ignore designerRef.value is dynamically added by form-create-designer + const rule = JSON.parse(designerRef.value.getJson()); + const fields: string[] = []; + rule.forEach((item: unknown) => { + fields.push(JSON.stringify(item)); + }); + return fields; +}; + +// 解码表单 Fields +export const decodeFields = (fields: string[]) => { + const rule: object[] = []; + fields.forEach((item) => { + rule.push(JSON.parse(item)); + }); + return rule; +}; + +// 设置表单的 Conf 和 Fields,适用 FcDesigner 场景 +export const setConfAndFields = ( + designerRef: object, + conf: string, + fields: string, +) => { + // @ts-ignore designerRef.value is dynamically added by form-create-designer + designerRef.value.setOption(JSON.parse(conf)); + // @ts-ignore designerRef.value is dynamically added by form-create-designer + designerRef.value.setRule(decodeFields(fields)); +}; + +// 设置表单的 Conf 和 Fields,适用 form-create 场景 +export const setConfAndFields2 = ( + detailPreview: object, + conf: string, + fields: string[], + value?: object, +) => { + if (isRef(detailPreview)) { + // @ts-ignore detailPreview.value is dynamically added by form-create-designer + detailPreview = detailPreview.value; + } + // @ts-ignore detailPreview properties are dynamically added by form-create-designer + detailPreview.option = JSON.parse(conf); + // @ts-ignore detailPreview properties are dynamically added by form-create-designer + detailPreview.rule = decodeFields(fields); + if (value) { + // @ts-ignore detailPreview properties are dynamically added by form-create-designer + detailPreview.value = value; + } +}; diff --git a/apps/web-naive/src/utils/formatTime.ts b/apps/web-naive/src/utils/formatTime.ts new file mode 100644 index 000000000..3d4e47228 --- /dev/null +++ b/apps/web-naive/src/utils/formatTime.ts @@ -0,0 +1,31 @@ +/** + * 将毫秒,转换成时间字符串。例如说,xx 分钟 + * + * @param ms 毫秒 + * @returns {string} 字符串 + */ +export function formatPast2(ms: number): string { + // 定义时间单位常量,便于维护 + const SECOND = 1000; + const MINUTE = 60 * SECOND; + const HOUR = 60 * MINUTE; + const DAY = 24 * HOUR; + + // 计算各时间单位 + const day = Math.floor(ms / DAY); + const hour = Math.floor((ms % DAY) / HOUR); + const minute = Math.floor((ms % HOUR) / MINUTE); + const second = Math.floor((ms % MINUTE) / SECOND); + + // 根据时间长短返回不同格式 + if (day > 0) { + return `${day} 天${hour} 小时 ${minute} 分钟`; + } + if (hour > 0) { + return `${hour} 小时 ${minute} 分钟`; + } + if (minute > 0) { + return `${minute} 分钟`; + } + return second > 0 ? `${second} 秒` : `${0} 秒`; +} diff --git a/apps/web-naive/src/utils/index.ts b/apps/web-naive/src/utils/index.ts new file mode 100644 index 000000000..022e6441d --- /dev/null +++ b/apps/web-naive/src/utils/index.ts @@ -0,0 +1,7 @@ +export * from './constants'; +export * from './dict'; +export * from './formatTime'; +export * from './formCreate'; +export * from './rangePickerProps'; +export * from './routerHelper'; +export * from './validator'; diff --git a/apps/web-naive/src/utils/rangePickerProps.ts b/apps/web-naive/src/utils/rangePickerProps.ts new file mode 100644 index 000000000..245e3d81c --- /dev/null +++ b/apps/web-naive/src/utils/rangePickerProps.ts @@ -0,0 +1,59 @@ +import type { Dayjs } from 'dayjs'; + +import dayjs from 'dayjs'; + +import { $t } from '#/locales'; + +/** 时间段选择器拓展 */ +export function getRangePickerDefaultProps() { + return { + format: 'YYYY-MM-DD HH:mm:ss', + placeholder: [ + $t('utils.rangePicker.beginTime'), + $t('utils.rangePicker.endTime'), + ], + ranges: { + [$t('utils.rangePicker.today')]: () => + [dayjs().startOf('day'), dayjs().endOf('day')] as [Dayjs, Dayjs], + [$t('utils.rangePicker.last7Days')]: () => + [dayjs().subtract(7, 'day').startOf('day'), dayjs().endOf('day')] as [ + Dayjs, + Dayjs, + ], + [$t('utils.rangePicker.last30Days')]: () => + [dayjs().subtract(30, 'day').startOf('day'), dayjs().endOf('day')] as [ + Dayjs, + Dayjs, + ], + [$t('utils.rangePicker.yesterday')]: () => + [ + dayjs().subtract(1, 'day').startOf('day'), + dayjs().subtract(1, 'day').endOf('day'), + ] as [Dayjs, Dayjs], + [$t('utils.rangePicker.thisWeek')]: () => + [dayjs().startOf('week'), dayjs().endOf('day')] as [Dayjs, Dayjs], + [$t('utils.rangePicker.thisMonth')]: () => + [dayjs().startOf('month'), dayjs().endOf('day')] as [Dayjs, Dayjs], + [$t('utils.rangePicker.lastWeek')]: () => + [dayjs().subtract(1, 'week').startOf('day'), dayjs().endOf('day')] as [ + Dayjs, + Dayjs, + ], + }, + showTime: { + defaultValue: [ + dayjs('00:00:00', 'HH:mm:ss'), + dayjs('23:59:59', 'HH:mm:ss'), + ], + format: 'HH:mm:ss', + }, + transformDateFunc: (dates: any) => { + if (dates && dates.length === 2) { + // 格式化为后台支持的时间格式 + return [dates.createTime[0], dates.createTime[1]].join(','); + } + return {}; + }, + valueFormat: 'YYYY-MM-DD HH:mm:ss', + }; +} diff --git a/apps/web-naive/src/utils/routerHelper.ts b/apps/web-naive/src/utils/routerHelper.ts new file mode 100644 index 000000000..d4c7ac5d0 --- /dev/null +++ b/apps/web-naive/src/utils/routerHelper.ts @@ -0,0 +1,15 @@ +import { defineAsyncComponent } from 'vue'; + +const modules = import.meta.glob('../views/**/*.{vue,tsx}'); +/** + * 注册一个异步组件 + * @param componentPath 例:/bpm/oa/leave/detail + */ +export const registerComponent = (componentPath: string) => { + for (const item in modules) { + if (item.includes(componentPath)) { + // 使用异步组件的方式来动态加载组件 + return defineAsyncComponent(modules[item] as any); + } + } +}; diff --git a/apps/web-naive/src/utils/validator.ts b/apps/web-naive/src/utils/validator.ts new file mode 100644 index 000000000..3ae62f787 --- /dev/null +++ b/apps/web-naive/src/utils/validator.ts @@ -0,0 +1,17 @@ +// 参数校验,对标 Hutool 的 Validator 工具类 + +/** 手机号正则表达式(中国) */ +const MOBILE_REGEX = /(?:0|86|\+86)?1[3-9]\d{9}/; + +/** + * 验证是否为手机号码(中国) + * + * @param value 值 + * @returns 是否为手机号码(中国) + */ +export function isMobile(value?: null | string): boolean { + if (!value) { + return false; + } + return MOBILE_REGEX.test(value); +} diff --git a/apps/web-naive/src/views/_core/authentication/code-login.vue b/apps/web-naive/src/views/_core/authentication/code-login.vue index acfd1fd78..b8c55d84e 100644 --- a/apps/web-naive/src/views/_core/authentication/code-login.vue +++ b/apps/web-naive/src/views/_core/authentication/code-login.vue @@ -2,24 +2,100 @@ import type { VbenFormSchema } from '@vben/common-ui'; import type { Recordable } from '@vben/types'; -import { computed, ref } from 'vue'; +import type { AuthApi } from '#/api'; + +import { computed, onMounted, ref } from 'vue'; import { AuthenticationCodeLogin, z } from '@vben/common-ui'; +import { isTenantEnable } from '@vben/hooks'; import { $t } from '@vben/locales'; +import { useAccessStore } from '@vben/stores'; + +import { message } from '#/adapter/naive'; +import { sendSmsCode } from '#/api'; +import { getTenantByWebsite, getTenantSimpleList } from '#/api/core/auth'; +import { useAuthStore } from '#/store'; defineOptions({ name: 'CodeLogin' }); +const authStore = useAuthStore(); +const accessStore = useAccessStore(); +const tenantEnable = isTenantEnable(); + const loading = ref(false); -const CODE_LENGTH = 6; +const CODE_LENGTH = 4; + +const loginRef = ref(); + +/** 获取租户列表,并默认选中 */ +const tenantList = ref([]); // 租户列表 +async function fetchTenantList() { + if (!tenantEnable) { + return; + } + try { + // 获取租户列表、域名对应租户 + const websiteTenantPromise = getTenantByWebsite(window.location.hostname); + tenantList.value = await getTenantSimpleList(); + + // 选中租户:域名 > store 中的租户 > 首个租户 + let tenantId: null | number = null; + const websiteTenant = await websiteTenantPromise; + if (websiteTenant?.id) { + tenantId = websiteTenant.id; + } + // 如果没有从域名获取到租户,尝试从 store 中获取 + if (!tenantId && accessStore.tenantId) { + tenantId = accessStore.tenantId; + } + // 如果还是没有租户,使用列表中的第一个 + if (!tenantId && tenantList.value?.[0]?.id) { + tenantId = tenantList.value[0].id; + } + + // 设置选中的租户编号 + accessStore.setTenantId(tenantId); + loginRef.value.getFormApi().setFieldValue('tenantId', tenantId?.toString()); + } catch (error) { + console.error('获取租户列表失败:', error); + } +} + +/** 组件挂载时获取租户信息 */ +onMounted(() => { + fetchTenantList(); +}); const formSchema = computed((): VbenFormSchema[] => { return [ + { + component: 'VbenSelect', + componentProps: { + options: tenantList.value.map((item) => ({ + label: item.name, + value: item.id.toString(), + })), + placeholder: $t('authentication.tenantTip'), + }, + fieldName: 'tenantId', + label: $t('authentication.tenant'), + rules: z.string().min(1, { message: $t('authentication.tenantTip') }), + dependencies: { + triggerFields: ['tenantId'], + if: tenantEnable, + trigger(values) { + if (values.tenantId) { + accessStore.setTenantId(Number(values.tenantId)); + } + }, + }, + }, { component: 'VbenInput', componentProps: { placeholder: $t('authentication.mobile'), }, - fieldName: 'phoneNumber', + fieldName: 'mobile', label: $t('authentication.mobile'), rules: z .string() @@ -40,6 +116,29 @@ const formSchema = computed((): VbenFormSchema[] => { return text; }, placeholder: $t('authentication.code'), + handleSendCode: async () => { + loading.value = true; + try { + const formApi = loginRef.value?.getFormApi(); + if (!formApi) { + throw new Error('表单未准备好'); + } + // 验证手机号 + await formApi.validateField('mobile'); + const isMobileValid = await formApi.isFieldValid('mobile'); + if (!isMobileValid) { + throw new Error('请输入有效的手机号码'); + } + + // 发送验证码 + const { mobile } = await formApi.getValues(); + const scene = 21; // 场景:短信验证码登录 + await sendSmsCode({ mobile, scene }); + message.success('验证码发送成功'); + } finally { + loading.value = false; + } + }, }, fieldName: 'code', label: $t('authentication.code'), @@ -55,13 +154,17 @@ const formSchema = computed((): VbenFormSchema[] => { * @param values 登录表单数据 */ async function handleLogin(values: Recordable) { - // eslint-disable-next-line no-console - console.log(values); + try { + await authStore.authLogin('mobile', values); + } catch (error) { + console.error('Error in handleLogin:', error); + } }