Merge remote-tracking branch 'yudao/dev' into dev-new
# Conflicts: # pnpm-lock.yamlpull/98/head
commit
e6fd331971
|
|
@ -1,3 +1,3 @@
|
||||||
export { default as CropperAvatar } from './cropper-avatar.vue';
|
export { default as CropperAvatar } from './cropper-avatar.vue';
|
||||||
export { default as CropperImage } from './cropper.vue';
|
export { default as CropperImage } from './cropper.vue';
|
||||||
export type { Cropper } from './typing';
|
export type { CropperType } from './typing';
|
||||||
|
|
|
||||||
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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 版本"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,26 @@
|
||||||
# 应用标题
|
# 应用标题
|
||||||
VITE_APP_TITLE=Vben Admin Naive
|
VITE_APP_TITLE=芋道管理系统
|
||||||
|
|
||||||
# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
|
# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
|
||||||
VITE_APP_NAMESPACE=vben-web-naive
|
VITE_APP_NAMESPACE=yudao-vben-naive
|
||||||
|
|
||||||
# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密
|
# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密
|
||||||
VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key
|
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'
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,19 @@ VITE_PORT=5888
|
||||||
|
|
||||||
VITE_BASE=/
|
VITE_BASE=/
|
||||||
|
|
||||||
|
# 请求路径
|
||||||
|
VITE_BASE_URL=http://127.0.0.1:48080
|
||||||
# 接口地址
|
# 接口地址
|
||||||
VITE_GLOB_API_URL=/api
|
VITE_GLOB_API_URL=/admin-api
|
||||||
|
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
|
||||||
# 是否开启 Nitro Mock服务,true 为开启,false 为关闭
|
VITE_UPLOAD_TYPE=server
|
||||||
VITE_NITRO_MOCK=true
|
|
||||||
|
|
||||||
# 是否打开 devtools,true 为打开,false 为关闭
|
# 是否打开 devtools,true 为打开,false 为关闭
|
||||||
VITE_DEVTOOLS=false
|
VITE_DEVTOOLS=false
|
||||||
|
|
||||||
# 是否注入全局loading
|
# 是否注入全局loading
|
||||||
VITE_INJECT_APP_LOADING=true
|
VITE_INJECT_APP_LOADING=true
|
||||||
|
|
||||||
|
# 默认登录用户名
|
||||||
|
VITE_APP_DEFAULT_USERNAME=admin
|
||||||
|
# 默认登录密码
|
||||||
|
VITE_APP_DEFAULT_PASSWORD=admin123
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
VITE_BASE=/
|
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
|
# 是否开启压缩,可以设置为 none, brotli, gzip
|
||||||
VITE_COMPRESS=none
|
VITE_COMPRESS=none
|
||||||
|
|
|
||||||
|
|
@ -41,9 +41,15 @@
|
||||||
"@vben/types": "workspace:*",
|
"@vben/types": "workspace:*",
|
||||||
"@vben/utils": "workspace:*",
|
"@vben/utils": "workspace:*",
|
||||||
"@vueuse/core": "catalog:",
|
"@vueuse/core": "catalog:",
|
||||||
|
"cropperjs": "catalog:",
|
||||||
|
"crypto-js": "catalog:",
|
||||||
|
"dayjs": "catalog:",
|
||||||
"naive-ui": "catalog:",
|
"naive-ui": "catalog:",
|
||||||
"pinia": "catalog:",
|
"pinia": "catalog:",
|
||||||
"vue": "catalog:",
|
"vue": "catalog:",
|
||||||
"vue-router": "catalog:"
|
"vue-router": "catalog:"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/crypto-js": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@ import type { ComponentType } from './component';
|
||||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||||
import { $t } from '@vben/locales';
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
|
/** 手机号正则表达式(中国) */
|
||||||
|
const MOBILE_REGEX = /(?:0|86|\+86)?1[3-9]\d{9}/;
|
||||||
|
|
||||||
setupVbenForm<ComponentType>({
|
setupVbenForm<ComponentType>({
|
||||||
config: {
|
config: {
|
||||||
// naive-ui组件的空值为null,不能是undefined,否则重置表单时不生效
|
// naive-ui组件的空值为null,不能是undefined,否则重置表单时不生效
|
||||||
|
|
@ -32,6 +35,25 @@ setupVbenForm<ComponentType>({
|
||||||
}
|
}
|
||||||
return true;
|
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;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,16 @@
|
||||||
|
import type { Recordable } from '@vben/types';
|
||||||
|
|
||||||
import { h } from 'vue';
|
import { h } from 'vue';
|
||||||
|
|
||||||
|
import { IconifyIcon } from '@vben/icons';
|
||||||
|
import { $te } from '@vben/locales';
|
||||||
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
|
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';
|
import { useVbenForm } from './form';
|
||||||
|
|
||||||
|
|
@ -20,16 +28,32 @@ setupVbenVxeTable({
|
||||||
// 全局禁用vxe-table的表单配置,使用formOptions
|
// 全局禁用vxe-table的表单配置,使用formOptions
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
|
toolbarConfig: {
|
||||||
|
import: false, // 是否导入
|
||||||
|
export: false, // 是否导出
|
||||||
|
refresh: true, // 是否刷新
|
||||||
|
print: false, // 是否打印
|
||||||
|
zoom: true, // 是否缩放
|
||||||
|
custom: true, // 是否自定义配置
|
||||||
|
},
|
||||||
|
customConfig: {
|
||||||
|
mode: 'modal',
|
||||||
|
},
|
||||||
proxyConfig: {
|
proxyConfig: {
|
||||||
autoLoad: true,
|
autoLoad: true,
|
||||||
response: {
|
response: {
|
||||||
result: 'items',
|
result: 'list',
|
||||||
total: 'total',
|
total: 'total',
|
||||||
list: 'items',
|
|
||||||
},
|
},
|
||||||
showActiveMsg: true,
|
showActiveMsg: true,
|
||||||
showResponseMsg: false,
|
showResponseMsg: false,
|
||||||
},
|
},
|
||||||
|
pagerConfig: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
sortConfig: {
|
||||||
|
multiple: true,
|
||||||
|
},
|
||||||
round: true,
|
round: true,
|
||||||
showOverflow: true,
|
showOverflow: true,
|
||||||
size: 'small',
|
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<Recordable<any>> = {
|
||||||
|
delete: {
|
||||||
|
type: 'error',
|
||||||
|
concent: $t('common.delete'),
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
concent: $t('common.edit'),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const operations: Array<Recordable<any>> = (
|
||||||
|
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<any> = {};
|
||||||
|
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<any>, 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<any>) {
|
||||||
|
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 的全局配置,比如自定义格式化
|
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
|
||||||
// vxeUI.formats.add
|
// 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,
|
useVbenForm,
|
||||||
});
|
});
|
||||||
|
|
||||||
export { useVbenVxeGrid };
|
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<T = Recordable<any>> = {
|
||||||
|
code: string;
|
||||||
|
row: T;
|
||||||
|
};
|
||||||
|
export type OnActionClickFn<T = Recordable<any>> = (
|
||||||
|
params: OnActionClickParams<T>,
|
||||||
|
) => void;
|
||||||
export type * from '@vben/plugins/vxe-table';
|
export type * from '@vben/plugins/vxe-table';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import type { AuthPermissionInfo } from '@vben/types';
|
||||||
|
|
||||||
import { baseRequestClient, requestClient } from '#/api/request';
|
import { baseRequestClient, requestClient } from '#/api/request';
|
||||||
|
|
||||||
export namespace AuthApi {
|
export namespace AuthApi {
|
||||||
|
|
@ -5,47 +7,151 @@ export namespace AuthApi {
|
||||||
export interface LoginParams {
|
export interface LoginParams {
|
||||||
password?: string;
|
password?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
|
captchaVerification?: string;
|
||||||
|
// 绑定社交登录时,需要传递如下参数
|
||||||
|
socialType?: number;
|
||||||
|
socialCode?: string;
|
||||||
|
socialState?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 登录接口返回值 */
|
/** 登录接口返回值 */
|
||||||
export interface LoginResult {
|
export interface LoginResult {
|
||||||
accessToken: string;
|
accessToken: string;
|
||||||
|
refreshToken: string;
|
||||||
|
userId: number;
|
||||||
|
expiresTime: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RefreshTokenResult {
|
/** 租户信息返回值 */
|
||||||
data: string;
|
export interface TenantResult {
|
||||||
status: number;
|
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) {
|
export async function loginApi(data: AuthApi.LoginParams) {
|
||||||
return requestClient.post<AuthApi.LoginResult>('/auth/login', data);
|
return requestClient.post<AuthApi.LoginResult>('/system/auth/login', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** 刷新 accessToken */
|
||||||
* 刷新accessToken
|
export async function refreshTokenApi(refreshToken: string) {
|
||||||
*/
|
return baseRequestClient.post(
|
||||||
export async function refreshTokenApi() {
|
`/system/auth/refresh-token?refreshToken=${refreshToken}`,
|
||||||
return baseRequestClient.post<AuthApi.RefreshTokenResult>('/auth/refresh', {
|
);
|
||||||
withCredentials: true,
|
}
|
||||||
|
|
||||||
|
/** 退出登录 */
|
||||||
|
export async function logoutApi(accessToken: string) {
|
||||||
|
return baseRequestClient.post(
|
||||||
|
'/system/auth/logout',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取权限信息 */
|
||||||
|
export async function getAuthPermissionInfoApi() {
|
||||||
|
return requestClient.get<AuthPermissionInfo>(
|
||||||
|
'/system/auth/get-permission-info',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取租户列表 */
|
||||||
|
export async function getTenantSimpleList() {
|
||||||
|
return requestClient.get<AuthApi.TenantResult[]>(
|
||||||
|
`/system/tenant/simple-list`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 使用租户域名,获得租户信息 */
|
||||||
|
export async function getTenantByWebsite(website: string) {
|
||||||
|
return requestClient.get<AuthApi.TenantResult>(
|
||||||
|
`/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 socialLogin(data: AuthApi.SocialLoginParams) {
|
||||||
*/
|
return requestClient.post<AuthApi.LoginResult>(
|
||||||
export async function logoutApi() {
|
'/system/auth/social-login',
|
||||||
return baseRequestClient.post('/auth/logout', {
|
data,
|
||||||
withCredentials: true,
|
);
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取用户权限码
|
|
||||||
*/
|
|
||||||
export async function getAccessCodesApi() {
|
|
||||||
return requestClient.get<string[]>('/auth/codes');
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1 @@
|
||||||
export * from './auth';
|
export * from './auth';
|
||||||
export * from './menu';
|
|
||||||
export * from './user';
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
import type { RouteRecordStringComponent } from '@vben/types';
|
|
||||||
|
|
||||||
import { requestClient } from '#/api/request';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取用户所有菜单
|
|
||||||
*/
|
|
||||||
export async function getAllMenusApi() {
|
|
||||||
return requestClient.get<RouteRecordStringComponent[]>('/menu/all');
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
import type { UserInfo } from '@vben/types';
|
|
||||||
|
|
||||||
import { requestClient } from '#/api/request';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取用户信息
|
|
||||||
*/
|
|
||||||
export async function getUserInfoApi() {
|
|
||||||
return requestClient.get<UserInfo>('/user/info');
|
|
||||||
}
|
|
||||||
|
|
@ -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<PageResult<InfraApiAccessLogApi.ApiAccessLog>>(
|
||||||
|
'/infra/api-access-log/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导出 API 访问日志 */
|
||||||
|
export function exportApiAccessLog(params: any) {
|
||||||
|
return requestClient.download('/infra/api-access-log/export-excel', {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<InfraApiErrorLogApi.ApiErrorLog>>(
|
||||||
|
'/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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -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<InfraCodegenApi.CodegenTable[]>(
|
||||||
|
'/infra/codegen/table/list?',
|
||||||
|
{
|
||||||
|
params: { dataSourceConfigId },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询列表代码生成表定义 */
|
||||||
|
export function getCodegenTablePage(params: PageParam) {
|
||||||
|
return requestClient.get<PageResult<InfraCodegenApi.CodegenTable>>(
|
||||||
|
'/infra/codegen/table/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询详情代码生成表定义 */
|
||||||
|
export function getCodegenTable(tableId: number) {
|
||||||
|
return requestClient.get<InfraCodegenApi.CodegenDetail>(
|
||||||
|
'/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<InfraCodegenApi.CodegenPreview[]>(
|
||||||
|
'/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<InfraCodegenApi.DatabaseTable[]>(
|
||||||
|
'/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 },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<InfraConfigApi.Config>>(
|
||||||
|
'/infra/config/page',
|
||||||
|
{
|
||||||
|
params,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询参数详情 */
|
||||||
|
export function getConfig(id: number) {
|
||||||
|
return requestClient.get<InfraConfigApi.Config>(`/infra/config/get?id=${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据参数键名查询参数值 */
|
||||||
|
export function getConfigKey(configKey: string) {
|
||||||
|
return requestClient.get<string>(
|
||||||
|
`/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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -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<InfraDataSourceConfigApi.DataSourceConfig[]>(
|
||||||
|
'/infra/data-source-config/list',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询数据源配置详情 */
|
||||||
|
export function getDataSourceConfig(id: number) {
|
||||||
|
return requestClient.get<InfraDataSourceConfigApi.DataSourceConfig>(
|
||||||
|
`/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}`);
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<Demo01ContactApi.Demo01Contact>>(
|
||||||
|
'/infra/demo01-contact/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询示例联系人详情 */
|
||||||
|
export function getDemo01Contact(id: number) {
|
||||||
|
return requestClient.get<Demo01ContactApi.Demo01Contact>(
|
||||||
|
`/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);
|
||||||
|
}
|
||||||
|
|
@ -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<Demo02CategoryApi.Demo02Category[]>(
|
||||||
|
'/infra/demo02-category/list',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询示例分类详情 */
|
||||||
|
export function getDemo02Category(id: number) {
|
||||||
|
return requestClient.get<Demo02CategoryApi.Demo02Category>(
|
||||||
|
`/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);
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<Demo03StudentApi.Demo03Student>>(
|
||||||
|
'/infra/demo03-student/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询学生详情 */
|
||||||
|
export function getDemo03Student(id: number) {
|
||||||
|
return requestClient.get<Demo03StudentApi.Demo03Student>(
|
||||||
|
`/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<PageResult<Demo03StudentApi.Demo03Course>>(
|
||||||
|
`/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<Demo03StudentApi.Demo03Course>(
|
||||||
|
`/infra/demo03-student/demo03-course/get?id=${id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 子表(学生班级) ====================
|
||||||
|
|
||||||
|
/** 获得学生班级分页 */
|
||||||
|
export function getDemo03GradePage(params: PageParam) {
|
||||||
|
return requestClient.get<PageResult<Demo03StudentApi.Demo03Grade>>(
|
||||||
|
`/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<Demo03StudentApi.Demo03Grade>(
|
||||||
|
`/infra/demo03-student/demo03-grade/get?id=${id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<Demo03StudentApi.Demo03Student>>(
|
||||||
|
'/infra/demo03-student/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询学生详情 */
|
||||||
|
export function getDemo03Student(id: number) {
|
||||||
|
return requestClient.get<Demo03StudentApi.Demo03Student>(
|
||||||
|
`/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<Demo03StudentApi.Demo03Course[]>(
|
||||||
|
`/infra/demo03-student/demo03-course/list-by-student-id?studentId=${studentId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 子表(学生班级) ====================
|
||||||
|
|
||||||
|
/** 获得学生班级 */
|
||||||
|
export function getDemo03GradeByStudentId(studentId: number) {
|
||||||
|
return requestClient.get<Demo03StudentApi.Demo03Grade>(
|
||||||
|
`/infra/demo03-student/demo03-grade/get-by-student-id?studentId=${studentId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<Demo03StudentApi.Demo03Student>>(
|
||||||
|
'/infra/demo03-student/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询学生详情 */
|
||||||
|
export function getDemo03Student(id: number) {
|
||||||
|
return requestClient.get<Demo03StudentApi.Demo03Student>(
|
||||||
|
`/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<Demo03StudentApi.Demo03Course[]>(
|
||||||
|
`/infra/demo03-student/demo03-course/list-by-student-id?studentId=${studentId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 子表(学生班级) ====================
|
||||||
|
|
||||||
|
/** 获得学生班级 */
|
||||||
|
export function getDemo03GradeByStudentId(studentId: number) {
|
||||||
|
return requestClient.get<Demo03StudentApi.Demo03Grade>(
|
||||||
|
`/infra/demo03-student/demo03-grade/get-by-student-id?studentId=${studentId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<InfraFileConfigApi.FileConfig>>(
|
||||||
|
'/infra/file-config/page',
|
||||||
|
{
|
||||||
|
params,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询文件配置详情 */
|
||||||
|
export function getFileConfig(id: number) {
|
||||||
|
return requestClient.get<InfraFileConfigApi.FileConfig>(
|
||||||
|
`/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}`);
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<InfraFileApi.File>>('/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<InfraFileApi.FilePresignedUrlRespVO>(
|
||||||
|
'/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 });
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<InfraJobLogApi.JobLog>>(
|
||||||
|
'/infra/job-log/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询任务日志详情 */
|
||||||
|
export function getJobLog(id: number) {
|
||||||
|
return requestClient.get<InfraJobLogApi.JobLog>(
|
||||||
|
`/infra/job-log/get?id=${id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导出定时任务日志 */
|
||||||
|
export function exportJobLog(params: any) {
|
||||||
|
return requestClient.download('/infra/job-log/export-excel', { params });
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<InfraJobApi.Job>>('/infra/job/page', {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询任务详情 */
|
||||||
|
export function getJob(id: number) {
|
||||||
|
return requestClient.get<InfraJobApi.Job>(`/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}`);
|
||||||
|
}
|
||||||
|
|
@ -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<InfraRedisApi.RedisMonitorInfo>(
|
||||||
|
'/infra/redis/get-monitor-info',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
*/
|
*/
|
||||||
import type { RequestClientOptions } from '@vben/request';
|
import type { RequestClientOptions } from '@vben/request';
|
||||||
|
|
||||||
import { useAppConfig } from '@vben/hooks';
|
import { isTenantEnable, useAppConfig } from '@vben/hooks';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import {
|
import {
|
||||||
authenticateResponseInterceptor,
|
authenticateResponseInterceptor,
|
||||||
|
|
@ -19,6 +19,7 @@ import { useAuthStore } from '#/store';
|
||||||
import { refreshTokenApi } from './core';
|
import { refreshTokenApi } from './core';
|
||||||
|
|
||||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||||
|
const tenantEnable = isTenantEnable();
|
||||||
|
|
||||||
function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
||||||
const client = new RequestClient({
|
const client = new RequestClient({
|
||||||
|
|
@ -49,8 +50,16 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
||||||
*/
|
*/
|
||||||
async function doRefreshToken() {
|
async function doRefreshToken() {
|
||||||
const accessStore = useAccessStore();
|
const accessStore = useAccessStore();
|
||||||
const resp = await refreshTokenApi();
|
const refreshToken = accessStore.refreshToken as string;
|
||||||
const newToken = resp.data;
|
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);
|
accessStore.setAccessToken(newToken);
|
||||||
return newToken;
|
return newToken;
|
||||||
}
|
}
|
||||||
|
|
@ -66,6 +75,14 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
||||||
|
|
||||||
config.headers.Authorization = formatToken(accessStore.accessToken);
|
config.headers.Authorization = formatToken(accessStore.accessToken);
|
||||||
config.headers['Accept-Language'] = preferences.app.locale;
|
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;
|
return config;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -96,7 +113,12 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
||||||
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
|
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
|
||||||
// 当前mock接口返回的错误字段是 error 或者 message
|
// 当前mock接口返回的错误字段是 error 或者 message
|
||||||
const responseData = error?.response?.data ?? {};
|
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);
|
message.error(errorMessage || msg);
|
||||||
}),
|
}),
|
||||||
|
|
@ -110,3 +132,17 @@ export const requestClient = createRequestClient(apiURL, {
|
||||||
});
|
});
|
||||||
|
|
||||||
export const baseRequestClient = new RequestClient({ baseURL: 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;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -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<SystemAreaApi.Area[]>('/system/area/tree');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获得 IP 对应的地区名 */
|
||||||
|
export function getAreaByIp(ip: string) {
|
||||||
|
return requestClient.get<string>(`/system/area/get-by-ip?ip=${ip}`);
|
||||||
|
}
|
||||||
|
|
@ -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<SystemDeptApi.Dept[]>('/system/dept/simple-list');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询部门列表 */
|
||||||
|
export async function getDeptList() {
|
||||||
|
return requestClient.get('/system/dept/list');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询部门详情 */
|
||||||
|
export async function getDept(id: number) {
|
||||||
|
return requestClient.get<SystemDeptApi.Dept>(`/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}`);
|
||||||
|
}
|
||||||
|
|
@ -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 });
|
||||||
|
}
|
||||||
|
|
@ -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 });
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<SystemLoginLogApi.LoginLog>>(
|
||||||
|
'/system/login-log/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导出登录日志 */
|
||||||
|
export function exportLoginLog(params: any) {
|
||||||
|
return requestClient.download('/system/login-log/export-excel', { params });
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<SystemMailAccountApi.MailAccount>>(
|
||||||
|
'/system/mail-account/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询邮箱账号详情 */
|
||||||
|
export function getMailAccount(id: number) {
|
||||||
|
return requestClient.get<SystemMailAccountApi.MailAccount>(
|
||||||
|
`/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<SystemMailAccountApi.MailAccount[]>(
|
||||||
|
'/system/mail-account/simple-list',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<SystemMailLogApi.MailLog>>(
|
||||||
|
'/system/mail-log/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询邮件日志详情 */
|
||||||
|
export function getMailLog(id: number) {
|
||||||
|
return requestClient.get<SystemMailLogApi.MailLog>(
|
||||||
|
`/system/mail-log/get?id=${id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重新发送邮件 */
|
||||||
|
export function resendMail(id: number) {
|
||||||
|
return requestClient.put(`/system/mail-log/resend?id=${id}`);
|
||||||
|
}
|
||||||
|
|
@ -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<string, any>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询邮件模版列表 */
|
||||||
|
export function getMailTemplatePage(params: PageParam) {
|
||||||
|
return requestClient.get<PageResult<SystemMailTemplateApi.MailTemplate>>(
|
||||||
|
'/system/mail-template/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询邮件模版详情 */
|
||||||
|
export function getMailTemplate(id: number) {
|
||||||
|
return requestClient.get<SystemMailTemplateApi.MailTemplate>(
|
||||||
|
`/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);
|
||||||
|
}
|
||||||
|
|
@ -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<SystemMenuApi.Menu[]>('/system/menu/simple-list');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询菜单列表 */
|
||||||
|
export async function getMenuList(params?: Record<string, any>) {
|
||||||
|
return requestClient.get<SystemMenuApi.Menu[]>('/system/menu/list', {
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取菜单详情 */
|
||||||
|
export async function getMenu(id: number) {
|
||||||
|
return requestClient.get<SystemMenuApi.Menu>(`/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}`);
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<SystemNoticeApi.Notice>>(
|
||||||
|
'/system/notice/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询公告详情 */
|
||||||
|
export function getNotice(id: number) {
|
||||||
|
return requestClient.get<SystemNoticeApi.Notice>(
|
||||||
|
`/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}`);
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<SystemNotifyMessageApi.NotifyMessage>>(
|
||||||
|
'/system/notify-message/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获得我的站内信分页 */
|
||||||
|
export function getMyNotifyMessagePage(params: PageParam) {
|
||||||
|
return requestClient.get<PageResult<SystemNotifyMessageApi.NotifyMessage>>(
|
||||||
|
'/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<SystemNotifyMessageApi.NotifyMessage[]>(
|
||||||
|
'/system/notify-message/get-unread-list',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获得当前用户的未读站内信数量 */
|
||||||
|
export function getUnreadNotifyMessageCount() {
|
||||||
|
return requestClient.get<number>('/system/notify-message/get-unread-count');
|
||||||
|
}
|
||||||
|
|
@ -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<string, any>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询站内信模板列表 */
|
||||||
|
export function getNotifyTemplatePage(params: PageParam) {
|
||||||
|
return requestClient.get<PageResult<SystemNotifyTemplateApi.NotifyTemplate>>(
|
||||||
|
'/system/notify-template/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询站内信模板详情 */
|
||||||
|
export function getNotifyTemplate(id: number) {
|
||||||
|
return requestClient.get<SystemNotifyTemplateApi.NotifyTemplate>(
|
||||||
|
`/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);
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<SystemOAuth2ClientApi.OAuth2Client>>(
|
||||||
|
'/system/oauth2-client/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询 OAuth2.0 客户端详情 */
|
||||||
|
export function getOAuth2Client(id: number) {
|
||||||
|
return requestClient.get<SystemOAuth2ClientApi.OAuth2Client>(
|
||||||
|
`/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}`);
|
||||||
|
}
|
||||||
|
|
@ -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<SystemOAuth2ClientApi.AuthorizeInfoRespVO>(
|
||||||
|
`/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<string, boolean> = {};
|
||||||
|
for (const scope of checkedScopes) {
|
||||||
|
scopes[scope] = true;
|
||||||
|
}
|
||||||
|
for (const scope of uncheckedScopes) {
|
||||||
|
scopes[scope] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发起请求
|
||||||
|
return requestClient.post<string>('/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),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<SystemOAuth2TokenApi.OAuth2Token>>(
|
||||||
|
'/system/oauth2-token/page',
|
||||||
|
{
|
||||||
|
params,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除 OAuth2.0 令牌 */
|
||||||
|
export function deleteOAuth2Token(accessToken: string) {
|
||||||
|
return requestClient.delete(
|
||||||
|
`/system/oauth2-token/delete?accessToken=${accessToken}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<SystemOperateLogApi.OperateLog>>(
|
||||||
|
'/system/operate-log/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导出操作日志 */
|
||||||
|
export function exportOperateLog(params: any) {
|
||||||
|
return requestClient.download('/system/operate-log/export-excel', { params });
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<SystemPostApi.Post>>(
|
||||||
|
'/system/post/page',
|
||||||
|
{
|
||||||
|
params,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取岗位精简信息列表 */
|
||||||
|
export function getSimplePostList() {
|
||||||
|
return requestClient.get<SystemPostApi.Post[]>('/system/post/simple-list');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询岗位详情 */
|
||||||
|
export function getPost(id: number) {
|
||||||
|
return requestClient.get<SystemPostApi.Post>(`/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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<SystemRoleApi.Role>>(
|
||||||
|
'/system/role/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询角色(精简)列表 */
|
||||||
|
export function getSimpleRoleList() {
|
||||||
|
return requestClient.get<SystemRoleApi.Role[]>('/system/role/simple-list');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询角色详情 */
|
||||||
|
export function getRole(id: number) {
|
||||||
|
return requestClient.get<SystemRoleApi.Role>(`/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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<SystemSmsChannelApi.SmsChannel>>(
|
||||||
|
'/system/sms-channel/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获得短信渠道精简列表 */
|
||||||
|
export function getSimpleSmsChannelList() {
|
||||||
|
return requestClient.get<SystemSmsChannelApi.SmsChannel[]>(
|
||||||
|
'/system/sms-channel/simple-list',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询短信渠道详情 */
|
||||||
|
export function getSmsChannel(id: number) {
|
||||||
|
return requestClient.get<SystemSmsChannelApi.SmsChannel>(
|
||||||
|
`/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 });
|
||||||
|
}
|
||||||
|
|
@ -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<string, any>;
|
||||||
|
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<PageResult<SystemSmsLogApi.SmsLog>>(
|
||||||
|
'/system/sms-log/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导出短信日志 */
|
||||||
|
export function exportSmsLog(params: any) {
|
||||||
|
return requestClient.download('/system/sms-log/export-excel', { params });
|
||||||
|
}
|
||||||
|
|
@ -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<string, any>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询短信模板列表 */
|
||||||
|
export function getSmsTemplatePage(params: PageParam) {
|
||||||
|
return requestClient.get<PageResult<SystemSmsTemplateApi.SmsTemplate>>(
|
||||||
|
'/system/sms-template/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询短信模板详情 */
|
||||||
|
export function getSmsTemplate(id: number) {
|
||||||
|
return requestClient.get<SystemSmsTemplateApi.SmsTemplate>(
|
||||||
|
`/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);
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<SystemSocialClientApi.SocialClient>>(
|
||||||
|
'/system/social-client/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询社交客户端详情 */
|
||||||
|
export function getSocialClient(id: number) {
|
||||||
|
return requestClient.get<SystemSocialClientApi.SocialClient>(
|
||||||
|
`/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}`);
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<SystemSocialUserApi.SocialUser>>(
|
||||||
|
'/system/social-user/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询社交用户详情 */
|
||||||
|
export function getSocialUser(id: number) {
|
||||||
|
return requestClient.get<SystemSocialUserApi.SocialUser>(
|
||||||
|
`/system/social-user/get?id=${id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 社交绑定,使用 code 授权码 */
|
||||||
|
export function socialBind(data: SystemSocialUserApi.SocialUserBindReqVO) {
|
||||||
|
return requestClient.post<boolean>('/system/social-user/bind', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 取消社交绑定 */
|
||||||
|
export function socialUnbind(data: SystemSocialUserApi.SocialUserUnbindReqVO) {
|
||||||
|
return requestClient.delete<boolean>('/system/social-user/unbind', { data });
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获得绑定社交用户列表 */
|
||||||
|
export function getBindSocialUserList() {
|
||||||
|
return requestClient.get<SystemSocialUserApi.SocialUser[]>(
|
||||||
|
'/system/social-user/get-bind-list',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<SystemTenantPackageApi.TenantPackage>>(
|
||||||
|
'/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<SystemTenantPackageApi.TenantPackage[]>(
|
||||||
|
'/system/tenant-package/get-simple-list',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<SystemTenantApi.Tenant>>(
|
||||||
|
'/system/tenant/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取租户精简信息列表 */
|
||||||
|
export function getSimpleTenantList() {
|
||||||
|
return requestClient.get<SystemTenantApi.Tenant[]>(
|
||||||
|
'/system/tenant/simple-list',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询租户详情 */
|
||||||
|
export function getTenant(id: number) {
|
||||||
|
return requestClient.get<SystemTenantApi.Tenant>(
|
||||||
|
`/system/tenant/get?id=${id}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取租户精简信息列表 */
|
||||||
|
export function getTenantList() {
|
||||||
|
return requestClient.get<SystemTenantApi.Tenant[]>(
|
||||||
|
'/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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -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<PageResult<SystemUserApi.User>>(
|
||||||
|
'/system/user/page',
|
||||||
|
{ params },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询用户详情 */
|
||||||
|
export function getUser(id: number) {
|
||||||
|
return requestClient.get<SystemUserApi.User>(`/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<SystemUserApi.User[]>('/system/user/simple-list');
|
||||||
|
}
|
||||||
|
|
@ -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<SystemUserProfileApi.UserProfileRespVO>(
|
||||||
|
'/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);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,159 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { CSSProperties } from 'vue';
|
||||||
|
|
||||||
|
import type { CropperAvatarProps } from './typing';
|
||||||
|
|
||||||
|
import { computed, ref, unref, watch, watchEffect } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
|
|
||||||
|
import { NButton } from 'naive-ui';
|
||||||
|
|
||||||
|
import { message } from '#/adapter/naive';
|
||||||
|
|
||||||
|
import cropperModal from './cropper-modal.vue';
|
||||||
|
|
||||||
|
defineOptions({ name: 'CropperAvatar' });
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<CropperAvatarProps>(), {
|
||||||
|
width: 200,
|
||||||
|
value: '',
|
||||||
|
showBtn: true,
|
||||||
|
btnProps: () => ({}),
|
||||||
|
btnText: '',
|
||||||
|
uploadApi: () => Promise.resolve(),
|
||||||
|
size: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:value', 'change']);
|
||||||
|
|
||||||
|
const sourceValue = ref(props.value || '');
|
||||||
|
const prefixCls = 'cropper-avatar';
|
||||||
|
const [CropperModal, modalApi] = useVbenModal({
|
||||||
|
connectedComponent: cropperModal,
|
||||||
|
});
|
||||||
|
|
||||||
|
const getClass = computed(() => [prefixCls]);
|
||||||
|
|
||||||
|
const getWidth = computed(() => `${`${props.width}`.replace(/px/, '')}px`);
|
||||||
|
|
||||||
|
const getIconWidth = computed(
|
||||||
|
() => `${Number.parseInt(`${props.width}`.replace(/px/, '')) / 2}px`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const getStyle = computed((): CSSProperties => ({ width: unref(getWidth) }));
|
||||||
|
|
||||||
|
const getImageWrapperStyle = computed(
|
||||||
|
(): CSSProperties => ({ height: unref(getWidth), width: unref(getWidth) }),
|
||||||
|
);
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
sourceValue.value = props.value || '';
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => sourceValue.value,
|
||||||
|
(v: string) => {
|
||||||
|
emit('update:value', v);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
function handleUploadSuccess({ data, source }: any) {
|
||||||
|
sourceValue.value = source;
|
||||||
|
emit('change', { data, source });
|
||||||
|
message.success($t('ui.cropper.uploadSuccess'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeModal = () => modalApi.close();
|
||||||
|
const openModal = () => modalApi.open();
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
closeModal,
|
||||||
|
openModal,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="getClass" :style="getStyle">
|
||||||
|
<div
|
||||||
|
:class="`${prefixCls}-image-wrapper`"
|
||||||
|
:style="getImageWrapperStyle"
|
||||||
|
@click="openModal"
|
||||||
|
>
|
||||||
|
<div :class="`${prefixCls}-image-mask`" :style="getImageWrapperStyle">
|
||||||
|
<span
|
||||||
|
:style="{
|
||||||
|
...getImageWrapperStyle,
|
||||||
|
width: `${getIconWidth}`,
|
||||||
|
height: `${getIconWidth}`,
|
||||||
|
lineHeight: `${getIconWidth}`,
|
||||||
|
}"
|
||||||
|
class="icon-[ant-design--cloud-upload-outlined] text-[#d6d6d6]"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
<img v-if="sourceValue" :src="sourceValue" alt="avatar" />
|
||||||
|
</div>
|
||||||
|
<NButton
|
||||||
|
v-if="showBtn"
|
||||||
|
:class="`${prefixCls}-upload-btn`"
|
||||||
|
@click="openModal"
|
||||||
|
v-bind="btnProps"
|
||||||
|
>
|
||||||
|
{{ btnText ? btnText : $t('ui.cropper.selectImage') }}
|
||||||
|
</NButton>
|
||||||
|
|
||||||
|
<CropperModal
|
||||||
|
:size="size"
|
||||||
|
:src="sourceValue"
|
||||||
|
:upload-api="uploadApi"
|
||||||
|
@upload-success="handleUploadSuccess"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.cropper-avatar {
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&-image-wrapper {
|
||||||
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-image-mask {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: inherit;
|
||||||
|
height: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
background: rgb(0 0 0 / 40%);
|
||||||
|
border: inherit;
|
||||||
|
border-radius: inherit;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.4s;
|
||||||
|
|
||||||
|
::v-deep(svg) {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-image-mask:hover {
|
||||||
|
opacity: 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-upload-btn {
|
||||||
|
margin: 10px auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,370 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { UploadFileInfo } from 'naive-ui';
|
||||||
|
|
||||||
|
import type { CropendResult, CropperModalProps, CropperType } from './typing';
|
||||||
|
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { useVbenModal } from '@vben/common-ui';
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
|
import { dataURLtoBlob, isFunction } from '@vben/utils';
|
||||||
|
|
||||||
|
import { NAvatar, NButton, NSpace, NTooltip, NUpload } from 'naive-ui';
|
||||||
|
|
||||||
|
import CropperImage from './cropper.vue';
|
||||||
|
|
||||||
|
defineOptions({ name: 'CropperModal' });
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<CropperModalProps>(), {
|
||||||
|
circled: true,
|
||||||
|
size: 0,
|
||||||
|
src: '',
|
||||||
|
uploadApi: () => Promise.resolve(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['uploadSuccess', 'uploadError', 'register']);
|
||||||
|
|
||||||
|
let filename = '';
|
||||||
|
const src = ref(props.src || '');
|
||||||
|
const previewSource = ref('');
|
||||||
|
const cropper = ref<CropperType>();
|
||||||
|
let scaleX = 1;
|
||||||
|
let scaleY = 1;
|
||||||
|
|
||||||
|
const prefixCls = 'cropper-am';
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
onConfirm: handleOk,
|
||||||
|
onOpenChange(isOpen) {
|
||||||
|
if (isOpen) {
|
||||||
|
// 打开时,进行 loading 加载。后续 CropperImage 组件加载完毕,会自动关闭 loading(通过 handleReady)
|
||||||
|
modalLoading(true);
|
||||||
|
} else {
|
||||||
|
// 关闭时,清空右侧预览
|
||||||
|
previewSource.value = '';
|
||||||
|
modalLoading(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function modalLoading(loading: boolean) {
|
||||||
|
modalApi.setState({ confirmLoading: loading, loading });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block upload
|
||||||
|
function handleBeforeUpload(file: UploadFileInfo) {
|
||||||
|
if (props.size > 0 && file.size > 1024 * 1024 * props.size) {
|
||||||
|
emit('uploadError', { msg: $t('ui.cropper.imageTooBig') });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsDataURL(file.file);
|
||||||
|
src.value = '';
|
||||||
|
previewSource.value = '';
|
||||||
|
reader.addEventListener('load', (e) => {
|
||||||
|
src.value = (e.target?.result as string) ?? '';
|
||||||
|
filename = file.name;
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCropend({ imgBase64 }: CropendResult) {
|
||||||
|
previewSource.value = imgBase64;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleReady(cropperInstance: CropperType) {
|
||||||
|
cropper.value = cropperInstance;
|
||||||
|
// 画布加载完毕 关闭 loading
|
||||||
|
modalLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handlerToolbar(event: string, arg?: number) {
|
||||||
|
if (event === 'scaleX') {
|
||||||
|
scaleX = arg = scaleX === -1 ? 1 : -1;
|
||||||
|
}
|
||||||
|
if (event === 'scaleY') {
|
||||||
|
scaleY = arg = scaleY === -1 ? 1 : -1;
|
||||||
|
}
|
||||||
|
(cropper?.value as any)?.[event]?.(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleOk() {
|
||||||
|
const uploadApi = props.uploadApi;
|
||||||
|
if (uploadApi && isFunction(uploadApi)) {
|
||||||
|
if (!previewSource.value) {
|
||||||
|
message.warn('未选择图片');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const blob = dataURLtoBlob(previewSource.value);
|
||||||
|
try {
|
||||||
|
modalLoading(true);
|
||||||
|
const url = await uploadApi({ file: blob, filename, name: 'file' });
|
||||||
|
emit('uploadSuccess', { data: url, source: previewSource.value });
|
||||||
|
await modalApi.close();
|
||||||
|
} finally {
|
||||||
|
modalLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Modal
|
||||||
|
v-bind="$attrs"
|
||||||
|
:confirm-text="$t('ui.cropper.okText')"
|
||||||
|
:fullscreen-button="false"
|
||||||
|
:title="$t('ui.cropper.modalTitle')"
|
||||||
|
class="w-[50%]"
|
||||||
|
>
|
||||||
|
<div :class="prefixCls">
|
||||||
|
<div :class="`${prefixCls}-left`" class="w-full">
|
||||||
|
<div :class="`${prefixCls}-cropper`">
|
||||||
|
<CropperImage
|
||||||
|
v-if="src"
|
||||||
|
:circled="circled"
|
||||||
|
:src="src"
|
||||||
|
height="300px"
|
||||||
|
@cropend="handleCropend"
|
||||||
|
@ready="handleReady"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div :class="`${prefixCls}-toolbar`">
|
||||||
|
<NSpace>
|
||||||
|
<NUpload
|
||||||
|
@before-upload="handleBeforeUpload"
|
||||||
|
:file-list="[]"
|
||||||
|
accept="image/*"
|
||||||
|
>
|
||||||
|
<NTooltip placement="bottom">
|
||||||
|
<template #trigger>
|
||||||
|
<NButton size="small" type="primary">
|
||||||
|
<template #icon>
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<span class="icon-[ant-design--upload-outlined]"></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
{{ $t('ui.cropper.selectImage') }}
|
||||||
|
</NTooltip>
|
||||||
|
</NUpload>
|
||||||
|
<NTooltip placement="bottom">
|
||||||
|
<template #trigger>
|
||||||
|
<NButton
|
||||||
|
:disabled="!src"
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
@click="handlerToolbar('reset')"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<span class="icon-[ant-design--reload-outlined]"></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
{{ $t('ui.cropper.btn_reset') }}
|
||||||
|
</NTooltip>
|
||||||
|
<NTooltip placement="bottom">
|
||||||
|
<template #trigger>
|
||||||
|
<NButton
|
||||||
|
:disabled="!src"
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
@click="handlerToolbar('rotate', -45)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<span
|
||||||
|
class="icon-[ant-design--rotate-left-outlined]"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
{{ $t('ui.cropper.btn_rotate_left') }}
|
||||||
|
</NTooltip>
|
||||||
|
<NTooltip placement="bottom">
|
||||||
|
<template #trigger>
|
||||||
|
<NButton
|
||||||
|
:disabled="!src"
|
||||||
|
pre-icon="ant-design:rotate-right-outlined"
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
@click="handlerToolbar('rotate', 45)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<span
|
||||||
|
class="icon-[ant-design--rotate-right-outlined]"
|
||||||
|
></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
{{ $t('ui.cropper.btn_rotate_right') }}
|
||||||
|
</NTooltip>
|
||||||
|
<NTooltip placement="bottom">
|
||||||
|
<template #trigger>
|
||||||
|
<NButton
|
||||||
|
:disabled="!src"
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
@click="handlerToolbar('scaleX')"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<span class="icon-[vaadin--arrows-long-h]"></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
{{ $t('ui.cropper.btn_scale_x') }}
|
||||||
|
</NTooltip>
|
||||||
|
<NTooltip placement="bottom">
|
||||||
|
<template #trigger>
|
||||||
|
<NButton
|
||||||
|
:disabled="!src"
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
@click="handlerToolbar('scaleY')"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<span class="icon-[vaadin--arrows-long-v]"></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
{{ $t('ui.cropper.btn_scale_y') }}
|
||||||
|
</NTooltip>
|
||||||
|
<NTooltip placement="bottom">
|
||||||
|
<template #trigger>
|
||||||
|
<NButton
|
||||||
|
:disabled="!src"
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
@click="handlerToolbar('zoom', 0.1)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<span class="icon-[ant-design--zoom-in-outlined]"></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
{{ $t('ui.cropper.btn_zoom_in') }}
|
||||||
|
</NTooltip>
|
||||||
|
<NTooltip placement="bottom">
|
||||||
|
<template #trigger>
|
||||||
|
<NButton
|
||||||
|
:disabled="!src"
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
@click="handlerToolbar('zoom', -0.1)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<div class="flex items-center justify-center">
|
||||||
|
<span class="icon-[ant-design--zoom-out-outlined]"></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
{{ $t('ui.cropper.btn_zoom_out') }}
|
||||||
|
</NTooltip>
|
||||||
|
</NSpace>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div :class="`${prefixCls}-right`">
|
||||||
|
<div :class="`${prefixCls}-preview`">
|
||||||
|
<img
|
||||||
|
v-if="previewSource"
|
||||||
|
:alt="$t('ui.cropper.preview')"
|
||||||
|
:src="previewSource"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<template v-if="previewSource">
|
||||||
|
<div :class="`${prefixCls}-group`">
|
||||||
|
<NAvatar :src="previewSource" size="large" />
|
||||||
|
<NAvatar :size="48" :src="previewSource" />
|
||||||
|
<NAvatar :size="64" :src="previewSource" />
|
||||||
|
<NAvatar :size="80" :src="previewSource" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.cropper-am {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
&-left,
|
||||||
|
&-right {
|
||||||
|
height: 340px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-left {
|
||||||
|
width: 55%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-right {
|
||||||
|
width: 45%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-cropper {
|
||||||
|
height: 300px;
|
||||||
|
background: #eee;
|
||||||
|
background-image:
|
||||||
|
linear-gradient(
|
||||||
|
45deg,
|
||||||
|
rgb(0 0 0 / 25%) 25%,
|
||||||
|
transparent 0,
|
||||||
|
transparent 75%,
|
||||||
|
rgb(0 0 0 / 25%) 0
|
||||||
|
),
|
||||||
|
linear-gradient(
|
||||||
|
45deg,
|
||||||
|
rgb(0 0 0 / 25%) 25%,
|
||||||
|
transparent 0,
|
||||||
|
transparent 75%,
|
||||||
|
rgb(0 0 0 / 25%) 0
|
||||||
|
);
|
||||||
|
background-position:
|
||||||
|
0 0,
|
||||||
|
12px 12px;
|
||||||
|
background-size: 24px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-preview {
|
||||||
|
width: 220px;
|
||||||
|
height: 220px;
|
||||||
|
margin: 0 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
padding-top: 8px;
|
||||||
|
margin-top: 8px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,173 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { CSSProperties } from 'vue';
|
||||||
|
|
||||||
|
import type { CropperProps } from './typing';
|
||||||
|
|
||||||
|
import { computed, onMounted, onUnmounted, ref, unref, useAttrs } from 'vue';
|
||||||
|
|
||||||
|
import { useDebounceFn } from '@vueuse/core';
|
||||||
|
import Cropper from 'cropperjs';
|
||||||
|
|
||||||
|
import { defaultOptions } from './typing';
|
||||||
|
|
||||||
|
import 'cropperjs/dist/cropper.css';
|
||||||
|
|
||||||
|
defineOptions({ name: 'CropperImage' });
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<CropperProps>(), {
|
||||||
|
src: '',
|
||||||
|
alt: '',
|
||||||
|
circled: false,
|
||||||
|
realTimePreview: true,
|
||||||
|
height: '360px',
|
||||||
|
crossorigin: undefined,
|
||||||
|
imageStyle: () => ({}),
|
||||||
|
options: () => ({}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['cropend', 'ready', 'cropendError']);
|
||||||
|
const attrs = useAttrs();
|
||||||
|
|
||||||
|
type ElRef<T extends HTMLElement = HTMLDivElement> = null | T;
|
||||||
|
const imgElRef = ref<ElRef<HTMLImageElement>>();
|
||||||
|
const cropper = ref<Cropper | null>();
|
||||||
|
const isReady = ref(false);
|
||||||
|
|
||||||
|
const prefixCls = 'cropper-image';
|
||||||
|
const debounceRealTimeCropped = useDebounceFn(realTimeCropped, 80);
|
||||||
|
|
||||||
|
const getImageStyle = computed((): CSSProperties => {
|
||||||
|
return {
|
||||||
|
height: props.height,
|
||||||
|
maxWidth: '100%',
|
||||||
|
...props.imageStyle,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const getClass = computed(() => {
|
||||||
|
return [
|
||||||
|
prefixCls,
|
||||||
|
attrs.class,
|
||||||
|
{
|
||||||
|
[`${prefixCls}--circled`]: props.circled,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const getWrapperStyle = computed((): CSSProperties => {
|
||||||
|
return { height: `${`${props.height}`.replace(/px/, '')}px` };
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(init);
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
cropper.value?.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
const imgEl = unref(imgElRef);
|
||||||
|
if (!imgEl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cropper.value = new Cropper(imgEl, {
|
||||||
|
...defaultOptions,
|
||||||
|
ready: () => {
|
||||||
|
isReady.value = true;
|
||||||
|
realTimeCropped();
|
||||||
|
emit('ready', cropper.value);
|
||||||
|
},
|
||||||
|
crop() {
|
||||||
|
debounceRealTimeCropped();
|
||||||
|
},
|
||||||
|
zoom() {
|
||||||
|
debounceRealTimeCropped();
|
||||||
|
},
|
||||||
|
cropmove() {
|
||||||
|
debounceRealTimeCropped();
|
||||||
|
},
|
||||||
|
...props.options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Real-time display preview
|
||||||
|
function realTimeCropped() {
|
||||||
|
props.realTimePreview && cropped();
|
||||||
|
}
|
||||||
|
|
||||||
|
// event: return base64 and width and height information after cropping
|
||||||
|
function cropped() {
|
||||||
|
if (!cropper.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const imgInfo = cropper.value.getData();
|
||||||
|
const canvas = props.circled
|
||||||
|
? getRoundedCanvas()
|
||||||
|
: cropper.value.getCroppedCanvas();
|
||||||
|
canvas.toBlob((blob) => {
|
||||||
|
if (!blob) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const fileReader: FileReader = new FileReader();
|
||||||
|
fileReader.readAsDataURL(blob);
|
||||||
|
fileReader.onloadend = (e) => {
|
||||||
|
emit('cropend', {
|
||||||
|
imgBase64: e.target?.result ?? '',
|
||||||
|
imgInfo,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line unicorn/prefer-add-event-listener
|
||||||
|
fileReader.onerror = () => {
|
||||||
|
emit('cropendError');
|
||||||
|
};
|
||||||
|
}, 'image/png');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a circular picture canvas
|
||||||
|
function getRoundedCanvas() {
|
||||||
|
const sourceCanvas = cropper.value!.getCroppedCanvas();
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const context = canvas.getContext('2d')!;
|
||||||
|
const width = sourceCanvas.width;
|
||||||
|
const height = sourceCanvas.height;
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
context.imageSmoothingEnabled = true;
|
||||||
|
context.drawImage(sourceCanvas, 0, 0, width, height);
|
||||||
|
context.globalCompositeOperation = 'destination-in';
|
||||||
|
context.beginPath();
|
||||||
|
context.arc(
|
||||||
|
width / 2,
|
||||||
|
height / 2,
|
||||||
|
Math.min(width, height) / 2,
|
||||||
|
0,
|
||||||
|
2 * Math.PI,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
context.fill();
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :class="getClass" :style="getWrapperStyle">
|
||||||
|
<img
|
||||||
|
v-show="isReady"
|
||||||
|
ref="imgElRef"
|
||||||
|
:alt="alt"
|
||||||
|
:crossorigin="crossorigin"
|
||||||
|
:src="src"
|
||||||
|
:style="getImageStyle"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.cropper-image {
|
||||||
|
&--circled {
|
||||||
|
.cropper-view-box,
|
||||||
|
.cropper-face {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export { default as CropperAvatar } from './cropper-avatar.vue';
|
||||||
|
export { default as CropperImage } from './cropper.vue';
|
||||||
|
export type { CropperType } from './typing';
|
||||||
|
|
@ -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<any>;
|
||||||
|
size?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CropperModalProps {
|
||||||
|
circled?: boolean;
|
||||||
|
uploadApi?: (params: apiFunParams) => Promise<any>;
|
||||||
|
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 };
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
// TODO color 待适配
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
import { NTag } from 'naive-ui';
|
||||||
|
|
||||||
|
// import { isHexColor } from '@/utils/color' // TODO @芋艿:【可优化】增加 cssClass 的处理 https://gitee.com/yudaocode/yudao-ui-admin-vben/blob/v2.4.1/src/components/DictTag/src/DictTag.vue#L60
|
||||||
|
import { getDictObj } from '#/utils';
|
||||||
|
|
||||||
|
interface DictTagProps {
|
||||||
|
/**
|
||||||
|
* 字典类型
|
||||||
|
*/
|
||||||
|
type: string;
|
||||||
|
/**
|
||||||
|
* 字典值
|
||||||
|
*/
|
||||||
|
value: any;
|
||||||
|
/**
|
||||||
|
* 图标
|
||||||
|
*/
|
||||||
|
icon?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<DictTagProps>();
|
||||||
|
|
||||||
|
/** 获取字典标签 */
|
||||||
|
const dictTag = computed(() => {
|
||||||
|
// 校验参数有效性
|
||||||
|
if (!props.type || props.value === undefined || props.value === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取字典对象
|
||||||
|
const dict = getDictObj(props.type, String(props.value));
|
||||||
|
if (!dict) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理颜色类型
|
||||||
|
let colorType = dict.colorType;
|
||||||
|
switch (colorType) {
|
||||||
|
case 'danger': {
|
||||||
|
colorType = 'error';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'info': {
|
||||||
|
colorType = 'default';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'primary': {
|
||||||
|
colorType = 'processing';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
if (!colorType) {
|
||||||
|
colorType = 'default';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: dict.label || '',
|
||||||
|
colorType,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NTag v-if="dictTag">
|
||||||
|
{{ dictTag.label }}
|
||||||
|
</NTag>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as DictTag } from './dict-tag.vue';
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { isDocAlertEnable } from '@vben/hooks';
|
||||||
|
import { openWindow } from '@vben/utils';
|
||||||
|
|
||||||
|
import { NAlert, NText } from 'naive-ui';
|
||||||
|
|
||||||
|
export interface DocAlertProps {
|
||||||
|
/**
|
||||||
|
* 文档标题
|
||||||
|
*/
|
||||||
|
title: string;
|
||||||
|
/**
|
||||||
|
* 文档 URL 地址
|
||||||
|
*/
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<DocAlertProps>();
|
||||||
|
|
||||||
|
/** 跳转 URL 链接 */
|
||||||
|
const goToUrl = () => {
|
||||||
|
openWindow(props.url);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NAlert v-if="isDocAlertEnable()" type="info" show-icon class="mb-2 rounded">
|
||||||
|
<template #message>
|
||||||
|
<NText @click="goToUrl"> 【{{ title }}】文档地址:{{ url }} </NText>
|
||||||
|
</template>
|
||||||
|
</NAlert>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as DocAlert } from './doc-alert.vue';
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
interface IFrameProps {
|
||||||
|
/** iframe 的源地址 */
|
||||||
|
src: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<IFrameProps>();
|
||||||
|
|
||||||
|
const loading = ref(true);
|
||||||
|
const height = ref('');
|
||||||
|
const frameRef = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
height.value = `${document.documentElement.clientHeight - 94.5}px`;
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
init();
|
||||||
|
}, 300);
|
||||||
|
});
|
||||||
|
// TODO @芋艿:优化:未来使用 vben 自带的内链实现
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-loading="loading" :style="`height:${height}`">
|
||||||
|
<iframe
|
||||||
|
ref="frameRef"
|
||||||
|
:src="props.src"
|
||||||
|
style="width: 100%; height: 100%"
|
||||||
|
frameborder="no"
|
||||||
|
scrolling="auto"
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as IFrame } from './iframe.vue';
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export { default as TableToolbar } from './table-toolbar.vue';
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
<!-- add by puhui999:vxe table 工具栏二次封装,提供给 vxe 原生列表使用 -->
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { VxeToolbarInstance } from 'vxe-table';
|
||||||
|
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { useContentMaximize, useRefresh } from '@vben/hooks';
|
||||||
|
import { Fullscreen, RefreshCw, Search } from '@vben/icons';
|
||||||
|
|
||||||
|
import { NButton } from 'naive-ui';
|
||||||
|
import { VxeToolbar } from 'vxe-table';
|
||||||
|
|
||||||
|
/** 列表工具栏封装 */
|
||||||
|
defineOptions({ name: 'TableToolbar' });
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
hiddenSearch: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emits = defineEmits(['update:hiddenSearch']);
|
||||||
|
|
||||||
|
const toolbarRef = ref<VxeToolbarInstance>();
|
||||||
|
const { toggleMaximizeAndTabbarHidden } = useContentMaximize();
|
||||||
|
const { refresh } = useRefresh();
|
||||||
|
|
||||||
|
/** 隐藏搜索栏 */
|
||||||
|
function onHiddenSearchBar() {
|
||||||
|
emits('update:hiddenSearch', !props.hiddenSearch);
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
getToolbarRef: () => toolbarRef.value,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VxeToolbar ref="toolbarRef" custom>
|
||||||
|
<template #toolPrefix>
|
||||||
|
<slot></slot>
|
||||||
|
<!-- TODO @puhui999:貌似 icon 没和 vxe 对上。可以考虑用 /Users/yunai/Java/yudao-ui-admin-vben-v5/packages/icons/src/iconify -->
|
||||||
|
<NButton
|
||||||
|
class="ml-2 font-[8px]"
|
||||||
|
shape="circle"
|
||||||
|
@click="onHiddenSearchBar"
|
||||||
|
>
|
||||||
|
<Search :size="15" />
|
||||||
|
</NButton>
|
||||||
|
<NButton class="ml-2 font-[8px]" shape="circle" @click="refresh">
|
||||||
|
<RefreshCw :size="15" />
|
||||||
|
</NButton>
|
||||||
|
<NButton
|
||||||
|
class="ml-2 font-[8px]"
|
||||||
|
shape="circle"
|
||||||
|
@click="toggleMaximizeAndTabbarHidden"
|
||||||
|
>
|
||||||
|
<Fullscreen :size="15" />
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
</VxeToolbar>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,221 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { UploadFile, UploadProps } from 'naive-ui';
|
||||||
|
import type { UploadRequestOption } from 'naive-ui/lib/vc-upload/interface';
|
||||||
|
|
||||||
|
import type { AxiosResponse } from '@vben/request';
|
||||||
|
|
||||||
|
import type { AxiosProgressEvent } from '#/api/infra/file';
|
||||||
|
|
||||||
|
import { ref, toRefs, watch } from 'vue';
|
||||||
|
|
||||||
|
import { CloudUpload } from '@vben/icons';
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
|
import { isFunction, isObject, isString } from '@vben/utils';
|
||||||
|
|
||||||
|
import { NButton, NUpload } from 'naive-ui';
|
||||||
|
|
||||||
|
import { message } from '#/adapter/naive';
|
||||||
|
|
||||||
|
import { checkFileType } from './helper';
|
||||||
|
import { UploadResultStatus } from './typing';
|
||||||
|
import { useUpload, useUploadType } from './use-upload';
|
||||||
|
|
||||||
|
defineOptions({ name: 'FileUpload', inheritAttrs: false });
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
// 根据后缀,或者其他
|
||||||
|
accept?: string[];
|
||||||
|
api?: (
|
||||||
|
file: File,
|
||||||
|
onUploadProgress?: AxiosProgressEvent,
|
||||||
|
) => Promise<AxiosResponse<any>>;
|
||||||
|
// 上传的目录
|
||||||
|
directory?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
helpText?: string;
|
||||||
|
// 最大数量的文件,Infinity不限制
|
||||||
|
maxNumber?: number;
|
||||||
|
// 文件最大多少MB
|
||||||
|
maxSize?: number;
|
||||||
|
// 是否支持多选
|
||||||
|
multiple?: boolean;
|
||||||
|
// support xxx.xxx.xx
|
||||||
|
resultField?: string;
|
||||||
|
// 是否显示下面的描述
|
||||||
|
showDescription?: boolean;
|
||||||
|
value?: string | string[];
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
value: () => [],
|
||||||
|
directory: undefined,
|
||||||
|
disabled: false,
|
||||||
|
helpText: '',
|
||||||
|
maxSize: 2,
|
||||||
|
maxNumber: 1,
|
||||||
|
accept: () => [],
|
||||||
|
multiple: false,
|
||||||
|
api: undefined,
|
||||||
|
resultField: '',
|
||||||
|
showDescription: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const emit = defineEmits(['change', 'update:value', 'delete']);
|
||||||
|
const { accept, helpText, maxNumber, maxSize } = toRefs(props);
|
||||||
|
const isInnerOperate = ref<boolean>(false);
|
||||||
|
const { getStringAccept } = useUploadType({
|
||||||
|
acceptRef: accept,
|
||||||
|
helpTextRef: helpText,
|
||||||
|
maxNumberRef: maxNumber,
|
||||||
|
maxSizeRef: maxSize,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fileList = ref<UploadProps['fileList']>([]);
|
||||||
|
const isLtMsg = ref<boolean>(true); // 文件大小错误提示
|
||||||
|
const isActMsg = ref<boolean>(true); // 文件类型错误提示
|
||||||
|
const isFirstRender = ref<boolean>(true); // 是否第一次渲染
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
(v) => {
|
||||||
|
if (isInnerOperate.value) {
|
||||||
|
isInnerOperate.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let value: string[] = [];
|
||||||
|
if (v) {
|
||||||
|
if (Array.isArray(v)) {
|
||||||
|
value = v;
|
||||||
|
} else {
|
||||||
|
value.push(v);
|
||||||
|
}
|
||||||
|
fileList.value = value.map((item, i) => {
|
||||||
|
if (item && isString(item)) {
|
||||||
|
return {
|
||||||
|
uid: `${-i}`,
|
||||||
|
name: item.slice(Math.max(0, item.lastIndexOf('/') + 1)),
|
||||||
|
status: UploadResultStatus.DONE,
|
||||||
|
url: item,
|
||||||
|
};
|
||||||
|
} else if (item && isObject(item)) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}) as UploadProps['fileList'];
|
||||||
|
}
|
||||||
|
if (!isFirstRender.value) {
|
||||||
|
emit('change', value);
|
||||||
|
isFirstRender.value = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleRemove = async (file: UploadFile) => {
|
||||||
|
if (fileList.value) {
|
||||||
|
const index = fileList.value.findIndex((item) => item.uid === file.uid);
|
||||||
|
index !== -1 && fileList.value.splice(index, 1);
|
||||||
|
const value = getValue();
|
||||||
|
isInnerOperate.value = true;
|
||||||
|
emit('update:value', value);
|
||||||
|
emit('change', value);
|
||||||
|
emit('delete', file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const beforeUpload = async (file: File) => {
|
||||||
|
const { maxSize, accept } = props;
|
||||||
|
const isAct = checkFileType(file, accept);
|
||||||
|
if (!isAct) {
|
||||||
|
message.error($t('ui.upload.acceptUpload', [accept]));
|
||||||
|
isActMsg.value = false;
|
||||||
|
// 防止弹出多个错误提示
|
||||||
|
setTimeout(() => (isActMsg.value = true), 1000);
|
||||||
|
}
|
||||||
|
const isLt = file.size / 1024 / 1024 > maxSize;
|
||||||
|
if (isLt) {
|
||||||
|
message.error($t('ui.upload.maxSizeMultiple', [maxSize]));
|
||||||
|
isLtMsg.value = false;
|
||||||
|
// 防止弹出多个错误提示
|
||||||
|
setTimeout(() => (isLtMsg.value = true), 1000);
|
||||||
|
}
|
||||||
|
return (isAct && !isLt) || Upload.LIST_IGNORE;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function customRequest(info: UploadRequestOption<any>) {
|
||||||
|
let { api } = props;
|
||||||
|
if (!api || !isFunction(api)) {
|
||||||
|
api = useUpload(props.directory).httpRequest;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// 上传文件
|
||||||
|
const progressEvent: AxiosProgressEvent = (e) => {
|
||||||
|
const percent = Math.trunc((e.loaded / e.total!) * 100);
|
||||||
|
info.onProgress!({ percent });
|
||||||
|
};
|
||||||
|
const res = await api?.(info.file as File, progressEvent);
|
||||||
|
info.onSuccess!(res);
|
||||||
|
message.success($t('ui.upload.uploadSuccess'));
|
||||||
|
|
||||||
|
// 更新文件
|
||||||
|
const value = getValue();
|
||||||
|
isInnerOperate.value = true;
|
||||||
|
emit('update:value', value);
|
||||||
|
emit('change', value);
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error);
|
||||||
|
info.onError!(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValue() {
|
||||||
|
const list = (fileList.value || [])
|
||||||
|
.filter((item) => item?.status === UploadResultStatus.DONE)
|
||||||
|
.map((item: any) => {
|
||||||
|
if (item?.response && props?.resultField) {
|
||||||
|
return item?.response;
|
||||||
|
}
|
||||||
|
return item?.url || item?.response?.url || item?.response;
|
||||||
|
});
|
||||||
|
// add by 芋艿:【特殊】单个文件的情况,获取首个元素,保证返回的是 String 类型
|
||||||
|
if (props.maxNumber === 1) {
|
||||||
|
return list.length > 0 ? list[0] : '';
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NUpload
|
||||||
|
v-bind="$attrs"
|
||||||
|
v-model:file-list="fileList"
|
||||||
|
:accept="getStringAccept"
|
||||||
|
:before-upload="beforeUpload"
|
||||||
|
:custom-request="customRequest"
|
||||||
|
:disabled="disabled"
|
||||||
|
:max-count="maxNumber"
|
||||||
|
:multiple="multiple"
|
||||||
|
list-type="text"
|
||||||
|
:progress="{ showInfo: true }"
|
||||||
|
@remove="handleRemove"
|
||||||
|
>
|
||||||
|
<div v-if="fileList && fileList.length < maxNumber">
|
||||||
|
<NButton>
|
||||||
|
<CloudUpload />
|
||||||
|
{{ $t('ui.upload.upload') }}
|
||||||
|
</NButton>
|
||||||
|
</div>
|
||||||
|
<div v-if="showDescription" class="mt-2 flex flex-wrap items-center">
|
||||||
|
请上传不超过
|
||||||
|
<div class="text-primary mx-1 font-bold">{{ maxSize }}MB</div>
|
||||||
|
的
|
||||||
|
<div class="text-primary mx-1 font-bold">{{ accept.join('/') }}</div>
|
||||||
|
格式文件
|
||||||
|
</div>
|
||||||
|
</NUpload>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,276 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { UploadFile, UploadProps } from 'naive-ui';
|
||||||
|
import type { UploadRequestOption } from 'naive-ui/lib/vc-upload/interface';
|
||||||
|
|
||||||
|
import type { AxiosResponse } from '@vben/request';
|
||||||
|
|
||||||
|
import type { UploadListType } from './typing';
|
||||||
|
|
||||||
|
import type { AxiosProgressEvent } from '#/api/infra/file';
|
||||||
|
|
||||||
|
import { ref, toRefs, watch } from 'vue';
|
||||||
|
|
||||||
|
import { CloudUpload } from '@vben/icons';
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
|
import { isFunction, isObject, isString } from '@vben/utils';
|
||||||
|
|
||||||
|
import { NModal, NUpload } from 'naive-ui';
|
||||||
|
|
||||||
|
import { message } from '#/adapter/naive';
|
||||||
|
|
||||||
|
import { checkImgType, defaultImageAccepts } from './helper';
|
||||||
|
import { UploadResultStatus } from './typing';
|
||||||
|
import { useUpload, useUploadType } from './use-upload';
|
||||||
|
|
||||||
|
defineOptions({ name: 'ImageUpload', inheritAttrs: false });
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
// 根据后缀,或者其他
|
||||||
|
accept?: string[];
|
||||||
|
api?: (
|
||||||
|
file: File,
|
||||||
|
onUploadProgress?: AxiosProgressEvent,
|
||||||
|
) => Promise<AxiosResponse<any>>;
|
||||||
|
// 上传的目录
|
||||||
|
directory?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
helpText?: string;
|
||||||
|
listType?: UploadListType;
|
||||||
|
// 最大数量的文件,Infinity不限制
|
||||||
|
maxNumber?: number;
|
||||||
|
// 文件最大多少MB
|
||||||
|
maxSize?: number;
|
||||||
|
// 是否支持多选
|
||||||
|
multiple?: boolean;
|
||||||
|
// support xxx.xxx.xx
|
||||||
|
resultField?: string;
|
||||||
|
// 是否显示下面的描述
|
||||||
|
showDescription?: boolean;
|
||||||
|
value?: string | string[];
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
value: () => [],
|
||||||
|
directory: undefined,
|
||||||
|
disabled: false,
|
||||||
|
listType: 'picture-card',
|
||||||
|
helpText: '',
|
||||||
|
maxSize: 2,
|
||||||
|
maxNumber: 1,
|
||||||
|
accept: () => defaultImageAccepts,
|
||||||
|
multiple: false,
|
||||||
|
api: undefined,
|
||||||
|
resultField: '',
|
||||||
|
showDescription: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const emit = defineEmits(['change', 'update:value', 'delete']);
|
||||||
|
const { accept, helpText, maxNumber, maxSize } = toRefs(props);
|
||||||
|
const isInnerOperate = ref<boolean>(false);
|
||||||
|
const { getStringAccept } = useUploadType({
|
||||||
|
acceptRef: accept,
|
||||||
|
helpTextRef: helpText,
|
||||||
|
maxNumberRef: maxNumber,
|
||||||
|
maxSizeRef: maxSize,
|
||||||
|
});
|
||||||
|
const previewOpen = ref<boolean>(false); // 是否展示预览
|
||||||
|
const previewImage = ref<string>(''); // 预览图片
|
||||||
|
const previewTitle = ref<string>(''); // 预览标题
|
||||||
|
|
||||||
|
const fileList = ref<UploadProps['fileList']>([]);
|
||||||
|
const isLtMsg = ref<boolean>(true); // 文件大小错误提示
|
||||||
|
const isActMsg = ref<boolean>(true); // 文件类型错误提示
|
||||||
|
const isFirstRender = ref<boolean>(true); // 是否第一次渲染
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.value,
|
||||||
|
async (v) => {
|
||||||
|
if (isInnerOperate.value) {
|
||||||
|
isInnerOperate.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let value: string | string[] = [];
|
||||||
|
if (v) {
|
||||||
|
if (Array.isArray(v)) {
|
||||||
|
value = v;
|
||||||
|
} else {
|
||||||
|
value.push(v);
|
||||||
|
}
|
||||||
|
fileList.value = value.map((item, i) => {
|
||||||
|
if (item && isString(item)) {
|
||||||
|
return {
|
||||||
|
uid: `${-i}`,
|
||||||
|
name: item.slice(Math.max(0, item.lastIndexOf('/') + 1)),
|
||||||
|
status: UploadResultStatus.DONE,
|
||||||
|
url: item,
|
||||||
|
};
|
||||||
|
} else if (item && isObject(item)) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}) as UploadProps['fileList'];
|
||||||
|
}
|
||||||
|
if (!isFirstRender.value) {
|
||||||
|
emit('change', value);
|
||||||
|
isFirstRender.value = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
function getBase64<T extends ArrayBuffer | null | string>(file: File) {
|
||||||
|
return new Promise<T>((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
reader.addEventListener('load', () => {
|
||||||
|
resolve(reader.result as T);
|
||||||
|
});
|
||||||
|
reader.addEventListener('error', (error) => reject(error));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePreview = async (file: UploadFile) => {
|
||||||
|
if (!file.url && !file.preview) {
|
||||||
|
file.preview = await getBase64<string>(file.originFileObj!);
|
||||||
|
}
|
||||||
|
previewImage.value = file.url || file.preview || '';
|
||||||
|
previewOpen.value = true;
|
||||||
|
previewTitle.value =
|
||||||
|
file.name ||
|
||||||
|
previewImage.value.slice(
|
||||||
|
Math.max(0, previewImage.value.lastIndexOf('/') + 1),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemove = async (file: UploadFile) => {
|
||||||
|
if (fileList.value) {
|
||||||
|
const index = fileList.value.findIndex((item) => item.uid === file.uid);
|
||||||
|
index !== -1 && fileList.value.splice(index, 1);
|
||||||
|
const value = getValue();
|
||||||
|
isInnerOperate.value = true;
|
||||||
|
emit('update:value', value);
|
||||||
|
emit('change', value);
|
||||||
|
emit('delete', file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => {
|
||||||
|
previewOpen.value = false;
|
||||||
|
previewTitle.value = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const beforeUpload = async (file: File) => {
|
||||||
|
const { maxSize, accept } = props;
|
||||||
|
const isAct = checkImgType(file, accept);
|
||||||
|
if (!isAct) {
|
||||||
|
message.error($t('ui.upload.acceptUpload', [accept]));
|
||||||
|
isActMsg.value = false;
|
||||||
|
// 防止弹出多个错误提示
|
||||||
|
setTimeout(() => (isActMsg.value = true), 1000);
|
||||||
|
}
|
||||||
|
const isLt = file.size / 1024 / 1024 > maxSize;
|
||||||
|
if (isLt) {
|
||||||
|
message.error($t('ui.upload.maxSizeMultiple', [maxSize]));
|
||||||
|
isLtMsg.value = false;
|
||||||
|
// 防止弹出多个错误提示
|
||||||
|
setTimeout(() => (isLtMsg.value = true), 1000);
|
||||||
|
}
|
||||||
|
return (isAct && !isLt) || Upload.LIST_IGNORE;
|
||||||
|
};
|
||||||
|
|
||||||
|
async function customRequest(info: UploadRequestOption<any>) {
|
||||||
|
let { api } = props;
|
||||||
|
if (!api || !isFunction(api)) {
|
||||||
|
api = useUpload(props.directory).httpRequest;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// 上传文件
|
||||||
|
const progressEvent: AxiosProgressEvent = (e) => {
|
||||||
|
const percent = Math.trunc((e.loaded / e.total!) * 100);
|
||||||
|
info.onProgress!({ percent });
|
||||||
|
};
|
||||||
|
const res = await api?.(info.file as File, progressEvent);
|
||||||
|
info.onSuccess!(res);
|
||||||
|
message.success($t('ui.upload.uploadSuccess'));
|
||||||
|
|
||||||
|
// 更新文件
|
||||||
|
const value = getValue();
|
||||||
|
isInnerOperate.value = true;
|
||||||
|
emit('update:value', value);
|
||||||
|
emit('change', value);
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error);
|
||||||
|
info.onError!(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getValue() {
|
||||||
|
const list = (fileList.value || [])
|
||||||
|
.filter((item) => item?.status === UploadResultStatus.DONE)
|
||||||
|
.map((item: any) => {
|
||||||
|
if (item?.response && props?.resultField) {
|
||||||
|
return item?.response;
|
||||||
|
}
|
||||||
|
return item?.url || item?.response?.url || item?.response;
|
||||||
|
});
|
||||||
|
// add by 芋艿:【特殊】单个文件的情况,获取首个元素,保证返回的是 String 类型
|
||||||
|
if (props.maxNumber === 1) {
|
||||||
|
return list.length > 0 ? list[0] : '';
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<NUpload
|
||||||
|
v-bind="$attrs"
|
||||||
|
v-model:file-list="fileList"
|
||||||
|
:accept="getStringAccept"
|
||||||
|
:before-upload="beforeUpload"
|
||||||
|
:custom-request="customRequest"
|
||||||
|
:disabled="disabled"
|
||||||
|
:list-type="listType"
|
||||||
|
:max-count="maxNumber"
|
||||||
|
:multiple="multiple"
|
||||||
|
:progress="{ showInfo: true }"
|
||||||
|
@preview="handlePreview"
|
||||||
|
@remove="handleRemove"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="fileList && fileList.length < maxNumber"
|
||||||
|
class="flex flex-col items-center justify-center"
|
||||||
|
>
|
||||||
|
<CloudUpload />
|
||||||
|
<div class="mt-2">{{ $t('ui.upload.imgUpload') }}</div>
|
||||||
|
</div>
|
||||||
|
</NUpload>
|
||||||
|
<div
|
||||||
|
v-if="showDescription"
|
||||||
|
class="mt-2 flex flex-wrap items-center text-[14px]"
|
||||||
|
>
|
||||||
|
请上传不超过
|
||||||
|
<div class="text-primary mx-1 font-bold">{{ maxSize }}MB</div>
|
||||||
|
的
|
||||||
|
<div class="text-primary mx-1 font-bold">{{ accept.join('/') }}</div>
|
||||||
|
格式文件
|
||||||
|
</div>
|
||||||
|
<NModal
|
||||||
|
:footer="null"
|
||||||
|
:open="previewOpen"
|
||||||
|
:title="previewTitle"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
>
|
||||||
|
<img :src="previewImage" alt="" class="w-full" />
|
||||||
|
</NModal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.ant-upload-select-picture-card {
|
||||||
|
@apply flex items-center justify-center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { default as FileUpload } from './file-upload.vue';
|
||||||
|
export { default as ImageUpload } from './image-upload.vue';
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
export enum UploadResultStatus {
|
||||||
|
DONE = 'done',
|
||||||
|
ERROR = 'error',
|
||||||
|
SUCCESS = 'success',
|
||||||
|
UPLOADING = 'uploading',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UploadListType = 'picture' | 'picture-card' | 'text';
|
||||||
|
|
@ -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<string[]>;
|
||||||
|
helpTextRef: Ref<string>;
|
||||||
|
maxNumberRef: Ref<number>;
|
||||||
|
maxSizeRef: Ref<number>;
|
||||||
|
}) {
|
||||||
|
// 文件类型限制
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { NotificationItem } from '@vben/layouts';
|
import type { NotificationItem } from '@vben/layouts';
|
||||||
|
|
||||||
import { computed, ref, watch } from 'vue';
|
import { computed, onMounted, ref, watch } from 'vue';
|
||||||
|
|
||||||
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
|
import { AuthenticationLoginExpiredModal, useVbenModal } from '@vben/common-ui';
|
||||||
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants';
|
||||||
import { useWatermark } from '@vben/hooks';
|
import { useWatermark } from '@vben/hooks';
|
||||||
import { BookOpenText, CircleHelp, MdiGithub } from '@vben/icons';
|
import {
|
||||||
|
AntdProfileOutlined,
|
||||||
|
BookOpenText,
|
||||||
|
CircleHelp,
|
||||||
|
MdiGithub,
|
||||||
|
} from '@vben/icons';
|
||||||
import {
|
import {
|
||||||
BasicLayout,
|
BasicLayout,
|
||||||
LockScreen,
|
LockScreen,
|
||||||
|
|
@ -15,52 +20,43 @@ import {
|
||||||
} from '@vben/layouts';
|
} from '@vben/layouts';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import { useAccessStore, useUserStore } from '@vben/stores';
|
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||||
import { openWindow } from '@vben/utils';
|
import { formatDateTime, openWindow } from '@vben/utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getUnreadNotifyMessageCount,
|
||||||
|
getUnreadNotifyMessageList,
|
||||||
|
updateAllNotifyMessageRead,
|
||||||
|
updateNotifyMessageRead,
|
||||||
|
} from '#/api/system/notify/message';
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
import { router } from '#/router';
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
import LoginForm from '#/views/_core/authentication/login.vue';
|
import LoginForm from '#/views/_core/authentication/login.vue';
|
||||||
|
|
||||||
const notifications = ref<NotificationItem[]>([
|
import Help from './components/help.vue';
|
||||||
{
|
import TenantDropdown from './components/tenant-dropdown.vue';
|
||||||
avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB',
|
|
||||||
date: '3小时前',
|
|
||||||
isRead: true,
|
|
||||||
message: '描述信息描述信息描述信息',
|
|
||||||
title: '收到了 14 份新周报',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
avatar: 'https://avatar.vercel.sh/1',
|
|
||||||
date: '刚刚',
|
|
||||||
isRead: false,
|
|
||||||
message: '描述信息描述信息描述信息',
|
|
||||||
title: '朱偏右 回复了你',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
avatar: 'https://avatar.vercel.sh/1',
|
|
||||||
date: '2024-01-01',
|
|
||||||
isRead: false,
|
|
||||||
message: '描述信息描述信息描述信息',
|
|
||||||
title: '曲丽丽 评论了你',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
avatar: 'https://avatar.vercel.sh/satori',
|
|
||||||
date: '1天前',
|
|
||||||
isRead: false,
|
|
||||||
message: '描述信息描述信息描述信息',
|
|
||||||
title: '代办提醒',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
const accessStore = useAccessStore();
|
const accessStore = useAccessStore();
|
||||||
const { destroyWatermark, updateWatermark } = useWatermark();
|
const { destroyWatermark, updateWatermark } = useWatermark();
|
||||||
const showDot = computed(() =>
|
|
||||||
notifications.value.some((item) => !item.isRead),
|
const notifications = ref<NotificationItem[]>([]);
|
||||||
);
|
const unreadCount = ref(0);
|
||||||
|
const showDot = computed(() => unreadCount.value > 0);
|
||||||
|
|
||||||
|
const [HelpModal, helpModalApi] = useVbenModal({
|
||||||
|
connectedComponent: Help,
|
||||||
|
});
|
||||||
|
|
||||||
const menus = computed(() => [
|
const menus = computed(() => [
|
||||||
|
{
|
||||||
|
handler: () => {
|
||||||
|
router.push({ name: 'Profile' });
|
||||||
|
},
|
||||||
|
icon: AntdProfileOutlined,
|
||||||
|
text: $t('ui.widgets.profile'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
handler: () => {
|
handler: () => {
|
||||||
openWindow(VBEN_DOC_URL, {
|
openWindow(VBEN_DOC_URL, {
|
||||||
|
|
@ -81,9 +77,7 @@ const menus = computed(() => [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
handler: () => {
|
handler: () => {
|
||||||
openWindow(`${VBEN_GITHUB_URL}/issues`, {
|
helpModalApi.open();
|
||||||
target: '_blank',
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
icon: CircleHelp,
|
icon: CircleHelp,
|
||||||
text: $t('ui.widgets.qa'),
|
text: $t('ui.widgets.qa'),
|
||||||
|
|
@ -98,20 +92,83 @@ async function handleLogout() {
|
||||||
await authStore.logout(false);
|
await authStore.logout(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleNoticeClear() {
|
/** 获得未读消息数 */
|
||||||
|
async function handleNotificationGetUnreadCount() {
|
||||||
|
unreadCount.value = await getUnreadNotifyMessageCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获得消息列表 */
|
||||||
|
async function handleNotificationGetList() {
|
||||||
|
const list = await getUnreadNotifyMessageList();
|
||||||
|
notifications.value = list.map((item) => ({
|
||||||
|
avatar: preferences.app.defaultAvatar,
|
||||||
|
date: formatDateTime(item.createTime) as string,
|
||||||
|
isRead: false,
|
||||||
|
id: item.id,
|
||||||
|
message: item.templateContent,
|
||||||
|
title: item.templateNickname,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 跳转我的站内信 */
|
||||||
|
function handleNotificationViewAll() {
|
||||||
|
router.push({
|
||||||
|
name: 'MyNotifyMessage',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 标记所有已读 */
|
||||||
|
async function handleNotificationMakeAll() {
|
||||||
|
await updateAllNotifyMessageRead();
|
||||||
|
unreadCount.value = 0;
|
||||||
notifications.value = [];
|
notifications.value = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMakeAll() {
|
/** 清空通知 */
|
||||||
notifications.value.forEach((item) => (item.isRead = true));
|
async function handleNotificationClear() {
|
||||||
|
handleNotificationMakeAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 标记单个已读 */
|
||||||
|
async function handleNotificationRead(item: NotificationItem) {
|
||||||
|
if (!item.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await updateNotifyMessageRead([item.id]);
|
||||||
|
await handleNotificationGetUnreadCount();
|
||||||
|
notifications.value = notifications.value.filter((n) => n.id !== item.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 处理通知打开 */
|
||||||
|
function handleNotificationOpen(open: boolean) {
|
||||||
|
if (!open) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleNotificationGetList();
|
||||||
|
handleNotificationGetUnreadCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 初始化 ==========
|
||||||
|
onMounted(() => {
|
||||||
|
// 首次加载未读数量
|
||||||
|
handleNotificationGetUnreadCount();
|
||||||
|
// 轮询刷新未读数量
|
||||||
|
setInterval(
|
||||||
|
() => {
|
||||||
|
if (userStore.userInfo) {
|
||||||
|
handleNotificationGetUnreadCount();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
1000 * 60 * 2,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => preferences.app.watermark,
|
() => preferences.app.watermark,
|
||||||
async (enable) => {
|
async (enable) => {
|
||||||
if (enable) {
|
if (enable) {
|
||||||
await updateWatermark({
|
await updateWatermark({
|
||||||
content: `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`,
|
content: `${userStore.userInfo?.id} - ${userStore.userInfo?.nickname}`,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
destroyWatermark();
|
destroyWatermark();
|
||||||
|
|
@ -129,9 +186,9 @@ watch(
|
||||||
<UserDropdown
|
<UserDropdown
|
||||||
:avatar
|
:avatar
|
||||||
:menus
|
:menus
|
||||||
:text="userStore.userInfo?.realName"
|
:text="userStore.userInfo?.nickname"
|
||||||
description="ann.vben@gmail.com"
|
:description="userStore.userInfo?.email"
|
||||||
tag-text="Pro"
|
:tag-text="userStore.userInfo?.username"
|
||||||
@logout="handleLogout"
|
@logout="handleLogout"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -139,10 +196,16 @@ watch(
|
||||||
<Notification
|
<Notification
|
||||||
:dot="showDot"
|
:dot="showDot"
|
||||||
:notifications="notifications"
|
:notifications="notifications"
|
||||||
@clear="handleNoticeClear"
|
@clear="handleNotificationClear"
|
||||||
@make-all="handleMakeAll"
|
@make-all="handleNotificationMakeAll"
|
||||||
|
@view-all="handleNotificationViewAll"
|
||||||
|
@open="handleNotificationOpen"
|
||||||
|
@read="handleNotificationRead"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
<template #header-right-1>
|
||||||
|
<TenantDropdown class="w-30 mr-2" />
|
||||||
|
</template>
|
||||||
<template #extra>
|
<template #extra>
|
||||||
<AuthenticationLoginExpiredModal
|
<AuthenticationLoginExpiredModal
|
||||||
v-model:open="accessStore.loginExpired"
|
v-model:open="accessStore.loginExpired"
|
||||||
|
|
@ -155,4 +218,5 @@ watch(
|
||||||
<LockScreen :avatar @to-login="handleLogout" />
|
<LockScreen :avatar @to-login="handleLogout" />
|
||||||
</template>
|
</template>
|
||||||
</BasicLayout>
|
</BasicLayout>
|
||||||
|
<HelpModal />
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useVbenModal, VbenButton, VbenButtonGroup } from '@vben/common-ui';
|
||||||
|
import { openWindow } from '@vben/utils';
|
||||||
|
|
||||||
|
import { NImage, NTag } from 'naive-ui';
|
||||||
|
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
const [Modal, modalApi] = useVbenModal({
|
||||||
|
draggable: true,
|
||||||
|
overlayBlur: 5,
|
||||||
|
footer: false,
|
||||||
|
onCancel() {
|
||||||
|
modalApi.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Modal class="w-[40%]" :title="$t('ui.widgets.qa')">
|
||||||
|
<div class="mt-2 flex flex-col">
|
||||||
|
<div class="mt-2 flex flex-row">
|
||||||
|
<VbenButtonGroup class="basis-1/3" :gap="2" border size="large">
|
||||||
|
<p class="p-2">项目地址:</p>
|
||||||
|
<VbenButton
|
||||||
|
variant="link"
|
||||||
|
@click="
|
||||||
|
openWindow('https://gitee.com/yudaocode/yudao-ui-admin-vben')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Gitee
|
||||||
|
</VbenButton>
|
||||||
|
<VbenButton
|
||||||
|
variant="link"
|
||||||
|
@click="
|
||||||
|
openWindow('https://github.com/yudaocode/yudao-ui-admin-vben')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Github
|
||||||
|
</VbenButton>
|
||||||
|
</VbenButtonGroup>
|
||||||
|
|
||||||
|
<VbenButtonGroup class="basis-1/3" :gap="2" border size="large">
|
||||||
|
<p class="p-2">issues:</p>
|
||||||
|
<VbenButton
|
||||||
|
variant="link"
|
||||||
|
@click="
|
||||||
|
openWindow(
|
||||||
|
'https://gitee.com/yudaocode/yudao-ui-admin-vben/issues',
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Gitee
|
||||||
|
</VbenButton>
|
||||||
|
<VbenButton
|
||||||
|
variant="link"
|
||||||
|
@click="
|
||||||
|
openWindow(
|
||||||
|
'https://github.com/yudaocode/yudao-ui-admin-vben/issues',
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
Github
|
||||||
|
</VbenButton>
|
||||||
|
</VbenButtonGroup>
|
||||||
|
|
||||||
|
<VbenButtonGroup class="basis-1/3" :gap="2" border size="large">
|
||||||
|
<p class="p-2">开发文档:</p>
|
||||||
|
<VbenButton
|
||||||
|
variant="link"
|
||||||
|
@click="openWindow('https://doc.iocoder.cn/quick-start/')"
|
||||||
|
>
|
||||||
|
项目文档
|
||||||
|
</VbenButton>
|
||||||
|
<VbenButton variant="link" @click="openWindow('https://antdv.com/')">
|
||||||
|
antdv 文档
|
||||||
|
</VbenButton>
|
||||||
|
</VbenButtonGroup>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2 flex justify-center">
|
||||||
|
<span>
|
||||||
|
<NImage src="/wx-xingyu.png" alt="数舵科技" />
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p class="mt-2 flex justify-center pt-4 text-sm italic">
|
||||||
|
本项目采用<NTag color="blue">MIT</NTag>开源协议,个人与企业可100%
|
||||||
|
免费使用。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { SelectValue } from 'naive-ui/es/select';
|
||||||
|
|
||||||
|
import type { SystemTenantApi } from '#/api/system/tenant';
|
||||||
|
|
||||||
|
import { onMounted, ref } from 'vue';
|
||||||
|
|
||||||
|
import { useAccess } from '@vben/access';
|
||||||
|
import { isTenantEnable, useTabs } from '@vben/hooks';
|
||||||
|
import { useAccessStore } from '@vben/stores';
|
||||||
|
|
||||||
|
import { NSelect } from 'naive-ui';
|
||||||
|
|
||||||
|
import { message } from '#/adapter/naive';
|
||||||
|
import { getSimpleTenantList } from '#/api/system/tenant';
|
||||||
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
|
const { closeOtherTabs, refreshTab } = useTabs();
|
||||||
|
|
||||||
|
const { hasAccessByCodes } = useAccess();
|
||||||
|
const accessStore = useAccessStore();
|
||||||
|
|
||||||
|
const tenantEnable = isTenantEnable();
|
||||||
|
|
||||||
|
const value = ref<number>(accessStore.visitTenantId ?? undefined); // 当前访问的租户 ID
|
||||||
|
const tenants = ref<SystemTenantApi.Tenant[]>([]); // 租户列表
|
||||||
|
|
||||||
|
async function handleChange(id: SelectValue) {
|
||||||
|
// 设置访问租户 ID
|
||||||
|
accessStore.setVisitTenantId(id as number);
|
||||||
|
// 关闭其他标签页,只保留当前页
|
||||||
|
await closeOtherTabs();
|
||||||
|
// 刷新当前页面
|
||||||
|
await refreshTab();
|
||||||
|
// 提示切换成功
|
||||||
|
const tenant = tenants.value.find((item) => item.id === id);
|
||||||
|
if (tenant) {
|
||||||
|
message.success(`切换当前租户为: ${tenant.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (!tenantEnable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tenants.value = await getSimpleTenantList();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div v-if="tenantEnable && hasAccessByCodes(['system:tenant:visit'])">
|
||||||
|
<NSelect
|
||||||
|
v-model:value="value"
|
||||||
|
:field-names="{ label: 'name', value: 'id' }"
|
||||||
|
:options="tenants"
|
||||||
|
:placeholder="$t('page.tenant.placeholder')"
|
||||||
|
:dropdown-style="{ position: 'fixed', zIndex: 1666 }"
|
||||||
|
allow-clear
|
||||||
|
class="w-40"
|
||||||
|
@change="handleChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -10,5 +10,23 @@
|
||||||
"title": "Dashboard",
|
"title": "Dashboard",
|
||||||
"analytics": "Analytics",
|
"analytics": "Analytics",
|
||||||
"workspace": "Workspace"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 版本"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -10,5 +10,23 @@
|
||||||
"title": "概览",
|
"title": "概览",
|
||||||
"analytics": "分析页",
|
"analytics": "分析页",
|
||||||
"workspace": "工作台"
|
"workspace": "工作台"
|
||||||
|
},
|
||||||
|
"action": {
|
||||||
|
"action": "操作",
|
||||||
|
"add": "新增",
|
||||||
|
"edit": "编辑",
|
||||||
|
"delete": "删除",
|
||||||
|
"save": "保存",
|
||||||
|
"import": "导入",
|
||||||
|
"export": "导出",
|
||||||
|
"submit": "提交",
|
||||||
|
"cancel": "取消",
|
||||||
|
"confirm": "确认",
|
||||||
|
"reset": "重置",
|
||||||
|
"search": "搜索"
|
||||||
|
},
|
||||||
|
"tenant": {
|
||||||
|
"placeholder": "请选择租户",
|
||||||
|
"success": "切换租户成功"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"rangePicker": {
|
||||||
|
"today": "今天",
|
||||||
|
"last7Days": "最近 7 天",
|
||||||
|
"last30Days": "最近 30 天",
|
||||||
|
"yesterday": "昨天",
|
||||||
|
"thisWeek": "本周",
|
||||||
|
"thisMonth": "本月",
|
||||||
|
"lastWeek": "上周",
|
||||||
|
"lastMonth": "上月",
|
||||||
|
"beginTime": "开始时间",
|
||||||
|
"endTime": "结束时间"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,18 @@ import { defineOverridesPreferences } from '@vben/preferences';
|
||||||
export const overridesPreferences = defineOverridesPreferences({
|
export const overridesPreferences = defineOverridesPreferences({
|
||||||
// overrides
|
// overrides
|
||||||
app: {
|
app: {
|
||||||
|
/** 后端路由模式 */
|
||||||
|
accessMode: 'backend',
|
||||||
name: import.meta.env.VITE_APP_TITLE,
|
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',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,21 @@
|
||||||
import type {
|
import type {
|
||||||
|
AppRouteRecordRaw,
|
||||||
ComponentRecordType,
|
ComponentRecordType,
|
||||||
GenerateMenuAndRoutesOptions,
|
GenerateMenuAndRoutesOptions,
|
||||||
} from '@vben/types';
|
} from '@vben/types';
|
||||||
|
|
||||||
import { generateAccessible } from '@vben/access';
|
import { generateAccessible } from '@vben/access';
|
||||||
import { preferences } from '@vben/preferences';
|
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 { BasicLayout, IFrameView } from '#/layouts';
|
||||||
import { $t } from '#/locales';
|
|
||||||
|
|
||||||
const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue');
|
const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue');
|
||||||
|
|
||||||
async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
||||||
const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue');
|
const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue');
|
||||||
|
const accessStore = useAccessStore();
|
||||||
|
|
||||||
const layoutMap: ComponentRecordType = {
|
const layoutMap: ComponentRecordType = {
|
||||||
BasicLayout,
|
BasicLayout,
|
||||||
|
|
@ -24,10 +25,10 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
||||||
return await generateAccessible(preferences.app.accessMode, {
|
return await generateAccessible(preferences.app.accessMode, {
|
||||||
...options,
|
...options,
|
||||||
fetchMenuListAsync: async () => {
|
fetchMenuListAsync: async () => {
|
||||||
message.loading(`${$t('common.loadingMenu')}...`, {
|
// 由于 yudao 通过 accessStore 读取,所以不在进行 message.loading 提示
|
||||||
duration: 1.5,
|
// 补充说明:accessStore.accessMenus 一开始是 AppRouteRecordRaw 类型(后端加载),后面被赋值成 MenuRecordRaw 类型(前端转换)
|
||||||
});
|
const accessMenus = accessStore.accessMenus as AppRouteRecordRaw[];
|
||||||
return await getAllMenusApi();
|
return convertServerMenuToRouteRecordStringComponent(accessMenus);
|
||||||
},
|
},
|
||||||
// 可以指定没有权限跳转403页面
|
// 可以指定没有权限跳转403页面
|
||||||
forbiddenComponent,
|
forbiddenComponent,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
import type { Router } from 'vue-router';
|
import type { Router } from 'vue-router';
|
||||||
|
|
||||||
import { LOGIN_PATH } from '@vben/constants';
|
import { LOGIN_PATH } from '@vben/constants';
|
||||||
|
import { $t } from '@vben/locales';
|
||||||
import { preferences } from '@vben/preferences';
|
import { preferences } from '@vben/preferences';
|
||||||
import { useAccessStore, useUserStore } from '@vben/stores';
|
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||||
import { startProgress, stopProgress } from '@vben/utils';
|
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 { accessRoutes, coreRouteNames } from '#/router/routes';
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore, useDictStore } from '#/store';
|
||||||
|
|
||||||
import { generateAccess } from './access';
|
import { generateAccess } from './access';
|
||||||
|
|
||||||
|
|
@ -49,6 +52,7 @@ function setupAccessGuard(router: Router) {
|
||||||
const accessStore = useAccessStore();
|
const accessStore = useAccessStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
|
const dictStore = useDictStore();
|
||||||
|
|
||||||
// 基本路由,这些路由不需要进入权限拦截
|
// 基本路由,这些路由不需要进入权限拦截
|
||||||
if (coreRouteNames.includes(to.name as string)) {
|
if (coreRouteNames.includes(to.name as string)) {
|
||||||
|
|
@ -89,10 +93,26 @@ function setupAccessGuard(router: Router) {
|
||||||
if (accessStore.isAccessChecked) {
|
if (accessStore.isAccessChecked) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载字典数据(不阻塞加载)
|
||||||
|
dictStore.setDictCacheByApi(getSimpleDictDataList);
|
||||||
|
|
||||||
// 生成路由表
|
// 生成路由表
|
||||||
// 当前登录用户拥有的角色标识列表
|
// 当前登录用户拥有的角色标识列表
|
||||||
const userInfo = userStore.userInfo || (await authStore.fetchUserInfo());
|
let userInfo = userStore.userInfo;
|
||||||
const userRoles = userInfo.roles ?? [];
|
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({
|
const { accessibleMenus, accessibleRoutes } = await generateAccess({
|
||||||
|
|
@ -106,9 +126,10 @@ function setupAccessGuard(router: Router) {
|
||||||
accessStore.setAccessMenus(accessibleMenus);
|
accessStore.setAccessMenus(accessibleMenus);
|
||||||
accessStore.setAccessRoutes(accessibleRoutes);
|
accessStore.setAccessRoutes(accessibleRoutes);
|
||||||
accessStore.setIsAccessChecked(true);
|
accessStore.setIsAccessChecked(true);
|
||||||
|
userStore.setUserRoles(userRoles);
|
||||||
const redirectPath = (from.query.redirect ??
|
const redirectPath = (from.query.redirect ??
|
||||||
(to.path === preferences.app.defaultHomePath
|
(to.path === preferences.app.defaultHomePath
|
||||||
? userInfo.homePath || preferences.app.defaultHomePath
|
? userInfo?.homePath || preferences.app.defaultHomePath
|
||||||
: to.fullPath)) as string;
|
: to.fullPath)) as string;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { resetStaticRoutes } from '@vben/utils';
|
||||||
|
|
||||||
import { createRouterGuard } from './guard';
|
import { createRouterGuard } from './guard';
|
||||||
import { routes } from './routes';
|
import { routes } from './routes';
|
||||||
|
import { setupBaiduTongJi } from './tongji';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @zh_CN 创建vue-router实例
|
* @zh_CN 创建vue-router实例
|
||||||
|
|
@ -33,5 +34,7 @@ const resetRoutes = () => resetStaticRoutes(router, routes);
|
||||||
|
|
||||||
// 创建路由守卫
|
// 创建路由守卫
|
||||||
createRouterGuard(router);
|
createRouterGuard(router);
|
||||||
|
// 设置百度统计
|
||||||
|
setupBaiduTongJi(router);
|
||||||
|
|
||||||
export { resetRoutes, router };
|
export { resetRoutes, router };
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,23 @@ const coreRoutes: RouteRecordRaw[] = [
|
||||||
title: $t('page.auth.register'),
|
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'),
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -34,4 +34,14 @@ const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
|
||||||
|
|
||||||
/** 有权限校验的路由列表,包含动态路由和静态路由 */
|
/** 有权限校验的路由列表,包含动态路由和静态路由 */
|
||||||
const accessRoutes = [...dynamicRoutes, ...staticRoutes];
|
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 };
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,15 @@ const routes: RouteRecordRaw[] = [
|
||||||
name: 'Dashboard',
|
name: 'Dashboard',
|
||||||
path: '/dashboard',
|
path: '/dashboard',
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
name: 'Workspace',
|
||||||
|
path: '/workspace',
|
||||||
|
component: () => import('#/views/dashboard/workspace/index.vue'),
|
||||||
|
meta: {
|
||||||
|
icon: 'carbon:workspace',
|
||||||
|
title: $t('page.dashboard.workspace'),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Analytics',
|
name: 'Analytics',
|
||||||
path: '/analytics',
|
path: '/analytics',
|
||||||
|
|
@ -22,17 +31,18 @@ const routes: RouteRecordRaw[] = [
|
||||||
title: $t('page.dashboard.analytics'),
|
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;
|
export default routes;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -1,82 +1,81 @@
|
||||||
import type { RouteRecordRaw } from 'vue-router';
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
import {
|
// import {
|
||||||
VBEN_ANT_PREVIEW_URL,
|
// VBEN_DOC_URL,
|
||||||
VBEN_DOC_URL,
|
// VBEN_ELE_PREVIEW_URL,
|
||||||
VBEN_ELE_PREVIEW_URL,
|
// VBEN_GITHUB_URL,
|
||||||
VBEN_GITHUB_URL,
|
// VBEN_LOGO_URL,
|
||||||
VBEN_LOGO_URL,
|
// VBEN_NAIVE_PREVIEW_URL,
|
||||||
} from '@vben/constants';
|
// } from '@vben/constants';
|
||||||
import { SvgAntdvLogoIcon } from '@vben/icons';
|
//
|
||||||
|
// import { IFrameView } from '#/layouts';
|
||||||
import { IFrameView } from '#/layouts';
|
// import { $t } from '#/locales';
|
||||||
import { $t } from '#/locales';
|
|
||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
// {
|
||||||
meta: {
|
// meta: {
|
||||||
badgeType: 'dot',
|
// badgeType: 'dot',
|
||||||
icon: VBEN_LOGO_URL,
|
// icon: VBEN_LOGO_URL,
|
||||||
order: 9998,
|
// order: 9998,
|
||||||
title: $t('demos.vben.title'),
|
// title: $t('demos.vben.title'),
|
||||||
},
|
// },
|
||||||
name: 'VbenProject',
|
// name: 'VbenProject',
|
||||||
path: '/vben-admin',
|
// path: '/vben-admin',
|
||||||
children: [
|
// children: [
|
||||||
{
|
// {
|
||||||
name: 'VbenDocument',
|
// name: 'VbenDocument',
|
||||||
path: '/vben-admin/document',
|
// path: '/vben-admin/document',
|
||||||
component: IFrameView,
|
// component: IFrameView,
|
||||||
meta: {
|
// meta: {
|
||||||
icon: 'lucide:book-open-text',
|
// icon: 'lucide:book-open-text',
|
||||||
link: VBEN_DOC_URL,
|
// link: VBEN_DOC_URL,
|
||||||
title: $t('demos.vben.document'),
|
// title: $t('demos.vben.document'),
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
name: 'VbenGithub',
|
// name: 'VbenGithub',
|
||||||
path: '/vben-admin/github',
|
// path: '/vben-admin/github',
|
||||||
component: IFrameView,
|
// component: IFrameView,
|
||||||
meta: {
|
// meta: {
|
||||||
icon: 'mdi:github',
|
// icon: 'mdi:github',
|
||||||
link: VBEN_GITHUB_URL,
|
// link: VBEN_GITHUB_URL,
|
||||||
title: 'Github',
|
// title: 'Github',
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
name: 'VbenAntd',
|
// name: 'VbenNaive',
|
||||||
path: '/vben-admin/antd',
|
// path: '/vben-admin/naive',
|
||||||
component: IFrameView,
|
// component: IFrameView,
|
||||||
meta: {
|
// meta: {
|
||||||
badgeType: 'dot',
|
// badgeType: 'dot',
|
||||||
icon: SvgAntdvLogoIcon,
|
// icon: 'logos:naiveui',
|
||||||
link: VBEN_ANT_PREVIEW_URL,
|
// link: VBEN_NAIVE_PREVIEW_URL,
|
||||||
title: $t('demos.vben.antdv'),
|
// title: $t('demos.vben.naive-ui'),
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
name: 'VbenElementPlus',
|
// name: 'VbenElementPlus',
|
||||||
path: '/vben-admin/ele',
|
// path: '/vben-admin/ele',
|
||||||
component: IFrameView,
|
// component: IFrameView,
|
||||||
meta: {
|
// meta: {
|
||||||
badgeType: 'dot',
|
// badgeType: 'dot',
|
||||||
icon: 'logos:element',
|
// icon: 'logos:element',
|
||||||
link: VBEN_ELE_PREVIEW_URL,
|
// link: VBEN_ELE_PREVIEW_URL,
|
||||||
title: $t('demos.vben.element-plus'),
|
// title: $t('demos.vben.element-plus'),
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
],
|
// ],
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
name: 'VbenAbout',
|
// name: 'VbenAbout',
|
||||||
path: '/vben-admin/about',
|
// path: '/vben-admin/about',
|
||||||
component: () => import('#/views/_core/about/index.vue'),
|
// component: () => import('#/views/_core/about/index.vue'),
|
||||||
meta: {
|
// meta: {
|
||||||
icon: 'lucide:copyright',
|
// icon: 'lucide:copyright',
|
||||||
title: $t('demos.vben.about'),
|
// title: $t('demos.vben.about'),
|
||||||
order: 9999,
|
// order: 9999,
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default routes;
|
export default routes; // update by 芋艿:不展示
|
||||||
|
|
|
||||||
|
|
@ -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 };
|
||||||
|
|
@ -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 { ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
@ -10,7 +12,14 @@ import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
|
||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
import { notification } from '#/adapter/naive';
|
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';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', () => {
|
export const useAuthStore = defineStore('auth', () => {
|
||||||
|
|
@ -23,9 +32,12 @@ export const useAuthStore = defineStore('auth', () => {
|
||||||
/**
|
/**
|
||||||
* 异步处理登录操作
|
* 异步处理登录操作
|
||||||
* Asynchronously handle the login process
|
* Asynchronously handle the login process
|
||||||
|
* @param type 登录类型
|
||||||
* @param params 登录表单数据
|
* @param params 登录表单数据
|
||||||
|
* @param onSuccess 登录成功后的回调函数
|
||||||
*/
|
*/
|
||||||
async function authLogin(
|
async function authLogin(
|
||||||
|
type: 'mobile' | 'register' | 'social' | 'username',
|
||||||
params: Recordable<any>,
|
params: Recordable<any>,
|
||||||
onSuccess?: () => Promise<void> | void,
|
onSuccess?: () => Promise<void> | void,
|
||||||
) {
|
) {
|
||||||
|
|
@ -33,23 +45,30 @@ export const useAuthStore = defineStore('auth', () => {
|
||||||
let userInfo: null | UserInfo = null;
|
let userInfo: null | UserInfo = null;
|
||||||
try {
|
try {
|
||||||
loginLoading.value = true;
|
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
|
// 如果成功获取到 accessToken
|
||||||
if (accessToken) {
|
if (accessToken) {
|
||||||
// 将 accessToken 存储到 accessStore 中
|
|
||||||
accessStore.setAccessToken(accessToken);
|
accessStore.setAccessToken(accessToken);
|
||||||
|
accessStore.setRefreshToken(refreshToken);
|
||||||
|
|
||||||
// 获取用户信息并存储到 accessStore 中
|
// 获取用户信息并存储到 userStore、accessStore 中
|
||||||
const [fetchUserInfoResult, accessCodes] = await Promise.all([
|
// TODO @芋艿:清理掉 accessCodes 相关的逻辑
|
||||||
fetchUserInfo(),
|
// const [fetchUserInfoResult, accessCodes] = await Promise.all([
|
||||||
getAccessCodesApi(),
|
// fetchUserInfo(),
|
||||||
]);
|
// // getAccessCodesApi(),
|
||||||
|
// ]);
|
||||||
|
const fetchUserInfoResult = await fetchUserInfo();
|
||||||
|
|
||||||
userInfo = fetchUserInfoResult;
|
userInfo = fetchUserInfoResult.user;
|
||||||
|
|
||||||
userStore.setUserInfo(userInfo);
|
|
||||||
accessStore.setAccessCodes(accessCodes);
|
|
||||||
|
|
||||||
if (accessStore.loginExpired) {
|
if (accessStore.loginExpired) {
|
||||||
accessStore.setLoginExpired(false);
|
accessStore.setLoginExpired(false);
|
||||||
|
|
@ -61,10 +80,10 @@ export const useAuthStore = defineStore('auth', () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userInfo?.realName) {
|
if (userInfo?.nickname) {
|
||||||
notification.success({
|
notification.success({
|
||||||
content: $t('authentication.loginSuccess'),
|
content: $t('authentication.loginSuccess'),
|
||||||
description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`,
|
description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.nickname}`,
|
||||||
duration: 3000,
|
duration: 3000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -80,7 +99,10 @@ export const useAuthStore = defineStore('auth', () => {
|
||||||
|
|
||||||
async function logout(redirect: boolean = true) {
|
async function logout(redirect: boolean = true) {
|
||||||
try {
|
try {
|
||||||
await logoutApi();
|
const accessToken = accessStore.accessToken as string;
|
||||||
|
if (accessToken) {
|
||||||
|
await logoutApi(accessToken);
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// 不做任何处理
|
// 不做任何处理
|
||||||
}
|
}
|
||||||
|
|
@ -99,10 +121,16 @@ export const useAuthStore = defineStore('auth', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchUserInfo() {
|
async function fetchUserInfo() {
|
||||||
let userInfo: null | UserInfo = null;
|
// 加载
|
||||||
userInfo = await getUserInfoApi();
|
let authPermissionInfo: AuthPermissionInfo | null = null;
|
||||||
userStore.setUserInfo(userInfo);
|
authPermissionInfo = await getAuthPermissionInfoApi();
|
||||||
return userInfo;
|
// userStore
|
||||||
|
userStore.setUserInfo(authPermissionInfo.user);
|
||||||
|
userStore.setUserRoles(authPermissionInfo.roles);
|
||||||
|
// accessStore
|
||||||
|
accessStore.setAccessMenus(authPermissionInfo.menus);
|
||||||
|
accessStore.setAccessCodes(authPermissionInfo.permissions);
|
||||||
|
return authPermissionInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
function $reset() {
|
function $reset() {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { acceptHMRUpdate, defineStore } from 'pinia';
|
||||||
|
|
||||||
|
export interface DictItem {
|
||||||
|
colorType?: string;
|
||||||
|
cssClass?: string;
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Dict = Record<string, DictItem[]>;
|
||||||
|
|
||||||
|
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<string, any>) => Promise<Record<string, any>[]>,
|
||||||
|
params: Record<string, any> = {},
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
export * from './auth';
|
export * from './auth';
|
||||||
|
export * from './dict';
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue