f
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Deploy Website on push / Rerun on failure (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Deploy Website on push / Rerun on failure (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
This commit is contained in:
5
apps/web-antd/.env
Normal file
5
apps/web-antd/.env
Normal file
@@ -0,0 +1,5 @@
|
||||
# 应用标题
|
||||
VITE_APP_TITLE=全能查
|
||||
|
||||
# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
|
||||
VITE_APP_NAMESPACE=qnc
|
||||
7
apps/web-antd/.env.analyze
Normal file
7
apps/web-antd/.env.analyze
Normal file
@@ -0,0 +1,7 @@
|
||||
# public path
|
||||
VITE_BASE=/
|
||||
|
||||
# Basic interface address SPA
|
||||
VITE_GLOB_API_URL=/api
|
||||
|
||||
VITE_VISUALIZER=true
|
||||
16
apps/web-antd/.env.development
Normal file
16
apps/web-antd/.env.development
Normal file
@@ -0,0 +1,16 @@
|
||||
# 端口号
|
||||
VITE_PORT=5666
|
||||
|
||||
VITE_BASE=/
|
||||
|
||||
# 接口地址
|
||||
VITE_GLOB_API_URL=/api/v1/admin
|
||||
|
||||
# 是否开启 Nitro Mock服务,true 为开启,false 为关闭
|
||||
VITE_NITRO_MOCK=false
|
||||
|
||||
# 是否打开 devtools,true 为打开,false 为关闭
|
||||
VITE_DEVTOOLS=true
|
||||
|
||||
# 是否注入全局loading
|
||||
VITE_INJECT_APP_LOADING=true
|
||||
19
apps/web-antd/.env.production
Normal file
19
apps/web-antd/.env.production
Normal file
@@ -0,0 +1,19 @@
|
||||
VITE_BASE=/
|
||||
|
||||
# 接口地址
|
||||
VITE_GLOB_API_URL=/api/v1/admin
|
||||
|
||||
# 是否开启压缩,可以设置为 none, brotli, gzip
|
||||
VITE_COMPRESS=none
|
||||
|
||||
# 是否开启 PWA
|
||||
VITE_PWA=false
|
||||
|
||||
# vue-router 的模式
|
||||
VITE_ROUTER_HISTORY=hash
|
||||
|
||||
# 是否注入全局loading
|
||||
VITE_INJECT_APP_LOADING=true
|
||||
|
||||
# 打包后是否生成dist.zip
|
||||
VITE_ARCHIVER=true
|
||||
22
apps/web-antd/index.html
Normal file
22
apps/web-antd/index.html
Normal file
@@ -0,0 +1,22 @@
|
||||
<!doctype html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="renderer" content="webkit" />
|
||||
<meta name="description" content="全能查" />
|
||||
<meta name="keywords" content="全能查" />
|
||||
<meta name="author" content="全能查" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
|
||||
/>
|
||||
<!-- 由 vite 注入 VITE_APP_TITLE 变量,在 .env 文件内配置 -->
|
||||
<title><%= VITE_APP_TITLE %></title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
54
apps/web-antd/package.json
Normal file
54
apps/web-antd/package.json
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "@vben/web-antd",
|
||||
"version": "5.5.4",
|
||||
"homepage": "https://vben.pro",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
|
||||
"directory": "apps/web-antd"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
"name": "vben",
|
||||
"email": "ann.vben@gmail.com",
|
||||
"url": "https://github.com/anncwb"
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm vite build --mode production",
|
||||
"build:analyze": "pnpm vite build --mode analyze",
|
||||
"dev": "pnpm vite --mode development",
|
||||
"preview": "vite preview",
|
||||
"typecheck": "vue-tsc --noEmit --skipLibCheck"
|
||||
},
|
||||
"imports": {
|
||||
"#/*": "./src/*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vben-core/composables": "workspace:*",
|
||||
"@vben-core/menu-ui": "workspace:*",
|
||||
"@vben-core/shadcn-ui": "workspace:*",
|
||||
"@vben/access": "workspace:*",
|
||||
"@vben/common-ui": "workspace:*",
|
||||
"@vben/constants": "workspace:*",
|
||||
"@vben/hooks": "workspace:*",
|
||||
"@vben/icons": "workspace:*",
|
||||
"@vben/layouts": "workspace:*",
|
||||
"@vben/locales": "workspace:*",
|
||||
"@vben/plugins": "workspace:*",
|
||||
"@vben/preferences": "workspace:*",
|
||||
"@vben/request": "workspace:*",
|
||||
"@vben/stores": "workspace:*",
|
||||
"@vben/styles": "workspace:*",
|
||||
"@vben/types": "workspace:*",
|
||||
"@vben/utils": "workspace:*",
|
||||
"@vueuse/core": "catalog:",
|
||||
"ant-design-vue": "catalog:",
|
||||
"dayjs": "catalog:",
|
||||
"pinia": "catalog:",
|
||||
"sortablejs": "catalog:",
|
||||
"vue": "catalog:",
|
||||
"vue-router": "catalog:"
|
||||
}
|
||||
}
|
||||
1
apps/web-antd/postcss.config.mjs
Normal file
1
apps/web-antd/postcss.config.mjs
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from '@vben/tailwind-config/postcss';
|
||||
BIN
apps/web-antd/public/favicon.ico
Normal file
BIN
apps/web-antd/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
apps/web-antd/public/logo.png
Normal file
BIN
apps/web-antd/public/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
210
apps/web-antd/src/adapter/component/index.ts
Normal file
210
apps/web-antd/src/adapter/component/index.ts
Normal file
@@ -0,0 +1,210 @@
|
||||
/**
|
||||
* 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
|
||||
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
||||
*/
|
||||
|
||||
import type { Component } from 'vue';
|
||||
|
||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import {
|
||||
defineAsyncComponent,
|
||||
defineComponent,
|
||||
getCurrentInstance,
|
||||
h,
|
||||
ref,
|
||||
} from 'vue';
|
||||
|
||||
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { notification } from 'ant-design-vue';
|
||||
|
||||
const AutoComplete = defineAsyncComponent(
|
||||
() => import('ant-design-vue/es/auto-complete'),
|
||||
);
|
||||
const Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));
|
||||
const Checkbox = defineAsyncComponent(
|
||||
() => import('ant-design-vue/es/checkbox'),
|
||||
);
|
||||
const CheckboxGroup = defineAsyncComponent(() =>
|
||||
import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup),
|
||||
);
|
||||
const DatePicker = defineAsyncComponent(
|
||||
() => import('ant-design-vue/es/date-picker'),
|
||||
);
|
||||
const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider'));
|
||||
const Input = defineAsyncComponent(() => import('ant-design-vue/es/input'));
|
||||
const InputNumber = defineAsyncComponent(
|
||||
() => import('ant-design-vue/es/input-number'),
|
||||
);
|
||||
const InputPassword = defineAsyncComponent(() =>
|
||||
import('ant-design-vue/es/input').then((res) => res.InputPassword),
|
||||
);
|
||||
const Mentions = defineAsyncComponent(
|
||||
() => import('ant-design-vue/es/mentions'),
|
||||
);
|
||||
const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio'));
|
||||
const RadioGroup = defineAsyncComponent(() =>
|
||||
import('ant-design-vue/es/radio').then((res) => res.RadioGroup),
|
||||
);
|
||||
const RangePicker = defineAsyncComponent(() =>
|
||||
import('ant-design-vue/es/date-picker').then((res) => res.RangePicker),
|
||||
);
|
||||
const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate'));
|
||||
const Select = defineAsyncComponent(() => import('ant-design-vue/es/select'));
|
||||
const Space = defineAsyncComponent(() => import('ant-design-vue/es/space'));
|
||||
const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch'));
|
||||
const Textarea = defineAsyncComponent(() =>
|
||||
import('ant-design-vue/es/input').then((res) => res.Textarea),
|
||||
);
|
||||
const TimePicker = defineAsyncComponent(
|
||||
() => import('ant-design-vue/es/time-picker'),
|
||||
);
|
||||
const TreeSelect = defineAsyncComponent(
|
||||
() => import('ant-design-vue/es/tree-select'),
|
||||
);
|
||||
const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload'));
|
||||
|
||||
const RichText = defineAsyncComponent(() =>
|
||||
import('@vben-core/shadcn-ui').then((m) => m.RichText),
|
||||
);
|
||||
|
||||
const withDefaultPlaceholder = <T extends Component>(
|
||||
component: T,
|
||||
type: 'input' | 'select',
|
||||
componentProps: Recordable<any> = {},
|
||||
) => {
|
||||
return defineComponent({
|
||||
inheritAttrs: false,
|
||||
name: component.name,
|
||||
setup: (props: any, { attrs, expose, slots }) => {
|
||||
const placeholder =
|
||||
props?.placeholder ||
|
||||
attrs?.placeholder ||
|
||||
$t(`ui.placeholder.${type}`);
|
||||
// 透传组件暴露的方法
|
||||
const innerRef = ref();
|
||||
const publicApi: Recordable<any> = {};
|
||||
expose(publicApi);
|
||||
const instance = getCurrentInstance();
|
||||
instance?.proxy?.$nextTick(() => {
|
||||
for (const key in innerRef.value) {
|
||||
if (typeof innerRef.value[key] === 'function') {
|
||||
publicApi[key] = innerRef.value[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
return () =>
|
||||
h(
|
||||
component,
|
||||
{ ...componentProps, placeholder, ...props, ...attrs, ref: innerRef },
|
||||
slots,
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||
export type ComponentType =
|
||||
| 'ApiSelect'
|
||||
| 'ApiTreeSelect'
|
||||
| 'AutoComplete'
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'DatePicker'
|
||||
| 'DefaultButton'
|
||||
| 'Divider'
|
||||
| 'IconPicker'
|
||||
| 'Input'
|
||||
| 'InputNumber'
|
||||
| 'InputPassword'
|
||||
| 'Mentions'
|
||||
| 'PrimaryButton'
|
||||
| 'Radio'
|
||||
| 'RadioGroup'
|
||||
| 'RangePicker'
|
||||
| 'Rate'
|
||||
| 'RichText'
|
||||
| 'Select'
|
||||
| 'Space'
|
||||
| 'Switch'
|
||||
| 'Textarea'
|
||||
| 'TimePicker'
|
||||
| 'TreeSelect'
|
||||
| 'Upload'
|
||||
| BaseFormComponentType;
|
||||
|
||||
async function initComponentAdapter() {
|
||||
const components: Partial<Record<ComponentType, Component>> = {
|
||||
// 如果你的组件体积比较大,可以使用异步加载
|
||||
// Button: () =>
|
||||
// import('xxx').then((res) => res.Button),
|
||||
ApiSelect: withDefaultPlaceholder(ApiComponent, 'select', {
|
||||
component: Select,
|
||||
loadingSlot: 'suffixIcon',
|
||||
visibleEvent: 'onDropdownVisibleChange',
|
||||
modelPropName: 'value',
|
||||
}),
|
||||
ApiTreeSelect: withDefaultPlaceholder(ApiComponent, 'select', {
|
||||
component: TreeSelect,
|
||||
fieldNames: { label: 'label', value: 'value', children: 'children' },
|
||||
loadingSlot: 'suffixIcon',
|
||||
modelPropName: 'value',
|
||||
optionsPropName: 'treeData',
|
||||
visibleEvent: 'onVisibleChange',
|
||||
}),
|
||||
AutoComplete,
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
// 自定义默认按钮
|
||||
DefaultButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'default' }, slots);
|
||||
},
|
||||
Divider,
|
||||
IconPicker: withDefaultPlaceholder(IconPicker, 'select', {
|
||||
iconSlot: 'addonAfter',
|
||||
inputComponent: Input,
|
||||
modelValueProp: 'value',
|
||||
}),
|
||||
Input: withDefaultPlaceholder(Input, 'input'),
|
||||
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
|
||||
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
|
||||
Mentions: withDefaultPlaceholder(Mentions, 'input'),
|
||||
// 自定义主要按钮
|
||||
PrimaryButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'primary' }, slots);
|
||||
},
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RangePicker,
|
||||
Rate,
|
||||
Select: withDefaultPlaceholder(Select, 'select'),
|
||||
Space,
|
||||
Switch,
|
||||
Textarea: withDefaultPlaceholder(Textarea, 'input'),
|
||||
TimePicker,
|
||||
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
|
||||
Upload,
|
||||
RichText,
|
||||
};
|
||||
|
||||
// 将组件注册到全局共享状态中
|
||||
globalShareState.setComponents(components);
|
||||
|
||||
// 定义全局共享状态中的消息提示
|
||||
globalShareState.defineMessage({
|
||||
// 复制成功消息提示
|
||||
copyPreferencesSuccess: (title, content) => {
|
||||
notification.success({
|
||||
description: content,
|
||||
message: title,
|
||||
placement: 'bottomRight',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export { initComponentAdapter };
|
||||
48
apps/web-antd/src/adapter/form.ts
Normal file
48
apps/web-antd/src/adapter/form.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import type {
|
||||
VbenFormSchema as FormSchema,
|
||||
VbenFormProps,
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import type { ComponentType } from './component';
|
||||
|
||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
setupVbenForm<ComponentType>({
|
||||
config: {
|
||||
// ant design vue组件库默认都是 v-model:value
|
||||
baseModelPropName: 'value',
|
||||
|
||||
// 一些组件是 v-model:checked 或者 v-model:fileList
|
||||
modelPropNameMap: {
|
||||
Checkbox: 'checked',
|
||||
Radio: 'checked',
|
||||
Switch: 'checked',
|
||||
Upload: 'fileList',
|
||||
TinyMCE: 'modelValue',
|
||||
},
|
||||
},
|
||||
defineRules: {
|
||||
// 输入项目必填国际化适配
|
||||
required: (value, _params, ctx) => {
|
||||
if (value === undefined || value === null || value.length === 0) {
|
||||
return $t('ui.formRules.required', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
// 选择项目必填国际化适配
|
||||
selectRequired: (value, _params, ctx) => {
|
||||
if (value === undefined || value === null) {
|
||||
return $t('ui.formRules.selectRequired', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const useVbenForm = useForm<ComponentType>;
|
||||
|
||||
export { useVbenForm, z };
|
||||
|
||||
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||
export type { VbenFormProps };
|
||||
276
apps/web-antd/src/adapter/vxe-table.ts
Normal file
276
apps/web-antd/src/adapter/vxe-table.ts
Normal file
@@ -0,0 +1,276 @@
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { h } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
import { $te } from '@vben/locales';
|
||||
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
|
||||
import { get, isFunction, isString } from '@vben/utils';
|
||||
|
||||
import { objectOmit } from '@vueuse/core';
|
||||
import { Button, Image, Popconfirm, Switch, Tag } from 'ant-design-vue';
|
||||
|
||||
import { $t } from '#/locales';
|
||||
|
||||
import { useVbenForm } from './form';
|
||||
|
||||
setupVbenVxeTable({
|
||||
configVxeTable: (vxeUI) => {
|
||||
vxeUI.setConfig({
|
||||
grid: {
|
||||
align: 'center',
|
||||
border: false,
|
||||
columnConfig: {
|
||||
resizable: true,
|
||||
},
|
||||
|
||||
formConfig: {
|
||||
// 全局禁用vxe-table的表单配置,使用formOptions
|
||||
enabled: false,
|
||||
},
|
||||
minHeight: 180,
|
||||
proxyConfig: {
|
||||
autoLoad: true,
|
||||
response: {
|
||||
result: 'items',
|
||||
total: 'total',
|
||||
list: '',
|
||||
},
|
||||
showActiveMsg: true,
|
||||
showResponseMsg: false,
|
||||
},
|
||||
round: true,
|
||||
showOverflow: true,
|
||||
size: 'small',
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 解决vxeTable在热更新时可能会出错的问题
|
||||
*/
|
||||
vxeUI.renderer.forEach((_item, key) => {
|
||||
if (key.startsWith('Cell')) {
|
||||
vxeUI.renderer.delete(key);
|
||||
}
|
||||
});
|
||||
|
||||
// 表格配置项可以用 cellRender: { name: 'CellImage' },
|
||||
vxeUI.renderer.add('CellImage', {
|
||||
renderTableDefault(_renderOpts, params) {
|
||||
const { column, row } = params;
|
||||
return h(Image, { src: row[column.field] });
|
||||
},
|
||||
});
|
||||
|
||||
// 表格配置项可以用 cellRender: { name: 'CellLink' },
|
||||
vxeUI.renderer.add('CellLink', {
|
||||
renderTableDefault(renderOpts) {
|
||||
const { props } = renderOpts;
|
||||
return h(
|
||||
Button,
|
||||
{ size: 'small', type: 'link' },
|
||||
{ default: () => props?.text },
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// 单元格渲染: Tag
|
||||
vxeUI.renderer.add('CellTag', {
|
||||
renderTableDefault({ options, props }, { column, row }) {
|
||||
const value = get(row, column.field);
|
||||
const tagOptions = options ?? [
|
||||
{ color: 'success', label: $t('common.enabled'), value: 1 },
|
||||
{ color: 'error', label: $t('common.disabled'), value: 0 },
|
||||
];
|
||||
const tagItem = tagOptions.find((item) => item.value === value);
|
||||
return h(
|
||||
Tag,
|
||||
{
|
||||
...props,
|
||||
...objectOmit(tagItem ?? {}, ['label']),
|
||||
},
|
||||
{ default: () => tagItem?.label ?? value },
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
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(Switch, finallyProps);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 注册表格的操作按钮渲染器
|
||||
*/
|
||||
vxeUI.renderer.add('CellOperation', {
|
||||
renderTableDefault({ attrs, options, props }, { column, row }) {
|
||||
const defaultProps = { size: 'small', type: 'link', ...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: {
|
||||
danger: true,
|
||||
text: $t('common.delete'),
|
||||
},
|
||||
edit: {
|
||||
text: $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,
|
||||
text: $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(
|
||||
Button,
|
||||
{
|
||||
...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.text);
|
||||
return content;
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function renderConfirm(opt: Recordable<any>) {
|
||||
return h(
|
||||
Popconfirm,
|
||||
{
|
||||
getPopupContainer(el) {
|
||||
return (
|
||||
el
|
||||
.closest('.vxe-table--viewport-wrapper')
|
||||
?.querySelector('.vxe-table--main-wrapper')
|
||||
?.querySelector('tbody') || document.body
|
||||
);
|
||||
},
|
||||
placement: 'topLeft',
|
||||
title: $t('ui.actionTitle.delete', [attrs?.nameTitle || '']),
|
||||
...props,
|
||||
...opt,
|
||||
icon: undefined,
|
||||
onConfirm: () => {
|
||||
attrs?.onClick?.({
|
||||
code: opt.code,
|
||||
row,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
default: () => renderBtn({ ...opt }, false),
|
||||
description: () =>
|
||||
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',
|
||||
style: { justifyContent: align },
|
||||
},
|
||||
btns,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
|
||||
// vxeUI.formats.add
|
||||
},
|
||||
useVbenForm,
|
||||
});
|
||||
|
||||
export { useVbenVxeGrid };
|
||||
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';
|
||||
594
apps/web-antd/src/api/agent/agent.ts
Normal file
594
apps/web-antd/src/api/agent/agent.ts
Normal file
@@ -0,0 +1,594 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace AgentApi {
|
||||
export interface AgentListItem {
|
||||
id: number;
|
||||
user_id: number;
|
||||
agent_code: number;
|
||||
level: number; // 1=普通,2=黄金,3=钻石
|
||||
level_name: string;
|
||||
region: string;
|
||||
mobile: string;
|
||||
wechat_id?: string;
|
||||
team_leader_id?: number;
|
||||
balance: number;
|
||||
total_earnings: number;
|
||||
frozen_balance: number;
|
||||
withdrawn_amount: number;
|
||||
is_real_name: boolean;
|
||||
create_time: string;
|
||||
}
|
||||
|
||||
export interface AgentList {
|
||||
total: number;
|
||||
items: AgentListItem[];
|
||||
}
|
||||
|
||||
export interface GetAgentListParams {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
mobile?: string;
|
||||
region?: string;
|
||||
level?: number;
|
||||
team_leader_id?: number;
|
||||
id?: number;
|
||||
create_time_start?: string;
|
||||
create_time_end?: string;
|
||||
order_by?: string;
|
||||
order_type?: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
export interface AgentLinkListItem {
|
||||
agent_id: number;
|
||||
product_id: number;
|
||||
product_name: string;
|
||||
set_price: number;
|
||||
actual_base_price: number;
|
||||
link_identifier: string;
|
||||
create_time: string;
|
||||
}
|
||||
|
||||
export interface AgentLinkList {
|
||||
total: number;
|
||||
items: AgentLinkListItem[];
|
||||
}
|
||||
|
||||
export interface GetAgentLinkListParams {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
agent_id?: number;
|
||||
product_id?: number;
|
||||
product_name?: string;
|
||||
link_identifier?: string;
|
||||
}
|
||||
|
||||
// 代理佣金相关接口
|
||||
export interface AgentCommissionListItem {
|
||||
id: number;
|
||||
agent_id: number;
|
||||
order_id: number;
|
||||
amount: number;
|
||||
product_name: string;
|
||||
status: number;
|
||||
create_time: string;
|
||||
}
|
||||
|
||||
export interface AgentCommissionList {
|
||||
total: number;
|
||||
items: AgentCommissionListItem[];
|
||||
}
|
||||
|
||||
export interface GetAgentCommissionListParams {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
agent_id?: number;
|
||||
product_name?: string;
|
||||
status?: number;
|
||||
}
|
||||
|
||||
// 代理返佣相关接口
|
||||
export interface AgentRebateListItem {
|
||||
id: number;
|
||||
agent_id: number;
|
||||
source_agent_id: number;
|
||||
order_id: number;
|
||||
rebate_type: number; // 1=直接上级返佣,2=钻石上级返佣,3=黄金上级返佣
|
||||
amount: number;
|
||||
create_time: string;
|
||||
}
|
||||
|
||||
export interface AgentRebateList {
|
||||
total: number;
|
||||
items: AgentRebateListItem[];
|
||||
}
|
||||
|
||||
export interface GetAgentRebateListParams {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
agent_id?: number;
|
||||
source_agent_id?: number;
|
||||
rebate_type?: number;
|
||||
}
|
||||
|
||||
// 代理提现相关接口
|
||||
export interface AgentWithdrawalListItem {
|
||||
id: number;
|
||||
agent_id: number;
|
||||
withdraw_no: string;
|
||||
amount: number;
|
||||
tax_amount: number;
|
||||
actual_amount: number;
|
||||
status: number; // 1=待审核,2=审核通过,3=审核拒绝,4=提现中,5=提现成功,6=提现失败
|
||||
payee_account: string;
|
||||
payee_name: string;
|
||||
remark: string;
|
||||
create_time: string;
|
||||
}
|
||||
|
||||
export interface AgentWithdrawalList {
|
||||
total: number;
|
||||
items: AgentWithdrawalListItem[];
|
||||
}
|
||||
|
||||
export interface GetAgentWithdrawalListParams {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
agent_id?: number;
|
||||
status?: number;
|
||||
withdraw_no?: string;
|
||||
}
|
||||
|
||||
export interface AuditWithdrawalParams {
|
||||
withdrawal_id: number;
|
||||
status: number; // 2=通过,3=拒绝
|
||||
remark: string;
|
||||
}
|
||||
|
||||
// 代理产品配置列表项
|
||||
export interface AgentProductionConfigItem {
|
||||
id: number;
|
||||
product_id: number;
|
||||
product_name: string;
|
||||
base_price: number;
|
||||
price_range_min: number;
|
||||
price_range_max: number;
|
||||
price_threshold: number;
|
||||
price_fee_rate: number;
|
||||
create_time: string;
|
||||
}
|
||||
|
||||
// 代理产品配置列表响应
|
||||
export interface AgentProductionConfigList {
|
||||
total: number;
|
||||
items: AgentProductionConfigItem[];
|
||||
}
|
||||
|
||||
// 获取代理产品配置列表参数
|
||||
export interface GetAgentProductionConfigListParams {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
product_name?: string;
|
||||
product_id?: number;
|
||||
id?: number;
|
||||
}
|
||||
|
||||
// 更新代理产品配置参数
|
||||
export interface UpdateAgentProductionConfigParams {
|
||||
id: number;
|
||||
base_price: number;
|
||||
price_range_max: number;
|
||||
price_threshold?: number;
|
||||
price_fee_rate?: number;
|
||||
}
|
||||
|
||||
// 更新代理产品配置响应
|
||||
export interface UpdateAgentProductionConfigResp {
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
// 代理升级记录相关接口
|
||||
export interface AgentUpgradeListItem {
|
||||
id: number;
|
||||
agent_id: number;
|
||||
from_level: number;
|
||||
to_level: number;
|
||||
upgrade_type: number; // 1=自主付费,2=钻石升级下级
|
||||
upgrade_fee: number;
|
||||
rebate_amount: number;
|
||||
status: number; // 1=待处理,2=已完成,3=已失败
|
||||
create_time: string;
|
||||
}
|
||||
|
||||
export interface AgentUpgradeList {
|
||||
total: number;
|
||||
items: AgentUpgradeListItem[];
|
||||
}
|
||||
|
||||
export interface GetAgentUpgradeListParams {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
agent_id?: number;
|
||||
upgrade_type?: number;
|
||||
status?: number;
|
||||
}
|
||||
|
||||
// 代理订单相关接口
|
||||
export interface AgentOrderListItem {
|
||||
id: number;
|
||||
agent_id: number;
|
||||
order_id: number;
|
||||
product_id: number;
|
||||
product_name: string;
|
||||
order_amount: number;
|
||||
set_price: number;
|
||||
actual_base_price: number;
|
||||
price_cost: number;
|
||||
agent_profit: number;
|
||||
process_status: number; // 0=待处理,1=处理成功,2=处理失败
|
||||
create_time: string;
|
||||
}
|
||||
|
||||
export interface AgentOrderList {
|
||||
total: number;
|
||||
items: AgentOrderListItem[];
|
||||
}
|
||||
|
||||
export interface GetAgentOrderListParams {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
agent_id?: number;
|
||||
order_id?: number;
|
||||
process_status?: number;
|
||||
}
|
||||
|
||||
// 邀请码管理相关接口
|
||||
export interface InviteCodeListItem {
|
||||
id: number;
|
||||
code: string;
|
||||
agent_id: number; // 0表示平台发放
|
||||
agent_mobile: string;
|
||||
target_level: number;
|
||||
status: number; // 0=未使用,1=已使用,2=已失效
|
||||
used_user_id?: number;
|
||||
used_agent_id?: number;
|
||||
used_time?: string;
|
||||
expire_time?: string;
|
||||
remark?: string;
|
||||
create_time: string;
|
||||
}
|
||||
|
||||
export interface InviteCodeList {
|
||||
total: number;
|
||||
items: InviteCodeListItem[];
|
||||
}
|
||||
|
||||
export interface GetInviteCodeListParams {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
code?: string;
|
||||
agent_id?: number;
|
||||
target_level?: number;
|
||||
status?: number;
|
||||
}
|
||||
|
||||
export interface GenerateDiamondInviteCodeParams {
|
||||
count: number;
|
||||
expire_days?: number; // 可选,0表示不过期
|
||||
remark?: string;
|
||||
}
|
||||
|
||||
export interface GenerateDiamondInviteCodeResp {
|
||||
codes: string[];
|
||||
}
|
||||
|
||||
// 系统配置相关接口(价格配置已移除,改为产品配置表管理)
|
||||
export interface AgentConfig {
|
||||
level_bonus: {
|
||||
diamond: number;
|
||||
gold: number;
|
||||
normal: number;
|
||||
};
|
||||
upgrade_fee: {
|
||||
normal_to_gold: number;
|
||||
normal_to_diamond: number;
|
||||
gold_to_diamond: number;
|
||||
};
|
||||
upgrade_rebate: {
|
||||
normal_to_gold_rebate: number;
|
||||
to_diamond_rebate: number;
|
||||
};
|
||||
direct_parent_rebate: {
|
||||
diamond: number; // 直接上级是钻石的返佣金额(6元)
|
||||
gold: number; // 直接上级是黄金的返佣金额(3元)
|
||||
normal: number; // 直接上级是普通的返佣金额(2元)
|
||||
};
|
||||
max_gold_rebate_amount: number; // 黄金代理最大返佣金额(3元)
|
||||
commission_freeze: {
|
||||
ratio: number; // 佣金冻结比例(例如:0.1表示10%)
|
||||
threshold: number; // 佣金冻结阈值(订单单价达到此金额才触发冻结,单位:元)
|
||||
days: number; // 佣金冻结解冻天数(单位:天,例如:30表示30天后解冻)
|
||||
};
|
||||
tax_rate: number;
|
||||
tax_exemption_amount: number;
|
||||
gold_max_uplift_amount: number;
|
||||
diamond_max_uplift_amount: number;
|
||||
}
|
||||
|
||||
export interface UpdateAgentConfigParams {
|
||||
level_bonus?: {
|
||||
diamond?: number;
|
||||
gold?: number;
|
||||
normal?: number;
|
||||
};
|
||||
upgrade_fee?: {
|
||||
normal_to_gold?: number;
|
||||
normal_to_diamond?: number;
|
||||
gold_to_diamond?: number;
|
||||
};
|
||||
upgrade_rebate?: {
|
||||
normal_to_gold_rebate?: number;
|
||||
to_diamond_rebate?: number;
|
||||
};
|
||||
direct_parent_rebate?: {
|
||||
diamond?: number; // 直接上级是钻石的返佣金额(6元)
|
||||
gold?: number; // 直接上级是黄金的返佣金额(3元)
|
||||
normal?: number; // 直接上级是普通的返佣金额(2元)
|
||||
};
|
||||
max_gold_rebate_amount?: number; // 黄金代理最大返佣金额(3元)
|
||||
commission_freeze?: {
|
||||
ratio?: number; // 佣金冻结比例(例如:0.1表示10%)
|
||||
threshold?: number; // 佣金冻结阈值(订单单价达到此金额才触发冻结,单位:元)
|
||||
days?: number; // 佣金冻结解冻天数(单位:天,例如:30表示30天后解冻)
|
||||
};
|
||||
tax_rate?: number;
|
||||
tax_exemption_amount?: number;
|
||||
gold_max_uplift_amount?: number;
|
||||
diamond_max_uplift_amount?: number;
|
||||
}
|
||||
|
||||
// 实名认证相关接口
|
||||
export interface AgentRealNameListItem {
|
||||
id: number;
|
||||
agent_id: number;
|
||||
name: string;
|
||||
id_card: string; // 加密,需要脱敏显示
|
||||
mobile: string; // 加密
|
||||
status: number; // 1=未验证,2=已通过
|
||||
verify_time?: string;
|
||||
create_time: string;
|
||||
}
|
||||
|
||||
export interface AgentRealNameList {
|
||||
total: number;
|
||||
items: AgentRealNameListItem[];
|
||||
}
|
||||
|
||||
export interface GetAgentRealNameListParams {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
agent_id?: number;
|
||||
status?: number;
|
||||
}
|
||||
|
||||
// 代理奖励相关接口
|
||||
export interface AgentRewardListItem {
|
||||
id: number;
|
||||
agent_id: number;
|
||||
type: string; // 奖励类型:register=注册奖励,first_order=首单奖励,level_up=升级奖励
|
||||
amount: number;
|
||||
order_id?: number;
|
||||
status: string; // pending=待发放,paid=已发放,failed=发放失败
|
||||
create_time: string;
|
||||
pay_time?: string;
|
||||
}
|
||||
|
||||
export interface AgentRewardList {
|
||||
total: number;
|
||||
items: AgentRewardListItem[];
|
||||
}
|
||||
|
||||
export interface GetAgentRewardListParams {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
agent_id?: number;
|
||||
type?: string;
|
||||
status?: string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代理列表数据
|
||||
* @param params 查询参数
|
||||
*/
|
||||
async function getAgentList(params: AgentApi.GetAgentListParams) {
|
||||
return requestClient.get<AgentApi.AgentList>('/agent/list', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代理推广链接列表
|
||||
*/
|
||||
async function getAgentLinkList(params: AgentApi.GetAgentLinkListParams) {
|
||||
return requestClient.get<AgentApi.AgentLinkList>('/agent/link/list', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代理佣金列表
|
||||
*/
|
||||
async function getAgentCommissionList(
|
||||
params: AgentApi.GetAgentCommissionListParams,
|
||||
) {
|
||||
return requestClient.get<AgentApi.AgentCommissionList>(
|
||||
'/agent/commission/list',
|
||||
{
|
||||
params,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代理提现列表
|
||||
*/
|
||||
async function getAgentWithdrawalList(
|
||||
params: AgentApi.GetAgentWithdrawalListParams,
|
||||
) {
|
||||
return requestClient.get<AgentApi.AgentWithdrawalList>(
|
||||
'/agent/withdrawal/list',
|
||||
{
|
||||
params,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代理产品配置列表
|
||||
*/
|
||||
async function getAgentProductionConfigList(
|
||||
params: AgentApi.GetAgentProductionConfigListParams,
|
||||
) {
|
||||
return requestClient.get<AgentApi.AgentProductionConfigList>(
|
||||
'/agent/product_config/list',
|
||||
{
|
||||
params,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新代理产品配置
|
||||
*/
|
||||
async function updateAgentProductionConfig(
|
||||
params: AgentApi.UpdateAgentProductionConfigParams,
|
||||
) {
|
||||
return requestClient.post<AgentApi.UpdateAgentProductionConfigResp>(
|
||||
'/agent/product_config/update',
|
||||
params,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代理返佣记录列表
|
||||
*/
|
||||
async function getAgentRebateList(params: AgentApi.GetAgentRebateListParams) {
|
||||
return requestClient.get<AgentApi.AgentRebateList>('/agent/rebate/list', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代理升级记录列表
|
||||
*/
|
||||
async function getAgentUpgradeList(
|
||||
params: AgentApi.GetAgentUpgradeListParams,
|
||||
) {
|
||||
return requestClient.get<AgentApi.AgentUpgradeList>('/agent/upgrade/list', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代理订单列表
|
||||
*/
|
||||
async function getAgentOrderList(params: AgentApi.GetAgentOrderListParams) {
|
||||
return requestClient.get<AgentApi.AgentOrderList>('/agent/order/list', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取邀请码列表
|
||||
*/
|
||||
async function getInviteCodeList(params: AgentApi.GetInviteCodeListParams) {
|
||||
return requestClient.get<AgentApi.InviteCodeList>(
|
||||
'/agent/invite_code/list',
|
||||
{
|
||||
params,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成钻石邀请码
|
||||
*/
|
||||
async function generateDiamondInviteCode(
|
||||
params: AgentApi.GenerateDiamondInviteCodeParams,
|
||||
) {
|
||||
return requestClient.post<AgentApi.GenerateDiamondInviteCodeResp>(
|
||||
'/agent/invite_code/diamond/generate',
|
||||
params,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统配置
|
||||
*/
|
||||
async function getAgentConfig() {
|
||||
return requestClient.get<AgentApi.AgentConfig>('/agent/config');
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新系统配置
|
||||
*/
|
||||
async function updateAgentConfig(params: AgentApi.UpdateAgentConfigParams) {
|
||||
return requestClient.post<{ success: boolean }>(
|
||||
'/agent/config/update',
|
||||
params,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实名认证列表
|
||||
*/
|
||||
async function getAgentRealNameList(
|
||||
params: AgentApi.GetAgentRealNameListParams,
|
||||
) {
|
||||
return requestClient.get<AgentApi.AgentRealNameList>(
|
||||
'/agent/real_name/list',
|
||||
{
|
||||
params,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核提现
|
||||
*/
|
||||
async function auditWithdrawal(params: AgentApi.AuditWithdrawalParams) {
|
||||
return requestClient.post<{ success: boolean }>(
|
||||
'/agent/withdrawal/audit',
|
||||
params,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取代理奖励列表
|
||||
*/
|
||||
async function getAgentRewardList(
|
||||
params: AgentApi.GetAgentRewardListParams,
|
||||
) {
|
||||
return requestClient.get<AgentApi.AgentRewardList>('/agent/reward/list', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
auditWithdrawal,
|
||||
generateDiamondInviteCode,
|
||||
getAgentCommissionList,
|
||||
getAgentConfig,
|
||||
getAgentLinkList,
|
||||
getAgentList,
|
||||
getAgentOrderList,
|
||||
getAgentProductionConfigList,
|
||||
getAgentRebateList,
|
||||
getAgentRealNameList,
|
||||
getAgentRewardList,
|
||||
getAgentUpgradeList,
|
||||
getAgentWithdrawalList,
|
||||
getInviteCodeList,
|
||||
updateAgentConfig,
|
||||
updateAgentProductionConfig,
|
||||
};
|
||||
1
apps/web-antd/src/api/agent/index.ts
Normal file
1
apps/web-antd/src/api/agent/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './agent';
|
||||
52
apps/web-antd/src/api/core/auth.ts
Normal file
52
apps/web-antd/src/api/core/auth.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { baseRequestClient, requestClient } from '#/api/request';
|
||||
|
||||
export namespace AuthApi {
|
||||
/** 登录接口参数 */
|
||||
export interface LoginParams {
|
||||
password?: string;
|
||||
username?: string;
|
||||
}
|
||||
|
||||
/** 登录接口返回值 */
|
||||
export interface LoginResult {
|
||||
access_token: string;
|
||||
}
|
||||
|
||||
export interface RefreshTokenResult {
|
||||
data: string;
|
||||
status: number;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*/
|
||||
export async function loginApi(data: AuthApi.LoginParams) {
|
||||
return requestClient.post<AuthApi.LoginResult>('/auth/login', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新accessToken
|
||||
*/
|
||||
export async function refreshTokenApi() {
|
||||
return baseRequestClient.post<AuthApi.RefreshTokenResult>('/auth/refresh', {
|
||||
withCredentials: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
export async function logoutApi() {
|
||||
return baseRequestClient.post('/auth/logout', {
|
||||
withCredentials: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户权限码
|
||||
*/
|
||||
export async function getAccessCodesApi() {
|
||||
// return requestClient.get<string[]>('/auth/codes');
|
||||
return [];
|
||||
}
|
||||
3
apps/web-antd/src/api/core/index.ts
Normal file
3
apps/web-antd/src/api/core/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './auth';
|
||||
export * from './menu';
|
||||
export * from './user';
|
||||
10
apps/web-antd/src/api/core/menu.ts
Normal file
10
apps/web-antd/src/api/core/menu.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { RouteRecordStringComponent } from '@vben/types';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 获取用户所有菜单
|
||||
*/
|
||||
export async function getAllMenusApi() {
|
||||
return requestClient.get<RouteRecordStringComponent[]>('/menu/all');
|
||||
}
|
||||
10
apps/web-antd/src/api/core/user.ts
Normal file
10
apps/web-antd/src/api/core/user.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { UserInfo } from '@vben/types';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
export async function getUserInfoApi() {
|
||||
return requestClient.get<UserInfo>('/user/info');
|
||||
}
|
||||
36
apps/web-antd/src/api/index.ts
Normal file
36
apps/web-antd/src/api/index.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
export * from './agent';
|
||||
export * from './core';
|
||||
export * from './notification';
|
||||
export * from './order';
|
||||
export * from './platform-user';
|
||||
export * from './product-manage';
|
||||
export * from './system';
|
||||
export interface ApiResponse<T = any> {
|
||||
code: number;
|
||||
data: T;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface PageResult<T> {
|
||||
items: T[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
$http: {
|
||||
delete<T = any>(url: string, config?: any): Promise<ApiResponse<T>>;
|
||||
get<T = any>(url: string, config?: any): Promise<ApiResponse<T>>;
|
||||
post<T = any>(
|
||||
url: string,
|
||||
data?: any,
|
||||
config?: any,
|
||||
): Promise<ApiResponse<T>>;
|
||||
put<T = any>(
|
||||
url: string,
|
||||
data?: any,
|
||||
config?: any,
|
||||
): Promise<ApiResponse<T>>;
|
||||
};
|
||||
}
|
||||
}
|
||||
105
apps/web-antd/src/api/notification/index.ts
Normal file
105
apps/web-antd/src/api/notification/index.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace NotificationApi {
|
||||
export interface NotificationItem {
|
||||
id: number;
|
||||
title: string;
|
||||
content?: string;
|
||||
notification_page: string;
|
||||
start_date: string;
|
||||
start_time: string;
|
||||
end_date: string;
|
||||
end_time: string;
|
||||
status: number;
|
||||
create_time: string;
|
||||
update_time: string;
|
||||
}
|
||||
|
||||
export interface NotificationList {
|
||||
total: number;
|
||||
items: NotificationItem[];
|
||||
}
|
||||
|
||||
export interface CreateNotificationRequest {
|
||||
title: string;
|
||||
content: string;
|
||||
notification_page: string;
|
||||
start_date: string;
|
||||
start_time: string;
|
||||
end_date: string;
|
||||
end_time: string;
|
||||
status: 0 | 1;
|
||||
}
|
||||
|
||||
export interface CreateNotificationResponse {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface UpdateNotificationRequest
|
||||
extends Partial<CreateNotificationRequest> {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export interface UpdateNotificationResponse {
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export interface DeleteNotificationResponse {
|
||||
success: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取通知列表
|
||||
*/
|
||||
async function getNotificationList(params: Recordable<any>) {
|
||||
return requestClient.get<NotificationApi.NotificationList>(
|
||||
'/notification/list',
|
||||
{
|
||||
params,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建通知
|
||||
*/
|
||||
async function createNotification(
|
||||
data: NotificationApi.CreateNotificationRequest,
|
||||
) {
|
||||
return requestClient.post<NotificationApi.CreateNotificationResponse>(
|
||||
'/notification/create',
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新通知
|
||||
*/
|
||||
async function updateNotification(
|
||||
id: number,
|
||||
data: NotificationApi.UpdateNotificationRequest,
|
||||
) {
|
||||
return requestClient.put<NotificationApi.UpdateNotificationResponse>(
|
||||
`/notification/update/${id}`,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除通知
|
||||
*/
|
||||
async function deleteNotification(id: number) {
|
||||
return requestClient.delete<NotificationApi.DeleteNotificationResponse>(
|
||||
`/notification/delete/${id}`,
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
createNotification,
|
||||
deleteNotification,
|
||||
getNotificationList,
|
||||
updateNotification,
|
||||
};
|
||||
1
apps/web-antd/src/api/order/index.ts
Normal file
1
apps/web-antd/src/api/order/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './order';
|
||||
59
apps/web-antd/src/api/order/order.ts
Normal file
59
apps/web-antd/src/api/order/order.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace OrderApi {
|
||||
export interface Order {
|
||||
id: string;
|
||||
order_no: string;
|
||||
platform_order_id: string;
|
||||
product_name: string;
|
||||
payment_platform: 'alipay' | 'appleiap' | 'wechat';
|
||||
payment_scene: 'app' | 'h5' | 'mini_program' | 'public_account';
|
||||
amount: number;
|
||||
status: 'closed' | 'failed' | 'paid' | 'pending' | 'refunded';
|
||||
query_state: 'cleaned' | 'failed' | 'pending' | 'processing' | 'success';
|
||||
create_time: string;
|
||||
pay_time: null | string;
|
||||
refund_time: null | string;
|
||||
}
|
||||
|
||||
export interface OrderList {
|
||||
total: number;
|
||||
items: Order[];
|
||||
}
|
||||
|
||||
export interface RefundOrderRequest {
|
||||
refund_amount: number;
|
||||
refund_reason: string;
|
||||
}
|
||||
|
||||
export interface RefundOrderResponse {
|
||||
status: string;
|
||||
refund_no: string;
|
||||
amount: number;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单列表数据
|
||||
*/
|
||||
async function getOrderList(params: Recordable<any>) {
|
||||
return requestClient.get<OrderApi.OrderList>('/order/list', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单退款
|
||||
* @param id 订单 ID
|
||||
* @param data 退款请求数据
|
||||
*/
|
||||
async function refundOrder(id: string, data: OrderApi.RefundOrderRequest) {
|
||||
return requestClient.post<OrderApi.RefundOrderResponse>(
|
||||
`/order/refund/${id}`,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
export { getOrderList, refundOrder };
|
||||
184
apps/web-antd/src/api/order/query.ts
Normal file
184
apps/web-antd/src/api/order/query.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace OrderQueryApi {
|
||||
export interface QueryItem {
|
||||
feature: Recordable<any>;
|
||||
data: Recordable<any>;
|
||||
}
|
||||
|
||||
export interface QueryDetail {
|
||||
id: string;
|
||||
order_id: string;
|
||||
user_id: string;
|
||||
product_name: string;
|
||||
query_params: Recordable<any>;
|
||||
query_data: QueryItem[];
|
||||
create_time: string;
|
||||
update_time: string;
|
||||
query_state: string;
|
||||
}
|
||||
|
||||
export interface GetQueryDetailRequest {
|
||||
order_id: string;
|
||||
}
|
||||
|
||||
export interface GetQueryDetailResponse {
|
||||
id: string;
|
||||
order_id: string;
|
||||
user_id: string;
|
||||
product_name: string;
|
||||
query_params: Recordable<any>;
|
||||
query_data: QueryItem[];
|
||||
create_time: string;
|
||||
update_time: string;
|
||||
query_state: string;
|
||||
}
|
||||
|
||||
// 清理日志相关接口定义
|
||||
export interface GetQueryCleanupLogListRequest {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
status?: number;
|
||||
start_time?: string;
|
||||
end_time?: string;
|
||||
}
|
||||
|
||||
export interface QueryCleanupLogItem {
|
||||
id: number;
|
||||
cleanup_time: string;
|
||||
cleanup_before: string;
|
||||
status: number;
|
||||
affected_rows: number;
|
||||
error_msg: string;
|
||||
remark: string;
|
||||
create_time: string;
|
||||
}
|
||||
|
||||
export interface GetQueryCleanupLogListResponse {
|
||||
total: number;
|
||||
items: QueryCleanupLogItem[];
|
||||
}
|
||||
|
||||
// 清理详情相关接口定义
|
||||
export interface GetQueryCleanupDetailListRequest {
|
||||
log_id: number;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
}
|
||||
|
||||
export interface QueryCleanupDetailItem {
|
||||
id: number;
|
||||
cleanup_log_id: number;
|
||||
query_id: number;
|
||||
order_id: number;
|
||||
user_id: number;
|
||||
product_id: number;
|
||||
query_state: string;
|
||||
create_time_old: string;
|
||||
create_time: string;
|
||||
}
|
||||
|
||||
export interface GetQueryCleanupDetailListResponse {
|
||||
total: number;
|
||||
items: QueryCleanupDetailItem[];
|
||||
}
|
||||
|
||||
// 清理配置相关接口定义
|
||||
export interface GetQueryCleanupConfigListRequest {
|
||||
status?: number;
|
||||
}
|
||||
|
||||
export interface QueryCleanupConfigItem {
|
||||
id: number;
|
||||
config_key: string;
|
||||
config_value: string;
|
||||
config_desc: string;
|
||||
status: number;
|
||||
create_time: string;
|
||||
update_time: string;
|
||||
}
|
||||
|
||||
export interface GetQueryCleanupConfigListResponse {
|
||||
items: QueryCleanupConfigItem[];
|
||||
}
|
||||
|
||||
export interface UpdateQueryCleanupConfigRequest {
|
||||
id: number;
|
||||
config_value: string;
|
||||
status: number;
|
||||
}
|
||||
|
||||
export interface UpdateQueryCleanupConfigResponse {
|
||||
success: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单查询详情
|
||||
* @param orderId 订单ID
|
||||
*/
|
||||
async function getOrderQueryDetail(orderId: string) {
|
||||
return requestClient.get<OrderQueryApi.GetQueryDetailResponse>(
|
||||
`/query/detail/${orderId}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取清理日志列表
|
||||
*/
|
||||
async function getQueryCleanupLogList(
|
||||
params: OrderQueryApi.GetQueryCleanupLogListRequest,
|
||||
) {
|
||||
return requestClient.get<OrderQueryApi.GetQueryCleanupLogListResponse>(
|
||||
'/query/cleanup/logs',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取清理详情列表
|
||||
* @param logId 清理日志ID
|
||||
*/
|
||||
async function getQueryCleanupDetailList(
|
||||
logId: number,
|
||||
params: Omit<OrderQueryApi.GetQueryCleanupDetailListRequest, 'log_id'>,
|
||||
) {
|
||||
return requestClient.get<OrderQueryApi.GetQueryCleanupDetailListResponse>(
|
||||
`/query/cleanup/details/${logId}`,
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取清理配置列表
|
||||
*/
|
||||
async function getQueryCleanupConfigList(
|
||||
params?: OrderQueryApi.GetQueryCleanupConfigListRequest,
|
||||
) {
|
||||
return requestClient.get<OrderQueryApi.GetQueryCleanupConfigListResponse>(
|
||||
'/query/cleanup/configs',
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新清理配置
|
||||
*/
|
||||
async function updateQueryCleanupConfig(
|
||||
data: OrderQueryApi.UpdateQueryCleanupConfigRequest,
|
||||
) {
|
||||
return requestClient.put<OrderQueryApi.UpdateQueryCleanupConfigResponse>(
|
||||
'/query/cleanup/config',
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
getOrderQueryDetail,
|
||||
getQueryCleanupConfigList,
|
||||
getQueryCleanupDetailList,
|
||||
getQueryCleanupLogList,
|
||||
updateQueryCleanupConfig,
|
||||
};
|
||||
53
apps/web-antd/src/api/platform-user/index.ts
Normal file
53
apps/web-antd/src/api/platform-user/index.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace PlatformUserApi {
|
||||
export interface PlatformUserItem {
|
||||
id: number;
|
||||
mobile: string;
|
||||
nickname: string;
|
||||
info: string;
|
||||
inside: number;
|
||||
create_time: string;
|
||||
update_time: string;
|
||||
}
|
||||
|
||||
export interface PlatformUserList {
|
||||
total: number;
|
||||
items: PlatformUserItem[];
|
||||
}
|
||||
|
||||
export interface UpdatePlatformUserRequest {
|
||||
mobile: string;
|
||||
nickname: string;
|
||||
info: string;
|
||||
inside: number;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取平台用户列表数据
|
||||
*/
|
||||
async function getPlatformUserList(params: Recordable<any>) {
|
||||
return requestClient.get<PlatformUserApi.PlatformUserList>(
|
||||
'/platform_user/list',
|
||||
{
|
||||
params,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新平台用户
|
||||
* @param id 用户 ID
|
||||
* @param data 用户数据
|
||||
*/
|
||||
async function updatePlatformUser(
|
||||
id: number,
|
||||
data: PlatformUserApi.UpdatePlatformUserRequest,
|
||||
) {
|
||||
return requestClient.put(`/platform_user/update/${id}`, data);
|
||||
}
|
||||
|
||||
export { getPlatformUserList, updatePlatformUser };
|
||||
137
apps/web-antd/src/api/product-manage/feature.ts
Normal file
137
apps/web-antd/src/api/product-manage/feature.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace FeatureApi {
|
||||
export interface FeatureItem {
|
||||
id: number;
|
||||
api_id: string;
|
||||
name: string;
|
||||
create_time: string;
|
||||
update_time: string;
|
||||
}
|
||||
|
||||
export interface FeatureList {
|
||||
total: number;
|
||||
items: FeatureItem[];
|
||||
}
|
||||
|
||||
export interface CreateFeatureRequest {
|
||||
api_id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface UpdateFeatureRequest {
|
||||
api_id?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface FeatureExampleItem {
|
||||
id: number;
|
||||
feature_id: number;
|
||||
api_id: string;
|
||||
data: string;
|
||||
create_time: string;
|
||||
update_time: string;
|
||||
}
|
||||
|
||||
export interface ConfigFeatureExampleRequest {
|
||||
feature_id: number;
|
||||
data: string;
|
||||
}
|
||||
|
||||
export interface ConfigFeatureExampleResponse {
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export interface GetFeatureExampleRequest {
|
||||
feature_id: number;
|
||||
}
|
||||
|
||||
export interface GetFeatureExampleResponse {
|
||||
id: number;
|
||||
feature_id: number;
|
||||
api_id: string;
|
||||
data: string;
|
||||
create_time: string;
|
||||
update_time: string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模块列表数据
|
||||
*/
|
||||
async function getFeatureList(params: Recordable<any>) {
|
||||
return requestClient.get<FeatureApi.FeatureList>('/feature/list', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模块详情
|
||||
* @param id 模块ID
|
||||
*/
|
||||
async function getFeatureDetail(id: number) {
|
||||
return requestClient.get<FeatureApi.FeatureItem>(`/feature/detail/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建模块
|
||||
* @param data 模块数据
|
||||
*/
|
||||
async function createFeature(data: FeatureApi.CreateFeatureRequest) {
|
||||
return requestClient.post<{ id: number }>('/feature/create', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新模块
|
||||
* @param id 模块ID
|
||||
* @param data 模块数据
|
||||
*/
|
||||
async function updateFeature(
|
||||
id: number,
|
||||
data: FeatureApi.UpdateFeatureRequest,
|
||||
) {
|
||||
return requestClient.put<{ success: boolean }>(`/feature/update/${id}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除模块
|
||||
* @param id 模块ID
|
||||
*/
|
||||
async function deleteFeature(id: number) {
|
||||
return requestClient.delete<{ success: boolean }>(`/feature/delete/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置功能示例数据
|
||||
* @param data 示例数据配置
|
||||
*/
|
||||
async function configFeatureExample(
|
||||
data: FeatureApi.ConfigFeatureExampleRequest,
|
||||
) {
|
||||
return requestClient.post<FeatureApi.ConfigFeatureExampleResponse>(
|
||||
'/feature/config-example',
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取功能示例数据
|
||||
* @param featureId 功能ID
|
||||
*/
|
||||
async function getFeatureExample(featureId: number) {
|
||||
return requestClient.get<FeatureApi.GetFeatureExampleResponse>(
|
||||
`/feature/example/${featureId}`,
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
configFeatureExample,
|
||||
createFeature,
|
||||
deleteFeature,
|
||||
getFeatureDetail,
|
||||
getFeatureExample,
|
||||
getFeatureList,
|
||||
updateFeature,
|
||||
};
|
||||
2
apps/web-antd/src/api/product-manage/index.ts
Normal file
2
apps/web-antd/src/api/product-manage/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './feature';
|
||||
export * from './product';
|
||||
144
apps/web-antd/src/api/product-manage/product.ts
Normal file
144
apps/web-antd/src/api/product-manage/product.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace ProductApi {
|
||||
export interface ProductItem {
|
||||
id: number;
|
||||
product_name: string;
|
||||
product_en: string;
|
||||
description: string;
|
||||
notes: string;
|
||||
cost_price: number;
|
||||
sell_price: number;
|
||||
create_time: string;
|
||||
update_time: string;
|
||||
}
|
||||
|
||||
export interface ProductList {
|
||||
total: number;
|
||||
items: ProductItem[];
|
||||
}
|
||||
|
||||
export interface CreateProductRequest {
|
||||
product_name: string;
|
||||
product_en: string;
|
||||
description: string;
|
||||
notes?: string;
|
||||
cost_price: number;
|
||||
sell_price: number;
|
||||
}
|
||||
|
||||
export interface UpdateProductRequest {
|
||||
product_name?: string;
|
||||
product_en?: string;
|
||||
description?: string;
|
||||
notes?: string;
|
||||
cost_price?: number;
|
||||
sell_price?: number;
|
||||
}
|
||||
|
||||
export interface ProductFeatureListItem {
|
||||
id: number;
|
||||
product_id: number;
|
||||
feature_id: number;
|
||||
api_id: string;
|
||||
name: string;
|
||||
sort: number;
|
||||
enable: number;
|
||||
is_important: number;
|
||||
create_time: string;
|
||||
update_time: string;
|
||||
}
|
||||
|
||||
export interface ProductFeatureItem {
|
||||
feature_id: number;
|
||||
sort: number;
|
||||
enable: number;
|
||||
is_important: number;
|
||||
}
|
||||
|
||||
export interface UpdateProductFeaturesRequest {
|
||||
features: ProductFeatureItem[];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取产品列表数据
|
||||
*/
|
||||
async function getProductList(params: Recordable<any>) {
|
||||
return requestClient.get<ProductApi.ProductList>('/product/list', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取产品详情
|
||||
* @param id 产品ID
|
||||
*/
|
||||
async function getProductDetail(id: number) {
|
||||
return requestClient.get<ProductApi.ProductItem>(`/product/detail/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建产品
|
||||
* @param data 产品数据
|
||||
*/
|
||||
async function createProduct(data: ProductApi.CreateProductRequest) {
|
||||
return requestClient.post<{ id: number }>('/product/create', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新产品
|
||||
* @param id 产品ID
|
||||
* @param data 产品数据
|
||||
*/
|
||||
async function updateProduct(
|
||||
id: number,
|
||||
data: ProductApi.UpdateProductRequest,
|
||||
) {
|
||||
return requestClient.put<{ success: boolean }>(`/product/update/${id}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除产品
|
||||
* @param id 产品ID
|
||||
*/
|
||||
async function deleteProduct(id: number) {
|
||||
return requestClient.delete<{ success: boolean }>(`/product/delete/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取产品功能列表
|
||||
* @param productId 产品ID
|
||||
*/
|
||||
async function getProductFeatureList(productId: number) {
|
||||
return requestClient.get<ProductApi.ProductFeatureListItem[]>(
|
||||
`/product/feature/list/${productId}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新产品功能关联
|
||||
* @param productId 产品ID
|
||||
* @param data 功能列表数据
|
||||
*/
|
||||
async function updateProductFeatures(
|
||||
productId: number,
|
||||
data: ProductApi.UpdateProductFeaturesRequest,
|
||||
) {
|
||||
return requestClient.put<{ success: boolean }>(
|
||||
`/product/feature/update/${productId}`,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
createProduct,
|
||||
deleteProduct,
|
||||
getProductDetail,
|
||||
getProductFeatureList,
|
||||
getProductList,
|
||||
updateProduct,
|
||||
updateProductFeatures,
|
||||
};
|
||||
113
apps/web-antd/src/api/request.ts
Normal file
113
apps/web-antd/src/api/request.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* 该文件可自行根据业务逻辑进行调整
|
||||
*/
|
||||
import type { RequestClientOptions } from '@vben/request';
|
||||
|
||||
import { useAppConfig } from '@vben/hooks';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import {
|
||||
authenticateResponseInterceptor,
|
||||
defaultResponseInterceptor,
|
||||
errorMessageResponseInterceptor,
|
||||
RequestClient,
|
||||
} from '@vben/request';
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
import { refreshTokenApi } from './core';
|
||||
|
||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||
|
||||
function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
||||
const client = new RequestClient({
|
||||
...options,
|
||||
baseURL,
|
||||
});
|
||||
|
||||
/**
|
||||
* 重新认证逻辑
|
||||
*/
|
||||
async function doReAuthenticate() {
|
||||
console.warn('Access token or refresh token is invalid or expired. ');
|
||||
const accessStore = useAccessStore();
|
||||
const authStore = useAuthStore();
|
||||
accessStore.setAccessToken(null);
|
||||
if (
|
||||
preferences.app.loginExpiredMode === 'modal' &&
|
||||
accessStore.isAccessChecked
|
||||
) {
|
||||
accessStore.setLoginExpired(true);
|
||||
} else {
|
||||
await authStore.logout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token逻辑
|
||||
*/
|
||||
async function doRefreshToken() {
|
||||
const accessStore = useAccessStore();
|
||||
const resp = await refreshTokenApi();
|
||||
const newToken = resp.data;
|
||||
accessStore.setAccessToken(newToken);
|
||||
return newToken;
|
||||
}
|
||||
|
||||
function formatToken(token: null | string) {
|
||||
return token ? `Bearer ${token}` : null;
|
||||
}
|
||||
|
||||
// 请求头处理
|
||||
client.addRequestInterceptor({
|
||||
fulfilled: async (config) => {
|
||||
const accessStore = useAccessStore();
|
||||
|
||||
config.headers.Authorization = formatToken(accessStore.accessToken);
|
||||
config.headers['Accept-Language'] = preferences.app.locale;
|
||||
return config;
|
||||
},
|
||||
});
|
||||
|
||||
// 处理返回的响应数据格式
|
||||
client.addResponseInterceptor(
|
||||
defaultResponseInterceptor({
|
||||
codeField: 'code',
|
||||
dataField: 'data',
|
||||
successCode: 200,
|
||||
}),
|
||||
);
|
||||
|
||||
// token过期的处理
|
||||
client.addResponseInterceptor(
|
||||
authenticateResponseInterceptor({
|
||||
client,
|
||||
doReAuthenticate,
|
||||
doRefreshToken,
|
||||
enableRefreshToken: preferences.app.enableRefreshToken,
|
||||
formatToken,
|
||||
}),
|
||||
);
|
||||
|
||||
// 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
|
||||
client.addResponseInterceptor(
|
||||
errorMessageResponseInterceptor((msg: string, error) => {
|
||||
// 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
|
||||
// 当前mock接口返回的错误字段是 error 或者 message
|
||||
const responseData = error?.response?.data ?? {};
|
||||
const errorMessage = responseData?.error ?? responseData?.msg ?? '';
|
||||
// 如果没有错误信息,则会根据状态码进行提示
|
||||
message.error(errorMessage || msg);
|
||||
}),
|
||||
);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
export const requestClient = createRequestClient(apiURL, {
|
||||
responseReturn: 'data',
|
||||
});
|
||||
|
||||
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
|
||||
165
apps/web-antd/src/api/system/api.ts
Normal file
165
apps/web-antd/src/api/system/api.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace SystemApiApi {
|
||||
export interface SystemApiItem {
|
||||
id: string;
|
||||
role_id?: string;
|
||||
api_id?: string;
|
||||
api_name: string;
|
||||
api_code: string;
|
||||
method: string;
|
||||
url: string;
|
||||
status: 0 | 1;
|
||||
description?: string;
|
||||
create_time?: string;
|
||||
update_time?: string;
|
||||
}
|
||||
|
||||
export interface SystemApi {
|
||||
list: SystemApiItem[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface SystemApiAllResponse {
|
||||
items: SystemApiItem[];
|
||||
}
|
||||
|
||||
export interface SystemRoleApiResponse {
|
||||
items: null | SystemApiItem[];
|
||||
}
|
||||
|
||||
export interface RoleApiItem {
|
||||
id: string;
|
||||
role_id: string;
|
||||
api_id: string;
|
||||
api_name: string;
|
||||
api_code: string;
|
||||
method: string;
|
||||
url: string;
|
||||
status: 0 | 1;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface RoleApi {
|
||||
list: RoleApiItem[];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取API列表数据
|
||||
*/
|
||||
async function getApiList(params: Recordable<any>) {
|
||||
return requestClient.get<SystemApiApi.SystemApi>('/api/list', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取API详情
|
||||
* @param id API ID
|
||||
*/
|
||||
async function getApiDetail(id: string) {
|
||||
return requestClient.get<SystemApiApi.SystemApiItem>(`/api/detail/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建API
|
||||
* @param data API数据
|
||||
*/
|
||||
async function createApi(
|
||||
data: Omit<SystemApiApi.SystemApiItem, 'create_time' | 'id' | 'update_time'>,
|
||||
) {
|
||||
return requestClient.post('/api/create', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新API
|
||||
* @param id API ID
|
||||
* @param data API数据
|
||||
*/
|
||||
async function updateApi(
|
||||
id: string,
|
||||
data: Omit<SystemApiApi.SystemApiItem, 'create_time' | 'id' | 'update_time'>,
|
||||
) {
|
||||
return requestClient.put(`/api/update/${id}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除API
|
||||
* @param id API ID
|
||||
*/
|
||||
async function deleteApi(id: number) {
|
||||
return requestClient.delete(`/api/delete/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量更新API状态
|
||||
* @param data.ids API ID数组
|
||||
* @param data.status 状态值
|
||||
*/
|
||||
async function batchUpdateApiStatus(data: { ids: number[]; status: 0 | 1 }) {
|
||||
return requestClient.put('/api/batch-update-status', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色API权限列表
|
||||
* @param roleId 角色ID
|
||||
*/
|
||||
async function getRoleApiList(roleId: string) {
|
||||
return requestClient.get<SystemApiApi.SystemRoleApiResponse>(
|
||||
`/role/${roleId}/api/list`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分配角色API权限
|
||||
* @param data.api_ids API ID数组
|
||||
* @param data.role_id 角色ID
|
||||
*/
|
||||
async function assignRoleApi(data: { api_ids: string[]; role_id: string }) {
|
||||
return requestClient.post('/role/api/assign', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除角色API权限
|
||||
* @param data.api_ids API ID数组
|
||||
* @param data.role_id 角色ID
|
||||
*/
|
||||
async function removeRoleApi(data: { api_ids: string[]; role_id: string }) {
|
||||
return requestClient.post('/role/api/remove', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新角色API权限(全量更新)
|
||||
* @param data.api_ids API ID数组
|
||||
* @param data.role_id 角色ID
|
||||
*/
|
||||
async function updateRoleApi(data: { api_ids: string[]; role_id: string }) {
|
||||
return requestClient.put('/role/api/update', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有API列表(用于权限分配)
|
||||
* @param params.status 状态过滤
|
||||
*/
|
||||
async function getAllApiList(params?: { status?: number }) {
|
||||
return requestClient.get<SystemApiApi.SystemApiAllResponse>('/api/all', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
assignRoleApi,
|
||||
batchUpdateApiStatus,
|
||||
createApi,
|
||||
deleteApi,
|
||||
getAllApiList,
|
||||
getApiDetail,
|
||||
getApiList,
|
||||
getRoleApiList,
|
||||
removeRoleApi,
|
||||
updateApi,
|
||||
updateRoleApi,
|
||||
};
|
||||
54
apps/web-antd/src/api/system/dept.ts
Normal file
54
apps/web-antd/src/api/system/dept.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace SystemDeptApi {
|
||||
export interface SystemDept {
|
||||
[key: string]: any;
|
||||
children?: SystemDept[];
|
||||
id: string;
|
||||
name: string;
|
||||
remark?: string;
|
||||
status: 0 | 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门列表数据
|
||||
*/
|
||||
async function getDeptList() {
|
||||
return requestClient.get<Array<SystemDeptApi.SystemDept>>(
|
||||
'/system/dept/list',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建部门
|
||||
* @param data 部门数据
|
||||
*/
|
||||
async function createDept(
|
||||
data: Omit<SystemDeptApi.SystemDept, 'children' | 'id'>,
|
||||
) {
|
||||
return requestClient.post('/system/dept', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新部门
|
||||
*
|
||||
* @param id 部门 ID
|
||||
* @param data 部门数据
|
||||
*/
|
||||
async function updateDept(
|
||||
id: string,
|
||||
data: Omit<SystemDeptApi.SystemDept, 'children' | 'id'>,
|
||||
) {
|
||||
return requestClient.put(`/system/dept/${id}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除部门
|
||||
* @param id 部门 ID
|
||||
*/
|
||||
async function deleteDept(id: string) {
|
||||
return requestClient.delete(`/system/dept/${id}`);
|
||||
}
|
||||
|
||||
export { createDept, deleteDept, getDeptList, updateDept };
|
||||
5
apps/web-antd/src/api/system/index.ts
Normal file
5
apps/web-antd/src/api/system/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from './api';
|
||||
export * from './dept';
|
||||
export * from './menu';
|
||||
export * from './role';
|
||||
export * from './user';
|
||||
156
apps/web-antd/src/api/system/menu.ts
Normal file
156
apps/web-antd/src/api/system/menu.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace SystemMenuApi {
|
||||
/** 徽标颜色集合 */
|
||||
export const BadgeVariants = [
|
||||
'default',
|
||||
'destructive',
|
||||
'primary',
|
||||
'success',
|
||||
'warning',
|
||||
] as const;
|
||||
/** 徽标类型集合 */
|
||||
export const BadgeTypes = ['dot', 'normal'] as const;
|
||||
/** 菜单类型集合 */
|
||||
export const MenuTypes = [
|
||||
'catalog',
|
||||
'menu',
|
||||
'embedded',
|
||||
'link',
|
||||
'button',
|
||||
] as const;
|
||||
/** 系统菜单 */
|
||||
export interface SystemMenu {
|
||||
[key: string]: any;
|
||||
/** 后端权限标识 */
|
||||
authCode: string;
|
||||
/** 子级 */
|
||||
children?: SystemMenu[];
|
||||
/** 组件 */
|
||||
component?: string;
|
||||
/** 菜单ID */
|
||||
id: string;
|
||||
/** 菜单元数据 */
|
||||
meta?: {
|
||||
/** 激活时显示的图标 */
|
||||
activeIcon?: string;
|
||||
/** 作为路由时,需要激活的菜单的Path */
|
||||
activePath?: string;
|
||||
/** 固定在标签栏 */
|
||||
affixTab?: boolean;
|
||||
/** 在标签栏固定的顺序 */
|
||||
affixTabOrder?: number;
|
||||
/** 徽标内容(当徽标类型为normal时有效) */
|
||||
badge?: string;
|
||||
/** 徽标类型 */
|
||||
badgeType?: (typeof BadgeTypes)[number];
|
||||
/** 徽标颜色 */
|
||||
badgeVariants?: (typeof BadgeVariants)[number];
|
||||
/** 在菜单中隐藏下级 */
|
||||
hideChildrenInMenu?: boolean;
|
||||
/** 在面包屑中隐藏 */
|
||||
hideInBreadcrumb?: boolean;
|
||||
/** 在菜单中隐藏 */
|
||||
hideInMenu?: boolean;
|
||||
/** 在标签栏中隐藏 */
|
||||
hideInTab?: boolean;
|
||||
/** 菜单图标 */
|
||||
icon?: string;
|
||||
/** 内嵌Iframe的URL */
|
||||
iframeSrc?: string;
|
||||
/** 是否缓存页面 */
|
||||
keepAlive?: boolean;
|
||||
/** 外链页面的URL */
|
||||
link?: string;
|
||||
/** 同一个路由最大打开的标签数 */
|
||||
maxNumOfOpenTab?: number;
|
||||
/** 无需基础布局 */
|
||||
noBasicLayout?: boolean;
|
||||
/** 是否在新窗口打开 */
|
||||
openInNewWindow?: boolean;
|
||||
/** 菜单排序 */
|
||||
order?: number;
|
||||
/** 额外的路由参数 */
|
||||
query?: Recordable<any>;
|
||||
/** 菜单标题 */
|
||||
title?: string;
|
||||
};
|
||||
/** 菜单名称 */
|
||||
name: string;
|
||||
/** 路由路径 */
|
||||
path: string;
|
||||
/** 父级ID */
|
||||
pid: string;
|
||||
/** 重定向 */
|
||||
redirect?: string;
|
||||
/** 菜单类型 */
|
||||
type: (typeof MenuTypes)[number];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取菜单数据列表
|
||||
*/
|
||||
async function getMenuList() {
|
||||
return requestClient.get<Array<SystemMenuApi.SystemMenu>>('/menu/list');
|
||||
}
|
||||
|
||||
async function isMenuNameExists(
|
||||
name: string,
|
||||
id?: SystemMenuApi.SystemMenu['id'],
|
||||
) {
|
||||
return requestClient.get<boolean>('/menu/name-exists', {
|
||||
params: { id, name },
|
||||
});
|
||||
}
|
||||
|
||||
async function isMenuPathExists(
|
||||
path: string,
|
||||
id?: SystemMenuApi.SystemMenu['id'],
|
||||
) {
|
||||
return requestClient.get<boolean>('/menu/path-exists', {
|
||||
params: { id, path },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建菜单
|
||||
* @param data 菜单数据
|
||||
*/
|
||||
async function createMenu(
|
||||
data: Omit<SystemMenuApi.SystemMenu, 'children' | 'id'>,
|
||||
) {
|
||||
return requestClient.post('/menu/create', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新菜单
|
||||
*
|
||||
* @param id 菜单 ID
|
||||
* @param data 菜单数据
|
||||
*/
|
||||
async function updateMenu(
|
||||
id: string,
|
||||
data: Omit<SystemMenuApi.SystemMenu, 'children' | 'id'>,
|
||||
) {
|
||||
return requestClient.put(`/menu/update/${id}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除菜单
|
||||
* @param id 菜单 ID
|
||||
*/
|
||||
async function deleteMenu(id: string) {
|
||||
return requestClient.delete(`/menu/delete/${id}`);
|
||||
}
|
||||
|
||||
export {
|
||||
createMenu,
|
||||
deleteMenu,
|
||||
getMenuList,
|
||||
isMenuNameExists,
|
||||
isMenuPathExists,
|
||||
updateMenu,
|
||||
};
|
||||
61
apps/web-antd/src/api/system/role.ts
Normal file
61
apps/web-antd/src/api/system/role.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace SystemRoleApi {
|
||||
export interface SystemRoleItem {
|
||||
id: string;
|
||||
role_name: string;
|
||||
role_code: string;
|
||||
description?: string;
|
||||
status: 0 | 1;
|
||||
sort: number;
|
||||
create_time: string;
|
||||
menu_ids: string[];
|
||||
}
|
||||
|
||||
export interface SystemRole {
|
||||
total: number;
|
||||
items: SystemRoleItem[];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色列表数据
|
||||
*/
|
||||
async function getRoleList(params: Recordable<any>) {
|
||||
return requestClient.get<SystemRoleApi.SystemRole>('/role/list', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建角色
|
||||
* @param data 角色数据
|
||||
*/
|
||||
async function createRole(data: Omit<SystemRoleApi.SystemRoleItem, 'id'>) {
|
||||
return requestClient.post('/role/create', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新角色
|
||||
*
|
||||
* @param id 角色 ID
|
||||
* @param data 角色数据
|
||||
*/
|
||||
async function updateRole(
|
||||
id: string,
|
||||
data: Omit<SystemRoleApi.SystemRoleItem, 'id'>,
|
||||
) {
|
||||
return requestClient.put(`/role/update/${id}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除角色
|
||||
* @param id 角色 ID
|
||||
*/
|
||||
async function deleteRole(id: string) {
|
||||
return requestClient.delete(`/role/delete/${id}`);
|
||||
}
|
||||
|
||||
export { createRole, deleteRole, getRoleList, updateRole };
|
||||
64
apps/web-antd/src/api/system/user.ts
Normal file
64
apps/web-antd/src/api/system/user.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export namespace SystemUserApi {
|
||||
export interface SystemUser {
|
||||
[key: string]: any;
|
||||
id: string;
|
||||
name: string;
|
||||
permissions: string[];
|
||||
remark?: string;
|
||||
status: 0 | 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色列表数据
|
||||
*/
|
||||
async function getUserList(params: Recordable<any>) {
|
||||
return requestClient.get<Array<SystemUserApi.SystemUser>>('/user/list', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建角色
|
||||
* @param data 角色数据
|
||||
*/
|
||||
async function createUser(data: Omit<SystemUserApi.SystemUser, 'id'>) {
|
||||
return requestClient.post('/user/create', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新角色
|
||||
*
|
||||
* @param id 角色 ID
|
||||
* @param data 角色数据
|
||||
*/
|
||||
async function updateUser(
|
||||
id: string,
|
||||
data: Omit<SystemUserApi.SystemUser, 'id'>,
|
||||
) {
|
||||
return requestClient.put(`/user/update/${id}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除角色
|
||||
* @param id 角色 ID
|
||||
*/
|
||||
async function deleteUser(id: string) {
|
||||
return requestClient.delete(`/user/delete/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置用户密码
|
||||
* @param id 用户 ID
|
||||
* @param data 新密码数据
|
||||
* @param data.password 新密码
|
||||
*/
|
||||
async function resetPassword(id: string, data: { password: string }) {
|
||||
return requestClient.post(`/reset-password/${id}`, data);
|
||||
}
|
||||
|
||||
export { createUser, deleteUser, getUserList, resetPassword, updateUser };
|
||||
39
apps/web-antd/src/app.vue
Normal file
39
apps/web-antd/src/app.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useAntdDesignTokens } from '@vben/hooks';
|
||||
import { preferences, usePreferences } from '@vben/preferences';
|
||||
|
||||
import { App, ConfigProvider, theme } from 'ant-design-vue';
|
||||
|
||||
import { antdLocale } from '#/locales';
|
||||
|
||||
defineOptions({ name: 'App' });
|
||||
|
||||
const { isDark } = usePreferences();
|
||||
const { tokens } = useAntdDesignTokens();
|
||||
|
||||
const tokenTheme = computed(() => {
|
||||
const algorithm = isDark.value
|
||||
? [theme.darkAlgorithm]
|
||||
: [theme.defaultAlgorithm];
|
||||
|
||||
// antd 紧凑模式算法
|
||||
if (preferences.app.compact) {
|
||||
algorithm.push(theme.compactAlgorithm);
|
||||
}
|
||||
|
||||
return {
|
||||
algorithm,
|
||||
token: tokens,
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ConfigProvider :locale="antdLocale" :theme="tokenTheme">
|
||||
<App>
|
||||
<RouterView />
|
||||
</App>
|
||||
</ConfigProvider>
|
||||
</template>
|
||||
72
apps/web-antd/src/bootstrap.ts
Normal file
72
apps/web-antd/src/bootstrap.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { createApp, watchEffect } from 'vue';
|
||||
|
||||
import { registerAccessDirective } from '@vben/access';
|
||||
import { registerLoadingDirective } from '@vben/common-ui/es/loading';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { initStores } from '@vben/stores';
|
||||
import '@vben/styles';
|
||||
import '@vben/styles/antd';
|
||||
|
||||
import { useTitle } from '@vueuse/core';
|
||||
|
||||
import { $t, setupI18n } from '#/locales';
|
||||
|
||||
import { initComponentAdapter } from './adapter/component';
|
||||
import App from './app.vue';
|
||||
import { router } from './router';
|
||||
|
||||
async function bootstrap(namespace: string) {
|
||||
// 初始化组件适配器
|
||||
await initComponentAdapter();
|
||||
|
||||
// // 设置弹窗的默认配置
|
||||
// setDefaultModalProps({
|
||||
// fullscreenButton: false,
|
||||
// });
|
||||
// // 设置抽屉的默认配置
|
||||
// setDefaultDrawerProps({
|
||||
// zIndex: 1020,
|
||||
// });
|
||||
|
||||
const app = createApp(App);
|
||||
|
||||
// 注册v-loading指令
|
||||
registerLoadingDirective(app, {
|
||||
loading: 'loading', // 在这里可以自定义指令名称,也可以明确提供false表示不注册这个指令
|
||||
spinning: 'spinning',
|
||||
});
|
||||
|
||||
// 国际化 i18n 配置
|
||||
await setupI18n(app);
|
||||
|
||||
// 配置 pinia-tore
|
||||
await initStores(app, { namespace });
|
||||
|
||||
// 安装权限指令
|
||||
registerAccessDirective(app);
|
||||
|
||||
// 初始化 tippy
|
||||
const { initTippy } = await import('@vben/common-ui/es/tippy');
|
||||
initTippy(app);
|
||||
|
||||
// 配置路由及路由守卫
|
||||
app.use(router);
|
||||
|
||||
// 配置Motion插件
|
||||
const { MotionPlugin } = await import('@vben/plugins/motion');
|
||||
app.use(MotionPlugin);
|
||||
|
||||
// 动态更新标题
|
||||
watchEffect(() => {
|
||||
if (preferences.app.dynamicTitle) {
|
||||
const routeTitle = router.currentRoute.value.meta?.title;
|
||||
const pageTitle =
|
||||
(routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name;
|
||||
useTitle(pageTitle);
|
||||
}
|
||||
});
|
||||
|
||||
app.mount('#app');
|
||||
}
|
||||
|
||||
export { bootstrap };
|
||||
21
apps/web-antd/src/layouts/auth.vue
Normal file
21
apps/web-antd/src/layouts/auth.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { AuthPageLayout } from '@vben/layouts';
|
||||
import { preferences } from '@vben/preferences';
|
||||
|
||||
const appName = computed(() => preferences.app.name);
|
||||
const logo = computed(() => preferences.logo.source);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AuthPageLayout
|
||||
:app-name="appName"
|
||||
:logo="logo"
|
||||
:page-description="appName"
|
||||
page-title="大型中后台管理系统"
|
||||
>
|
||||
<!-- 自定义工具栏 -->
|
||||
<!-- <template #toolbar></template> -->
|
||||
</AuthPageLayout>
|
||||
</template>
|
||||
90
apps/web-antd/src/layouts/basic.vue
Normal file
90
apps/web-antd/src/layouts/basic.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<script lang="ts" setup>
|
||||
import type { NotificationItem } from '@vben/layouts';
|
||||
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { AuthenticationLoginExpiredModal } from '@vben/common-ui';
|
||||
import { useWatermark } from '@vben/hooks';
|
||||
import { BasicLayout, LockScreen, UserDropdown } from '@vben/layouts';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||
|
||||
import { useAuthStore } from '#/store';
|
||||
import LoginForm from '#/views/_core/authentication/login.vue';
|
||||
|
||||
const notifications = ref<NotificationItem[]>([]);
|
||||
|
||||
const userStore = useUserStore();
|
||||
const authStore = useAuthStore();
|
||||
const accessStore = useAccessStore();
|
||||
const { destroyWatermark, updateWatermark } = useWatermark();
|
||||
const _showDot = computed(() =>
|
||||
notifications.value.some((item) => !item.isRead),
|
||||
);
|
||||
|
||||
const menus = computed(() => []);
|
||||
|
||||
const avatar = computed(() => {
|
||||
return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar;
|
||||
});
|
||||
|
||||
async function handleLogout() {
|
||||
await authStore.logout(false);
|
||||
}
|
||||
|
||||
function _handleNoticeClear() {
|
||||
notifications.value = [];
|
||||
}
|
||||
|
||||
function _handleMakeAll() {
|
||||
notifications.value.forEach((item) => (item.isRead = true));
|
||||
}
|
||||
watch(
|
||||
() => preferences.app.watermark,
|
||||
async (enable) => {
|
||||
if (enable) {
|
||||
await updateWatermark({
|
||||
content: `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`,
|
||||
});
|
||||
} else {
|
||||
destroyWatermark();
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BasicLayout @clear-preferences-and-logout="handleLogout">
|
||||
<template #user-dropdown>
|
||||
<UserDropdown
|
||||
:avatar
|
||||
:menus
|
||||
:description="userStore.userInfo?.username"
|
||||
tag-text="全能查"
|
||||
@logout="handleLogout"
|
||||
/>
|
||||
</template>
|
||||
<!-- <template #notification>
|
||||
<Notification
|
||||
:dot="showDot"
|
||||
:notifications="notifications"
|
||||
@clear="handleNoticeClear"
|
||||
@make-all="handleMakeAll"
|
||||
/>
|
||||
</template> -->
|
||||
<template #extra>
|
||||
<AuthenticationLoginExpiredModal
|
||||
v-model:open="accessStore.loginExpired"
|
||||
:avatar
|
||||
>
|
||||
<LoginForm />
|
||||
</AuthenticationLoginExpiredModal>
|
||||
</template>
|
||||
<template #lock-screen>
|
||||
<LockScreen :avatar @to-login="handleLogout" />
|
||||
</template>
|
||||
</BasicLayout>
|
||||
</template>
|
||||
6
apps/web-antd/src/layouts/index.ts
Normal file
6
apps/web-antd/src/layouts/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
const BasicLayout = () => import('./basic.vue');
|
||||
const AuthPageLayout = () => import('./auth.vue');
|
||||
|
||||
const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView);
|
||||
|
||||
export { AuthPageLayout, BasicLayout, IFrameView };
|
||||
3
apps/web-antd/src/locales/README.md
Normal file
3
apps/web-antd/src/locales/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# locale
|
||||
|
||||
每个app使用的国际化可能不同,这里用于扩展国际化的功能,例如扩展 dayjs、antd组件库的多语言切换,以及app本身的国际化文件。
|
||||
102
apps/web-antd/src/locales/index.ts
Normal file
102
apps/web-antd/src/locales/index.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import type { Locale } from 'ant-design-vue/es/locale';
|
||||
|
||||
import type { App } from 'vue';
|
||||
|
||||
import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import {
|
||||
$t,
|
||||
setupI18n as coreSetup,
|
||||
loadLocalesMapFromDir,
|
||||
} from '@vben/locales';
|
||||
import { preferences } from '@vben/preferences';
|
||||
|
||||
import antdEnLocale from 'ant-design-vue/es/locale/en_US';
|
||||
import antdDefaultLocale from 'ant-design-vue/es/locale/zh_CN';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const antdLocale = ref<Locale>(antdDefaultLocale);
|
||||
|
||||
const modules = import.meta.glob('./langs/**/*.json');
|
||||
|
||||
const localesMap = loadLocalesMapFromDir(
|
||||
/\.\/langs\/([^/]+)\/(.*)\.json$/,
|
||||
modules,
|
||||
);
|
||||
/**
|
||||
* 加载应用特有的语言包
|
||||
* 这里也可以改造为从服务端获取翻译数据
|
||||
* @param lang
|
||||
*/
|
||||
async function loadMessages(lang: SupportedLanguagesType) {
|
||||
const [appLocaleMessages] = await Promise.all([
|
||||
localesMap[lang]?.(),
|
||||
loadThirdPartyMessage(lang),
|
||||
]);
|
||||
return appLocaleMessages?.default;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载第三方组件库的语言包
|
||||
* @param lang
|
||||
*/
|
||||
async function loadThirdPartyMessage(lang: SupportedLanguagesType) {
|
||||
await Promise.all([loadAntdLocale(lang), loadDayjsLocale(lang)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载dayjs的语言包
|
||||
* @param lang
|
||||
*/
|
||||
async function loadDayjsLocale(lang: SupportedLanguagesType) {
|
||||
let locale;
|
||||
switch (lang) {
|
||||
case 'en-US': {
|
||||
locale = await import('dayjs/locale/en');
|
||||
break;
|
||||
}
|
||||
case 'zh-CN': {
|
||||
locale = await import('dayjs/locale/zh-cn');
|
||||
break;
|
||||
}
|
||||
// 默认使用英语
|
||||
default: {
|
||||
locale = await import('dayjs/locale/en');
|
||||
}
|
||||
}
|
||||
if (locale) {
|
||||
dayjs.locale(locale);
|
||||
} else {
|
||||
console.error(`Failed to load dayjs locale for ${lang}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载antd的语言包
|
||||
* @param lang
|
||||
*/
|
||||
async function loadAntdLocale(lang: SupportedLanguagesType) {
|
||||
switch (lang) {
|
||||
case 'en-US': {
|
||||
antdLocale.value = antdEnLocale;
|
||||
break;
|
||||
}
|
||||
case 'zh-CN': {
|
||||
antdLocale.value = antdDefaultLocale;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function setupI18n(app: App, options: LocaleSetupOptions = {}) {
|
||||
await coreSetup(app, {
|
||||
defaultLocale: preferences.app.locale,
|
||||
loadMessages,
|
||||
missingWarn: !import.meta.env.PROD,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export { $t, antdLocale, setupI18n };
|
||||
31
apps/web-antd/src/locales/lang/zh-CN/platform-user.ts
Normal file
31
apps/web-antd/src/locales/lang/zh-CN/platform-user.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
export default {
|
||||
platformUser: {
|
||||
name: '平台用户',
|
||||
list: '平台用户列表',
|
||||
field: {
|
||||
mobile: '手机号',
|
||||
nickname: '昵称',
|
||||
platform: '用户平台',
|
||||
inside: '是否内部用户',
|
||||
createTime: '创建时间',
|
||||
updateTime: '更新时间',
|
||||
info: '备注信息',
|
||||
},
|
||||
search: {
|
||||
mobile: '请输入手机号',
|
||||
nickname: '请输入昵称',
|
||||
platform: '请选择用户平台',
|
||||
inside: '请选择是否内部用户',
|
||||
createTime: '请选择创建时间',
|
||||
},
|
||||
operation: '操作',
|
||||
platformMap: {
|
||||
h5: 'H5',
|
||||
wechat: '微信',
|
||||
},
|
||||
insideMap: {
|
||||
1: '是',
|
||||
0: '否',
|
||||
},
|
||||
},
|
||||
};
|
||||
12
apps/web-antd/src/locales/langs/en-US/demos.json
Normal file
12
apps/web-antd/src/locales/langs/en-US/demos.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
14
apps/web-antd/src/locales/langs/en-US/page.json
Normal file
14
apps/web-antd/src/locales/langs/en-US/page.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"auth": {
|
||||
"login": "Login",
|
||||
"register": "Register",
|
||||
"codeLogin": "Code Login",
|
||||
"qrcodeLogin": "Qr Code Login",
|
||||
"forgetPassword": "Forget Password"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Dashboard",
|
||||
"analytics": "Analytics",
|
||||
"workspace": "Workspace"
|
||||
}
|
||||
}
|
||||
99
apps/web-antd/src/locales/langs/en-US/system.json
Normal file
99
apps/web-antd/src/locales/langs/en-US/system.json
Normal file
@@ -0,0 +1,99 @@
|
||||
{
|
||||
"title": "System Management",
|
||||
"dept": {
|
||||
"name": "Department",
|
||||
"title": "Department Management",
|
||||
"deptName": "Department Name",
|
||||
"status": "Status",
|
||||
"createTime": "Create Time",
|
||||
"remark": "Remark",
|
||||
"operation": "Operation",
|
||||
"parentDept": "Parent Department"
|
||||
},
|
||||
"menu": {
|
||||
"title": "Menu Management",
|
||||
"parent": "Parent Menu",
|
||||
"menuTitle": "Title",
|
||||
"menuName": "Menu Name",
|
||||
"name": "Menu",
|
||||
"type": "Type",
|
||||
"typeCatalog": "Catalog",
|
||||
"typeMenu": "Menu",
|
||||
"typeButton": "Button",
|
||||
"typeLink": "Link",
|
||||
"typeEmbedded": "Embedded",
|
||||
"icon": "Icon",
|
||||
"activeIcon": "Active Icon",
|
||||
"activePath": "Active Path",
|
||||
"path": "Route Path",
|
||||
"component": "Component",
|
||||
"status": "Status",
|
||||
"authCode": "Auth Code",
|
||||
"badge": "Badge",
|
||||
"operation": "Operation",
|
||||
"linkSrc": "Link Address",
|
||||
"affixTab": "Affix In Tabs",
|
||||
"keepAlive": "Keep Alive",
|
||||
"hideInMenu": "Hide In Menu",
|
||||
"hideInTab": "Hide In Tabbar",
|
||||
"hideChildrenInMenu": "Hide Children In Menu",
|
||||
"hideInBreadcrumb": "Hide In Breadcrumb",
|
||||
"advancedSettings": "Other Settings",
|
||||
"activePathMustExist": "The path could not find a valid menu",
|
||||
"activePathHelp": "When jumping to the current route, \nthe menu path that needs to be activated must be specified when it does not display in the navigation menu.",
|
||||
"badgeType": {
|
||||
"title": "Badge Type",
|
||||
"dot": "Dot",
|
||||
"normal": "Text",
|
||||
"none": "None"
|
||||
},
|
||||
"badgeVariants": "Badge Style"
|
||||
},
|
||||
"role": {
|
||||
"title": "Role Management",
|
||||
"list": "Role List",
|
||||
"name": "Role",
|
||||
"roleName": "Role Name",
|
||||
"id": "Role ID",
|
||||
"status": "Status",
|
||||
"remark": "Remark",
|
||||
"createTime": "Creation Time",
|
||||
"operation": "Operation",
|
||||
"permissions": "Permissions",
|
||||
"setPermissions": "Permissions",
|
||||
"setApiPermissions": "API Permissions",
|
||||
"roleCode": "Role Code",
|
||||
"description": "Description"
|
||||
},
|
||||
"api": {
|
||||
"title": "API Management",
|
||||
"list": "API List",
|
||||
"name": "API",
|
||||
"apiName": "API Name",
|
||||
"apiCode": "API Code",
|
||||
"method": "Request Method",
|
||||
"url": "API URL",
|
||||
"status": "Status",
|
||||
"description": "Description",
|
||||
"createTime": "Create Time",
|
||||
"operation": "Operation",
|
||||
"permissions": "Permissions",
|
||||
"setPermissions": "Set Permissions"
|
||||
},
|
||||
"user": {
|
||||
"title": "User Management",
|
||||
"name": "User",
|
||||
"list": "User List",
|
||||
"userName": "Username",
|
||||
"realName": "Real Name",
|
||||
"status": "Status",
|
||||
"setPermissions": "Set Permissions",
|
||||
"createTime": "Create Time",
|
||||
"operation": "Operation",
|
||||
"resetPassword": "Reset Password",
|
||||
"newPassword": "New Password",
|
||||
"confirmPassword": "Confirm Password",
|
||||
"confirmPasswordRequired": "Please confirm password",
|
||||
"passwordMismatch": "The two passwords do not match"
|
||||
}
|
||||
}
|
||||
12
apps/web-antd/src/locales/langs/zh-CN/demos.json
Normal file
12
apps/web-antd/src/locales/langs/zh-CN/demos.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"title": "演示",
|
||||
"antd": "Ant Design Vue",
|
||||
"vben": {
|
||||
"title": "项目",
|
||||
"about": "关于",
|
||||
"document": "文档",
|
||||
"antdv": "Ant Design Vue 版本",
|
||||
"naive-ui": "Naive UI 版本",
|
||||
"element-plus": "Element Plus 版本"
|
||||
}
|
||||
}
|
||||
14
apps/web-antd/src/locales/langs/zh-CN/page.json
Normal file
14
apps/web-antd/src/locales/langs/zh-CN/page.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"auth": {
|
||||
"login": "登录",
|
||||
"register": "注册",
|
||||
"codeLogin": "验证码登录",
|
||||
"qrcodeLogin": "二维码登录",
|
||||
"forgetPassword": "忘记密码"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "概览",
|
||||
"analytics": "分析页",
|
||||
"workspace": "工作台"
|
||||
}
|
||||
}
|
||||
101
apps/web-antd/src/locales/langs/zh-CN/system.json
Normal file
101
apps/web-antd/src/locales/langs/zh-CN/system.json
Normal file
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"dept": {
|
||||
"list": "部门列表",
|
||||
"createTime": "创建时间",
|
||||
"deptName": "部门名称",
|
||||
"name": "部门",
|
||||
"operation": "操作",
|
||||
"parentDept": "上级部门",
|
||||
"remark": "备注",
|
||||
"status": "状态",
|
||||
"title": "部门管理"
|
||||
},
|
||||
"menu": {
|
||||
"list": "菜单列表",
|
||||
"activeIcon": "激活图标",
|
||||
"activePath": "激活路径",
|
||||
"activePathHelp": "跳转到当前路由时,需要激活的菜单路径。\n当不在导航菜单中显示时,需要指定激活路径",
|
||||
"activePathMustExist": "该路径未能找到有效的菜单",
|
||||
"advancedSettings": "其它设置",
|
||||
"affixTab": "固定在标签",
|
||||
"authCode": "权限标识",
|
||||
"badge": "徽章内容",
|
||||
"badgeVariants": "徽标样式",
|
||||
"badgeType": {
|
||||
"dot": "点",
|
||||
"none": "无",
|
||||
"normal": "文字",
|
||||
"title": "徽标类型"
|
||||
},
|
||||
"component": "页面组件",
|
||||
"hideChildrenInMenu": "隐藏子菜单",
|
||||
"hideInBreadcrumb": "在面包屑中隐藏",
|
||||
"hideInMenu": "隐藏菜单",
|
||||
"hideInTab": "在标签栏中隐藏",
|
||||
"icon": "图标",
|
||||
"keepAlive": "缓存标签页",
|
||||
"linkSrc": "链接地址",
|
||||
"menuName": "菜单名称",
|
||||
"menuTitle": "标题",
|
||||
"name": "菜单",
|
||||
"operation": "操作",
|
||||
"parent": "上级菜单",
|
||||
"path": "路由地址",
|
||||
"status": "状态",
|
||||
"title": "菜单管理",
|
||||
"type": "类型",
|
||||
"typeButton": "按钮",
|
||||
"typeCatalog": "目录",
|
||||
"typeEmbedded": "内嵌",
|
||||
"typeLink": "外链",
|
||||
"typeMenu": "菜单"
|
||||
},
|
||||
"role": {
|
||||
"title": "角色管理",
|
||||
"list": "角色列表",
|
||||
"name": "角色",
|
||||
"roleName": "角色名称",
|
||||
"id": "角色ID",
|
||||
"status": "状态",
|
||||
"remark": "备注",
|
||||
"createTime": "创建时间",
|
||||
"operation": "操作",
|
||||
"permissions": "权限",
|
||||
"setPermissions": "授权",
|
||||
"setApiPermissions": "API权限",
|
||||
"roleCode": "角色编号",
|
||||
"description": "描述"
|
||||
},
|
||||
"title": "系统管理",
|
||||
"api": {
|
||||
"title": "API管理",
|
||||
"list": "API列表",
|
||||
"name": "API",
|
||||
"apiName": "API名称",
|
||||
"apiCode": "API编码",
|
||||
"method": "请求方法",
|
||||
"url": "API地址",
|
||||
"status": "状态",
|
||||
"description": "描述",
|
||||
"createTime": "创建时间",
|
||||
"operation": "操作",
|
||||
"permissions": "权限",
|
||||
"setPermissions": "设置权限"
|
||||
},
|
||||
"user": {
|
||||
"title": "用户管理",
|
||||
"name": "用户",
|
||||
"list": "用户列表",
|
||||
"userName": "用户名",
|
||||
"realName": "真实姓名",
|
||||
"status": "状态",
|
||||
"setPermissions": "设置权限",
|
||||
"createTime": "创建时间",
|
||||
"operation": "操作",
|
||||
"resetPassword": "重置密码",
|
||||
"newPassword": "新密码",
|
||||
"confirmPassword": "确认密码",
|
||||
"confirmPasswordRequired": "请确认密码",
|
||||
"passwordMismatch": "两次输入的密码不一致"
|
||||
}
|
||||
}
|
||||
31
apps/web-antd/src/main.ts
Normal file
31
apps/web-antd/src/main.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { initPreferences } from '@vben/preferences';
|
||||
import { unmountGlobalLoading } from '@vben/utils';
|
||||
|
||||
import { overridesPreferences } from './preferences';
|
||||
|
||||
/**
|
||||
* 应用初始化完成之后再进行页面加载渲染
|
||||
*/
|
||||
async function initApplication() {
|
||||
// name用于指定项目唯一标识
|
||||
// 用于区分不同项目的偏好设置以及存储数据的key前缀以及其他一些需要隔离的数据
|
||||
const env = import.meta.env.PROD ? 'prod' : 'dev';
|
||||
const appVersion = import.meta.env.VITE_APP_VERSION;
|
||||
const namespace = `${import.meta.env.VITE_APP_NAMESPACE}-${appVersion}-${env}`;
|
||||
|
||||
// app偏好设置初始化
|
||||
await initPreferences({
|
||||
namespace,
|
||||
overrides: overridesPreferences,
|
||||
});
|
||||
|
||||
// 启动应用并挂载
|
||||
// vue应用主要逻辑及视图
|
||||
const { bootstrap } = await import('./bootstrap');
|
||||
await bootstrap(namespace);
|
||||
|
||||
// 移除并销毁loading
|
||||
unmountGlobalLoading();
|
||||
}
|
||||
|
||||
initApplication();
|
||||
27
apps/web-antd/src/preferences.ts
Normal file
27
apps/web-antd/src/preferences.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { defineOverridesPreferences } from '@vben/preferences';
|
||||
|
||||
/**
|
||||
* @description 项目配置文件
|
||||
* 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置
|
||||
* !!! 更改配置后请清空缓存,否则可能不生效
|
||||
*/
|
||||
export const overridesPreferences = defineOverridesPreferences({
|
||||
// overrides
|
||||
app: {
|
||||
name: import.meta.env.VITE_APP_TITLE,
|
||||
accessMode: 'backend',
|
||||
},
|
||||
logo: {
|
||||
source: 'https://ctrlph.quannengcha.com/logo.png',
|
||||
},
|
||||
copyright: {
|
||||
companyName: '海南海宇大数据有限公司',
|
||||
companySiteLink: 'https://www.quannengcha.com',
|
||||
date: '2025',
|
||||
icp: '琼ICP备2024048057号-2',
|
||||
icpLink: 'https://beian.miit.gov.cn/',
|
||||
},
|
||||
footer: {
|
||||
enable: false,
|
||||
},
|
||||
});
|
||||
42
apps/web-antd/src/router/access.ts
Normal file
42
apps/web-antd/src/router/access.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import type {
|
||||
ComponentRecordType,
|
||||
GenerateMenuAndRoutesOptions,
|
||||
} from '@vben/types';
|
||||
|
||||
import { generateAccessible } from '@vben/access';
|
||||
import { preferences } from '@vben/preferences';
|
||||
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { getAllMenusApi } from '#/api';
|
||||
import { BasicLayout, IFrameView } from '#/layouts';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue');
|
||||
|
||||
async function generateAccess(options: GenerateMenuAndRoutesOptions) {
|
||||
const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue');
|
||||
|
||||
const layoutMap: ComponentRecordType = {
|
||||
BasicLayout,
|
||||
IFrameView,
|
||||
};
|
||||
|
||||
return await generateAccessible(preferences.app.accessMode, {
|
||||
...options,
|
||||
fetchMenuListAsync: async () => {
|
||||
message.loading({
|
||||
content: `${$t('common.loadingMenu')}...`,
|
||||
duration: 1.5,
|
||||
});
|
||||
return await getAllMenusApi();
|
||||
},
|
||||
// 可以指定没有权限跳转403页面
|
||||
forbiddenComponent,
|
||||
// 如果 route.meta.menuVisibleWithForbidden = true
|
||||
layoutMap,
|
||||
pageMap,
|
||||
});
|
||||
}
|
||||
|
||||
export { generateAccess };
|
||||
133
apps/web-antd/src/router/guard.ts
Normal file
133
apps/web-antd/src/router/guard.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import type { Router } from 'vue-router';
|
||||
|
||||
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import { useAccessStore, useUserStore } from '@vben/stores';
|
||||
import { startProgress, stopProgress } from '@vben/utils';
|
||||
|
||||
import { accessRoutes, coreRouteNames } from '#/router/routes';
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
import { generateAccess } from './access';
|
||||
|
||||
/**
|
||||
* 通用守卫配置
|
||||
* @param router
|
||||
*/
|
||||
function setupCommonGuard(router: Router) {
|
||||
// 记录已经加载的页面
|
||||
const loadedPaths = new Set<string>();
|
||||
|
||||
router.beforeEach(async (to) => {
|
||||
to.meta.loaded = loadedPaths.has(to.path);
|
||||
|
||||
// 页面加载进度条
|
||||
if (!to.meta.loaded && preferences.transition.progress) {
|
||||
startProgress();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
router.afterEach((to) => {
|
||||
// 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行
|
||||
|
||||
loadedPaths.add(to.path);
|
||||
|
||||
// 关闭页面加载进度条
|
||||
if (preferences.transition.progress) {
|
||||
stopProgress();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限访问守卫配置
|
||||
* @param router
|
||||
*/
|
||||
function setupAccessGuard(router: Router) {
|
||||
router.beforeEach(async (to, from) => {
|
||||
const accessStore = useAccessStore();
|
||||
const userStore = useUserStore();
|
||||
const authStore = useAuthStore();
|
||||
|
||||
// 基本路由,这些路由不需要进入权限拦截
|
||||
if (coreRouteNames.includes(to.name as string)) {
|
||||
if (to.path === LOGIN_PATH && accessStore.accessToken) {
|
||||
return decodeURIComponent(
|
||||
(to.query?.redirect as string) ||
|
||||
userStore.userInfo?.homePath ||
|
||||
DEFAULT_HOME_PATH,
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// accessToken 检查
|
||||
if (!accessStore.accessToken) {
|
||||
// 明确声明忽略权限访问权限,则可以访问
|
||||
if (to.meta.ignoreAccess) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 没有访问权限,跳转登录页面
|
||||
if (to.fullPath !== LOGIN_PATH) {
|
||||
return {
|
||||
path: LOGIN_PATH,
|
||||
// 如不需要,直接删除 query
|
||||
query:
|
||||
to.fullPath === DEFAULT_HOME_PATH
|
||||
? {}
|
||||
: { redirect: encodeURIComponent(to.fullPath) },
|
||||
// 携带当前跳转的页面,登录后重新跳转该页面
|
||||
replace: true,
|
||||
};
|
||||
}
|
||||
return to;
|
||||
}
|
||||
|
||||
// 是否已经生成过动态路由
|
||||
if (accessStore.isAccessChecked) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 生成路由表
|
||||
// 当前登录用户拥有的角色标识列表
|
||||
const userInfo = userStore.userInfo || (await authStore.fetchUserInfo());
|
||||
const userRoles = userInfo.roles ?? [];
|
||||
|
||||
// 生成菜单和路由
|
||||
const { accessibleMenus, accessibleRoutes } = await generateAccess({
|
||||
roles: userRoles,
|
||||
router,
|
||||
// 则会在菜单中显示,但是访问会被重定向到403
|
||||
routes: accessRoutes,
|
||||
});
|
||||
|
||||
// 保存菜单信息和路由信息
|
||||
accessStore.setAccessMenus(accessibleMenus);
|
||||
accessStore.setAccessRoutes(accessibleRoutes);
|
||||
accessStore.setIsAccessChecked(true);
|
||||
const redirectPath = (from.query.redirect ??
|
||||
(to.path === DEFAULT_HOME_PATH
|
||||
? userInfo.homePath || DEFAULT_HOME_PATH
|
||||
: to.fullPath)) as string;
|
||||
|
||||
return {
|
||||
...router.resolve(decodeURIComponent(redirectPath)),
|
||||
replace: true,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目守卫配置
|
||||
* @param router
|
||||
*/
|
||||
function createRouterGuard(router: Router) {
|
||||
/** 通用 */
|
||||
setupCommonGuard(router);
|
||||
/** 权限访问 */
|
||||
setupAccessGuard(router);
|
||||
}
|
||||
|
||||
export { createRouterGuard };
|
||||
37
apps/web-antd/src/router/index.ts
Normal file
37
apps/web-antd/src/router/index.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import {
|
||||
createRouter,
|
||||
createWebHashHistory,
|
||||
createWebHistory,
|
||||
} from 'vue-router';
|
||||
|
||||
import { resetStaticRoutes } from '@vben/utils';
|
||||
|
||||
import { createRouterGuard } from './guard';
|
||||
import { routes } from './routes';
|
||||
|
||||
/**
|
||||
* @zh_CN 创建vue-router实例
|
||||
*/
|
||||
const router = createRouter({
|
||||
history:
|
||||
import.meta.env.VITE_ROUTER_HISTORY === 'hash'
|
||||
? createWebHashHistory(import.meta.env.VITE_BASE)
|
||||
: createWebHistory(import.meta.env.VITE_BASE),
|
||||
// 应该添加到路由的初始路由列表。
|
||||
routes,
|
||||
scrollBehavior: (to, _from, savedPosition) => {
|
||||
if (savedPosition) {
|
||||
return savedPosition;
|
||||
}
|
||||
return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 };
|
||||
},
|
||||
// 是否应该禁止尾部斜杠。
|
||||
// strict: true,
|
||||
});
|
||||
|
||||
const resetRoutes = () => resetStaticRoutes(router, routes);
|
||||
|
||||
// 创建路由守卫
|
||||
createRouterGuard(router);
|
||||
|
||||
export { resetRoutes, router };
|
||||
96
apps/web-antd/src/router/routes/core.ts
Normal file
96
apps/web-antd/src/router/routes/core.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
||||
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const BasicLayout = () => import('#/layouts/basic.vue');
|
||||
const AuthPageLayout = () => import('#/layouts/auth.vue');
|
||||
/** 全局404页面 */
|
||||
const fallbackNotFoundRoute: RouteRecordRaw = {
|
||||
component: () => import('#/views/_core/fallback/not-found.vue'),
|
||||
meta: {
|
||||
hideInBreadcrumb: true,
|
||||
hideInMenu: true,
|
||||
hideInTab: true,
|
||||
title: '404',
|
||||
},
|
||||
name: 'FallbackNotFound',
|
||||
path: '/:path(.*)*',
|
||||
};
|
||||
|
||||
/** 基本路由,这些路由是必须存在的 */
|
||||
const coreRoutes: RouteRecordRaw[] = [
|
||||
/**
|
||||
* 根路由
|
||||
* 使用基础布局,作为所有页面的父级容器,子级就不必配置BasicLayout。
|
||||
* 此路由必须存在,且不应修改
|
||||
*/
|
||||
{
|
||||
component: BasicLayout,
|
||||
meta: {
|
||||
hideInBreadcrumb: true,
|
||||
title: 'Root',
|
||||
},
|
||||
name: 'Root',
|
||||
path: '/',
|
||||
redirect: DEFAULT_HOME_PATH,
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
component: AuthPageLayout,
|
||||
meta: {
|
||||
hideInTab: true,
|
||||
title: 'Authentication',
|
||||
},
|
||||
name: 'Authentication',
|
||||
path: '/auth',
|
||||
redirect: LOGIN_PATH,
|
||||
children: [
|
||||
{
|
||||
name: 'Login',
|
||||
path: 'login',
|
||||
component: () => import('#/views/_core/authentication/login.vue'),
|
||||
meta: {
|
||||
title: $t('page.auth.login'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'CodeLogin',
|
||||
path: 'code-login',
|
||||
component: () => import('#/views/_core/authentication/code-login.vue'),
|
||||
meta: {
|
||||
title: $t('page.auth.codeLogin'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'QrCodeLogin',
|
||||
path: 'qrcode-login',
|
||||
component: () =>
|
||||
import('#/views/_core/authentication/qrcode-login.vue'),
|
||||
meta: {
|
||||
title: $t('page.auth.qrcodeLogin'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ForgetPassword',
|
||||
path: 'forget-password',
|
||||
component: () =>
|
||||
import('#/views/_core/authentication/forget-password.vue'),
|
||||
meta: {
|
||||
title: $t('page.auth.forgetPassword'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Register',
|
||||
path: 'register',
|
||||
component: () => import('#/views/_core/authentication/register.vue'),
|
||||
meta: {
|
||||
title: $t('page.auth.register'),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export { coreRoutes, fallbackNotFoundRoute };
|
||||
46
apps/web-antd/src/router/routes/index.ts
Normal file
46
apps/web-antd/src/router/routes/index.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { mergeRouteModules, traverseTreeValues } from '@vben/utils';
|
||||
|
||||
import { coreRoutes, fallbackNotFoundRoute } from './core';
|
||||
|
||||
const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', {
|
||||
eager: true,
|
||||
});
|
||||
|
||||
// 有需要可以自行打开注释,并创建文件夹
|
||||
// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true });
|
||||
// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true });
|
||||
|
||||
/** 动态路由 */
|
||||
const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles);
|
||||
|
||||
/** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统(不会显示在菜单中) */
|
||||
// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles);
|
||||
// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles);
|
||||
const staticRoutes: RouteRecordRaw[] = [];
|
||||
const externalRoutes: RouteRecordRaw[] = [];
|
||||
|
||||
/** 路由列表,由基本路由、外部路由和404兜底路由组成
|
||||
* 无需走权限验证(会一直显示在菜单中) */
|
||||
const routes: RouteRecordRaw[] = [
|
||||
...coreRoutes,
|
||||
...externalRoutes,
|
||||
fallbackNotFoundRoute,
|
||||
];
|
||||
|
||||
/** 基本路由列表,这些路由不需要进入权限拦截 */
|
||||
const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
|
||||
|
||||
/** 有权限校验的路由列表,包含动态路由和静态路由 */
|
||||
const accessRoutes = [...dynamicRoutes, ...staticRoutes];
|
||||
|
||||
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 };
|
||||
117
apps/web-antd/src/router/routes/modules/agent.ts
Normal file
117
apps/web-antd/src/router/routes/modules/agent.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
meta: {
|
||||
icon: 'mdi:account-group',
|
||||
order: 2000,
|
||||
title: '代理管理',
|
||||
},
|
||||
name: 'Agent',
|
||||
path: '/agent',
|
||||
children: [
|
||||
{
|
||||
path: '/agent/list',
|
||||
name: 'AgentList',
|
||||
meta: {
|
||||
icon: 'mdi:account-multiple',
|
||||
title: '代理列表',
|
||||
},
|
||||
component: () => import('#/views/agent/agent-list/list.vue'),
|
||||
},
|
||||
{
|
||||
path: '/agent/links',
|
||||
name: 'AgentLinks',
|
||||
meta: {
|
||||
icon: 'mdi:link-variant',
|
||||
title: '推广链接',
|
||||
},
|
||||
component: () => import('#/views/agent/agent-links/list.vue'),
|
||||
},
|
||||
{
|
||||
path: '/agent/commission',
|
||||
name: 'AgentCommission',
|
||||
meta: {
|
||||
icon: 'mdi:cash-multiple',
|
||||
title: '佣金记录',
|
||||
},
|
||||
component: () => import('#/views/agent/agent-commission/list.vue'),
|
||||
},
|
||||
{
|
||||
path: '/agent/rebate',
|
||||
name: 'AgentRebate',
|
||||
meta: {
|
||||
icon: 'mdi:currency-usd',
|
||||
title: '返佣记录',
|
||||
},
|
||||
component: () => import('#/views/agent/agent-rebate/list.vue'),
|
||||
},
|
||||
{
|
||||
path: '/agent/upgrade',
|
||||
name: 'AgentUpgrade',
|
||||
meta: {
|
||||
icon: 'mdi:arrow-up-circle',
|
||||
title: '升级记录',
|
||||
},
|
||||
component: () => import('#/views/agent/agent-upgrade/list.vue'),
|
||||
},
|
||||
{
|
||||
path: '/agent/order',
|
||||
name: 'AgentOrder',
|
||||
meta: {
|
||||
icon: 'mdi:package-variant',
|
||||
title: '订单记录',
|
||||
},
|
||||
component: () => import('#/views/agent/agent-order/list.vue'),
|
||||
},
|
||||
{
|
||||
path: '/agent/withdrawal',
|
||||
name: 'AgentWithdrawal',
|
||||
meta: {
|
||||
icon: 'mdi:bank-transfer-out',
|
||||
title: '提现记录',
|
||||
},
|
||||
component: () => import('#/views/agent/agent-withdrawal/list.vue'),
|
||||
},
|
||||
{
|
||||
path: '/agent/invite-code',
|
||||
name: 'AgentInviteCode',
|
||||
meta: {
|
||||
icon: 'mdi:ticket-confirmation',
|
||||
title: '邀请码管理',
|
||||
},
|
||||
component: () => import('#/views/agent/agent-invite-code/list.vue'),
|
||||
},
|
||||
{
|
||||
path: '/agent/config',
|
||||
name: 'AgentConfig',
|
||||
meta: {
|
||||
icon: 'mdi:cog',
|
||||
title: '系统配置',
|
||||
},
|
||||
component: () => import('#/views/agent/agent-config/list.vue'),
|
||||
},
|
||||
{
|
||||
path: '/agent/real-name',
|
||||
name: 'AgentRealName',
|
||||
meta: {
|
||||
icon: 'mdi:account-check',
|
||||
title: '实名认证',
|
||||
},
|
||||
component: () => import('#/views/agent/agent-real-name/list.vue'),
|
||||
},
|
||||
{
|
||||
path: '/agent/product-config',
|
||||
name: 'AgentProductConfig',
|
||||
meta: {
|
||||
icon: 'mdi:package-variant-closed',
|
||||
title: '产品配置',
|
||||
},
|
||||
component: () => import('#/views/agent/agent-product-config/list.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
|
||||
38
apps/web-antd/src/router/routes/modules/dashboard.ts
Normal file
38
apps/web-antd/src/router/routes/modules/dashboard.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
meta: {
|
||||
icon: 'lucide:layout-dashboard',
|
||||
order: -1,
|
||||
title: $t('page.dashboard.title'),
|
||||
},
|
||||
name: 'Dashboard',
|
||||
path: '/dashboard',
|
||||
children: [
|
||||
{
|
||||
name: 'Analytics',
|
||||
path: '/analytics',
|
||||
component: () => import('#/views/dashboard/analytics/index.vue'),
|
||||
meta: {
|
||||
affixTab: true,
|
||||
icon: 'lucide:area-chart',
|
||||
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'),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
25
apps/web-antd/src/router/routes/modules/order.ts
Normal file
25
apps/web-antd/src/router/routes/modules/order.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
meta: {
|
||||
icon: 'lucide:shopping-cart',
|
||||
order: 1000,
|
||||
title: '订单管理',
|
||||
},
|
||||
name: 'Order',
|
||||
path: '/order',
|
||||
component: () => import('#/views/order/order/index.vue'),
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
hideInMenu: true,
|
||||
title: '订单查询详情',
|
||||
},
|
||||
name: 'OrderQueryDetail',
|
||||
path: '/order/query/detail/:id',
|
||||
component: () => import('#/views/order/query/query-details.vue'),
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
64
apps/web-antd/src/router/routes/modules/system.ts
Normal file
64
apps/web-antd/src/router/routes/modules/system.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import type { RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import { $t } from '#/locales';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
meta: {
|
||||
icon: 'ion:settings-outline',
|
||||
order: 9997,
|
||||
title: $t('system.title'),
|
||||
},
|
||||
name: 'System',
|
||||
path: '/system',
|
||||
children: [
|
||||
{
|
||||
path: '/system/user',
|
||||
name: 'SystemUser',
|
||||
meta: {
|
||||
icon: 'mdi:account',
|
||||
title: $t('system.user.title'),
|
||||
},
|
||||
component: () => import('#/views/system/user/list.vue'),
|
||||
},
|
||||
{
|
||||
path: '/system/role',
|
||||
name: 'SystemRole',
|
||||
meta: {
|
||||
icon: 'mdi:account-group',
|
||||
title: $t('system.role.title'),
|
||||
},
|
||||
component: () => import('#/views/system/role/list.vue'),
|
||||
},
|
||||
{
|
||||
path: '/system/api',
|
||||
name: 'SystemApi',
|
||||
meta: {
|
||||
icon: 'mdi:api',
|
||||
title: $t('system.api.title'),
|
||||
},
|
||||
component: () => import('#/views/system/api/list.vue'),
|
||||
},
|
||||
{
|
||||
path: '/system/menu',
|
||||
name: 'SystemMenu',
|
||||
meta: {
|
||||
icon: 'mdi:menu',
|
||||
title: $t('system.menu.title'),
|
||||
},
|
||||
component: () => import('#/views/system/menu/list.vue'),
|
||||
},
|
||||
{
|
||||
path: '/system/dept',
|
||||
name: 'SystemDept',
|
||||
meta: {
|
||||
icon: 'charm:organisation',
|
||||
title: $t('system.dept.title'),
|
||||
},
|
||||
component: () => import('#/views/system/dept/list.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default routes;
|
||||
115
apps/web-antd/src/store/auth.ts
Normal file
115
apps/web-antd/src/store/auth.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import type { Recordable, UserInfo } from '@vben/types';
|
||||
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
|
||||
import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
|
||||
|
||||
import { notification } from 'ant-design-vue';
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api';
|
||||
import { $t } from '#/locales';
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const accessStore = useAccessStore();
|
||||
const userStore = useUserStore();
|
||||
const router = useRouter();
|
||||
|
||||
const loginLoading = ref(false);
|
||||
|
||||
/**
|
||||
* 异步处理登录操作
|
||||
* Asynchronously handle the login process
|
||||
* @param params 登录表单数据
|
||||
*/
|
||||
async function authLogin(
|
||||
params: Recordable<any>,
|
||||
onSuccess?: () => Promise<void> | void,
|
||||
) {
|
||||
// 异步处理用户登录操作并获取 accessToken
|
||||
let userInfo: null | UserInfo = null;
|
||||
try {
|
||||
loginLoading.value = true;
|
||||
const { access_token } = await loginApi(params);
|
||||
console.info('accessToken', access_token);
|
||||
// 如果成功获取到 accessToken
|
||||
if (access_token) {
|
||||
accessStore.setAccessToken(access_token);
|
||||
|
||||
// 获取用户信息并存储到 accessStore 中
|
||||
const [fetchUserInfoResult, accessCodes] = await Promise.all([
|
||||
fetchUserInfo(),
|
||||
getAccessCodesApi(),
|
||||
]);
|
||||
|
||||
userInfo = fetchUserInfoResult;
|
||||
|
||||
userStore.setUserInfo(userInfo);
|
||||
accessStore.setAccessCodes(accessCodes);
|
||||
|
||||
if (accessStore.loginExpired) {
|
||||
accessStore.setLoginExpired(false);
|
||||
} else {
|
||||
onSuccess
|
||||
? await onSuccess?.()
|
||||
: await router.push(userInfo.homePath || DEFAULT_HOME_PATH);
|
||||
}
|
||||
|
||||
if (userInfo?.realName) {
|
||||
notification.success({
|
||||
description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`,
|
||||
duration: 3,
|
||||
message: $t('authentication.loginSuccess'),
|
||||
});
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
loginLoading.value = false;
|
||||
}
|
||||
|
||||
return {
|
||||
userInfo,
|
||||
};
|
||||
}
|
||||
|
||||
async function logout(redirect: boolean = true) {
|
||||
try {
|
||||
await logoutApi();
|
||||
} catch {
|
||||
// 不做任何处理
|
||||
}
|
||||
resetAllStores();
|
||||
accessStore.setLoginExpired(false);
|
||||
|
||||
// 回登录页带上当前路由地址
|
||||
await router.replace({
|
||||
path: LOGIN_PATH,
|
||||
query: redirect
|
||||
? {
|
||||
redirect: encodeURIComponent(router.currentRoute.value.fullPath),
|
||||
}
|
||||
: {},
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchUserInfo() {
|
||||
let userInfo: null | UserInfo = null;
|
||||
userInfo = await getUserInfoApi();
|
||||
userStore.setUserInfo(userInfo);
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
function $reset() {
|
||||
loginLoading.value = false;
|
||||
}
|
||||
|
||||
return {
|
||||
$reset,
|
||||
authLogin,
|
||||
fetchUserInfo,
|
||||
loginLoading,
|
||||
logout,
|
||||
};
|
||||
});
|
||||
1
apps/web-antd/src/store/index.ts
Normal file
1
apps/web-antd/src/store/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './auth';
|
||||
113
apps/web-antd/src/utils/agent.ts
Normal file
113
apps/web-antd/src/utils/agent.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* 等级数字转中文名称
|
||||
*/
|
||||
export function getLevelName(level: number): string {
|
||||
const map: Record<number, string> = {
|
||||
1: '普通代理',
|
||||
2: '黄金代理',
|
||||
3: '钻石代理',
|
||||
};
|
||||
return map[level] || '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 返佣类型转中文
|
||||
*/
|
||||
export function getRebateTypeName(type: number): string {
|
||||
const map: Record<number, string> = {
|
||||
1: '直接上级返佣',
|
||||
2: '钻石上级返佣',
|
||||
3: '黄金上级返佣',
|
||||
};
|
||||
return map[type] || '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 升级类型转中文
|
||||
*/
|
||||
export function getUpgradeTypeName(type: number): string {
|
||||
const map: Record<number, string> = {
|
||||
1: '自主付费',
|
||||
2: '钻石升级下级',
|
||||
};
|
||||
return map[type] || '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 提现状态转中文
|
||||
*/
|
||||
export function getWithdrawalStatusName(status: number): string {
|
||||
const map: Record<number, string> = {
|
||||
1: '待审核',
|
||||
2: '审核通过',
|
||||
3: '审核拒绝',
|
||||
4: '提现中',
|
||||
5: '提现成功',
|
||||
6: '提现失败',
|
||||
};
|
||||
return map[status] || '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 订单处理状态转中文
|
||||
*/
|
||||
export function getOrderProcessStatusName(status: number): string {
|
||||
const map: Record<number, string> = {
|
||||
0: '待处理',
|
||||
1: '处理成功',
|
||||
2: '处理失败',
|
||||
};
|
||||
return map[status] || '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 升级状态转中文
|
||||
*/
|
||||
export function getUpgradeStatusName(status: number): string {
|
||||
const map: Record<number, string> = {
|
||||
1: '待处理',
|
||||
2: '已完成',
|
||||
3: '已失败',
|
||||
};
|
||||
return map[status] || '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 邀请码状态转中文
|
||||
*/
|
||||
export function getInviteCodeStatusName(status: number): string {
|
||||
const map: Record<number, string> = {
|
||||
0: '未使用',
|
||||
1: '已使用',
|
||||
2: '已失效',
|
||||
};
|
||||
return map[status] || '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 实名认证状态转中文
|
||||
*/
|
||||
export function getRealNameStatusName(status: number): string {
|
||||
const map: Record<number, string> = {
|
||||
1: '未验证',
|
||||
2: '已通过',
|
||||
};
|
||||
return map[status] || '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 身份证号脱敏显示
|
||||
*/
|
||||
export function maskIdCard(idCard: string): string {
|
||||
if (!idCard || idCard.length < 14) return idCard;
|
||||
return `${idCard.slice(0, 6)}${'*'.repeat(8)}${idCard.slice(-4)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机号脱敏显示
|
||||
*/
|
||||
export function maskMobile(mobile: string): string {
|
||||
if (!mobile || mobile.length !== 11) return mobile;
|
||||
return `${mobile.slice(0, 3)}****${mobile.slice(-4)}`;
|
||||
}
|
||||
|
||||
3
apps/web-antd/src/views/_core/README.md
Normal file
3
apps/web-antd/src/views/_core/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# \_core
|
||||
|
||||
此目录包含应用程序正常运行所需的基本视图。这些视图是应用程序布局中使用的视图。
|
||||
9
apps/web-antd/src/views/_core/about/index.vue
Normal file
9
apps/web-antd/src/views/_core/about/index.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import { About } from '@vben/common-ui';
|
||||
|
||||
defineOptions({ name: 'About' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<About />
|
||||
</template>
|
||||
69
apps/web-antd/src/views/_core/authentication/code-login.vue
Normal file
69
apps/web-antd/src/views/_core/authentication/code-login.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { AuthenticationCodeLogin, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
defineOptions({ name: 'CodeLogin' });
|
||||
|
||||
const loading = ref(false);
|
||||
const CODE_LENGTH = 6;
|
||||
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.mobile'),
|
||||
},
|
||||
fieldName: 'phoneNumber',
|
||||
label: $t('authentication.mobile'),
|
||||
rules: z
|
||||
.string()
|
||||
.min(1, { message: $t('authentication.mobileTip') })
|
||||
.refine((v) => /^\d{11}$/.test(v), {
|
||||
message: $t('authentication.mobileErrortip'),
|
||||
}),
|
||||
},
|
||||
{
|
||||
component: 'VbenPinInput',
|
||||
componentProps: {
|
||||
codeLength: CODE_LENGTH,
|
||||
createText: (countdown: number) => {
|
||||
const text =
|
||||
countdown > 0
|
||||
? $t('authentication.sendText', [countdown])
|
||||
: $t('authentication.sendCode');
|
||||
return text;
|
||||
},
|
||||
placeholder: $t('authentication.code'),
|
||||
},
|
||||
fieldName: 'code',
|
||||
label: $t('authentication.code'),
|
||||
rules: z.string().length(CODE_LENGTH, {
|
||||
message: $t('authentication.codeTip', [CODE_LENGTH]),
|
||||
}),
|
||||
},
|
||||
];
|
||||
});
|
||||
/**
|
||||
* 异步处理登录操作
|
||||
* Asynchronously handle the login process
|
||||
* @param values 登录表单数据
|
||||
*/
|
||||
async function handleLogin(values: Recordable<any>) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(values);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AuthenticationCodeLogin
|
||||
:form-schema="formSchema"
|
||||
:loading="loading"
|
||||
@submit="handleLogin"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,43 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { AuthenticationForgetPassword, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
defineOptions({ name: 'ForgetPassword' });
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: 'example@example.com',
|
||||
},
|
||||
fieldName: 'email',
|
||||
label: $t('authentication.email'),
|
||||
rules: z
|
||||
.string()
|
||||
.min(1, { message: $t('authentication.emailTip') })
|
||||
.email($t('authentication.emailValidErrorTip')),
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
function handleSubmit(value: Recordable<any>) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('reset email:', value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AuthenticationForgetPassword
|
||||
:form-schema="formSchema"
|
||||
:loading="loading"
|
||||
@submit="handleSubmit"
|
||||
/>
|
||||
</template>
|
||||
57
apps/web-antd/src/views/_core/authentication/login.vue
Normal file
57
apps/web-antd/src/views/_core/authentication/login.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
|
||||
import { computed, markRaw } from 'vue';
|
||||
|
||||
import { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { useAuthStore } from '#/store';
|
||||
|
||||
defineOptions({ name: 'Login' });
|
||||
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.usernameTip'),
|
||||
},
|
||||
fieldName: 'username',
|
||||
label: $t('authentication.username'),
|
||||
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
|
||||
},
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.password'),
|
||||
},
|
||||
fieldName: 'password',
|
||||
label: $t('authentication.password'),
|
||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
||||
},
|
||||
{
|
||||
component: markRaw(SliderCaptcha),
|
||||
fieldName: 'captcha',
|
||||
rules: z.boolean().refine((value) => value, {
|
||||
message: $t('authentication.verifyRequiredTip'),
|
||||
}),
|
||||
},
|
||||
];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AuthenticationLogin
|
||||
:form-schema="formSchema"
|
||||
:loading="authStore.loginLoading"
|
||||
:show-third-party-login="false"
|
||||
:show-register="false"
|
||||
:show-forget-password="false"
|
||||
:show-qrcode-login="false"
|
||||
:show-code-login="false"
|
||||
@submit="authStore.authLogin"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { AuthenticationQrCodeLogin } from '@vben/common-ui';
|
||||
import { LOGIN_PATH } from '@vben/constants';
|
||||
|
||||
defineOptions({ name: 'QrCodeLogin' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AuthenticationQrCodeLogin :login-path="LOGIN_PATH" />
|
||||
</template>
|
||||
96
apps/web-antd/src/views/_core/authentication/register.vue
Normal file
96
apps/web-antd/src/views/_core/authentication/register.vue
Normal file
@@ -0,0 +1,96 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VbenFormSchema } from '@vben/common-ui';
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { computed, h, ref } from 'vue';
|
||||
|
||||
import { AuthenticationRegister, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
defineOptions({ name: 'Register' });
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const formSchema = computed((): VbenFormSchema[] => {
|
||||
return [
|
||||
{
|
||||
component: 'VbenInput',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.usernameTip'),
|
||||
},
|
||||
fieldName: 'username',
|
||||
label: $t('authentication.username'),
|
||||
rules: z.string().min(1, { message: $t('authentication.usernameTip') }),
|
||||
},
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
passwordStrength: true,
|
||||
placeholder: $t('authentication.password'),
|
||||
},
|
||||
fieldName: 'password',
|
||||
label: $t('authentication.password'),
|
||||
renderComponentContent() {
|
||||
return {
|
||||
strengthText: () => $t('authentication.passwordStrength'),
|
||||
};
|
||||
},
|
||||
rules: z.string().min(1, { message: $t('authentication.passwordTip') }),
|
||||
},
|
||||
{
|
||||
component: 'VbenInputPassword',
|
||||
componentProps: {
|
||||
placeholder: $t('authentication.confirmPassword'),
|
||||
},
|
||||
dependencies: {
|
||||
rules(values) {
|
||||
const { password } = values;
|
||||
return z
|
||||
.string({ required_error: $t('authentication.passwordTip') })
|
||||
.min(1, { message: $t('authentication.passwordTip') })
|
||||
.refine((value) => value === password, {
|
||||
message: $t('authentication.confirmPasswordTip'),
|
||||
});
|
||||
},
|
||||
triggerFields: ['password'],
|
||||
},
|
||||
fieldName: 'confirmPassword',
|
||||
label: $t('authentication.confirmPassword'),
|
||||
},
|
||||
{
|
||||
component: 'VbenCheckbox',
|
||||
fieldName: 'agreePolicy',
|
||||
renderComponentContent: () => ({
|
||||
default: () =>
|
||||
h('span', [
|
||||
$t('authentication.agree'),
|
||||
h(
|
||||
'a',
|
||||
{
|
||||
class: 'vben-link ml-1 ',
|
||||
href: '',
|
||||
},
|
||||
`${$t('authentication.privacyPolicy')} & ${$t('authentication.terms')}`,
|
||||
),
|
||||
]),
|
||||
}),
|
||||
rules: z.boolean().refine((value) => !!value, {
|
||||
message: $t('authentication.agreeTip'),
|
||||
}),
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
function handleSubmit(value: Recordable<any>) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('register submit:', value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AuthenticationRegister
|
||||
:form-schema="formSchema"
|
||||
:loading="loading"
|
||||
@submit="handleSubmit"
|
||||
/>
|
||||
</template>
|
||||
7
apps/web-antd/src/views/_core/fallback/coming-soon.vue
Normal file
7
apps/web-antd/src/views/_core/fallback/coming-soon.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { Fallback } from '@vben/common-ui';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Fallback status="coming-soon" />
|
||||
</template>
|
||||
9
apps/web-antd/src/views/_core/fallback/forbidden.vue
Normal file
9
apps/web-antd/src/views/_core/fallback/forbidden.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import { Fallback } from '@vben/common-ui';
|
||||
|
||||
defineOptions({ name: 'Fallback403Demo' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Fallback status="403" />
|
||||
</template>
|
||||
@@ -0,0 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import { Fallback } from '@vben/common-ui';
|
||||
|
||||
defineOptions({ name: 'Fallback500Demo' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Fallback status="500" />
|
||||
</template>
|
||||
9
apps/web-antd/src/views/_core/fallback/not-found.vue
Normal file
9
apps/web-antd/src/views/_core/fallback/not-found.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import { Fallback } from '@vben/common-ui';
|
||||
|
||||
defineOptions({ name: 'Fallback404Demo' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Fallback status="404" />
|
||||
</template>
|
||||
9
apps/web-antd/src/views/_core/fallback/offline.vue
Normal file
9
apps/web-antd/src/views/_core/fallback/offline.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import { Fallback } from '@vben/common-ui';
|
||||
|
||||
defineOptions({ name: 'FallbackOfflineDemo' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Fallback status="offline" />
|
||||
</template>
|
||||
74
apps/web-antd/src/views/agent/agent-commission/data.ts
Normal file
74
apps/web-antd/src/views/agent/agent-commission/data.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
// 佣金记录列表列配置
|
||||
export function useCommissionColumns(): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'agent_id',
|
||||
title: '代理ID',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'order_id',
|
||||
title: '订单ID',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'amount',
|
||||
title: '佣金金额',
|
||||
width: 120,
|
||||
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||
`¥${cellValue.toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
field: 'product_name',
|
||||
title: '产品名称',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
title: '状态',
|
||||
width: 100,
|
||||
formatter: ({ cellValue }: { cellValue: number }) => {
|
||||
const statusMap: Record<number, string> = {
|
||||
0: '待结算',
|
||||
1: '已结算',
|
||||
2: '已取消',
|
||||
};
|
||||
return statusMap[cellValue] || '未知';
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'create_time',
|
||||
title: '创建时间',
|
||||
width: 160,
|
||||
sortable: true,
|
||||
sortType: 'string' as const,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// 佣金记录搜索表单配置
|
||||
export function useCommissionFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'product_name',
|
||||
label: '产品名称',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
fieldName: 'status',
|
||||
label: '状态',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
options: [
|
||||
{ label: '待结算', value: 0 },
|
||||
{ label: '已结算', value: 1 },
|
||||
{ label: '已取消', value: 2 },
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
64
apps/web-antd/src/views/agent/agent-commission/list.vue
Normal file
64
apps/web-antd/src/views/agent/agent-commission/list.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getAgentCommissionList } from '#/api/agent';
|
||||
|
||||
import { useCommissionColumns, useCommissionFormSchema } from './data';
|
||||
|
||||
interface Props {
|
||||
agentId?: number;
|
||||
}
|
||||
|
||||
interface QueryParams {
|
||||
currentPage: number;
|
||||
pageSize: number;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const queryParams = computed(() => ({
|
||||
...(props.agentId ? { agent_id: props.agentId } : {}),
|
||||
}));
|
||||
|
||||
const [Grid] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useCommissionFormSchema(),
|
||||
submitOnChange: true,
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useCommissionColumns(),
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({
|
||||
page,
|
||||
form,
|
||||
}: {
|
||||
form: Record<string, any>;
|
||||
page: QueryParams;
|
||||
}) => {
|
||||
return await getAgentCommissionList({
|
||||
...queryParams.value,
|
||||
...form,
|
||||
page: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
});
|
||||
},
|
||||
},
|
||||
props: {
|
||||
result: 'items',
|
||||
total: 'total',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page :auto-content-height="!agentId">
|
||||
<Grid :table-title="agentId ? '佣金记录列表' : '所有佣金记录'" />
|
||||
</Page>
|
||||
</template>
|
||||
274
apps/web-antd/src/views/agent/agent-config/list.vue
Normal file
274
apps/web-antd/src/views/agent/agent-config/list.vue
Normal file
@@ -0,0 +1,274 @@
|
||||
<script lang="ts" setup>
|
||||
import type { AgentApi } from '#/api/agent';
|
||||
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
import { Button, Card, Col, Form, InputNumber, Row, Space, message } from 'ant-design-vue';
|
||||
|
||||
import { getAgentConfig, updateAgentConfig } from '#/api/agent';
|
||||
|
||||
const loading = ref(false);
|
||||
const config = ref<AgentApi.AgentConfig | null>(null);
|
||||
|
||||
// 使用 reactive 管理表单数据(价格配置已移除,改为产品配置表管理)
|
||||
const formData = reactive<AgentApi.AgentConfig>({
|
||||
level_bonus: {
|
||||
normal: 0,
|
||||
gold: 0,
|
||||
diamond: 0,
|
||||
},
|
||||
upgrade_fee: {
|
||||
normal_to_gold: 0,
|
||||
normal_to_diamond: 0,
|
||||
gold_to_diamond: 0,
|
||||
},
|
||||
upgrade_rebate: {
|
||||
normal_to_gold_rebate: 0,
|
||||
to_diamond_rebate: 0,
|
||||
},
|
||||
direct_parent_rebate: {
|
||||
diamond: 0,
|
||||
gold: 0,
|
||||
normal: 0,
|
||||
},
|
||||
max_gold_rebate_amount: 0,
|
||||
commission_freeze: {
|
||||
ratio: 0,
|
||||
threshold: 0,
|
||||
days: 0,
|
||||
},
|
||||
tax_rate: 0,
|
||||
tax_exemption_amount: 0,
|
||||
gold_max_uplift_amount: 0,
|
||||
diamond_max_uplift_amount: 0,
|
||||
});
|
||||
|
||||
// 加载配置
|
||||
async function loadConfig() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await getAgentConfig();
|
||||
config.value = res;
|
||||
Object.assign(formData, res);
|
||||
} catch (error) {
|
||||
console.error('加载配置失败:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
async function handleSave() {
|
||||
try {
|
||||
const params: AgentApi.UpdateAgentConfigParams = {
|
||||
level_bonus: {
|
||||
normal: formData.level_bonus.normal,
|
||||
gold: formData.level_bonus.gold,
|
||||
diamond: formData.level_bonus.diamond,
|
||||
},
|
||||
upgrade_fee: {
|
||||
normal_to_gold: formData.upgrade_fee.normal_to_gold,
|
||||
normal_to_diamond: formData.upgrade_fee.normal_to_diamond,
|
||||
},
|
||||
upgrade_rebate: {
|
||||
normal_to_gold_rebate: formData.upgrade_rebate.normal_to_gold_rebate,
|
||||
to_diamond_rebate: formData.upgrade_rebate.to_diamond_rebate,
|
||||
},
|
||||
direct_parent_rebate: {
|
||||
diamond: formData.direct_parent_rebate.diamond,
|
||||
gold: formData.direct_parent_rebate.gold,
|
||||
normal: formData.direct_parent_rebate.normal,
|
||||
},
|
||||
max_gold_rebate_amount: formData.max_gold_rebate_amount,
|
||||
commission_freeze: {
|
||||
ratio: formData.commission_freeze.ratio,
|
||||
threshold: formData.commission_freeze.threshold,
|
||||
days: formData.commission_freeze.days,
|
||||
},
|
||||
tax_rate: formData.tax_rate,
|
||||
tax_exemption_amount: formData.tax_exemption_amount,
|
||||
gold_max_uplift_amount: formData.gold_max_uplift_amount,
|
||||
diamond_max_uplift_amount: formData.diamond_max_uplift_amount,
|
||||
};
|
||||
await updateAgentConfig(params);
|
||||
message.success('配置保存成功');
|
||||
loadConfig();
|
||||
} catch (error) {
|
||||
console.error('保存配置失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 重置配置
|
||||
function handleReset() {
|
||||
if (config.value) {
|
||||
Object.assign(formData, config.value);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadConfig();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<Card title="系统配置" :loading="loading">
|
||||
<Form layout="vertical">
|
||||
<Card title="等级奖金" size="small" class="mb-4">
|
||||
<Row :gutter="[16, 16]">
|
||||
<Col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||
<Form.Item :name="['level_bonus', 'normal']" label="普通代理奖金">
|
||||
<InputNumber v-model:value="formData.level_bonus.normal" :min="0" :precision="2" :step="0.01"
|
||||
style="width: 100%" addon-after="元" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||
<Form.Item :name="['level_bonus', 'gold']" label="黄金代理奖金">
|
||||
<InputNumber v-model:value="formData.level_bonus.gold" :min="0" :precision="2" :step="0.01"
|
||||
style="width: 100%" addon-after="元" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||
<Form.Item :name="['level_bonus', 'diamond']" label="钻石代理奖金">
|
||||
<InputNumber v-model:value="formData.level_bonus.diamond" :min="0" :precision="2" :step="0.01"
|
||||
style="width: 100%" addon-after="元" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
<Card title="等级最高价上调金额" size="small" class="mb-4">
|
||||
<Row :gutter="[16, 16]">
|
||||
<Col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<Form.Item name="gold_max_uplift_amount" label="黄金代理最高价上调金额">
|
||||
<InputNumber v-model:value="formData.gold_max_uplift_amount" :min="0" :precision="2" :step="0.01"
|
||||
style="width: 100%" addon-after="元" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<Form.Item name="diamond_max_uplift_amount" label="钻石代理最高价上调金额">
|
||||
<InputNumber v-model:value="formData.diamond_max_uplift_amount" :min="0" :precision="2" :step="0.01"
|
||||
style="width: 100%" addon-after="元" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
<Card title="升级费用" size="small" class="mb-4">
|
||||
<Row :gutter="[16, 16]">
|
||||
<Col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||
<Form.Item :name="['upgrade_fee', 'normal_to_gold']" label="普通→黄金">
|
||||
<InputNumber v-model:value="formData.upgrade_fee.normal_to_gold" :min="0" :precision="2" :step="0.01"
|
||||
style="width: 100%" addon-after="元" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||
<Form.Item :name="['upgrade_fee', 'normal_to_diamond']" label="普通→钻石">
|
||||
<InputNumber v-model:value="formData.upgrade_fee.normal_to_diamond" :min="0" :precision="2" :step="0.01"
|
||||
style="width: 100%" addon-after="元" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
<Card title="升级返佣" size="small" class="mb-4">
|
||||
<Row :gutter="[16, 16]">
|
||||
<Col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<Form.Item :name="['upgrade_rebate', 'normal_to_gold_rebate']" label="普通→黄金返佣">
|
||||
<InputNumber v-model:value="formData.upgrade_rebate.normal_to_gold_rebate" :min="0" :precision="2"
|
||||
:step="0.01" style="width: 100%" addon-after="元" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<Form.Item :name="['upgrade_rebate', 'to_diamond_rebate']" label="→钻石返佣">
|
||||
<InputNumber v-model:value="formData.upgrade_rebate.to_diamond_rebate" :min="0" :precision="2"
|
||||
:step="0.01" style="width: 100%" addon-after="元" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
<Card title="直接上级返佣配置" size="small" class="mb-4">
|
||||
<Row :gutter="[16, 16]">
|
||||
<Col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||
<Form.Item :name="['direct_parent_rebate', 'diamond']" label="直接上级是钻石的返佣金额">
|
||||
<InputNumber v-model:value="formData.direct_parent_rebate.diamond" :min="0" :precision="2" :step="0.01"
|
||||
style="width: 100%" addon-after="元" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||
<Form.Item :name="['direct_parent_rebate', 'gold']" label="直接上级是黄金的返佣金额">
|
||||
<InputNumber v-model:value="formData.direct_parent_rebate.gold" :min="0" :precision="2" :step="0.01"
|
||||
style="width: 100%" addon-after="元" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||
<Form.Item :name="['direct_parent_rebate', 'normal']" label="直接上级是普通的返佣金额">
|
||||
<InputNumber v-model:value="formData.direct_parent_rebate.normal" :min="0" :precision="2" :step="0.01"
|
||||
style="width: 100%" addon-after="元" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
<Card title="返佣限额配置" size="small" class="mb-4">
|
||||
<Row :gutter="[16, 16]">
|
||||
<Col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<Form.Item name="max_gold_rebate_amount" label="黄金代理最大返佣金额">
|
||||
<InputNumber v-model:value="formData.max_gold_rebate_amount" :min="0" :precision="2" :step="0.01"
|
||||
style="width: 100%" addon-after="元" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
<Card title="佣金冻结配置" size="small" class="mb-4">
|
||||
<Row :gutter="[16, 16]">
|
||||
<Col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||
<Form.Item :name="['commission_freeze', 'ratio']" label="佣金冻结比例(例如:0.1表示10%)">
|
||||
<InputNumber v-model:value="formData.commission_freeze.ratio" :min="0" :max="1" :precision="4"
|
||||
:step="0.0001" style="width: 100%" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||
<Form.Item :name="['commission_freeze', 'threshold']" label="佣金冻结阈值">
|
||||
<InputNumber v-model:value="formData.commission_freeze.threshold" :min="0" :precision="2" :step="0.01"
|
||||
style="width: 100%" addon-after="元" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col :xs="24" :sm="12" :md="8" :lg="8" :xl="8">
|
||||
<Form.Item :name="['commission_freeze', 'days']" label="佣金冻结解冻天数">
|
||||
<InputNumber v-model:value="formData.commission_freeze.days" :min="0" :precision="0" :step="1"
|
||||
style="width: 100%" addon-after="天" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
<Card title="税费配置" size="small" class="mb-4">
|
||||
<Row :gutter="[16, 16]">
|
||||
<Col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<Form.Item name="tax_rate" label="税率(例如:0.06表示6%)">
|
||||
<InputNumber v-model:value="formData.tax_rate" :min="0" :max="1" :precision="4" :step="0.0001"
|
||||
style="width: 100%" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
|
||||
<Form.Item name="tax_exemption_amount" label="免税额度">
|
||||
<InputNumber v-model:value="formData.tax_exemption_amount" :min="0" :precision="2" :step="0.01"
|
||||
style="width: 100%" addon-after="元" />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
<Space>
|
||||
<Button type="primary" @click="handleSave">保存</Button>
|
||||
<Button @click="handleReset">重置</Button>
|
||||
</Space>
|
||||
</Form>
|
||||
</Card>
|
||||
</Page>
|
||||
</template>
|
||||
109
apps/web-antd/src/views/agent/agent-invite-code/data.ts
Normal file
109
apps/web-antd/src/views/agent/agent-invite-code/data.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { getLevelName } from '#/utils/agent';
|
||||
|
||||
export function useInviteCodeColumns(): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'id',
|
||||
title: 'ID',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
field: 'code',
|
||||
title: '邀请码',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
field: 'agent_id',
|
||||
title: '发放代理ID',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
field: 'agent_mobile',
|
||||
title: '发放代理手机号',
|
||||
width: 140,
|
||||
},
|
||||
{
|
||||
field: 'target_level',
|
||||
title: '目标等级',
|
||||
width: 100,
|
||||
formatter: ({ cellValue }: { cellValue: number }) => {
|
||||
return getLevelName(cellValue);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
title: '状态',
|
||||
width: 100,
|
||||
cellRender: {
|
||||
name: 'CellTag',
|
||||
options: [
|
||||
{ value: 0, color: 'default', label: '未使用' },
|
||||
{ value: 1, color: 'success', label: '已使用' },
|
||||
{ value: 2, color: 'error', label: '已失效' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'used_user_id',
|
||||
title: '使用用户ID',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
field: 'used_agent_id',
|
||||
title: '使用代理ID',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
field: 'used_time',
|
||||
title: '使用时间',
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
field: 'expire_time',
|
||||
title: '过期时间',
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
title: '备注',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
field: 'create_time',
|
||||
title: '创建时间',
|
||||
width: 160,
|
||||
sortable: true,
|
||||
},
|
||||
] as const;
|
||||
}
|
||||
|
||||
export function useInviteCodeFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'code',
|
||||
label: '邀请码',
|
||||
},
|
||||
{
|
||||
component: 'InputNumber',
|
||||
fieldName: 'agent_id',
|
||||
label: '发放代理ID',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
fieldName: 'status',
|
||||
label: '状态',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
options: [
|
||||
{ label: '未使用', value: 0 },
|
||||
{ label: '已使用', value: 1 },
|
||||
{ label: '已失效', value: 2 },
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
175
apps/web-antd/src/views/agent/agent-invite-code/list.vue
Normal file
175
apps/web-antd/src/views/agent/agent-invite-code/list.vue
Normal file
@@ -0,0 +1,175 @@
|
||||
<script lang="ts" setup>
|
||||
import type { AgentApi } from '#/api/agent';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Button, Input, InputNumber, Modal, Space, message } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import {
|
||||
generateDiamondInviteCode,
|
||||
getInviteCodeList,
|
||||
} from '#/api/agent';
|
||||
|
||||
import { useInviteCodeColumns, useInviteCodeFormSchema } from './data';
|
||||
|
||||
interface QueryParams {
|
||||
currentPage: number;
|
||||
pageSize: number;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const generateModalVisible = ref(false);
|
||||
const generatedCodes = ref<string[]>([]);
|
||||
const generateForm = ref({
|
||||
count: 1,
|
||||
expire_days: 0,
|
||||
remark: '',
|
||||
});
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useInviteCodeFormSchema(),
|
||||
submitOnChange: true,
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useInviteCodeColumns(),
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({
|
||||
page,
|
||||
form,
|
||||
}: {
|
||||
form: Record<string, any>;
|
||||
page: QueryParams;
|
||||
}) => {
|
||||
return await getInviteCodeList({
|
||||
...form,
|
||||
target_level: 3,
|
||||
page: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
});
|
||||
},
|
||||
},
|
||||
props: {
|
||||
result: 'items',
|
||||
total: 'total',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 生成邀请码
|
||||
function handleGenerate() {
|
||||
generateForm.value = {
|
||||
count: 1,
|
||||
expire_days: 0,
|
||||
remark: '',
|
||||
};
|
||||
generatedCodes.value = [];
|
||||
generateModalVisible.value = true;
|
||||
}
|
||||
|
||||
// 确认生成
|
||||
async function confirmGenerate() {
|
||||
try {
|
||||
const res = await generateDiamondInviteCode({
|
||||
count: generateForm.value.count,
|
||||
expire_days:
|
||||
generateForm.value.expire_days > 0
|
||||
? generateForm.value.expire_days
|
||||
: undefined,
|
||||
remark: generateForm.value.remark || undefined,
|
||||
});
|
||||
generatedCodes.value = res.codes;
|
||||
message.success('邀请码生成成功');
|
||||
gridApi.query();
|
||||
} catch (error) {
|
||||
console.error('生成邀请码失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 复制邀请码
|
||||
function copyCodes() {
|
||||
const text = generatedCodes.value.join('\n');
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
message.success('邀请码已复制到剪贴板');
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
function closeGenerateModal() {
|
||||
generateModalVisible.value = false;
|
||||
generatedCodes.value = [];
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<Grid table-title="邀请码列表">
|
||||
<template #toolbar>
|
||||
<Button type="primary" @click="handleGenerate">生成钻石邀请码</Button>
|
||||
</template>
|
||||
</Grid>
|
||||
|
||||
<Modal v-model:open="generateModalVisible" :title="generatedCodes.length > 0 ? '生成的邀请码' : '生成钻石邀请码'" :width="600"
|
||||
@cancel="closeGenerateModal">
|
||||
<div v-if="generatedCodes.length === 0">
|
||||
<div class="mb-4">
|
||||
<label>生成数量:</label>
|
||||
<InputNumber v-model:value="generateForm.count" :min="1" :max="100" class="w-full" />
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label>过期天数(0表示不过期):</label>
|
||||
<InputNumber v-model:value="generateForm.expire_days" :min="0" class="w-full" />
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label>备注:</label>
|
||||
<Input.TextArea v-model:value="generateForm.remark" :rows="3" placeholder="请输入备注" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="mb-4">
|
||||
<strong>已生成 {{ generatedCodes.length }} 个邀请码:</strong>
|
||||
</div>
|
||||
<div class="code-list">
|
||||
<div v-for="(code, index) in generatedCodes" :key="index" class="code-item">
|
||||
{{ code }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<Button v-if="generatedCodes.length > 0" @click="closeGenerateModal">
|
||||
关闭
|
||||
</Button>
|
||||
<Button v-else @click="closeGenerateModal">取消</Button>
|
||||
<Button v-if="generatedCodes.length > 0" type="primary" @click="copyCodes">
|
||||
复制所有
|
||||
</Button>
|
||||
<Button v-else type="primary" @click="confirmGenerate">生成</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.code-list {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.code-item {
|
||||
padding: 4px 8px;
|
||||
font-family: monospace;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
67
apps/web-antd/src/views/agent/agent-links/data.ts
Normal file
67
apps/web-antd/src/views/agent/agent-links/data.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
// 推广链接列表列配置
|
||||
export function useLinkColumns(): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'agent_id',
|
||||
title: '代理ID',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'product_id',
|
||||
title: '产品ID',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'product_name',
|
||||
title: '产品名称',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
field: 'set_price',
|
||||
title: '设定价格',
|
||||
width: 120,
|
||||
formatter: ({ cellValue }) => `¥${cellValue.toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
field: 'actual_base_price',
|
||||
title: '实际底价',
|
||||
width: 120,
|
||||
formatter: ({ cellValue }) => `¥${cellValue.toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
field: 'link_identifier',
|
||||
title: '推广码',
|
||||
},
|
||||
{
|
||||
field: 'create_time',
|
||||
title: '创建时间',
|
||||
width: 160,
|
||||
sortable: true,
|
||||
sortType: 'string' as const,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// 推广链接搜索表单配置
|
||||
export function useLinkFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
component: 'InputNumber',
|
||||
fieldName: 'product_id',
|
||||
label: '产品ID',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'product_name',
|
||||
label: '产品名称',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'link_identifier',
|
||||
label: '推广码',
|
||||
},
|
||||
];
|
||||
}
|
||||
64
apps/web-antd/src/views/agent/agent-links/list.vue
Normal file
64
apps/web-antd/src/views/agent/agent-links/list.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getAgentLinkList } from '#/api/agent';
|
||||
|
||||
import { useLinkColumns, useLinkFormSchema } from './data';
|
||||
|
||||
interface Props {
|
||||
agentId?: number;
|
||||
}
|
||||
|
||||
interface QueryParams {
|
||||
currentPage: number;
|
||||
pageSize: number;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const queryParams = computed(() => ({
|
||||
...(props.agentId ? { agent_id: props.agentId } : {}),
|
||||
}));
|
||||
|
||||
const [Grid] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useLinkFormSchema(),
|
||||
submitOnChange: true,
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useLinkColumns(),
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({
|
||||
page,
|
||||
form,
|
||||
}: {
|
||||
form: Record<string, any>;
|
||||
page: QueryParams;
|
||||
}) => {
|
||||
return await getAgentLinkList({
|
||||
...queryParams.value,
|
||||
...form,
|
||||
page: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
});
|
||||
},
|
||||
},
|
||||
props: {
|
||||
result: 'items',
|
||||
total: 'total',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page :auto-content-height="!agentId">
|
||||
<Grid :table-title="agentId ? '推广链接列表' : '所有推广链接'" />
|
||||
</Page>
|
||||
</template>
|
||||
446
apps/web-antd/src/views/agent/agent-list/data.ts
Normal file
446
apps/web-antd/src/views/agent/agent-list/data.ts
Normal file
@@ -0,0 +1,446 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { getLevelName } from '#/utils/agent';
|
||||
|
||||
// 表单配置
|
||||
export function useFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'mobile',
|
||||
label: '手机号',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
fieldName: 'level',
|
||||
label: '等级',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
disabled: true,
|
||||
options: [
|
||||
{ label: '普通代理', value: 1 },
|
||||
{ label: '黄金代理', value: 2 },
|
||||
{ label: '钻石代理', value: 3 },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'region',
|
||||
label: '区域',
|
||||
rules: 'required',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'wechat_id',
|
||||
label: '微信号',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// 搜索表单配置
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'mobile',
|
||||
label: '手机号',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'region',
|
||||
label: '区域',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
fieldName: 'level',
|
||||
label: '等级',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
options: [
|
||||
{ label: '普通代理', value: 1 },
|
||||
{ label: '黄金代理', value: 2 },
|
||||
{ label: '钻石代理', value: 3 },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'InputNumber',
|
||||
fieldName: 'team_leader_id',
|
||||
label: '团队首领ID',
|
||||
},
|
||||
{
|
||||
component: 'RangePicker',
|
||||
fieldName: 'create_time',
|
||||
label: '创建时间',
|
||||
componentProps: {
|
||||
showTime: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
// 表格列配置
|
||||
export function useColumns(): VxeTableGridOptions['columns'] {
|
||||
const columns = [
|
||||
{
|
||||
field: 'id',
|
||||
title: 'ID',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
field: 'user_id',
|
||||
title: '用户ID',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'agent_code',
|
||||
title: '代理编码',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'level',
|
||||
title: '等级',
|
||||
width: 120,
|
||||
formatter: ({ cellValue }: { cellValue: number }) => {
|
||||
return getLevelName(cellValue);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'region',
|
||||
title: '区域',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
field: 'mobile',
|
||||
title: '手机号',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
cellRender: {
|
||||
name: 'CellTag',
|
||||
options: [
|
||||
{ value: true, color: 'success', label: '已认证' },
|
||||
{ value: false, color: 'default', label: '未认证' },
|
||||
],
|
||||
},
|
||||
field: 'is_real_name',
|
||||
title: '实名认证状态',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
field: 'wechat_id',
|
||||
title: '微信号',
|
||||
width: 120,
|
||||
visible: false,
|
||||
},
|
||||
{
|
||||
field: 'team_leader_id',
|
||||
title: '团队首领ID',
|
||||
width: 120,
|
||||
visible: false,
|
||||
},
|
||||
{
|
||||
field: 'balance',
|
||||
title: '钱包余额',
|
||||
width: 120,
|
||||
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||
`¥${cellValue.toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
field: 'total_earnings',
|
||||
title: '累计收益',
|
||||
width: 120,
|
||||
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||
`¥${cellValue.toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
field: 'frozen_balance',
|
||||
title: '冻结余额',
|
||||
width: 120,
|
||||
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||
`¥${cellValue.toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
field: 'withdrawn_amount',
|
||||
title: '提现总额',
|
||||
width: 120,
|
||||
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||
`¥${cellValue.toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
field: 'create_time',
|
||||
title: '成为代理时间',
|
||||
width: 160,
|
||||
sortable: true,
|
||||
sortType: 'string' as const,
|
||||
},
|
||||
{
|
||||
align: 'center' as const,
|
||||
slots: { default: 'operation' },
|
||||
field: 'operation',
|
||||
fixed: 'right' as const,
|
||||
title: '操作',
|
||||
width: 280,
|
||||
},
|
||||
];
|
||||
return columns;
|
||||
}
|
||||
// 推广链接列表列配置
|
||||
export function useLinkColumns(): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'product_name',
|
||||
title: '产品名称',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
field: 'price',
|
||||
title: '价格',
|
||||
width: 120,
|
||||
formatter: ({ cellValue }) => `¥${cellValue.toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
field: 'link_identifier',
|
||||
title: '推广码',
|
||||
},
|
||||
{
|
||||
field: 'create_time',
|
||||
title: '创建时间',
|
||||
width: 160,
|
||||
sortable: true,
|
||||
sortType: 'string' as const,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// 推广链接搜索表单配置
|
||||
export function useLinkFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'product_name',
|
||||
label: '产品名称',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'link_identifier',
|
||||
label: '推广码',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// 佣金记录列表列配置
|
||||
export function useCommissionColumns(): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'id',
|
||||
title: 'ID',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
field: 'agent_id',
|
||||
title: '代理ID',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'order_id',
|
||||
title: '订单ID',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'amount',
|
||||
title: '佣金金额',
|
||||
width: 120,
|
||||
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||
`¥${cellValue.toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
field: 'product_name',
|
||||
title: '产品名称',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
title: '状态',
|
||||
width: 100,
|
||||
formatter: ({ cellValue }: { cellValue: number }) => {
|
||||
const statusMap: Record<number, string> = {
|
||||
0: '待结算',
|
||||
1: '已结算',
|
||||
2: '已取消',
|
||||
};
|
||||
return statusMap[cellValue] || '未知';
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'create_time',
|
||||
title: '创建时间',
|
||||
width: 160,
|
||||
sortable: true,
|
||||
},
|
||||
] as const;
|
||||
}
|
||||
|
||||
// 佣金记录搜索表单配置
|
||||
export function useCommissionFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'product_name',
|
||||
label: '产品名称',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
fieldName: 'status',
|
||||
label: '状态',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
options: [
|
||||
{ label: '待结算', value: 0 },
|
||||
{ label: '已结算', value: 1 },
|
||||
{ label: '已取消', value: 2 },
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// 奖励记录列表列配置
|
||||
export function useRewardColumns(): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'id',
|
||||
title: 'ID',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
field: 'agent_id',
|
||||
title: '代理ID',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'relation_agent_id',
|
||||
title: '关联代理ID',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'amount',
|
||||
title: '奖励金额',
|
||||
width: 120,
|
||||
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||
`¥${cellValue.toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
field: 'type',
|
||||
title: '奖励类型',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
field: 'create_time',
|
||||
title: '创建时间',
|
||||
width: 160,
|
||||
sortable: true,
|
||||
},
|
||||
] as const;
|
||||
}
|
||||
|
||||
// 奖励记录搜索表单配置
|
||||
export function useRewardFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'type',
|
||||
label: '奖励类型',
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'relation_agent_id',
|
||||
label: '关联代理ID',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// 提现记录列表列配置
|
||||
export function useWithdrawalColumns(): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'id',
|
||||
title: 'ID',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
field: 'agent_id',
|
||||
title: '代理ID',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'withdraw_no',
|
||||
title: '提现单号',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
field: 'amount',
|
||||
title: '提现金额',
|
||||
width: 120,
|
||||
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||
`¥${cellValue.toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
title: '状态',
|
||||
width: 100,
|
||||
formatter: ({ cellValue }: { cellValue: number }) => {
|
||||
const statusMap: Record<number, string> = {
|
||||
0: '待审核',
|
||||
1: '已通过',
|
||||
2: '已拒绝',
|
||||
3: '已打款',
|
||||
};
|
||||
return statusMap[cellValue] || '未知';
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'payee_account',
|
||||
title: '收款账户',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
title: '备注',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
field: 'create_time',
|
||||
title: '创建时间',
|
||||
width: 160,
|
||||
sortable: true,
|
||||
},
|
||||
] as const;
|
||||
}
|
||||
|
||||
// 提现记录搜索表单配置
|
||||
export function useWithdrawalFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'withdraw_no',
|
||||
label: '提现单号',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
fieldName: 'status',
|
||||
label: '状态',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
options: [
|
||||
{ label: '待审核', value: 0 },
|
||||
{ label: '已通过', value: 1 },
|
||||
{ label: '已拒绝', value: 2 },
|
||||
{ label: '已打款', value: 3 },
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
329
apps/web-antd/src/views/agent/agent-list/list.vue
Normal file
329
apps/web-antd/src/views/agent/agent-list/list.vue
Normal file
@@ -0,0 +1,329 @@
|
||||
<script lang="ts" setup>
|
||||
import type {
|
||||
OnActionClickParams,
|
||||
VxeGridListeners,
|
||||
VxeTableGridOptions,
|
||||
} from '#/adapter/vxe-table';
|
||||
import type { AgentApi } from '#/api/agent';
|
||||
|
||||
import { computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import { Page, useVbenDrawer, useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Button, Card, Dropdown, Menu } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getAgentList } from '#/api/agent';
|
||||
|
||||
import { useColumns, useGridFormSchema } from './data';
|
||||
import CommissionModal from './modules/commission-modal.vue';
|
||||
import Form from './modules/form.vue';
|
||||
import LinkModal from './modules/link-modal.vue';
|
||||
import OrderModal from './modules/order-modal.vue';
|
||||
import RebateModal from './modules/rebate-modal.vue';
|
||||
import UpgradeModal from './modules/upgrade-modal.vue';
|
||||
import WithdrawalModal from './modules/withdrawal-modal.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
// 表单抽屉
|
||||
const [FormDrawer, formDrawerApi] = useVbenDrawer({
|
||||
connectedComponent: Form,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
// 推广链接弹窗
|
||||
const [LinkModalComponent, linkModalApi] = useVbenModal({
|
||||
connectedComponent: LinkModal,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
// 佣金记录弹窗
|
||||
const [CommissionModalComponent, commissionModalApi] = useVbenModal({
|
||||
connectedComponent: CommissionModal,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
// 返佣记录弹窗
|
||||
const [RebateModalComponent, rebateModalApi] = useVbenModal({
|
||||
connectedComponent: RebateModal,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
// 升级记录弹窗
|
||||
const [UpgradeModalComponent, upgradeModalApi] = useVbenModal({
|
||||
connectedComponent: UpgradeModal,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
// 订单记录弹窗
|
||||
const [OrderModalComponent, orderModalApi] = useVbenModal({
|
||||
connectedComponent: OrderModal,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
// 提现记录弹窗
|
||||
const [WithdrawalModalComponent, withdrawalModalApi] = useVbenModal({
|
||||
connectedComponent: WithdrawalModal,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
// 表格配置
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
fieldMappingTime: [
|
||||
['create_time', ['create_time_start', 'create_time_end']],
|
||||
],
|
||||
schema: useGridFormSchema(),
|
||||
submitOnChange: true,
|
||||
},
|
||||
gridEvents: {
|
||||
sortChange: () => {
|
||||
gridApi.query();
|
||||
},
|
||||
} as VxeGridListeners<AgentApi.AgentListItem>,
|
||||
gridOptions: {
|
||||
columns: useColumns(),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
sortConfig: {
|
||||
remote: true,
|
||||
multiple: false,
|
||||
trigger: 'default',
|
||||
orders: ['asc', 'desc', null],
|
||||
resetPage: true,
|
||||
},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page, sort }, formValues) => {
|
||||
const sortParams = sort
|
||||
? {
|
||||
order_by: sort.field,
|
||||
order_type: sort.order,
|
||||
}
|
||||
: {};
|
||||
|
||||
const res = await getAgentList({
|
||||
page: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
...sortParams,
|
||||
team_leader_id: route.query.team_leader_id
|
||||
? Number(route.query.team_leader_id)
|
||||
: undefined,
|
||||
});
|
||||
|
||||
return {
|
||||
...res,
|
||||
sort: sort || null,
|
||||
};
|
||||
},
|
||||
},
|
||||
props: {
|
||||
result: 'items',
|
||||
total: 'total',
|
||||
},
|
||||
autoLoad: true,
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
},
|
||||
toolbarConfig: {
|
||||
custom: true,
|
||||
export: false,
|
||||
refresh: { code: 'query' },
|
||||
search: true,
|
||||
zoom: true,
|
||||
},
|
||||
} as VxeTableGridOptions<AgentApi.AgentListItem>,
|
||||
});
|
||||
|
||||
// 更多操作菜单项
|
||||
const moreMenuItems = [
|
||||
{
|
||||
key: 'links',
|
||||
label: '推广链接',
|
||||
},
|
||||
{
|
||||
key: 'rebate',
|
||||
label: '返佣记录',
|
||||
},
|
||||
{
|
||||
key: 'upgrade',
|
||||
label: '升级记录',
|
||||
},
|
||||
{
|
||||
key: 'order',
|
||||
label: '订单记录',
|
||||
},
|
||||
{
|
||||
key: 'withdrawal',
|
||||
label: '提现记录',
|
||||
},
|
||||
];
|
||||
|
||||
// 团队首领信息
|
||||
const teamLeaderId = computed(() => route.query.team_leader_id);
|
||||
|
||||
// 返回团队首领列表
|
||||
function onBackToParent() {
|
||||
router.replace({
|
||||
query: {
|
||||
...route.query,
|
||||
team_leader_id: undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 操作处理函数
|
||||
function onActionClick(
|
||||
e:
|
||||
| OnActionClickParams<AgentApi.AgentListItem>
|
||||
| { code: string; row: AgentApi.AgentListItem },
|
||||
) {
|
||||
switch (e.code) {
|
||||
case 'commission': {
|
||||
onViewCommission(e.row);
|
||||
break;
|
||||
}
|
||||
case 'edit': {
|
||||
onEdit(e.row);
|
||||
break;
|
||||
}
|
||||
case 'links': {
|
||||
onViewLinks(e.row);
|
||||
break;
|
||||
}
|
||||
case 'rebate': {
|
||||
onViewRebate(e.row);
|
||||
break;
|
||||
}
|
||||
case 'upgrade': {
|
||||
onViewUpgrade(e.row);
|
||||
break;
|
||||
}
|
||||
case 'order': {
|
||||
onViewOrder(e.row);
|
||||
break;
|
||||
}
|
||||
case 'view-sub-agent': {
|
||||
router.replace({
|
||||
query: {
|
||||
...route.query,
|
||||
team_leader_id: e.row.id,
|
||||
},
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'withdrawal': {
|
||||
onViewWithdrawal(e.row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 编辑处理
|
||||
function onEdit(row: AgentApi.AgentListItem) {
|
||||
formDrawerApi.setData(row).open();
|
||||
}
|
||||
|
||||
// 查看推广链接
|
||||
function onViewLinks(row: AgentApi.AgentListItem) {
|
||||
linkModalApi.setData({ agentId: row.id }).open();
|
||||
}
|
||||
|
||||
// 查看佣金记录
|
||||
function onViewCommission(row: AgentApi.AgentListItem) {
|
||||
commissionModalApi.setData({ agentId: row.id }).open();
|
||||
}
|
||||
|
||||
// 查看返佣记录
|
||||
function onViewRebate(row: AgentApi.AgentListItem) {
|
||||
rebateModalApi.setData({ agentId: row.id }).open();
|
||||
}
|
||||
|
||||
// 查看升级记录
|
||||
function onViewUpgrade(row: AgentApi.AgentListItem) {
|
||||
upgradeModalApi.setData({ agentId: row.id }).open();
|
||||
}
|
||||
|
||||
// 查看订单记录
|
||||
function onViewOrder(row: AgentApi.AgentListItem) {
|
||||
orderModalApi.setData({ agentId: row.id }).open();
|
||||
}
|
||||
|
||||
// 查看提现记录
|
||||
function onViewWithdrawal(row: AgentApi.AgentListItem) {
|
||||
withdrawalModalApi.setData({ agentId: row.id }).open();
|
||||
}
|
||||
|
||||
// 刷新处理
|
||||
function onRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<FormDrawer @success="onRefresh" />
|
||||
<LinkModalComponent />
|
||||
<CommissionModalComponent />
|
||||
<RebateModalComponent />
|
||||
<UpgradeModalComponent />
|
||||
<OrderModalComponent />
|
||||
<WithdrawalModalComponent />
|
||||
|
||||
<!-- 团队首领信息卡片 -->
|
||||
<Card v-if="teamLeaderId" class="mb-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<Button @click="onBackToParent">返回上级列表</Button>
|
||||
<div>团队首领ID:{{ teamLeaderId }}</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Grid table-title="代理列表">
|
||||
<template #operation="{ row }">
|
||||
<div class="operation-buttons">
|
||||
<Button type="link" @click="onActionClick({ code: 'edit', row })">
|
||||
编辑
|
||||
</Button>
|
||||
<Button type="link" @click="onActionClick({ code: 'view-sub-agent', row })">
|
||||
查看下级
|
||||
</Button>
|
||||
<!-- <Button
|
||||
type="link"
|
||||
@click="onActionClick({ code: 'order-record', row })"
|
||||
>
|
||||
订单记录
|
||||
</Button> -->
|
||||
<Dropdown>
|
||||
<Button type="link">更多操作</Button>
|
||||
<template #overlay>
|
||||
<Menu :items="moreMenuItems" @click="(e) => onActionClick({ code: String(e.key), row })" />
|
||||
</template>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.parent-agent-card {
|
||||
margin-bottom: 16px;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.operation-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
gap: 4px;
|
||||
|
||||
:deep(.ant-btn-link) {
|
||||
padding: 0 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,32 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import CommissionList from '../../agent-commission/list.vue';
|
||||
|
||||
interface ModalData {
|
||||
agentId: number;
|
||||
}
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
title: '佣金记录列表',
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
const modalData = computed(() => modalApi.getData<ModalData>());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal class="w-[calc(100vw-200px)]" :footer="false">
|
||||
<div class="agent-commission-modal">
|
||||
<CommissionList :agent-id="modalData?.agentId" />
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.agent-commission-modal {
|
||||
padding: 16px;
|
||||
}
|
||||
</style>
|
||||
62
apps/web-antd/src/views/agent/agent-list/modules/form.vue
Normal file
62
apps/web-antd/src/views/agent/agent-list/modules/form.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<script lang="ts" setup>
|
||||
import type { AgentApi } from '#/api/agent';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useVbenDrawer } from '@vben/common-ui';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
|
||||
import { useFormSchema } from '../data';
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
|
||||
const formData = ref<AgentApi.AgentListItem>();
|
||||
|
||||
const [Form, formApi] = useVbenForm({
|
||||
schema: useFormSchema(),
|
||||
showDefaultActions: false,
|
||||
});
|
||||
|
||||
const id = ref();
|
||||
const [Drawer, drawerApi] = useVbenDrawer({
|
||||
async onConfirm() {
|
||||
const { valid } = await formApi.validate();
|
||||
if (!valid) return;
|
||||
const values = await formApi.getValues();
|
||||
drawerApi.lock();
|
||||
// TODO: 实现更新代理信息的接口
|
||||
// updateAgent(id.value, values as AgentApi.UpdateAgentRequest)
|
||||
// .then(() => {
|
||||
// emit('success');
|
||||
// drawerApi.close();
|
||||
// })
|
||||
// .catch(() => {
|
||||
// drawerApi.unlock();
|
||||
// });
|
||||
},
|
||||
onOpenChange(isOpen) {
|
||||
if (isOpen) {
|
||||
const data = drawerApi.getData<AgentApi.AgentListItem>();
|
||||
formApi.resetForm();
|
||||
if (data) {
|
||||
formData.value = data;
|
||||
id.value = data.id;
|
||||
formApi.setValues(data);
|
||||
} else {
|
||||
id.value = undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const getDrawerTitle = computed(() => {
|
||||
return formData.value?.id ? '编辑代理' : '创建代理';
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Drawer :title="getDrawerTitle">
|
||||
<Form />
|
||||
</Drawer>
|
||||
</template>
|
||||
@@ -0,0 +1,32 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import LinkList from '../../agent-links/list.vue';
|
||||
|
||||
interface ModalData {
|
||||
agentId: number;
|
||||
}
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
title: '推广链接列表',
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
const modalData = computed(() => modalApi.getData<ModalData>());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal class="w-[calc(100vw-200px)]" :footer="false">
|
||||
<div class="agent-link-modal">
|
||||
<LinkList :agent-id="modalData?.agentId" />
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.agent-link-modal {
|
||||
padding: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,33 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import OrderList from '../../agent-order/list.vue';
|
||||
|
||||
interface ModalData {
|
||||
agentId: number;
|
||||
}
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
title: '订单记录',
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
const modalData = computed(() => modalApi.getData<ModalData>());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal class="w-[calc(100vw-200px)]" :footer="false">
|
||||
<div class="agent-order-modal">
|
||||
<OrderList :agent-id="modalData?.agentId" />
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.agent-order-modal {
|
||||
padding: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import RebateList from '../../agent-rebate/list.vue';
|
||||
|
||||
interface ModalData {
|
||||
agentId: number;
|
||||
}
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
title: '返佣记录',
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
const modalData = computed(() => modalApi.getData<ModalData>());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal class="w-[calc(100vw-200px)]" :footer="false">
|
||||
<div class="agent-rebate-modal">
|
||||
<RebateList :agent-id="modalData?.agentId" />
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.agent-rebate-modal {
|
||||
padding: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import UpgradeList from '../../agent-upgrade/list.vue';
|
||||
|
||||
interface ModalData {
|
||||
agentId: number;
|
||||
}
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
title: '升级记录',
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
const modalData = computed(() => modalApi.getData<ModalData>());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal class="w-[calc(100vw-200px)]" :footer="false">
|
||||
<div class="agent-upgrade-modal">
|
||||
<UpgradeList :agent-id="modalData?.agentId" />
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.agent-upgrade-modal {
|
||||
padding: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import WithdrawalList from '../../agent-withdrawal/list.vue';
|
||||
|
||||
interface ModalData {
|
||||
agentId: number;
|
||||
}
|
||||
|
||||
const [Modal, modalApi] = useVbenModal({
|
||||
title: '提现记录',
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
const modalData = computed(() => modalApi.getData<ModalData>());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal class="w-[calc(100vw-200px)]" :footer="false">
|
||||
<div class="agent-withdrawal-modal">
|
||||
<WithdrawalList :agent-id="modalData?.agentId" />
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.agent-withdrawal-modal {
|
||||
padding: 16px;
|
||||
}
|
||||
</style>
|
||||
117
apps/web-antd/src/views/agent/agent-order/data.ts
Normal file
117
apps/web-antd/src/views/agent/agent-order/data.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { getOrderProcessStatusName } from '#/utils/agent';
|
||||
|
||||
export function useOrderColumns(): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'id',
|
||||
title: 'ID',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
field: 'agent_id',
|
||||
title: '代理ID',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'order_id',
|
||||
title: '订单ID',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'product_id',
|
||||
title: '产品ID',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'product_name',
|
||||
title: '产品名称',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
field: 'order_amount',
|
||||
title: '订单金额',
|
||||
width: 120,
|
||||
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||
`¥${cellValue.toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
field: 'set_price',
|
||||
title: '设定价格',
|
||||
width: 120,
|
||||
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||
`¥${cellValue.toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
field: 'actual_base_price',
|
||||
title: '实际底价',
|
||||
width: 120,
|
||||
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||
`¥${cellValue.toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
field: 'price_cost',
|
||||
title: '提价成本',
|
||||
width: 120,
|
||||
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||
`¥${cellValue.toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
field: 'agent_profit',
|
||||
title: '代理收益',
|
||||
width: 120,
|
||||
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||
`¥${cellValue.toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
field: 'process_status',
|
||||
title: '处理状态',
|
||||
width: 100,
|
||||
cellRender: {
|
||||
name: 'CellTag',
|
||||
options: [
|
||||
{ value: 0, color: 'warning', label: '待处理' },
|
||||
{ value: 1, color: 'success', label: '处理成功' },
|
||||
{ value: 2, color: 'error', label: '处理失败' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'create_time',
|
||||
title: '创建时间',
|
||||
width: 160,
|
||||
sortable: true,
|
||||
},
|
||||
] as const;
|
||||
}
|
||||
|
||||
export function useOrderFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
component: 'InputNumber',
|
||||
fieldName: 'agent_id',
|
||||
label: '代理ID',
|
||||
},
|
||||
{
|
||||
component: 'InputNumber',
|
||||
fieldName: 'order_id',
|
||||
label: '订单ID',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
fieldName: 'process_status',
|
||||
label: '处理状态',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
options: [
|
||||
{ label: '待处理', value: 0 },
|
||||
{ label: '处理成功', value: 1 },
|
||||
{ label: '处理失败', value: 2 },
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
64
apps/web-antd/src/views/agent/agent-order/list.vue
Normal file
64
apps/web-antd/src/views/agent/agent-order/list.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getAgentOrderList } from '#/api/agent';
|
||||
|
||||
import { useOrderColumns, useOrderFormSchema } from './data';
|
||||
|
||||
interface Props {
|
||||
agentId?: number;
|
||||
}
|
||||
|
||||
interface QueryParams {
|
||||
currentPage: number;
|
||||
pageSize: number;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const queryParams = computed(() => ({
|
||||
...(props.agentId ? { agent_id: props.agentId } : {}),
|
||||
}));
|
||||
|
||||
const [Grid] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useOrderFormSchema(),
|
||||
submitOnChange: true,
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useOrderColumns(),
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({
|
||||
page,
|
||||
form,
|
||||
}: {
|
||||
form: Record<string, any>;
|
||||
page: QueryParams;
|
||||
}) => {
|
||||
return await getAgentOrderList({
|
||||
...queryParams.value,
|
||||
...form,
|
||||
page: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
});
|
||||
},
|
||||
},
|
||||
props: {
|
||||
result: 'items',
|
||||
total: 'total',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page :auto-content-height="!agentId">
|
||||
<Grid :table-title="agentId ? '订单记录列表' : '所有订单记录'" />
|
||||
</Page>
|
||||
</template>
|
||||
193
apps/web-antd/src/views/agent/agent-product-config/data.ts
Normal file
193
apps/web-antd/src/views/agent/agent-product-config/data.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { h } from 'vue';
|
||||
|
||||
import { z } from '#/adapter/form';
|
||||
|
||||
// 代理产品配置列表列配置
|
||||
export function useColumns(): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'id',
|
||||
title: 'ID',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
field: 'product_name',
|
||||
title: '产品名称',
|
||||
},
|
||||
{
|
||||
field: 'base_price',
|
||||
title: '基础底价',
|
||||
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||
`¥${cellValue.toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
field: 'price_range_max',
|
||||
title: '最高定价',
|
||||
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||
`¥${cellValue.toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
field: 'price_threshold',
|
||||
title: '价格阈值',
|
||||
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||
`¥${cellValue.toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
field: 'price_fee_rate',
|
||||
title: '提价费率',
|
||||
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||
`${(cellValue * 100).toFixed(2)}%`,
|
||||
},
|
||||
{
|
||||
align: 'center',
|
||||
slots: { default: 'operation' },
|
||||
field: 'operation',
|
||||
fixed: 'right',
|
||||
title: '操作',
|
||||
width: 120,
|
||||
},
|
||||
] as const;
|
||||
}
|
||||
|
||||
// 代理产品配置搜索表单配置
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'product_name',
|
||||
label: '产品名称',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
// 代理产品配置编辑表单配置
|
||||
export function useFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
component: 'InputNumber',
|
||||
fieldName: 'base_price',
|
||||
label: '基础底价',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
min: 0,
|
||||
precision: 2,
|
||||
step: 0.01,
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'InputNumber',
|
||||
fieldName: 'price_range_max',
|
||||
label: '最高定价',
|
||||
rules: 'required',
|
||||
componentProps: {
|
||||
min: 0,
|
||||
precision: 2,
|
||||
step: 0.01,
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'InputNumber',
|
||||
fieldName: 'price_threshold',
|
||||
label: '价格阈值',
|
||||
defaultValue: undefined,
|
||||
componentProps: {
|
||||
min: 0,
|
||||
precision: 2,
|
||||
step: 0.01,
|
||||
placeholder: '可选,不设置则不限制',
|
||||
},
|
||||
rules: z
|
||||
.number({
|
||||
invalid_type_error: '请输入有效的数字',
|
||||
})
|
||||
.min(0, '价格阈值不能小于0')
|
||||
.optional()
|
||||
.nullable(),
|
||||
dependencies: {
|
||||
triggerFields: ['base_price'],
|
||||
rules(values) {
|
||||
// 动态校验:价格阈值不能低于基础底价
|
||||
const basePrice = values.base_price;
|
||||
if (basePrice !== undefined && basePrice !== null) {
|
||||
return z
|
||||
.number({
|
||||
invalid_type_error: '请输入有效的数字',
|
||||
})
|
||||
.min(0, '价格阈值不能小于0')
|
||||
.refine(
|
||||
(val) => {
|
||||
if (val === undefined || val === null) return true;
|
||||
return val >= basePrice;
|
||||
},
|
||||
{
|
||||
message: `价格阈值不能低于基础底价 ${basePrice}`,
|
||||
},
|
||||
)
|
||||
.optional()
|
||||
.nullable();
|
||||
}
|
||||
return z
|
||||
.number({
|
||||
invalid_type_error: '请输入有效的数字',
|
||||
})
|
||||
.min(0, '价格阈值不能小于0')
|
||||
.optional()
|
||||
.nullable();
|
||||
},
|
||||
trigger(_values, formApi) {
|
||||
// 当基础底价变化时,重新校验价格阈值
|
||||
formApi.validateField('price_threshold');
|
||||
},
|
||||
},
|
||||
suffix: () => h('span', { class: 'text-gray-400 text-xs' }, '可选'),
|
||||
},
|
||||
{
|
||||
component: 'InputNumber',
|
||||
fieldName: 'price_fee_rate',
|
||||
label: '提价费率',
|
||||
defaultValue: undefined,
|
||||
componentProps: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
precision: 4,
|
||||
step: 0.01,
|
||||
addonAfter: '%',
|
||||
controls: true,
|
||||
placeholder: '可选,不设置则不收费',
|
||||
validateTrigger: ['blur', 'change'],
|
||||
},
|
||||
rules: z
|
||||
.number({
|
||||
invalid_type_error: '请输入有效的数字',
|
||||
})
|
||||
.min(0, '提价费率不能小于0')
|
||||
.max(100, '提价费率不能大于100%')
|
||||
.optional()
|
||||
.nullable(),
|
||||
dependencies: {
|
||||
triggerFields: ['price_threshold'],
|
||||
required(values) {
|
||||
// 如果价格阈值有值,提价费率必填
|
||||
return values.price_threshold !== undefined && values.price_threshold !== null;
|
||||
},
|
||||
rules(values) {
|
||||
// 如果价格阈值有值,提价费率必填
|
||||
if (values.price_threshold !== undefined && values.price_threshold !== null) {
|
||||
return z.number().min(0, '提价费率不能小于0').max(100, '提价费率不能大于100%');
|
||||
}
|
||||
return z.number().optional().nullable();
|
||||
},
|
||||
trigger(values, formApi) {
|
||||
// 当价格阈值清空时,也清空提价费率
|
||||
if (values.price_threshold === undefined || values.price_threshold === null) {
|
||||
formApi.setFieldValue('price_fee_rate', undefined);
|
||||
}
|
||||
},
|
||||
},
|
||||
suffix: () => h('span', { class: 'text-gray-400 text-xs' }, '可选'),
|
||||
},
|
||||
];
|
||||
}
|
||||
127
apps/web-antd/src/views/agent/agent-product-config/list.vue
Normal file
127
apps/web-antd/src/views/agent/agent-product-config/list.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<script lang="ts" setup>
|
||||
import type {
|
||||
OnActionClickParams,
|
||||
VxeGridListeners,
|
||||
VxeTableGridOptions,
|
||||
} from '#/adapter/vxe-table';
|
||||
import type { AgentApi } from '#/api/agent';
|
||||
|
||||
import { Page, useVbenDrawer } from '@vben/common-ui';
|
||||
|
||||
import { Button, Space } from 'ant-design-vue';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getAgentProductionConfigList } from '#/api/agent';
|
||||
|
||||
import { useColumns, useGridFormSchema } from './data';
|
||||
import Form from './modules/form.vue';
|
||||
|
||||
// 表单抽屉
|
||||
const [FormDrawer, formDrawerApi] = useVbenDrawer({
|
||||
connectedComponent: Form,
|
||||
destroyOnClose: true,
|
||||
});
|
||||
|
||||
// 表格配置
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useGridFormSchema(),
|
||||
submitOnChange: true,
|
||||
},
|
||||
gridEvents: {
|
||||
sortChange: () => {
|
||||
gridApi.query();
|
||||
},
|
||||
} as VxeGridListeners<AgentApi.AgentProductionConfigItem>,
|
||||
gridOptions: {
|
||||
columns: useColumns(),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
sortConfig: {
|
||||
remote: true,
|
||||
multiple: false,
|
||||
trigger: 'default',
|
||||
orders: ['asc', 'desc', null],
|
||||
resetPage: true,
|
||||
},
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page, sort }, formValues) => {
|
||||
const sortParams = sort
|
||||
? {
|
||||
order_by: sort.field,
|
||||
order_type: sort.order,
|
||||
}
|
||||
: {};
|
||||
|
||||
const params: AgentApi.GetAgentProductionConfigListParams = {
|
||||
page: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
product_name: formValues.product_name,
|
||||
...sortParams,
|
||||
};
|
||||
|
||||
const res = await getAgentProductionConfigList(params);
|
||||
|
||||
return {
|
||||
...res,
|
||||
sort: sort || null,
|
||||
};
|
||||
},
|
||||
},
|
||||
props: {
|
||||
result: 'items',
|
||||
total: 'total',
|
||||
},
|
||||
autoLoad: true,
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
},
|
||||
toolbarConfig: {
|
||||
custom: true,
|
||||
export: false,
|
||||
refresh: { code: 'query' },
|
||||
search: true,
|
||||
zoom: true,
|
||||
},
|
||||
} as VxeTableGridOptions<AgentApi.AgentProductionConfigItem>,
|
||||
});
|
||||
|
||||
// 操作处理函数
|
||||
function onActionClick(
|
||||
e: OnActionClickParams<AgentApi.AgentProductionConfigItem>,
|
||||
) {
|
||||
switch (e.code) {
|
||||
case 'edit': {
|
||||
onEdit(e.row);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 编辑处理
|
||||
function onEdit(row: AgentApi.AgentProductionConfigItem) {
|
||||
formDrawerApi.setData(row).open();
|
||||
}
|
||||
|
||||
// 刷新处理
|
||||
function onRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<FormDrawer @success="onRefresh" />
|
||||
<Grid table-title="代理产品配置列表">
|
||||
<template #operation="{ row }">
|
||||
<Space>
|
||||
<Button type="link" @click="onActionClick({ code: 'edit', row })">
|
||||
配置
|
||||
</Button>
|
||||
</Space>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
@@ -0,0 +1,95 @@
|
||||
<script lang="ts" setup>
|
||||
import type { AgentApi } from '#/api/agent';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { Button, InputNumber } from 'ant-design-vue';
|
||||
|
||||
import { useVbenDrawer, useVbenForm } from '@vben/common-ui';
|
||||
|
||||
import { updateAgentProductionConfig } from '#/api/agent';
|
||||
|
||||
import { useFormSchema } from '../data';
|
||||
|
||||
const emit = defineEmits(['success']);
|
||||
|
||||
const formData = ref<AgentApi.AgentProductionConfigItem>();
|
||||
const id = ref<number>();
|
||||
|
||||
const [Form, formApi] = useVbenForm({
|
||||
schema: useFormSchema(),
|
||||
showDefaultActions: false,
|
||||
});
|
||||
const drawerTitle = ref('产品配置');
|
||||
|
||||
const [Drawer, drawerApi] = useVbenDrawer({
|
||||
title: drawerTitle.value,
|
||||
destroyOnClose: true,
|
||||
async onConfirm() {
|
||||
const valid = await formApi.validate();
|
||||
if (!valid || !id.value) return;
|
||||
|
||||
const values = await formApi.getValues();
|
||||
const params: AgentApi.UpdateAgentProductionConfigParams = {
|
||||
id: id.value,
|
||||
base_price: values.base_price,
|
||||
price_range_max: values.price_range_max,
|
||||
price_threshold: values.price_threshold ?? undefined,
|
||||
price_fee_rate: values.price_fee_rate ? values.price_fee_rate / 100 : undefined,
|
||||
};
|
||||
|
||||
await updateAgentProductionConfig(params);
|
||||
|
||||
emit('success');
|
||||
drawerApi.close();
|
||||
},
|
||||
onOpenChange(isOpen) {
|
||||
if (isOpen) {
|
||||
const data = drawerApi.getData<AgentApi.AgentProductionConfigItem>();
|
||||
formApi.resetForm();
|
||||
if (data) {
|
||||
formData.value = data;
|
||||
id.value = data.id;
|
||||
formApi.setValues({
|
||||
...data,
|
||||
price_threshold: data.price_threshold ?? undefined,
|
||||
price_fee_rate: data.price_fee_rate ? data.price_fee_rate * 100 : undefined,
|
||||
});
|
||||
} else {
|
||||
id.value = undefined;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Drawer :title="drawerTitle">
|
||||
<Form>
|
||||
<template #price_threshold="slotProps">
|
||||
<div class="flex items-center gap-2">
|
||||
<InputNumber :value="slotProps.componentField.modelValue" :min="0" :precision="2" :step="0.01"
|
||||
placeholder="可选,不设置则不限制" class="flex-1" @update:value="slotProps.componentField['onUpdate:modelValue']" />
|
||||
<Button type="link" size="small" @click="() => {
|
||||
formApi.setFieldValue('price_threshold', undefined);
|
||||
formApi.setFieldValue('price_fee_rate', undefined);
|
||||
}">
|
||||
不设置
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
<template #price_fee_rate="slotProps">
|
||||
<div class="flex items-center gap-2">
|
||||
<InputNumber :value="slotProps.componentField.modelValue" :min="0" :max="100" :precision="4" :step="0.01"
|
||||
addon-after="%" placeholder="可选,不设置则不收费" class="flex-1"
|
||||
@update:value="slotProps.componentField['onUpdate:modelValue']" />
|
||||
<Button type="link" size="small" @click="() => {
|
||||
formApi.setFieldValue('price_fee_rate', undefined);
|
||||
}">
|
||||
不设置
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
</Form>
|
||||
</Drawer>
|
||||
</template>
|
||||
86
apps/web-antd/src/views/agent/agent-real-name/data.ts
Normal file
86
apps/web-antd/src/views/agent/agent-real-name/data.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { maskIdCard, maskMobile, getRealNameStatusName } from '#/utils/agent';
|
||||
|
||||
export function useRealNameColumns(): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'id',
|
||||
title: 'ID',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
field: 'agent_id',
|
||||
title: '代理ID',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
title: '姓名',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
field: 'id_card',
|
||||
title: '身份证号',
|
||||
width: 180,
|
||||
formatter: ({ cellValue }: { cellValue: string }) => {
|
||||
return maskIdCard(cellValue);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'mobile',
|
||||
title: '手机号',
|
||||
width: 140,
|
||||
formatter: ({ cellValue }: { cellValue: string }) => {
|
||||
return maskMobile(cellValue);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
title: '状态',
|
||||
width: 100,
|
||||
cellRender: {
|
||||
name: 'CellTag',
|
||||
options: [
|
||||
{ value: 1, color: 'warning', label: '未验证' },
|
||||
{ value: 2, color: 'success', label: '已通过' },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'verify_time',
|
||||
title: '验证时间',
|
||||
width: 160,
|
||||
},
|
||||
{
|
||||
field: 'create_time',
|
||||
title: '创建时间',
|
||||
width: 160,
|
||||
sortable: true,
|
||||
},
|
||||
] as const;
|
||||
}
|
||||
|
||||
export function useRealNameFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
component: 'InputNumber',
|
||||
fieldName: 'agent_id',
|
||||
label: '代理ID',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
fieldName: 'status',
|
||||
label: '状态',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
options: [
|
||||
{ label: '未验证', value: 1 },
|
||||
{ label: '已通过', value: 2 },
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
65
apps/web-antd/src/views/agent/agent-real-name/list.vue
Normal file
65
apps/web-antd/src/views/agent/agent-real-name/list.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getAgentRealNameList } from '#/api/agent';
|
||||
|
||||
import { useRealNameColumns, useRealNameFormSchema } from './data';
|
||||
|
||||
interface Props {
|
||||
agentId?: number;
|
||||
}
|
||||
|
||||
interface QueryParams {
|
||||
currentPage: number;
|
||||
pageSize: number;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const queryParams = computed(() => ({
|
||||
...(props.agentId ? { agent_id: props.agentId } : {}),
|
||||
}));
|
||||
|
||||
const [Grid] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useRealNameFormSchema(),
|
||||
submitOnChange: true,
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useRealNameColumns(),
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({
|
||||
page,
|
||||
form,
|
||||
}: {
|
||||
form: Record<string, any>;
|
||||
page: QueryParams;
|
||||
}) => {
|
||||
return await getAgentRealNameList({
|
||||
...queryParams.value,
|
||||
...form,
|
||||
page: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
});
|
||||
},
|
||||
},
|
||||
props: {
|
||||
result: 'items',
|
||||
total: 'total',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page :auto-content-height="!agentId">
|
||||
<Grid :table-title="agentId ? '实名认证列表' : '所有实名认证'" />
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
79
apps/web-antd/src/views/agent/agent-rebate/data.ts
Normal file
79
apps/web-antd/src/views/agent/agent-rebate/data.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { getRebateTypeName } from '#/utils/agent';
|
||||
|
||||
export function useRebateColumns(): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'id',
|
||||
title: 'ID',
|
||||
width: 80,
|
||||
},
|
||||
{
|
||||
field: 'agent_id',
|
||||
title: '代理ID',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'source_agent_id',
|
||||
title: '来源代理ID',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
field: 'order_id',
|
||||
title: '订单ID',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
field: 'rebate_type',
|
||||
title: '返佣类型',
|
||||
width: 140,
|
||||
formatter: ({ cellValue }: { cellValue: number }) => {
|
||||
return getRebateTypeName(cellValue);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'amount',
|
||||
title: '返佣金额',
|
||||
width: 120,
|
||||
formatter: ({ cellValue }: { cellValue: number }) =>
|
||||
`¥${cellValue.toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
field: 'create_time',
|
||||
title: '创建时间',
|
||||
width: 160,
|
||||
sortable: true,
|
||||
},
|
||||
] as const;
|
||||
}
|
||||
|
||||
export function useRebateFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
component: 'InputNumber',
|
||||
fieldName: 'agent_id',
|
||||
label: '代理ID',
|
||||
},
|
||||
{
|
||||
component: 'InputNumber',
|
||||
fieldName: 'source_agent_id',
|
||||
label: '来源代理ID',
|
||||
},
|
||||
{
|
||||
component: 'Select',
|
||||
fieldName: 'rebate_type',
|
||||
label: '返佣类型',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
options: [
|
||||
{ label: '直接上级返佣', value: 1 },
|
||||
{ label: '钻石上级返佣', value: 2 },
|
||||
{ label: '黄金上级返佣', value: 3 },
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
65
apps/web-antd/src/views/agent/agent-rebate/list.vue
Normal file
65
apps/web-antd/src/views/agent/agent-rebate/list.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { getAgentRebateList } from '#/api/agent';
|
||||
|
||||
import { useRebateColumns, useRebateFormSchema } from './data';
|
||||
|
||||
interface Props {
|
||||
agentId?: number;
|
||||
}
|
||||
|
||||
interface QueryParams {
|
||||
currentPage: number;
|
||||
pageSize: number;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const queryParams = computed(() => ({
|
||||
...(props.agentId ? { agent_id: props.agentId } : {}),
|
||||
}));
|
||||
|
||||
const [Grid] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useRebateFormSchema(),
|
||||
submitOnChange: true,
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useRebateColumns(),
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({
|
||||
page,
|
||||
form,
|
||||
}: {
|
||||
form: Record<string, any>;
|
||||
page: QueryParams;
|
||||
}) => {
|
||||
return await getAgentRebateList({
|
||||
...queryParams.value,
|
||||
...form,
|
||||
page: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
});
|
||||
},
|
||||
},
|
||||
props: {
|
||||
result: 'items',
|
||||
total: 'total',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page :auto-content-height="!agentId">
|
||||
<Grid :table-title="agentId ? '返佣记录列表' : '所有返佣记录'" />
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
74
apps/web-antd/src/views/agent/agent-reward/data.ts
Normal file
74
apps/web-antd/src/views/agent/agent-reward/data.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
export function useRewardColumns(): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
title: '代理ID',
|
||||
field: 'agent_id',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '奖励类型',
|
||||
field: 'type',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '奖励金额',
|
||||
field: 'amount',
|
||||
width: 120,
|
||||
formatter: ({ cellValue }) => `¥${cellValue.toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
title: '关联订单',
|
||||
field: 'order_id',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
field: 'status',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
field: 'create_time',
|
||||
width: 180,
|
||||
},
|
||||
{
|
||||
title: '发放时间',
|
||||
field: 'pay_time',
|
||||
width: 180,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function useRewardFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'type',
|
||||
label: '奖励类型',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: [
|
||||
{ label: '注册奖励', value: 'register' },
|
||||
{ label: '首单奖励', value: 'first_order' },
|
||||
{ label: '升级奖励', value: 'level_up' },
|
||||
],
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'status',
|
||||
label: '状态',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: [
|
||||
{ label: '待发放', value: 'pending' },
|
||||
{ label: '已发放', value: 'paid' },
|
||||
{ label: '发放失败', value: 'failed' },
|
||||
],
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user