Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a2c5711d43 | |||
| 945f795f81 | |||
| bf7d0b3bc5 | |||
| cb7b22bca3 | |||
| d5a795de77 | |||
| 6a3aa3bf5d | |||
| 4ad3706dfa | |||
| 44426d8812 | |||
| c970931954 | |||
| ba1a7f392b | |||
| 16bb5afaa7 | |||
| 22ef893529 | |||
| 066cee46b0 | |||
| b4017597f9 | |||
| f27fc56fd4 | |||
| 47623d6b1e | |||
| 7426746239 | |||
| ecbd4eefd7 | |||
| 448531f9d3 | |||
| 4facb02777 | |||
| 929cb1966b | |||
| aa2f07de41 | |||
| 5c8cb3bc88 | |||
| bbdcef9d56 | |||
| c4f3235fcf | |||
| f842dcaf14 | |||
| 95338a3ddf | |||
| 4418a95822 | |||
| 62abb284a6 | |||
| 912ab4c321 | |||
| a5d3342d93 | |||
| 502c236b0d |
46
.gitea/workflows/deploy.yml
Normal file
46
.gitea/workflows/deploy.yml
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
name: Auto Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- wuxichen # 当前分支
|
||||||
|
- main
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-deploy:
|
||||||
|
runs-on: self-hosted
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Build and Deploy
|
||||||
|
run: |
|
||||||
|
# 使用持久化工作目录,避免每次重新克隆
|
||||||
|
WORK_DIR="/root/tashow-deploy"
|
||||||
|
|
||||||
|
# 首次运行时克隆代码
|
||||||
|
if [ ! -d "$WORK_DIR" ]; then
|
||||||
|
echo "首次部署,克隆代码..."
|
||||||
|
git clone http://gitea.tashowz.com/tashow/tashow-manager.git $WORK_DIR
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd $WORK_DIR
|
||||||
|
|
||||||
|
# 更新代码到最新版本
|
||||||
|
echo "更新代码..."
|
||||||
|
git fetch origin
|
||||||
|
git checkout ${{ github.ref_name }}
|
||||||
|
git reset --hard origin/${{ github.ref_name }}
|
||||||
|
|
||||||
|
# 安装依赖
|
||||||
|
echo "安装依赖..."
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# 构建项目
|
||||||
|
echo "构建项目..."
|
||||||
|
pnpm build
|
||||||
|
|
||||||
|
# 部署到服务器
|
||||||
|
echo "部署到服务器..."
|
||||||
|
rsync -av --delete dist/ /home/1panel/www/sites/admin.petshy.tashowz.com/index/
|
||||||
|
|
||||||
|
echo "部署成功!"
|
||||||
@@ -19,8 +19,7 @@ const Settings: ProLayoutProps & {
|
|||||||
pwa: true,
|
pwa: true,
|
||||||
logo: '/logo.svg',
|
logo: '/logo.svg',
|
||||||
iconfontUrl: '',
|
iconfontUrl: '',
|
||||||
splitMenus: true,
|
// splitMenus: true,
|
||||||
|
|
||||||
token: {
|
token: {
|
||||||
// 参见ts声明,demo 见文档,通过token 修改样式
|
// 参见ts声明,demo 见文档,通过token 修改样式
|
||||||
// 设置内容区域的边距
|
// 设置内容区域的边距
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ export default {
|
|||||||
// localhost:8000/api/** -> https://preview.pro.ant.design/api/**
|
// localhost:8000/api/** -> https://preview.pro.ant.design/api/**
|
||||||
'/admin-api/': {
|
'/admin-api/': {
|
||||||
// http://192.168.1.231:48080 伟强
|
// http://192.168.1.231:48080 伟强
|
||||||
|
// http://192.168.1.89:48086 子杰
|
||||||
// https://petshy.tashowz.com/
|
// https://petshy.tashowz.com/
|
||||||
target: 'https://petshy.tashowz.com',
|
target: 'http://192.168.1.89:48080',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -46,17 +46,18 @@ export default [
|
|||||||
// ],
|
// ],
|
||||||
|
|
||||||
// },
|
// },
|
||||||
{
|
// {
|
||||||
path: '/ai1',
|
// path: "/ai1",
|
||||||
name: 'AI1',
|
// name: "AI1",
|
||||||
routes: [
|
// // redirect: "tag",
|
||||||
{
|
// routes: [
|
||||||
name: 'ai样本',
|
// {
|
||||||
path: '/ai1/tag',
|
// name: "ai样本",
|
||||||
component: './ai/sample-tag',
|
// path: "tag",
|
||||||
},
|
// component: "./prod/list/components/service-rule/index",
|
||||||
],
|
// },
|
||||||
},
|
// ],
|
||||||
|
// },
|
||||||
// {
|
// {
|
||||||
// path: "/system",
|
// path: "/system",
|
||||||
// name: "系统管理",
|
// name: "系统管理",
|
||||||
|
|||||||
2
public/test.txt
Normal file
2
public/test.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
CI/CD Test - Deployed at 2026-03-02 16:23
|
||||||
|
This file is used to verify automatic deployment is working.
|
||||||
12
src/app.tsx
12
src/app.tsx
@@ -45,6 +45,7 @@ export async function getInitialState(): Promise<{
|
|||||||
throw new Error('No token found');
|
throw new Error('No token found');
|
||||||
}
|
}
|
||||||
const data = await getInfo();
|
const data = await getInfo();
|
||||||
|
console.log(data, 'data');
|
||||||
wsCache.set(CACHE_KEY.USER, data);
|
wsCache.set(CACHE_KEY.USER, data);
|
||||||
wsCache.set(CACHE_KEY.ROLE_ROUTERS, data.menus);
|
wsCache.set(CACHE_KEY.ROLE_ROUTERS, data.menus);
|
||||||
|
|
||||||
@@ -73,7 +74,6 @@ export async function getInitialState(): Promise<{
|
|||||||
await fetchUserInfo();
|
await fetchUserInfo();
|
||||||
}
|
}
|
||||||
const menus = wsCache.get(CACHE_KEY.ROLE_ROUTERS);
|
const menus = wsCache.get(CACHE_KEY.ROLE_ROUTERS);
|
||||||
console.log(111);
|
|
||||||
return {
|
return {
|
||||||
fetchUserInfo,
|
fetchUserInfo,
|
||||||
currentUser,
|
currentUser,
|
||||||
@@ -98,9 +98,9 @@ export const layout: RunTimeLayoutConfig = ({
|
|||||||
<Question key="doc" />,
|
<Question key="doc" />,
|
||||||
<SelectLang key="SelectLang" />,
|
<SelectLang key="SelectLang" />,
|
||||||
],
|
],
|
||||||
|
|
||||||
menu: {
|
menu: {
|
||||||
locale: false,
|
locale: false,
|
||||||
// 关闭国际化-
|
|
||||||
},
|
},
|
||||||
avatarProps: {
|
avatarProps: {
|
||||||
src: initialState?.currentUser?.user.avatar,
|
src: initialState?.currentUser?.user.avatar,
|
||||||
@@ -112,7 +112,7 @@ export const layout: RunTimeLayoutConfig = ({
|
|||||||
waterMarkProps: {
|
waterMarkProps: {
|
||||||
content: initialState?.currentUser?.user.nickname,
|
content: initialState?.currentUser?.user.nickname,
|
||||||
},
|
},
|
||||||
footerRender: () => <Footer />,
|
// footerRender: () => <Footer />,
|
||||||
onPageChange: () => {
|
onPageChange: () => {
|
||||||
const { location } = history;
|
const { location } = history;
|
||||||
// 如果没有登录,重定向到 login
|
// 如果没有登录,重定向到 login
|
||||||
@@ -146,8 +146,8 @@ export const layout: RunTimeLayoutConfig = ({
|
|||||||
useRoutes: true,
|
useRoutes: true,
|
||||||
},
|
},
|
||||||
menuHeaderRender: undefined,
|
menuHeaderRender: undefined,
|
||||||
// 自定义 403 页面
|
// // 自定义 403 页面
|
||||||
unAccessible: <div>unAccessible</div>,
|
// unAccessible: <div>unAccessible</div>,
|
||||||
// 增加一个 loading 的状态
|
// 增加一个 loading 的状态
|
||||||
childrenRender: (children) => {
|
childrenRender: (children) => {
|
||||||
// if (initialState?.loading) return <PageLoading />;
|
// if (initialState?.loading) return <PageLoading />;
|
||||||
@@ -238,7 +238,7 @@ export const request: RequestConfig = {
|
|||||||
// Umi 4 支持异步的 patchClientRoutes
|
// Umi 4 支持异步的 patchClientRoutes
|
||||||
export async function patchClientRoutes({ routes }: any) {
|
export async function patchClientRoutes({ routes }: any) {
|
||||||
const { wsCache } = useCache();
|
const { wsCache } = useCache();
|
||||||
|
console.log('patchClientRoutes', patchClientRoutes);
|
||||||
// 先尝试从缓存获取
|
// 先尝试从缓存获取
|
||||||
let menus = wsCache.get(CACHE_KEY.ROLE_ROUTERS);
|
let menus = wsCache.get(CACHE_KEY.ROLE_ROUTERS);
|
||||||
|
|
||||||
|
|||||||
60
src/components/EditableProTable/index.tsx
Normal file
60
src/components/EditableProTable/index.tsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import {
|
||||||
|
ActionType,
|
||||||
|
EditableFormInstance,
|
||||||
|
EditableProTable,
|
||||||
|
type EditableProTableProps,
|
||||||
|
type ParamsType,
|
||||||
|
ProColumns,
|
||||||
|
} from '@ant-design/pro-components';
|
||||||
|
import { Form, Table } from 'antd';
|
||||||
|
import React, { forwardRef, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
interface EditableProTableComProps<
|
||||||
|
T extends Record<string, any>,
|
||||||
|
U extends ParamsType = ParamsType,
|
||||||
|
> extends Omit<
|
||||||
|
EditableProTableProps<T, U>,
|
||||||
|
'editableFormRef' | 'recordCreatorProps'
|
||||||
|
> {
|
||||||
|
editableFormRef?: React.Ref<any>;
|
||||||
|
recordCreatorProps?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditableProTableCom = forwardRef(
|
||||||
|
<T extends Record<string, any>>(
|
||||||
|
props: EditableProTableComProps<T>,
|
||||||
|
ref: React.Ref<any>,
|
||||||
|
) => {
|
||||||
|
// 提取不应该直接传递给 EditableProTable 的属性
|
||||||
|
const { value, columns, ...restProps } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EditableProTable
|
||||||
|
value={value}
|
||||||
|
columns={columns}
|
||||||
|
actionRef={ref}
|
||||||
|
recordCreatorProps={false}
|
||||||
|
{...restProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// const EditableProTableCom = <T extends Record<string, any>>(
|
||||||
|
// props: EditableProTableComProps<T>
|
||||||
|
// ) => {
|
||||||
|
// // 提取不应该直接传递给 EditableProTable 的属性
|
||||||
|
// const { value, columns, ...restProps } = props;
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <EditableProTable
|
||||||
|
// value={value}
|
||||||
|
// columns={columns}
|
||||||
|
// recordCreatorProps={false}
|
||||||
|
// {...restProps}
|
||||||
|
// />
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
export default EditableProTableCom as <T extends Record<string, any>>(
|
||||||
|
props: EditableProTableComProps<T> & { ref?: React.Ref<any> },
|
||||||
|
) => React.ReactElement;
|
||||||
@@ -35,7 +35,6 @@ function EnhancedProTable<T extends BaseRecord, U extends ParamsType = any>(
|
|||||||
type={action.type}
|
type={action.type}
|
||||||
danger={action.danger}
|
danger={action.danger}
|
||||||
disabled={action.disabled}
|
disabled={action.disabled}
|
||||||
// icon={action.icon ?? <PlusOutlined />}
|
|
||||||
onClick={() => action.onClick()}
|
onClick={() => action.onClick()}
|
||||||
>
|
>
|
||||||
{action.label}
|
{action.label}
|
||||||
@@ -55,7 +54,6 @@ function EnhancedProTable<T extends BaseRecord, U extends ParamsType = any>(
|
|||||||
toolBarRender={toolBarRender}
|
toolBarRender={toolBarRender}
|
||||||
manualRequest={false}
|
manualRequest={false}
|
||||||
showSorterTooltip
|
showSorterTooltip
|
||||||
// scroll={{ x: "max-content" }}
|
|
||||||
scroll={scroll ? scroll : { x: 1200 }}
|
scroll={scroll ? scroll : { x: 1200 }}
|
||||||
components={components}
|
components={components}
|
||||||
search={
|
search={
|
||||||
@@ -83,7 +81,8 @@ function EnhancedProTable<T extends BaseRecord, U extends ParamsType = any>(
|
|||||||
? {
|
? {
|
||||||
showSizeChanger: true,
|
showSizeChanger: true,
|
||||||
showQuickJumper: true,
|
showQuickJumper: true,
|
||||||
pageSize: 10,
|
// pageSize: 10,
|
||||||
|
defaultPageSize: 10,
|
||||||
showTotal: formatPaginationTotal,
|
showTotal: formatPaginationTotal,
|
||||||
}
|
}
|
||||||
: false
|
: false
|
||||||
|
|||||||
@@ -32,10 +32,12 @@ const GroupTagCore: React.FC<GroupTagCoreProps> = (props) => {
|
|||||||
const [type, setType] = useState<'group' | 'tag'>('group');
|
const [type, setType] = useState<'group' | 'tag'>('group');
|
||||||
const [visible, setVisible] = useState<boolean>(false);
|
const [visible, setVisible] = useState<boolean>(false);
|
||||||
const [name, setName] = useState<string>('');
|
const [name, setName] = useState<string>('');
|
||||||
|
const [tagName, setTagName] = useState<string>('');
|
||||||
const [total, setTotal] = useState<number>(0);
|
const [total, setTotal] = useState<number>(0);
|
||||||
const [currentId, setCurrentId] = useState<number>();
|
const [currentId, setCurrentId] = useState<number>();
|
||||||
const [tagsModalValue, setTagsModalValue] = useState<{
|
const [tagsModalValue, setTagsModalValue] = useState<{
|
||||||
tagName?: string;
|
tagName?: string;
|
||||||
|
groupName?: string;
|
||||||
groupIds?: number[];
|
groupIds?: number[];
|
||||||
id?: number;
|
id?: number;
|
||||||
}>({});
|
}>({});
|
||||||
@@ -55,7 +57,11 @@ const GroupTagCore: React.FC<GroupTagCoreProps> = (props) => {
|
|||||||
const fetchTagsApi = useCallback(async () => {
|
const fetchTagsApi = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setLoadingTags(true);
|
setLoadingTags(true);
|
||||||
const res = await tagsApi.get({ groupId: currentId, pageNo: 1 });
|
const res = await tagsApi.get({
|
||||||
|
groupId: currentId,
|
||||||
|
pageNo: 1,
|
||||||
|
tagName: tagName,
|
||||||
|
});
|
||||||
const newGroup = { ...currentGroup, tags: res.list };
|
const newGroup = { ...currentGroup, tags: res.list };
|
||||||
const newData = groups.map((g) => (g.id === currentId ? newGroup : g));
|
const newData = groups.map((g) => (g.id === currentId ? newGroup : g));
|
||||||
setGroups(newData as GroupItem[]);
|
setGroups(newData as GroupItem[]);
|
||||||
@@ -86,7 +92,7 @@ const GroupTagCore: React.FC<GroupTagCoreProps> = (props) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (modalType === 'edit' && visible) {
|
if (modalType === 'edit' && visible) {
|
||||||
if (type === 'group' && currentGroup) {
|
if (type === 'group' && currentGroup) {
|
||||||
setName(currentGroup.groupName);
|
setName(currentGroup.groupName || '');
|
||||||
} else if (type === 'tag' && tagsModalValue) {
|
} else if (type === 'tag' && tagsModalValue) {
|
||||||
setName(tagsModalValue.tagName || '');
|
setName(tagsModalValue.tagName || '');
|
||||||
}
|
}
|
||||||
@@ -95,12 +101,16 @@ const GroupTagCore: React.FC<GroupTagCoreProps> = (props) => {
|
|||||||
setName('');
|
setName('');
|
||||||
}
|
}
|
||||||
}, [type, modalType, currentGroup, tagsModalValue, visible]);
|
}, [type, modalType, currentGroup, tagsModalValue, visible]);
|
||||||
const handleGroup = (type: 'add' | 'edit' | 'delete') => {
|
const handleGroup = (type: 'add' | 'edit' | 'delete', group?: GroupItem) => {
|
||||||
setType('group');
|
setType('group');
|
||||||
setModalType(type);
|
setModalType(type);
|
||||||
if (type === 'add' || type === 'edit') {
|
if (type === 'add' || type === 'edit') {
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
}
|
}
|
||||||
|
if (type === 'edit') {
|
||||||
|
console.log('group', group);
|
||||||
|
setTagsModalValue({ groupName: group?.groupName, id: group?.id });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTag = (type: 'add' | 'edit' | 'delete', tag?: TagItem) => {
|
const handleTag = (type: 'add' | 'edit' | 'delete', tag?: TagItem) => {
|
||||||
@@ -115,9 +125,8 @@ const GroupTagCore: React.FC<GroupTagCoreProps> = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleAdd = async (value: {
|
const handleAdd = async (value: {
|
||||||
groupName?: string;
|
|
||||||
groupIds?: number[];
|
groupIds?: number[];
|
||||||
tagName?: string;
|
groupName?: string;
|
||||||
}) => {
|
}) => {
|
||||||
if (type === 'group') {
|
if (type === 'group') {
|
||||||
const id = await groupsApi.create({ groupName: value.groupName });
|
const id = await groupsApi.create({ groupName: value.groupName });
|
||||||
@@ -132,15 +141,18 @@ const GroupTagCore: React.FC<GroupTagCoreProps> = (props) => {
|
|||||||
|
|
||||||
const handleEdit = async (value: {
|
const handleEdit = async (value: {
|
||||||
tagName?: string;
|
tagName?: string;
|
||||||
|
groupName?: string;
|
||||||
groupIds?: number[];
|
groupIds?: number[];
|
||||||
}) => {
|
}) => {
|
||||||
if (type === 'group') {
|
if (type === 'group') {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
await groupsApi.update({
|
||||||
await groupsApi.update({ id: currentGroup?.id, groupName: name });
|
id: currentGroup?.id,
|
||||||
|
groupName: value.groupName,
|
||||||
|
});
|
||||||
const newData = groups.map((item) => {
|
const newData = groups.map((item) => {
|
||||||
if (item.id === currentGroup?.id) {
|
if (item.id === currentGroup?.id) {
|
||||||
const itemData = { ...item, groupName: name };
|
const itemData = { ...item, groupName: value.groupName };
|
||||||
setCurrentGroup(itemData);
|
setCurrentGroup(itemData);
|
||||||
return itemData;
|
return itemData;
|
||||||
} else {
|
} else {
|
||||||
@@ -148,8 +160,8 @@ const GroupTagCore: React.FC<GroupTagCoreProps> = (props) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
setGroups(newData);
|
setGroups(newData);
|
||||||
} finally {
|
} catch (error) {
|
||||||
setLoading(false);
|
message.success('修改失败');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await tagsApi.update({
|
await tagsApi.update({
|
||||||
@@ -157,19 +169,10 @@ const GroupTagCore: React.FC<GroupTagCoreProps> = (props) => {
|
|||||||
id: tagsModalValue?.id,
|
id: tagsModalValue?.id,
|
||||||
...value,
|
...value,
|
||||||
});
|
});
|
||||||
message.success('修改成功');
|
fetchTagsApi();
|
||||||
const newTag = currentGroup?.tags?.map((tag) => {
|
|
||||||
if (tag.id === tagsModalValue?.id) {
|
|
||||||
return { ...tag, tagName: value.tagName };
|
|
||||||
} else {
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const newGroup = { ...currentGroup, tags: newTag };
|
|
||||||
setCurrentGroup(newGroup as GroupItem);
|
|
||||||
}
|
}
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
|
message.success('修改成功');
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async (type: 'group' | 'tag', id: number) => {
|
const handleDelete = async (type: 'group' | 'tag', id: number) => {
|
||||||
@@ -182,12 +185,13 @@ const GroupTagCore: React.FC<GroupTagCoreProps> = (props) => {
|
|||||||
setCurrentGroup(newGroups[0]);
|
setCurrentGroup(newGroups[0]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tagsApi.delete(id);
|
await tagsApi.delete(id);
|
||||||
|
fetchTagsApi();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleConfirm = useCallback(
|
const handleConfirm = useCallback(
|
||||||
async (value: { tagName?: string; groupIds?: number[] }) => {
|
async (value: { groupIds?: number[]; name?: string }) => {
|
||||||
if (modalType === 'add') {
|
if (modalType === 'add') {
|
||||||
await handleAdd(value);
|
await handleAdd(value);
|
||||||
}
|
}
|
||||||
@@ -201,6 +205,7 @@ const GroupTagCore: React.FC<GroupTagCoreProps> = (props) => {
|
|||||||
|
|
||||||
const handleCancle = useCallback(() => {
|
const handleCancle = useCallback(() => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
|
setTagsModalValue({});
|
||||||
}, [visible]);
|
}, [visible]);
|
||||||
|
|
||||||
const onTagItemChange = (tag: TagItem, e: CheckboxChangeEvent) => {
|
const onTagItemChange = (tag: TagItem, e: CheckboxChangeEvent) => {
|
||||||
@@ -229,6 +234,7 @@ const GroupTagCore: React.FC<GroupTagCoreProps> = (props) => {
|
|||||||
|
|
||||||
const onSearchTags = async (e: React.KeyboardEvent<HTMLInputElement>) => {
|
const onSearchTags = async (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
const searchValue = (e.target as HTMLInputElement).value;
|
const searchValue = (e.target as HTMLInputElement).value;
|
||||||
|
setTagName(searchValue);
|
||||||
const res = await tagsApi.get({
|
const res = await tagsApi.get({
|
||||||
groupId: currentId,
|
groupId: currentId,
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
@@ -238,11 +244,15 @@ const GroupTagCore: React.FC<GroupTagCoreProps> = (props) => {
|
|||||||
...currentGroup,
|
...currentGroup,
|
||||||
tags: res.list,
|
tags: res.list,
|
||||||
};
|
};
|
||||||
|
setTotal(res.total);
|
||||||
document.getElementById('scrollableDiv')?.scrollTo(0, 0);
|
document.getElementById('scrollableDiv')?.scrollTo(0, 0);
|
||||||
setCurrentGroup(newGroup as GroupItem);
|
setCurrentGroup(newGroup as GroupItem);
|
||||||
setPageNo(1);
|
setPageNo(1);
|
||||||
};
|
};
|
||||||
|
const onSearchTagsClear = async () => {
|
||||||
|
setTagName('');
|
||||||
|
fetchTagsApi();
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div className={`group-tag-core`}>
|
<div className={`group-tag-core`}>
|
||||||
<div className="search-wrapper">
|
<div className="search-wrapper">
|
||||||
@@ -251,6 +261,7 @@ const GroupTagCore: React.FC<GroupTagCoreProps> = (props) => {
|
|||||||
placeholder="搜索"
|
placeholder="搜索"
|
||||||
onPressEnter={onSearchTags}
|
onPressEnter={onSearchTags}
|
||||||
allowClear
|
allowClear
|
||||||
|
onClear={onSearchTagsClear}
|
||||||
disabled={isInEditMode}
|
disabled={isInEditMode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -284,7 +295,7 @@ const GroupTagCore: React.FC<GroupTagCoreProps> = (props) => {
|
|||||||
groups,
|
groups,
|
||||||
currentId,
|
currentId,
|
||||||
onGroupClick,
|
onGroupClick,
|
||||||
onEdit: () => handleGroup('edit'),
|
onEdit: (groud) => handleGroup('edit', groud),
|
||||||
onDelete: (id) => handleDelete('group', id),
|
onDelete: (id) => handleDelete('group', id),
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,19 +1,23 @@
|
|||||||
import { Form, Input, Modal, Select } from 'antd';
|
import { Form, Input, Modal, Select } from 'antd';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { getGroupListByYagId } from '@/services/ai/sample';
|
||||||
|
|
||||||
interface TagsModalProps {
|
interface TagsModalProps {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
type: 'group' | 'tag';
|
type: 'group' | 'tag';
|
||||||
modalType?: 'add' | 'edit' | 'delete';
|
modalType?: 'add' | 'edit' | 'delete';
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
groups: { id: number; groupName: string }[];
|
groups: { id?: number; groupName?: string }[];
|
||||||
value: {
|
value: {
|
||||||
id?: number;
|
id?: number;
|
||||||
tagName?: string;
|
tagName?: string;
|
||||||
groupIds?: number[];
|
groupIds?: number[];
|
||||||
groupName?: string;
|
groupName?: string;
|
||||||
};
|
};
|
||||||
onConfirm: (values: { tagName: string; groupIds?: number[] }) => void;
|
onConfirm: (
|
||||||
|
values: { groupIds?: number[]; name: string },
|
||||||
|
type: 'group' | 'tag',
|
||||||
|
) => void;
|
||||||
}
|
}
|
||||||
const TagsModal = (props: TagsModalProps) => {
|
const TagsModal = (props: TagsModalProps) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
@@ -21,8 +25,19 @@ const TagsModal = (props: TagsModalProps) => {
|
|||||||
const { value, visible, type, modalType, groups, onCancel, onConfirm } =
|
const { value, visible, type, modalType, groups, onCancel, onConfirm } =
|
||||||
props;
|
props;
|
||||||
|
|
||||||
|
const onGroupListByYagId = async (id: number) => {
|
||||||
|
const groupItem: { id?: number; groupName?: string }[] =
|
||||||
|
await getGroupListByYagId(id);
|
||||||
|
form.setFieldsValue({
|
||||||
|
groupIds: groupItem.map((item) => item.id),
|
||||||
|
});
|
||||||
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
form.setFieldsValue(value);
|
form.setFieldsValue(value);
|
||||||
|
console.log('value', value);
|
||||||
|
if (visible && type === 'tag' && value.id) {
|
||||||
|
onGroupListByYagId(value.id);
|
||||||
|
}
|
||||||
return () => {
|
return () => {
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -32,7 +47,7 @@ const TagsModal = (props: TagsModalProps) => {
|
|||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const values = await form.validateFields();
|
const values = await form.validateFields();
|
||||||
await onConfirm(values);
|
await onConfirm(values, type);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -58,7 +73,7 @@ const TagsModal = (props: TagsModalProps) => {
|
|||||||
wrapperCol={{ span: 20 }}
|
wrapperCol={{ span: 20 }}
|
||||||
style={{ maxWidth: 600, marginTop: 30 }}
|
style={{ maxWidth: 600, marginTop: 30 }}
|
||||||
>
|
>
|
||||||
{type === 'tag' && modalType === 'add' && (
|
{type === 'tag' && (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="分组"
|
label="分组"
|
||||||
name="groupIds"
|
name="groupIds"
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ interface GroupsProps {
|
|||||||
total?: number;
|
total?: number;
|
||||||
loadMoreData?: () => void;
|
loadMoreData?: () => void;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
onEdit?: () => void;
|
onEdit?: (group: GroupItem) => void;
|
||||||
onDelete?: (id: number) => void;
|
onDelete?: (id: number) => void;
|
||||||
}
|
}
|
||||||
export const renderGroups = (data: GroupsProps) => {
|
export const renderGroups = (data: GroupsProps) => {
|
||||||
@@ -67,7 +67,7 @@ export const renderGroups = (data: GroupsProps) => {
|
|||||||
icon={<EditFilled />}
|
icon={<EditFilled />}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onEdit?.();
|
onEdit?.(item);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
@@ -116,7 +116,7 @@ export const renderTags = (data: GroupTagProps) => {
|
|||||||
<Empty description="暂无分组" image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
<Empty description="暂无分组" image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
console.log(list.length);
|
console.log(list, total);
|
||||||
return (
|
return (
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
dataLength={list.length}
|
dataLength={list.length}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export interface TagItem {
|
|||||||
|
|
||||||
export interface GroupItem {
|
export interface GroupItem {
|
||||||
id: number;
|
id: number;
|
||||||
groupName: string;
|
groupName?: string;
|
||||||
createTime?: string;
|
createTime?: string;
|
||||||
tags?: TagItem[];
|
tags?: TagItem[];
|
||||||
}
|
}
|
||||||
|
|||||||
36
src/components/ModalCom/CommonModal.tsx
Normal file
36
src/components/ModalCom/CommonModal.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { Modal, type ModalProps } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const CommonModal: React.FC<ModalProps> = (props) => {
|
||||||
|
const { children } = props;
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
{...props}
|
||||||
|
width={'70vw'}
|
||||||
|
styles={{
|
||||||
|
body: {
|
||||||
|
height: '65vh',
|
||||||
|
overflowY: 'auto',
|
||||||
|
padding: '0 16px',
|
||||||
|
minWidth: '100%',
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
background: '#f5f5f5',
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
padding: '16px 24px',
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
padding: '16px 24px',
|
||||||
|
background: '#ffff',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(CommonModal);
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
.uploader-card {
|
.uploader-card {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
padding: 10px;
|
||||||
:global {
|
:global {
|
||||||
.ant-upload-drag {
|
.ant-upload-drag {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ const AudioUploader: React.FC<AudioUploaderProps> = ({
|
|||||||
onProgress?.({ percent: 10 });
|
onProgress?.({ percent: 10 });
|
||||||
// 调用后端接口
|
// 调用后端接口
|
||||||
const result = await uploadToServer(file as File);
|
const result = await uploadToServer(file as File);
|
||||||
console.log(result, 'res');
|
// console.log(result, 'res');
|
||||||
onProgress?.({ percent: 100 });
|
onProgress?.({ percent: 100 });
|
||||||
if (result) {
|
if (result) {
|
||||||
// 构造返回数据
|
// 构造返回数据
|
||||||
@@ -92,6 +92,7 @@ const AudioUploader: React.FC<AudioUploaderProps> = ({
|
|||||||
response: result,
|
response: result,
|
||||||
};
|
};
|
||||||
onSuccess?.(responseData);
|
onSuccess?.(responseData);
|
||||||
|
onChange?.(responseData);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(result.message || '上传失败');
|
throw new Error(result.message || '上传失败');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,41 +120,45 @@ const UploadImages: React.FC<{
|
|||||||
|
|
||||||
message.success('上传成功');
|
message.success('上传成功');
|
||||||
} else {
|
} else {
|
||||||
|
setUploading(false);
|
||||||
throw new Error('上传失败');
|
throw new Error('上传失败');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Upload error:', error);
|
setUploading(false);
|
||||||
onError?.(error as Error);
|
|
||||||
message.error(`上传失败: ${(error as Error).message}`);
|
message.error(`上传失败: ${(error as Error).message}`);
|
||||||
} finally {
|
} finally {
|
||||||
|
console.log('finally');
|
||||||
setUploading(false);
|
setUploading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<Spin spinning={uploading}>
|
<Spin spinning={uploading}>
|
||||||
<Upload
|
{uploading}
|
||||||
listType="picture-card"
|
<>
|
||||||
fileList={fileList}
|
<Upload
|
||||||
onPreview={handlePreview}
|
listType="picture-card"
|
||||||
onRemove={handleRemove}
|
fileList={fileList}
|
||||||
beforeUpload={beforeUpload}
|
onPreview={handlePreview}
|
||||||
customRequest={handleLoadImage}
|
onRemove={handleRemove}
|
||||||
multiple={multiple}
|
beforeUpload={beforeUpload}
|
||||||
accept={accept}
|
customRequest={handleLoadImage}
|
||||||
>
|
multiple={multiple}
|
||||||
{fileList.length >= maxCount ? null : uploadButton}
|
accept={accept}
|
||||||
</Upload>
|
>
|
||||||
{previewImage && (
|
{fileList.length >= maxCount ? null : uploadButton}
|
||||||
<Image
|
</Upload>
|
||||||
wrapperStyle={{ display: 'none' }}
|
{previewImage && (
|
||||||
preview={{
|
<Image
|
||||||
visible: previewOpen,
|
wrapperStyle={{ display: 'none' }}
|
||||||
onVisibleChange: (visible) => setPreviewOpen(visible),
|
preview={{
|
||||||
afterOpenChange: (visible) => !visible && setPreviewImage(''),
|
visible: previewOpen,
|
||||||
}}
|
onVisibleChange: (visible) => setPreviewOpen(visible),
|
||||||
src={previewImage}
|
afterOpenChange: (visible) => !visible && setPreviewImage(''),
|
||||||
/>
|
}}
|
||||||
)}
|
src={previewImage}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
</Spin>
|
</Spin>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { BadgeProps } from 'antd';
|
import type { BadgeProps } from 'antd';
|
||||||
|
|
||||||
export enum OrderStatus {
|
export enum OrderStatus {
|
||||||
All = 1,
|
All = 0,
|
||||||
PendingPayment = 10,
|
PendingPayment = 10,
|
||||||
PendingConfirmation = 20,
|
PendingConfirmation = 20,
|
||||||
PendingService = 30,
|
PendingService = 30,
|
||||||
@@ -53,3 +53,21 @@ export const mapOrderStatusToBadgeStatus = (
|
|||||||
|
|
||||||
return statusMap[statusColor] || 'default';
|
return statusMap[statusColor] || 'default';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum SalesStatus {
|
||||||
|
All = 0,
|
||||||
|
Apply = 10,
|
||||||
|
SellerAgree = 20,
|
||||||
|
BuyerDelivery = 30,
|
||||||
|
WaitRefund = 40,
|
||||||
|
Completed = 50,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SalesStatusLableMap: Record<SalesStatus, string> = {
|
||||||
|
[SalesStatus.All]: '所有售后单',
|
||||||
|
[SalesStatus.Apply]: '等待审核',
|
||||||
|
[SalesStatus.SellerAgree]: '审核通过',
|
||||||
|
[SalesStatus.BuyerDelivery]: '已完成',
|
||||||
|
[SalesStatus.WaitRefund]: '已取消',
|
||||||
|
[SalesStatus.Completed]: '已拒绝',
|
||||||
|
};
|
||||||
|
|||||||
@@ -75,7 +75,9 @@ ul,
|
|||||||
ol {
|
ol {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
.ant-statistic {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.ant-table {
|
.ant-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -93,6 +95,11 @@ ol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-table-row {
|
||||||
|
.ant-form-item {
|
||||||
|
margin-block: -16px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
.page-container {
|
.page-container {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ const Welcome: React.FC = () => {
|
|||||||
百业到家 是一个整合了 umi,Ant Design 和 ProComponents
|
百业到家 是一个整合了 umi,Ant Design 和 ProComponents
|
||||||
的脚手架方案。致力于在设计规范和基础组件的基础上,继续向上构建,提炼出典型模板/业务组件/配套设计资源,进一步提升企业级中后台产品设计研发过程中的『用户』和『设计者』的体验。
|
的脚手架方案。致力于在设计规范和基础组件的基础上,继续向上构建,提炼出典型模板/业务组件/配套设计资源,进一步提升企业级中后台产品设计研发过程中的『用户』和『设计者』的体验。
|
||||||
</p>
|
</p>
|
||||||
<div
|
{/* <div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
@@ -156,7 +156,7 @@ const Welcome: React.FC = () => {
|
|||||||
href="https://procomponents.ant.design"
|
href="https://procomponents.ant.design"
|
||||||
desc="ProComponents 是一个基于 Ant Design 做了更高抽象的模板组件,以 一个组件就是一个页面为开发理念,为中后台开发带来更好的体验。"
|
desc="ProComponents 是一个基于 Ant Design 做了更高抽象的模板组件,以 一个组件就是一个页面为开发理念,为中后台开发带来更好的体验。"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div> */}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
|
|||||||
130
src/pages/ai/model/config.tsx
Normal file
130
src/pages/ai/model/config.tsx
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import type {
|
||||||
|
ProColumns,
|
||||||
|
ProCoreActionType,
|
||||||
|
ProFormColumnsType,
|
||||||
|
} from '@ant-design/pro-components';
|
||||||
|
import { Modal, message, Switch } from 'antd';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import {
|
||||||
|
type AiModelRespVO,
|
||||||
|
updateModel,
|
||||||
|
updateModelStatus,
|
||||||
|
} from '@/services/ai/model';
|
||||||
|
|
||||||
|
export const baseDeptColumns: ProColumns<AiModelRespVO>[] = [
|
||||||
|
{
|
||||||
|
title: '模型名称',
|
||||||
|
dataIndex: 'modelName',
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '版本号',
|
||||||
|
dataIndex: 'version',
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '负载',
|
||||||
|
dataIndex: 'loadPercentage',
|
||||||
|
hideInSearch: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
valueType: 'switch',
|
||||||
|
hideInSearch: true,
|
||||||
|
render: (
|
||||||
|
_,
|
||||||
|
record: AiModelRespVO,
|
||||||
|
_index: number,
|
||||||
|
action: ProCoreActionType | undefined,
|
||||||
|
) => (
|
||||||
|
<Switch
|
||||||
|
checked={record.status === 1}
|
||||||
|
checkedChildren="启用"
|
||||||
|
unCheckedChildren="禁用"
|
||||||
|
onChange={(checked) => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '确认操作',
|
||||||
|
content: `确认要"${checked ? '启用' : '禁用'}${
|
||||||
|
record.modelName
|
||||||
|
}"类目吗?`,
|
||||||
|
onOk: async () => {
|
||||||
|
console.log(checked);
|
||||||
|
await updateModel({
|
||||||
|
...record,
|
||||||
|
status: checked ? 1 : 0,
|
||||||
|
});
|
||||||
|
message.success('修改成功');
|
||||||
|
action?.reload();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
dataIndex: 'createTime',
|
||||||
|
valueType: 'dateRange',
|
||||||
|
hideInSearch: true,
|
||||||
|
render: (_, record: AiModelRespVO) =>
|
||||||
|
dayjs(record.createTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const formColumns = (_type: string): ProFormColumnsType[] => [
|
||||||
|
{
|
||||||
|
title: '模型名称',
|
||||||
|
dataIndex: 'modelName',
|
||||||
|
formItemProps: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入模型名称',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '版本号',
|
||||||
|
dataIndex: 'version',
|
||||||
|
valueType: 'text',
|
||||||
|
formItemProps: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入部门名称',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '负载',
|
||||||
|
dataIndex: 'loadPercentage',
|
||||||
|
valueType: 'digit',
|
||||||
|
fieldProps: {
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
|
formItemProps: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入显示顺序',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
valueType: 'switch',
|
||||||
|
fieldProps: {
|
||||||
|
checkedChildren: '禁用',
|
||||||
|
unCheckedChildren: '启用',
|
||||||
|
defaultChecked: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
131
src/pages/ai/model/index.tsx
Normal file
131
src/pages/ai/model/index.tsx
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import { PlusOutlined } from '@ant-design/icons';
|
||||||
|
import type { ActionType, ProColumns } from '@ant-design/pro-components';
|
||||||
|
import { message, Popconfirm } from 'antd';
|
||||||
|
import React, { useCallback, useRef, useState } from 'react';
|
||||||
|
import ConfigurableDrawerForm, {
|
||||||
|
type ConfigurableDrawerFormRef,
|
||||||
|
} from '@/components/DrawerForm';
|
||||||
|
import EnhancedProTable from '@/components/EnhancedProTable';
|
||||||
|
import type { ToolbarAction } from '@/components/EnhancedProTable/types';
|
||||||
|
import { formStatusType } from '@/constants';
|
||||||
|
import {
|
||||||
|
type AiModelRespVO,
|
||||||
|
createModel,
|
||||||
|
delModel,
|
||||||
|
getModelList,
|
||||||
|
updateModel,
|
||||||
|
updateModelStatus,
|
||||||
|
} from '@/services/ai/model';
|
||||||
|
import { baseDeptColumns, formColumns } from './config';
|
||||||
|
|
||||||
|
const ModelPage = () => {
|
||||||
|
const configurableDrawerRef = useRef<ConfigurableDrawerFormRef>(null);
|
||||||
|
const tableRef = useRef<ActionType>(null);
|
||||||
|
|
||||||
|
const [type, setType] = useState<'create' | 'update'>('create');
|
||||||
|
const [id, setId] = useState<number>(0);
|
||||||
|
|
||||||
|
const handleEdit = (record: AiModelRespVO) => {
|
||||||
|
setType('update');
|
||||||
|
setId(record.id as number);
|
||||||
|
configurableDrawerRef.current?.open(record);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onFetch = async (params: { pageSize?: number; current?: number }) => {
|
||||||
|
const data = await getModelList({ ...params, pageNo: params.current });
|
||||||
|
return {
|
||||||
|
data: data.list,
|
||||||
|
success: true,
|
||||||
|
total: data.total,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAdd = () => {
|
||||||
|
setType('create');
|
||||||
|
configurableDrawerRef.current?.open({ status: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = useCallback(
|
||||||
|
async (values: AiModelRespVO) => {
|
||||||
|
if (type === 'create') {
|
||||||
|
console.log('values', values);
|
||||||
|
await createModel({ ...values, status: values.status ? 1 : 0 });
|
||||||
|
} else {
|
||||||
|
await updateModel({
|
||||||
|
...values,
|
||||||
|
status: values.status ? 1 : 0,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
tableRef.current?.reload();
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
[type, id],
|
||||||
|
);
|
||||||
|
|
||||||
|
const toolbarActions: ToolbarAction[] = [
|
||||||
|
{
|
||||||
|
key: 'add',
|
||||||
|
label: '新建',
|
||||||
|
type: 'primary',
|
||||||
|
icon: <PlusOutlined />,
|
||||||
|
onClick: handleAdd,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const actionColumns: ProColumns<AiModelRespVO> = {
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'option',
|
||||||
|
valueType: 'option',
|
||||||
|
fixed: 'right',
|
||||||
|
width: 120,
|
||||||
|
render: (
|
||||||
|
_text: React.ReactNode,
|
||||||
|
record: AiModelRespVO,
|
||||||
|
_: any,
|
||||||
|
action: any,
|
||||||
|
) => [
|
||||||
|
<a key="editable" onClick={() => handleEdit(record)}>
|
||||||
|
编辑
|
||||||
|
</a>,
|
||||||
|
<Popconfirm
|
||||||
|
title="是否删除?"
|
||||||
|
key="delete"
|
||||||
|
onConfirm={async () => {
|
||||||
|
await delModel(record.id as number);
|
||||||
|
message.success('删除成功');
|
||||||
|
action?.reload();
|
||||||
|
}}
|
||||||
|
okText="是"
|
||||||
|
cancelText="否"
|
||||||
|
>
|
||||||
|
<a>删除</a>
|
||||||
|
</Popconfirm>,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [...baseDeptColumns, actionColumns];
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<EnhancedProTable<AiModelRespVO>
|
||||||
|
ref={tableRef}
|
||||||
|
columns={columns}
|
||||||
|
request={onFetch}
|
||||||
|
toolbarActions={toolbarActions}
|
||||||
|
headerTitle="模型管理"
|
||||||
|
showIndex={false}
|
||||||
|
showSelection={false}
|
||||||
|
search={false}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ConfigurableDrawerForm
|
||||||
|
ref={configurableDrawerRef}
|
||||||
|
title={formStatusType[type]}
|
||||||
|
columns={formColumns(type)}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModelPage;
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { ProColumns } from '@ant-design/pro-components';
|
import type { ProColumns } from '@ant-design/pro-components';
|
||||||
|
import { Tag } from 'antd';
|
||||||
import GroupTagSelect from '@/components/GroupTag/GroupTagSelect';
|
import GroupTagSelect from '@/components/GroupTag/GroupTagSelect';
|
||||||
import {
|
import {
|
||||||
type AiSampleRespVO,
|
type AiSampleRespVO,
|
||||||
@@ -15,13 +16,60 @@ export const baseTenantColumns: ProColumns<AiSampleRespVO>[] = [
|
|||||||
{
|
{
|
||||||
title: '样本名称',
|
title: '样本名称',
|
||||||
dataIndex: 'sampleName',
|
dataIndex: 'sampleName',
|
||||||
// width: 500,
|
width: 200,
|
||||||
|
ellipsis: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '枚举标签',
|
||||||
|
dataIndex: 'enumTags',
|
||||||
|
width: 120,
|
||||||
|
ellipsis: true,
|
||||||
|
render: (_, record) => {
|
||||||
|
return (
|
||||||
|
record.enumTags?.map((tag) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{tag.enumValue}:<Tag key={tag.id}>{tag.tagName}</Tag>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}) || '-'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: '个性标签',
|
||||||
|
dataIndex: 'tags',
|
||||||
|
width: 200,
|
||||||
|
hideInSearch: true,
|
||||||
|
render: (_, record) => {
|
||||||
|
return record.tags?.map((tag) => {
|
||||||
|
return (
|
||||||
|
<Tag key={tag.id} style={{ marginBottom: 5, marginTop: 5 }}>
|
||||||
|
{tag.tagName}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '关联模型',
|
||||||
|
width: 100,
|
||||||
|
dataIndex: 'relatedModels',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '文件格式',
|
||||||
|
width: 100,
|
||||||
|
dataIndex: 'sampleMineType',
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: '注释',
|
title: '注释',
|
||||||
|
width: 100,
|
||||||
dataIndex: 'remark',
|
dataIndex: 'remark',
|
||||||
hideInSearch: true,
|
hideInSearch: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: '标签',
|
title: '标签',
|
||||||
hideInTable: true,
|
hideInTable: true,
|
||||||
@@ -52,16 +100,11 @@ export const baseTenantColumns: ProColumns<AiSampleRespVO>[] = [
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
editable
|
editable
|
||||||
placeholder="请选择技术栈"
|
placeholder="请选择标签"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: '样本格式',
|
|
||||||
hideInTable: true,
|
|
||||||
dataIndex: 'sample_mine_type',
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// export const formColumns = (data: {
|
// export const formColumns = (data: {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ProForm, ProFormGroup, ProFormText } from '@ant-design/pro-components';
|
import { ProForm, ProFormGroup, ProFormText } from '@ant-design/pro-components';
|
||||||
import { Button, message, Space, Tag } from 'antd';
|
import { Button, Empty, Input, message, Space, Tag } from 'antd';
|
||||||
|
import FormItem from 'antd/es/form/FormItem';
|
||||||
import type { RowSelectionType } from 'antd/es/table/interface';
|
import type { RowSelectionType } from 'antd/es/table/interface';
|
||||||
import type { FormInstance } from 'antd/lib';
|
import type { FormInstance } from 'antd/lib';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
@@ -13,6 +14,7 @@ import React, {
|
|||||||
import GroupTagModal from '@/components/GroupTag/GroupTagModal';
|
import GroupTagModal from '@/components/GroupTag/GroupTagModal';
|
||||||
import type { TagItem } from '@/components/GroupTag/types';
|
import type { TagItem } from '@/components/GroupTag/types';
|
||||||
import type { FileItem } from '@/components/RenameRule';
|
import type { FileItem } from '@/components/RenameRule';
|
||||||
|
import TagEditor from '@/components/TagEditor';
|
||||||
import {
|
import {
|
||||||
createSampleTag,
|
createSampleTag,
|
||||||
createSampleTagGroup,
|
createSampleTagGroup,
|
||||||
@@ -20,6 +22,8 @@ import {
|
|||||||
deleteSampleTag,
|
deleteSampleTag,
|
||||||
deleteSampleTagGroup,
|
deleteSampleTagGroup,
|
||||||
deleteSampleTagRelate,
|
deleteSampleTagRelate,
|
||||||
|
downloadSample,
|
||||||
|
downloadZipFile,
|
||||||
getSampleTagGroup,
|
getSampleTagGroup,
|
||||||
getSampleTagPage,
|
getSampleTagPage,
|
||||||
relateSample,
|
relateSample,
|
||||||
@@ -123,7 +127,10 @@ const SampleTagDetail = <T extends Record<string, any>>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 下载
|
// 下载
|
||||||
const handleDownloadAll = () => {};
|
const handleDownloadAll = async () => {
|
||||||
|
const ids = data?.map((sample) => sample.id) as number[];
|
||||||
|
downloadZipFile(ids);
|
||||||
|
};
|
||||||
|
|
||||||
const handleTagManager = () => {
|
const handleTagManager = () => {
|
||||||
setTagManagerVisible(true);
|
setTagManagerVisible(true);
|
||||||
@@ -158,167 +165,195 @@ const SampleTagDetail = <T extends Record<string, any>>(
|
|||||||
setTagManagerVisible(false);
|
setTagManagerVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onDownload = () => {
|
||||||
|
const item = data?.[0];
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = item?.sampleFilePath;
|
||||||
|
link.download = item?.sampleName; // 设置下载的文件名
|
||||||
|
link.style.display = 'none';
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ProForm name="validate_other" formRef={formRef} submitter={false}>
|
{data!.length > 0 ? (
|
||||||
{type === 'radio' && (
|
<>
|
||||||
<ProFormGroup title="预览">
|
<div className="sample-tag-detail">
|
||||||
{data?.[0].sampleFilePath && (
|
<ProForm name="validate_other" formRef={formRef} submitter={false}>
|
||||||
<audio
|
{type === 'radio' && (
|
||||||
controls
|
<ProFormGroup title="预览">
|
||||||
preload="true"
|
{data?.[0].sampleFilePath && (
|
||||||
crossOrigin="anonymous"
|
<audio
|
||||||
|
controls
|
||||||
|
preload="true"
|
||||||
|
crossOrigin="anonymous"
|
||||||
|
style={{ marginBottom: 24 }}
|
||||||
|
>
|
||||||
|
<source src={data[0].sampleFilePath} />
|
||||||
|
<track kind="captions" />
|
||||||
|
</audio>
|
||||||
|
)}
|
||||||
|
</ProFormGroup>
|
||||||
|
)}
|
||||||
|
<ProFormGroup title="基本信息">
|
||||||
|
{type === 'radio' && (
|
||||||
|
<ProFormText
|
||||||
|
width="md"
|
||||||
|
name="sampleName"
|
||||||
|
placeholder="请输入样本名称"
|
||||||
|
fieldProps={{
|
||||||
|
onBlur: async (e) => {
|
||||||
|
if (e.target.value) {
|
||||||
|
const newData =
|
||||||
|
data?.map((sample) => {
|
||||||
|
return {
|
||||||
|
id: sample.id,
|
||||||
|
sampleName: e.target.value,
|
||||||
|
};
|
||||||
|
}) || [];
|
||||||
|
await updateSamples(newData);
|
||||||
|
props?.onRefresh?.();
|
||||||
|
message.success('更新样本名称成功');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
rules={[{ required: true, message: '样本名称不能为空' }]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<ProFormText
|
||||||
|
width="md"
|
||||||
|
fieldProps={{
|
||||||
|
onBlur: async (e) => {
|
||||||
|
if (e.target.value) {
|
||||||
|
const newData =
|
||||||
|
data?.map((sample) => {
|
||||||
|
return {
|
||||||
|
id: sample.id,
|
||||||
|
remark: e.target.value,
|
||||||
|
};
|
||||||
|
}) || [];
|
||||||
|
await updateSamples(newData);
|
||||||
|
props?.onRefresh?.();
|
||||||
|
message.success('更新注释成功');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
name="remark"
|
||||||
|
placeholder="请输入注释"
|
||||||
|
/>
|
||||||
|
</ProFormGroup>
|
||||||
|
<ProFormGroup title="枚举标签" block></ProFormGroup>
|
||||||
|
<ProForm.Item name="tag1" label="物种">
|
||||||
|
<TagEditor maxCount={1} />
|
||||||
|
</ProForm.Item>
|
||||||
|
<ProForm.Item name="tag2" label="情绪">
|
||||||
|
<TagEditor maxCount={1} />
|
||||||
|
</ProForm.Item>
|
||||||
|
<ProFormGroup title="个性标签">
|
||||||
|
{/* <Form.Item name="tag"> */}
|
||||||
|
{forMap(value.tags || [])}
|
||||||
|
</ProFormGroup>
|
||||||
|
<Button
|
||||||
|
type="dashed"
|
||||||
|
block
|
||||||
style={{ marginBottom: 24 }}
|
style={{ marginBottom: 24 }}
|
||||||
|
onClick={handleAddTag}
|
||||||
>
|
>
|
||||||
<source src={data[0].sampleFilePath} />
|
添加标签
|
||||||
<track kind="captions" />
|
</Button>
|
||||||
</audio>
|
<ProFormGroup title="文本信息" block></ProFormGroup>
|
||||||
)}
|
<Space size={10} style={{ width: '100%', marginBottom: 12 }}>
|
||||||
</ProFormGroup>
|
<span>添加日期: </span>
|
||||||
)}
|
<span>
|
||||||
<ProFormGroup title="基本信息">
|
{dayjs(value.createTime).format('YYYY-MM-DD HH:mm:ss')}
|
||||||
{type === 'radio' && (
|
</span>
|
||||||
<ProFormText
|
</Space>
|
||||||
width="md"
|
<Space size={10} style={{ width: '100%', marginBottom: 12 }}>
|
||||||
name="sampleName"
|
<span>修改日期</span>
|
||||||
placeholder="请输入样本名称"
|
<span>
|
||||||
fieldProps={{
|
{dayjs(value.updateTime).format('YYYY-MM-DD HH:mm:ss')}
|
||||||
onBlur: async (e) => {
|
</span>
|
||||||
if (e.target.value) {
|
</Space>
|
||||||
const newData =
|
<Space size={10} style={{ width: '100%', marginBottom: 12 }}>
|
||||||
data?.map((sample) => {
|
<span>文件大小: </span>
|
||||||
return {
|
<span>{value.sampleSize}</span>
|
||||||
id: sample.id,
|
</Space>
|
||||||
sampleName: e.target.value,
|
<Space size={10} style={{ width: '100%', marginBottom: 12 }}>
|
||||||
};
|
<span>格式: </span>
|
||||||
}) || [];
|
<span>{value.sampleMineType}</span>
|
||||||
await updateSamples(newData);
|
</Space>
|
||||||
props?.onRefresh?.();
|
</ProForm>
|
||||||
message.success('更新成功');
|
<GroupTagModal
|
||||||
}
|
visible={modalVisible}
|
||||||
|
onCancel={() => setModalVisible(false)}
|
||||||
|
onChange={onListAddTag}
|
||||||
|
editable={false}
|
||||||
|
value={value?.tags}
|
||||||
|
request={{
|
||||||
|
groupsApi: {
|
||||||
|
get: getSampleTagGroup,
|
||||||
|
create: createSampleTagGroup,
|
||||||
|
delete: deleteSampleTagGroup,
|
||||||
|
update: updateSampleTagGroup,
|
||||||
|
},
|
||||||
|
tagsApi: {
|
||||||
|
get: getSampleTagPage,
|
||||||
|
create: createSampleTag,
|
||||||
|
delete: deleteSampleTag,
|
||||||
|
update: updateSampleTag,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
rules={[{ required: true, message: '样本名称不能为空' }]}
|
title="管理技术标签"
|
||||||
|
width={800}
|
||||||
|
height={500}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TagManager
|
||||||
|
visible={tagManagerVisible}
|
||||||
|
files={tagNames}
|
||||||
|
onOk={onRename}
|
||||||
|
onCancel={() => setTagManagerVisible(false)}
|
||||||
|
></TagManager>
|
||||||
|
{type === 'radio' ? (
|
||||||
|
<Space
|
||||||
|
style={{ width: '100%', justifyContent: 'center', padding: 12 }}
|
||||||
|
className="tag-manager-btns"
|
||||||
|
>
|
||||||
|
<Button color="danger" onClick={onDownload}>
|
||||||
|
下载
|
||||||
|
</Button>
|
||||||
|
<Button color="danger" variant="solid" onClick={handleDeleteAll}>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
) : (
|
||||||
|
<Space
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
justifyContent: 'center',
|
||||||
|
padding: 12,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}}
|
||||||
|
className="tag-manager-btns"
|
||||||
|
>
|
||||||
|
<Button block onClick={handleTagManager}>
|
||||||
|
批量重命名
|
||||||
|
</Button>
|
||||||
|
<Button block onClick={handleDownloadAll}>
|
||||||
|
下载到本地
|
||||||
|
</Button>
|
||||||
|
<Button block color="danger" onClick={handleDeleteAll}>
|
||||||
|
删除样本
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
)}
|
)}
|
||||||
<ProFormText
|
</>
|
||||||
width="md"
|
) : (
|
||||||
fieldProps={{
|
<Empty description="未选择样本" />
|
||||||
onBlur: async (e) => {
|
)}
|
||||||
if (e.target.value) {
|
|
||||||
const newData =
|
|
||||||
data?.map((sample) => {
|
|
||||||
return {
|
|
||||||
id: sample.id,
|
|
||||||
remark: e.target.value,
|
|
||||||
};
|
|
||||||
}) || [];
|
|
||||||
await updateSamples(newData);
|
|
||||||
props?.onRefresh?.();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
name="remark"
|
|
||||||
placeholder="请输入注释"
|
|
||||||
/>
|
|
||||||
</ProFormGroup>
|
|
||||||
|
|
||||||
<ProFormGroup title="标签">
|
|
||||||
{/* <Form.Item name="tag"> */}
|
|
||||||
{forMap(value.tags || [])}
|
|
||||||
</ProFormGroup>
|
|
||||||
<Button
|
|
||||||
type="dashed"
|
|
||||||
block
|
|
||||||
style={{ marginBottom: 24 }}
|
|
||||||
onClick={handleAddTag}
|
|
||||||
>
|
|
||||||
添加标签
|
|
||||||
</Button>
|
|
||||||
<ProFormGroup title="文本信息" block></ProFormGroup>
|
|
||||||
<Space size={10} style={{ width: '100%', marginBottom: 12 }}>
|
|
||||||
<span>添加日期: </span>
|
|
||||||
<span>{dayjs(value.createTime).format('YYYY-MM-DD HH:mm:ss')}</span>
|
|
||||||
</Space>
|
|
||||||
<Space size={10} style={{ width: '100%', marginBottom: 12 }}>
|
|
||||||
<span>修改日期</span>
|
|
||||||
<span>{dayjs(value.updateTime).format('YYYY-MM-DD HH:mm:ss')}</span>
|
|
||||||
</Space>
|
|
||||||
<Space size={10} style={{ width: '100%', marginBottom: 12 }}>
|
|
||||||
<span>文件大小: </span>
|
|
||||||
<span>{value.sampleSize}</span>
|
|
||||||
</Space>
|
|
||||||
<Space size={10} style={{ width: '100%', marginBottom: 12 }}>
|
|
||||||
<span>格式: </span>
|
|
||||||
<span>{value.sampleMineType}</span>
|
|
||||||
</Space>
|
|
||||||
|
|
||||||
{type === 'checkbox' && (
|
|
||||||
<>
|
|
||||||
<ProFormGroup title="其他"></ProFormGroup>
|
|
||||||
<Button
|
|
||||||
block
|
|
||||||
style={{ marginBottom: 24 }}
|
|
||||||
onClick={handleTagManager}
|
|
||||||
>
|
|
||||||
批量重命名
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
block
|
|
||||||
style={{ marginBottom: 24 }}
|
|
||||||
onClick={handleDownloadAll}
|
|
||||||
>
|
|
||||||
下载到本地
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
block
|
|
||||||
color="danger"
|
|
||||||
style={{ marginBottom: 24 }}
|
|
||||||
onClick={handleDeleteAll}
|
|
||||||
>
|
|
||||||
删除样本
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</ProForm>
|
|
||||||
<GroupTagModal
|
|
||||||
visible={modalVisible}
|
|
||||||
onCancel={() => setModalVisible(false)}
|
|
||||||
onChange={onListAddTag}
|
|
||||||
editable={false}
|
|
||||||
value={value?.tags}
|
|
||||||
request={{
|
|
||||||
groupsApi: {
|
|
||||||
get: getSampleTagGroup,
|
|
||||||
create: createSampleTagGroup,
|
|
||||||
delete: deleteSampleTagGroup,
|
|
||||||
update: updateSampleTagGroup,
|
|
||||||
},
|
|
||||||
tagsApi: {
|
|
||||||
get: getSampleTagPage,
|
|
||||||
create: createSampleTag,
|
|
||||||
delete: deleteSampleTag,
|
|
||||||
update: updateSampleTag,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
title="管理技术标签"
|
|
||||||
width={800}
|
|
||||||
height={500}
|
|
||||||
/>
|
|
||||||
<TagManager
|
|
||||||
visible={tagManagerVisible}
|
|
||||||
files={tagNames}
|
|
||||||
onOk={onRename}
|
|
||||||
onCancel={() => setTagManagerVisible(false)}
|
|
||||||
></TagManager>
|
|
||||||
<Space style={{ width: '100%', justifyContent: 'center', padding: 12 }}>
|
|
||||||
<Button color="danger" onClick={() => {}}>
|
|
||||||
下载
|
|
||||||
</Button>
|
|
||||||
<Button color="danger" variant="solid" onClick={handleDeleteAll}>
|
|
||||||
删除
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,18 +1,38 @@
|
|||||||
.tag-content {
|
.tag-content {
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
background: #fff;
|
background: #fff;
|
||||||
overflow: auto;
|
width: 100%;
|
||||||
|
height: calc(100vh - 90px);
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
:global {
|
:global {
|
||||||
.ant-pro-table {
|
.left {
|
||||||
flex: 1 auto;
|
background: #fff;
|
||||||
|
overflow: auto;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
.detail {
|
.detail {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
border-left: 1px solid #e8e8e8;
|
border-left: 1px solid #e8e8e8;
|
||||||
width: 400px;
|
width: 360px;
|
||||||
padding: 16px;
|
height: calc(100vh - 80px);
|
||||||
|
overflow-y: auto;
|
||||||
|
background: #fff;
|
||||||
|
overflow-x: hidden;
|
||||||
|
position: relative;
|
||||||
|
.ant-pro-form-group-title {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.sample-tag-detail {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
.tag-manager-btns {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 10px;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 0 10px #e8e8e8;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
form {
|
form {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import type { ActionType } from '@ant-design/pro-components';
|
import type { ActionType } from '@ant-design/pro-components';
|
||||||
import type { RowSelectionType } from 'antd/es/table/interface';
|
import type { RowSelectionType } from 'antd/es/table/interface';
|
||||||
|
import type { UploadFile } from 'antd/es/upload';
|
||||||
|
import type { UploadProps } from 'antd/lib/upload';
|
||||||
import React, { useRef, useState } from 'react';
|
import React, { useRef, useState } from 'react';
|
||||||
import EnhancedProTable from '@/components/EnhancedProTable';
|
import EnhancedProTable from '@/components/EnhancedProTable';
|
||||||
import type { ToolbarAction } from '@/components/EnhancedProTable/types';
|
import type { ToolbarAction } from '@/components/EnhancedProTable/types';
|
||||||
@@ -56,6 +58,7 @@ const SampleTag: React.FC = () => {
|
|||||||
const onFetch = async (params: SampleReqVo) => {
|
const onFetch = async (params: SampleReqVo) => {
|
||||||
const data = await getSamplePage({
|
const data = await getSamplePage({
|
||||||
...params,
|
...params,
|
||||||
|
pageNo: params.current,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
data: data.list,
|
data: data.list,
|
||||||
@@ -68,35 +71,43 @@ const SampleTag: React.FC = () => {
|
|||||||
tableRef.current?.onValuesChange({}, {});
|
tableRef.current?.onValuesChange({}, {});
|
||||||
type && setSelectedRows([]);
|
type && setSelectedRows([]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onChangeVideo = (file: UploadFile[] | UploadFile | null) => {
|
||||||
|
tableRef.current?.reload();
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<UploadCard />
|
|
||||||
<div className={styles['tag-content']}>
|
<div className={styles['tag-content']}>
|
||||||
<EnhancedProTable<AiSampleRespVO>
|
<div className="left">
|
||||||
ref={tableRef}
|
<UploadCard onChange={onChangeVideo} />
|
||||||
columns={baseTenantColumns}
|
<EnhancedProTable<AiSampleRespVO>
|
||||||
request={onFetch}
|
ref={tableRef}
|
||||||
toolbarActions={toolbarActions}
|
columns={baseTenantColumns}
|
||||||
headerTitle="样本列表"
|
request={onFetch}
|
||||||
showIndex={false}
|
toolbarActions={toolbarActions}
|
||||||
enableRowClick={true}
|
headerTitle="样本列表"
|
||||||
rowSelection={{
|
showIndex={false}
|
||||||
type: selectTableType,
|
enableRowClick={true}
|
||||||
selectedRowKeys: selectedRows.map((item) => item.id) as React.Key[],
|
scroll={{ x: 400 }}
|
||||||
onChange: (_, selectedRows) => {
|
rowSelection={{
|
||||||
setSelectedRows(selectedRows);
|
type: selectTableType,
|
||||||
},
|
selectedRowKeys: selectedRows.map(
|
||||||
}}
|
(item) => item.id,
|
||||||
/>
|
) as React.Key[],
|
||||||
{selectedRows.length > 0 && (
|
onChange: (_, selectedRows) => {
|
||||||
<div className="detail">
|
setSelectedRows(selectedRows);
|
||||||
<SampleTagDetail<AiSampleRespVO>
|
},
|
||||||
type={selectTableType}
|
}}
|
||||||
data={selectedRows}
|
/>
|
||||||
onRefresh={onRefresh}
|
</div>
|
||||||
/>
|
|
||||||
</div>
|
<div className="detail">
|
||||||
)}
|
<SampleTagDetail<AiSampleRespVO>
|
||||||
|
type={selectTableType}
|
||||||
|
data={selectedRows}
|
||||||
|
onRefresh={onRefresh}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<GroupTagModal
|
<GroupTagModal
|
||||||
visible={modalVisible}
|
visible={modalVisible}
|
||||||
|
|||||||
@@ -27,20 +27,60 @@ const ProdInfo = <T extends Record<string, any>>(props: ProdInfoProps<T>) => {
|
|||||||
name="prodName"
|
name="prodName"
|
||||||
label="商品名字"
|
label="商品名字"
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
width="xl"
|
colProps={{
|
||||||
|
span: 24,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ProFormText
|
||||||
|
name="abbreviation"
|
||||||
|
label="商品简称"
|
||||||
|
colProps={{
|
||||||
|
span: 24,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ProFormText
|
||||||
|
name="brief"
|
||||||
|
label="商品概述"
|
||||||
|
colProps={{
|
||||||
|
span: 24,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ProFormText
|
||||||
|
name="prodNumber"
|
||||||
|
label="商品编码"
|
||||||
|
colProps={{
|
||||||
|
span: 24,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ProFormText
|
||||||
|
name="brand"
|
||||||
|
label="品牌"
|
||||||
|
colProps={{
|
||||||
|
span: 24,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ProFormText
|
||||||
|
name="shopId"
|
||||||
|
label="商品所有权"
|
||||||
|
colProps={{
|
||||||
|
span: 24,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ProFormText
|
||||||
|
name="categoryName"
|
||||||
|
label="关联类目"
|
||||||
|
colProps={{
|
||||||
|
span: 24,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<ProFormText name="abbreviation" label="商品简称" width="xl" />
|
|
||||||
<ProFormText name="brief" label="商品概述" width="xl" />
|
|
||||||
<ProFormText name="prodNumber" label="商品编码" width="xl" />
|
|
||||||
<ProFormText name="brand" label="品牌" width="xl" />
|
|
||||||
<ProFormText name="shopId" label="商品所有权" width="xl" />
|
|
||||||
<ProFormText name="categoryName" label="关联类目" width="xl" />
|
|
||||||
<ProForm.Item
|
<ProForm.Item
|
||||||
name="tag"
|
name="tag"
|
||||||
label="商品标签"
|
label="商品标签"
|
||||||
tooltip="设置卖点标签,单标签限xx字符,最多x个标签"
|
tooltip="设置卖点标签,单标签限xx字符,最多x个标签"
|
||||||
layout="horizontal"
|
layout="horizontal"
|
||||||
width="xl"
|
colProps={{
|
||||||
|
span: 24,
|
||||||
|
}}
|
||||||
required={false}
|
required={false}
|
||||||
// getValueFromEvent={(e) => e.fileList}
|
// getValueFromEvent={(e) => e.fileList}
|
||||||
>
|
>
|
||||||
@@ -58,7 +98,9 @@ const ProdInfo = <T extends Record<string, any>>(props: ProdInfoProps<T>) => {
|
|||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
name="pic"
|
name="pic"
|
||||||
label="主图"
|
label="主图"
|
||||||
width="xl"
|
colProps={{
|
||||||
|
span: 24,
|
||||||
|
}}
|
||||||
extra="仅支持.jpg .png 格式,建议图片比例1:1,限1张"
|
extra="仅支持.jpg .png 格式,建议图片比例1:1,限1张"
|
||||||
// rules={[{ required: true }]}
|
// rules={[{ required: true }]}
|
||||||
// getValueFromEvent={(e) => e.fileList}
|
// getValueFromEvent={(e) => e.fileList}
|
||||||
@@ -70,7 +112,9 @@ const ProdInfo = <T extends Record<string, any>>(props: ProdInfoProps<T>) => {
|
|||||||
name="imgs"
|
name="imgs"
|
||||||
label="轮播图"
|
label="轮播图"
|
||||||
layout="horizontal"
|
layout="horizontal"
|
||||||
width="xl"
|
colProps={{
|
||||||
|
span: 24,
|
||||||
|
}}
|
||||||
extra="仅支持.jpg .png 格式,建议图片比例1:1,限7张"
|
extra="仅支持.jpg .png 格式,建议图片比例1:1,限7张"
|
||||||
// getValueFromEvent={(e) => e.fileList}
|
// getValueFromEvent={(e) => e.fileList}
|
||||||
>
|
>
|
||||||
@@ -80,16 +124,21 @@ const ProdInfo = <T extends Record<string, any>>(props: ProdInfoProps<T>) => {
|
|||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
name="whiteImg"
|
name="whiteImg"
|
||||||
label="白底图"
|
label="白底图"
|
||||||
width="xl"
|
colProps={{
|
||||||
|
span: 24,
|
||||||
|
}}
|
||||||
extra="仅支持.jpg .png 格式,建议图片比例1:1,限1张"
|
extra="仅支持.jpg .png 格式,建议图片比例1:1,限1张"
|
||||||
// getValueFromEvent={(e) => e.fileList}
|
// getValueFromEvent={(e) => e.fileList}
|
||||||
>
|
>
|
||||||
<UploadImages />
|
<UploadImages />
|
||||||
</ProForm.Item>
|
</ProForm.Item>
|
||||||
<ProForm.Item
|
<ProForm.Item
|
||||||
|
style={{ width: '100%' }}
|
||||||
name="video"
|
name="video"
|
||||||
label="主视频"
|
label="主视频"
|
||||||
width="xl"
|
colProps={{
|
||||||
|
span: 24,
|
||||||
|
}}
|
||||||
extra="仅支持.MP4 .MOV 格式,建议比例1:1、16:9,限1个"
|
extra="仅支持.MP4 .MOV 格式,建议比例1:1、16:9,限1个"
|
||||||
// getValueFromEvent={(e) => e.fileList}
|
// getValueFromEvent={(e) => e.fileList}
|
||||||
>
|
>
|
||||||
@@ -99,7 +148,9 @@ const ProdInfo = <T extends Record<string, any>>(props: ProdInfoProps<T>) => {
|
|||||||
name="content"
|
name="content"
|
||||||
label="图文介绍"
|
label="图文介绍"
|
||||||
rules={[{ required: true }]}
|
rules={[{ required: true }]}
|
||||||
width="xl"
|
colProps={{
|
||||||
|
span: 24,
|
||||||
|
}}
|
||||||
// getValueFromEvent={(e) => e.fileList}
|
// getValueFromEvent={(e) => e.fileList}
|
||||||
>
|
>
|
||||||
<TinymceEditor />
|
<TinymceEditor />
|
||||||
@@ -110,7 +161,7 @@ const ProdInfo = <T extends Record<string, any>>(props: ProdInfoProps<T>) => {
|
|||||||
name="seoShortName"
|
name="seoShortName"
|
||||||
label="短标题"
|
label="短标题"
|
||||||
colProps={{
|
colProps={{
|
||||||
span: 20,
|
span: 24,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ProFormText name="seoSearch" label="SEO标题" width={'xl'} />
|
<ProFormText name="seoSearch" label="SEO标题" width={'xl'} />
|
||||||
@@ -118,7 +169,9 @@ const ProdInfo = <T extends Record<string, any>>(props: ProdInfoProps<T>) => {
|
|||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
<ProForm.Item
|
<ProForm.Item
|
||||||
style={{ width: '100%' }}
|
colProps={{
|
||||||
|
span: 24,
|
||||||
|
}}
|
||||||
name="shareImage"
|
name="shareImage"
|
||||||
label="分享图"
|
label="分享图"
|
||||||
width="xl"
|
width="xl"
|
||||||
@@ -130,7 +183,9 @@ const ProdInfo = <T extends Record<string, any>>(props: ProdInfoProps<T>) => {
|
|||||||
name="shareContent"
|
name="shareContent"
|
||||||
label="分享话术"
|
label="分享话术"
|
||||||
placeholder={'请输入分享话术'}
|
placeholder={'请输入分享话术'}
|
||||||
width={'xl'}
|
colProps={{
|
||||||
|
span: 24,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</ProFormGroup>
|
</ProFormGroup>
|
||||||
<ProFormGroup>
|
<ProFormGroup>
|
||||||
|
|||||||
@@ -1,67 +1,85 @@
|
|||||||
import { ProForm, ProFormText } from '@ant-design/pro-components';
|
import { ProForm, ProFormText } from '@ant-design/pro-components';
|
||||||
|
import { Anchor, Col, Form, Row } from 'antd';
|
||||||
|
import ProdReservationConfig from './prodReservationConfig';
|
||||||
|
import ProdServiceAreasInfo from './prodServiceAreasInfo';
|
||||||
|
import ProdWeightConfig from './prodWeightConfig';
|
||||||
|
|
||||||
const ServiceRule: React.FC = () => {
|
const ServiceRule: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<ProForm<{
|
<ProForm
|
||||||
name: string;
|
onFinish={async (value) => console.log(value)}
|
||||||
company: string;
|
layout="horizontal"
|
||||||
}>
|
style={{ width: '100%' }}
|
||||||
grid
|
labelCol={{ span: '100px' }}
|
||||||
onFinish={async (values) => {
|
|
||||||
// await waitTime(2000);
|
|
||||||
console.log(values);
|
|
||||||
// message.success('提交成功');
|
|
||||||
}}
|
|
||||||
initialValues={{
|
|
||||||
name: '蚂蚁设计有限公司',
|
|
||||||
useMode: 'chapter',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<ProForm.Group>
|
<Row gutter={24}>
|
||||||
<ProFormText
|
<Col span={18}>
|
||||||
width="md"
|
<div id="prodServiceAreasInfo">
|
||||||
name="name"
|
<ProdServiceAreasInfo />
|
||||||
label="签约客户名称"
|
</div>
|
||||||
tooltip="最长为 24 位"
|
<div id="prodReservationConfig">
|
||||||
placeholder="请输入名称"
|
<ProForm.Group>
|
||||||
/>
|
<ProdReservationConfig />
|
||||||
<ProFormText
|
</ProForm.Group>
|
||||||
width="md"
|
</div>
|
||||||
name="company"
|
<div id="prodEmergencyInfoVO">
|
||||||
label="我方公司名称"
|
<ProForm.Group></ProForm.Group>
|
||||||
placeholder="请输入名称"
|
</div>
|
||||||
/>
|
<div id="productOrderLimitVO">
|
||||||
</ProForm.Group>
|
<ProForm.Group title="周期接单上限"></ProForm.Group>
|
||||||
<ProFormText width="sm" name="id" label="主合同编号" />
|
</div>
|
||||||
<ProForm.Item
|
<div id="prodAdditionalFeeDatesList">
|
||||||
label="数组数据"
|
<ProForm.Group title="特殊时段规则"></ProForm.Group>
|
||||||
name="dataSource"
|
</div>
|
||||||
// initialValue={defaultData}
|
<div id="prodAdditionalFeePeriodsList">
|
||||||
trigger="onValuesChange"
|
<ProForm.Group title="特殊日期规则"></ProForm.Group>
|
||||||
>
|
</div>
|
||||||
{/* <EditableProTable<DataSourceType>
|
<div id="prodWeightConfig">
|
||||||
rowKey="id"
|
<ProdWeightConfig />
|
||||||
toolBarRender={false}
|
</div>
|
||||||
columns={columns}
|
</Col>
|
||||||
recordCreatorProps={{
|
<Col span="400px">
|
||||||
newRecordType: 'dataSource',
|
<Anchor
|
||||||
position: 'top',
|
items={[
|
||||||
record: () => ({
|
{
|
||||||
id: Date.now(),
|
key: 'prodServiceAreasInfo',
|
||||||
addonBefore: 'ccccccc',
|
href: '#prodServiceAreasInfo',
|
||||||
decs: 'testdesc',
|
title: '可服务区域',
|
||||||
}),
|
},
|
||||||
}}
|
{
|
||||||
editable={{
|
key: 'prodReservationConfig',
|
||||||
type: 'multiple',
|
href: '#prodReservationConfig',
|
||||||
editableKeys,
|
title: '可预约时段',
|
||||||
onChange: setEditableRowKeys,
|
},
|
||||||
actionRender: (row, _, dom) => {
|
{
|
||||||
return [dom.delete];
|
key: 'prodEmergencyInfoVO',
|
||||||
},
|
href: '#prodEmergencyInfoVO',
|
||||||
}} */}
|
title: '紧急响应服务',
|
||||||
{/* /> */}
|
},
|
||||||
</ProForm.Item>
|
{
|
||||||
|
key: 'productOrderLimitVO',
|
||||||
|
href: '#productOrderLimitVO',
|
||||||
|
title: '周期接单上限',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'prodAdditionalFeeDatesList',
|
||||||
|
href: '#prodAdditionalFeeDatesList',
|
||||||
|
title: '特殊时段规则',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'prodAdditionalFeePeriodsList',
|
||||||
|
href: '#prodAdditionalFeePeriodsList',
|
||||||
|
title: '特殊日期规则',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'prodWeightConfig',
|
||||||
|
href: '#prodWeightConfig',
|
||||||
|
title: '体重/体型选项',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
</ProForm>
|
</ProForm>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
import {
|
||||||
|
ProFormCheckbox,
|
||||||
|
ProFormDigit,
|
||||||
|
ProFormRadio,
|
||||||
|
ProFormSelect,
|
||||||
|
ProFormSwitch,
|
||||||
|
ProFormText,
|
||||||
|
} from '@ant-design/pro-components';
|
||||||
|
import { Divider, Space, Typography } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import type { ProdReservationInfoVO } from '@/services/prod/prod-manager/rule';
|
||||||
|
|
||||||
|
const { Text, Title } = Typography;
|
||||||
|
type ValueConfig = {
|
||||||
|
reservationSwitch?: number;
|
||||||
|
prodReservationConfig?: ProdReservationInfoVO;
|
||||||
|
};
|
||||||
|
interface ProdReservationConfigProps {
|
||||||
|
value?: ValueConfig;
|
||||||
|
onChange?: (value?: ValueConfig) => void;
|
||||||
|
}
|
||||||
|
const ProdReservationConfig: React.FC<ProdReservationConfigProps> = (props) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Title level={4}>服务区域配置</Title>
|
||||||
|
<Text type="secondary">
|
||||||
|
默认服务区域为全城,开启此配置,可自定义可服务区域以及超区时的限制规则
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ProFormSwitch
|
||||||
|
name="reservationSwitch"
|
||||||
|
label="应用配置"
|
||||||
|
labelCol={{ span: 13 }}
|
||||||
|
fieldProps={{
|
||||||
|
onChange: (checked) => {
|
||||||
|
props.onChange?.({
|
||||||
|
reservationSwitch: checked ? 1 : 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
checkedChildren: '开启',
|
||||||
|
unCheckedChildren: '关闭',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Divider />
|
||||||
|
<ProFormDigit
|
||||||
|
name="advanceHours"
|
||||||
|
label="预约时间约定"
|
||||||
|
wrapperCol={{ span: 11 }}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入提前预约时间',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
fieldProps={{
|
||||||
|
placeholder: '请输入提前预约时间',
|
||||||
|
min: 0,
|
||||||
|
max: 24,
|
||||||
|
addonBefore: '提前',
|
||||||
|
addonAfter: '小时',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ProFormRadio.Group
|
||||||
|
name="reservationDateRange"
|
||||||
|
label="预约日期范围"
|
||||||
|
options={[
|
||||||
|
{ label: '7天', value: 7 },
|
||||||
|
{ label: '10天', value: 10 },
|
||||||
|
{ label: '15天', value: 15 },
|
||||||
|
{ label: '30天', value: 30 },
|
||||||
|
]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请选择预约日期范围',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<ProFormRadio.Group
|
||||||
|
name="allowChange"
|
||||||
|
label="更改预约时间"
|
||||||
|
options={[
|
||||||
|
{ label: '允许更改预约时间', value: 1 },
|
||||||
|
{ label: '禁止更改预约时间', value: 0 },
|
||||||
|
]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请选择更改预约时间',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<ProFormRadio.Group
|
||||||
|
name="changeTimeRule"
|
||||||
|
label="时间更改规则"
|
||||||
|
options={[
|
||||||
|
{ label: '服务开始前 1小时 可更改预约时间', value: 1 },
|
||||||
|
{ label: '服务开始前 2小时 可更改预约时间', value: 2 },
|
||||||
|
{ label: '服务开始前 3小时 可更改预约时间', value: 3 },
|
||||||
|
]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请选择时间更改规则',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<ProFormRadio.Group
|
||||||
|
name="maxChangeTimes"
|
||||||
|
label="更改次数规则"
|
||||||
|
options={[
|
||||||
|
{ label: '允许修改 1 次', value: 1 },
|
||||||
|
{ label: '允许修改 2 次', value: 2 },
|
||||||
|
{ label: '允许修改 3 次', value: 3 },
|
||||||
|
]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请选择时间更改规则',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(ProdReservationConfig);
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import {
|
||||||
|
ProFormCheckbox,
|
||||||
|
ProFormDigit,
|
||||||
|
ProFormRadio,
|
||||||
|
ProFormSelect,
|
||||||
|
ProFormSwitch,
|
||||||
|
ProFormText,
|
||||||
|
} from '@ant-design/pro-components';
|
||||||
|
import { Divider, Space, Typography } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import type { ProdServiceAreasInfoVO } from '@/services/prod/prod-manager/rule';
|
||||||
|
|
||||||
|
const { Text, Title } = Typography;
|
||||||
|
|
||||||
|
type ValueConfig = {
|
||||||
|
regionSwitch?: number;
|
||||||
|
prodServiceAreasInfo?: ProdServiceAreasInfoVO;
|
||||||
|
};
|
||||||
|
interface ProdServiceAreasInfoProps {
|
||||||
|
value?: ValueConfig;
|
||||||
|
onChange?: (value?: ValueConfig) => void;
|
||||||
|
}
|
||||||
|
const ProdServiceAreasInfo: React.FC<ProdServiceAreasInfoProps> = (props) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Title level={4}>服务区域配置</Title>
|
||||||
|
<Text type="secondary">
|
||||||
|
默认服务区域为全城,开启此配置,可自定义可服务区域以及超区时的限制规则
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<ProFormSwitch
|
||||||
|
name="regionSwitch"
|
||||||
|
label="应用配置"
|
||||||
|
labelCol={{ span: 13 }}
|
||||||
|
fieldProps={{
|
||||||
|
onChange: (checked) => {
|
||||||
|
props.onChange?.({
|
||||||
|
regionSwitch: checked ? 1 : 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
checkedChildren: '开启',
|
||||||
|
unCheckedChildren: '关闭',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<ProFormSelect
|
||||||
|
name="areaNameList"
|
||||||
|
label="服务区域"
|
||||||
|
options={[
|
||||||
|
'鼓楼区',
|
||||||
|
'台江区',
|
||||||
|
'晋安区',
|
||||||
|
'马尾区',
|
||||||
|
'闽侯县',
|
||||||
|
'福清市(县级市)',
|
||||||
|
'长乐区',
|
||||||
|
'罗源县',
|
||||||
|
'永泰县',
|
||||||
|
'平潭县',
|
||||||
|
]}
|
||||||
|
fieldProps={{
|
||||||
|
mode: 'multiple',
|
||||||
|
}}
|
||||||
|
placeholder="请选择服务区域"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请选择服务区域',
|
||||||
|
type: 'array',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<ProFormRadio.Group
|
||||||
|
name="ruleType"
|
||||||
|
label="超区规则"
|
||||||
|
options={[
|
||||||
|
{ label: '拒单', value: 0 },
|
||||||
|
{ label: '接单-收超区费', value: 2 },
|
||||||
|
{ label: '接单-收超区费-收超区费', value: 3 },
|
||||||
|
]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请选择服务区域',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<ProFormDigit
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请设置费用',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
label="设置费用"
|
||||||
|
name="fee"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(ProdServiceAreasInfo);
|
||||||
278
src/pages/prod/list/components/service-rule/prodWeightConfig.tsx
Normal file
278
src/pages/prod/list/components/service-rule/prodWeightConfig.tsx
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
import {
|
||||||
|
DragSortTable,
|
||||||
|
EditableProTable,
|
||||||
|
type ProColumns,
|
||||||
|
ProFormCheckbox,
|
||||||
|
ProFormDigit,
|
||||||
|
ProFormRadio,
|
||||||
|
ProFormSelect,
|
||||||
|
ProFormSwitch,
|
||||||
|
ProFormText,
|
||||||
|
} from '@ant-design/pro-components';
|
||||||
|
import { Divider, Space, Typography } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import type {
|
||||||
|
ProdWeightRangePricesDO,
|
||||||
|
ProdWeightRangePricesSaveInfoVO,
|
||||||
|
} from '@/services/prod/prod-manager/rule';
|
||||||
|
|
||||||
|
const { Text, Title } = Typography;
|
||||||
|
type ValueConfig = {
|
||||||
|
weightSwitch?: number;
|
||||||
|
prodWeightConfig?: ProdWeightRangePricesSaveInfoVO;
|
||||||
|
};
|
||||||
|
interface ProdWeightConfigProps {
|
||||||
|
value?: ValueConfig;
|
||||||
|
onChange?: (value?: ValueConfig) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '体型&体重',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
width: '20%',
|
||||||
|
render: (text: string, record: any) => {
|
||||||
|
return <ProFormText name={record.key} label={text} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '价格',
|
||||||
|
dataIndex: 'price',
|
||||||
|
key: 'price',
|
||||||
|
width: '20%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
dataIndex: 'price',
|
||||||
|
key: 'price',
|
||||||
|
width: '20%',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const prodWeightConfig: React.FC<ProdWeightConfigProps> = (props) => {
|
||||||
|
const { value } = props;
|
||||||
|
const columns: ProColumns<ProdWeightRangePricesDO>[] = [
|
||||||
|
{
|
||||||
|
title: '体型&体重 *',
|
||||||
|
dataIndex: 'weightRange',
|
||||||
|
width: '30%',
|
||||||
|
formItemProps: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
whitespace: true,
|
||||||
|
message: '此项是必填项',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '价格 *',
|
||||||
|
dataIndex: 'price',
|
||||||
|
width: '30%',
|
||||||
|
formItemProps: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
whitespace: true,
|
||||||
|
message: '此项是必填项',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
valueType: 'option',
|
||||||
|
width: 250,
|
||||||
|
render: () => {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Title level={4}>服务区域配置</Title>
|
||||||
|
<Text type="secondary">
|
||||||
|
默认服务区域为全城,开启此配置,可自定义可服务区域以及超区时的限制规则
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<ProFormSwitch
|
||||||
|
name="weightSwitch"
|
||||||
|
label="应用配置"
|
||||||
|
labelCol={{ span: 13 }}
|
||||||
|
fieldProps={{
|
||||||
|
onChange: (checked) => {
|
||||||
|
props.onChange?.({
|
||||||
|
weightSwitch: checked ? 1 : 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
checkedChildren: '开启',
|
||||||
|
unCheckedChildren: '关闭',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
<ProFormRadio.Group
|
||||||
|
name="isWeightCharge"
|
||||||
|
label="是否收费"
|
||||||
|
options={[
|
||||||
|
{
|
||||||
|
label: '是',
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '否',
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请选择是否收费',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
{/* <EditableProTable<ProdWeightRangePricesDO>
|
||||||
|
rowKey="id"
|
||||||
|
headerTitle="可拖拽编辑表格"
|
||||||
|
maxLength={10}
|
||||||
|
recordCreatorProps={{
|
||||||
|
position: 'bottom',
|
||||||
|
record: () => ({ id: Date.now().toString(), weightRange: '', price: 0, }),
|
||||||
|
}}
|
||||||
|
columns={columns}
|
||||||
|
value={dataSource}
|
||||||
|
onChange={setDataSource}
|
||||||
|
editable={{
|
||||||
|
type: 'multiple',
|
||||||
|
editableKeys,
|
||||||
|
onSave: async (rowKey, data) => {
|
||||||
|
console.log('保存数据:', data);
|
||||||
|
},
|
||||||
|
onChange: setEditableRowKeys,
|
||||||
|
}}
|
||||||
|
// 拖拽排序配置
|
||||||
|
dragSortKey="sort"
|
||||||
|
onDragSortEnd={(newDataSource) => {
|
||||||
|
console.log('排序后:', newDataSource);
|
||||||
|
setDataSource(newDataSource as DataType[]);
|
||||||
|
}}
|
||||||
|
/> */}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(prodWeightConfig);
|
||||||
|
|
||||||
|
// import { EditableProTable } from '@ant-design/pro-components';
|
||||||
|
// import type { ProColumns } from '@ant-design/pro-components';
|
||||||
|
// import { useState } from 'react';
|
||||||
|
// import { MenuOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
// interface DataType {
|
||||||
|
// id: string;
|
||||||
|
// name: string;
|
||||||
|
// age: number;
|
||||||
|
// email: string;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const DragEditableProTable: React.FC = () => {
|
||||||
|
// const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
|
||||||
|
// const [dataSource, setDataSource] = useState<DataType[]>([
|
||||||
|
// { id: '1', name: '张三', age: 25, email: 'zhangsan@example.com' },
|
||||||
|
// { id: '2', name: '李四', age: 30, email: 'lisi@example.com' },
|
||||||
|
// { id: '3', name: '王五', age: 28, email: 'wangwu@example.com' },
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
// const columns: ProColumns<DataType>[] = [
|
||||||
|
// {
|
||||||
|
// title: '排序',
|
||||||
|
// dataIndex: 'sort',
|
||||||
|
// width: 60,
|
||||||
|
// render: () => <MenuOutlined style={{ cursor: 'grab', color: '#999' }} />,
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// title: '姓名',
|
||||||
|
// dataIndex: 'name',
|
||||||
|
// formItemProps: {
|
||||||
|
// rules: [{ required: true, message: '请输入姓名' }],
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// title: '年龄',
|
||||||
|
// dataIndex: 'age',
|
||||||
|
// valueType: 'digit',
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// title: '邮箱',
|
||||||
|
// dataIndex: 'email',
|
||||||
|
// valueType: 'text',
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// title: '操作',
|
||||||
|
// valueType: 'option',
|
||||||
|
// width: 200,
|
||||||
|
// render: (text, record, _, action) => [
|
||||||
|
// <a
|
||||||
|
// key="editable"
|
||||||
|
// onClick={() => {
|
||||||
|
// action?.startEditable?.(record.id);
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// 编辑
|
||||||
|
// </a>,
|
||||||
|
// <a
|
||||||
|
// key="delete"
|
||||||
|
// onClick={() => {
|
||||||
|
// setDataSource(dataSource.filter((item) => item.id !== record.id));
|
||||||
|
// }}
|
||||||
|
// >
|
||||||
|
// 删除
|
||||||
|
// </a>,
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// ];
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <EditableProTable<DataType>
|
||||||
|
// rowKey="id"
|
||||||
|
// headerTitle="可拖拽编辑表格"
|
||||||
|
// maxLength={10}
|
||||||
|
// recordCreatorProps={{
|
||||||
|
// position: 'bottom',
|
||||||
|
// record: () => ({ id: Date.now().toString(), name: '', age: 0, email: '' }),
|
||||||
|
// }}
|
||||||
|
// columns={columns}
|
||||||
|
// value={dataSource}
|
||||||
|
// onChange={setDataSource}
|
||||||
|
// editable={{
|
||||||
|
// type: 'multiple',
|
||||||
|
// editableKeys,
|
||||||
|
// onSave: async (rowKey, data) => {
|
||||||
|
// console.log('保存数据:', data);
|
||||||
|
// },
|
||||||
|
// onChange: setEditableRowKeys,
|
||||||
|
// }}
|
||||||
|
// // 拖拽排序配置
|
||||||
|
// dragSortKey="sort"
|
||||||
|
// onDragSortEnd={(newDataSource) => {
|
||||||
|
// console.log('排序后:', newDataSource);
|
||||||
|
// setDataSource(newDataSource as DataType[]);
|
||||||
|
// }}
|
||||||
|
// />
|
||||||
|
// );
|
||||||
|
// };
|
||||||
|
|
||||||
|
// export default DragEditableProTable;
|
||||||
@@ -6,16 +6,9 @@ import ProdInfo from '@/pages/prod/list/components/prod-info';
|
|||||||
import Sku from '@/pages/prod/list/components/sku';
|
import Sku from '@/pages/prod/list/components/sku';
|
||||||
import type { Prod, ProdDetail, SkuConfig } from '@/services/prod/prod-manager';
|
import type { Prod, ProdDetail, SkuConfig } from '@/services/prod/prod-manager';
|
||||||
|
|
||||||
const waitTime = (time: number = 100) => {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve(true);
|
|
||||||
}, time);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const ProdDetailPage: React.FC<{
|
const ProdDetailPage: React.FC<{
|
||||||
data?: Prod;
|
data?: Prod;
|
||||||
|
type?: string;
|
||||||
formRef?: React.RefObject<FormInstance<any>>;
|
formRef?: React.RefObject<FormInstance<any>>;
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
const { data, formRef } = props;
|
const { data, formRef } = props;
|
||||||
@@ -42,8 +35,8 @@ const ProdDetailPage: React.FC<{
|
|||||||
formRef={formRef}
|
formRef={formRef}
|
||||||
submitter={false}
|
submitter={false}
|
||||||
onFinish={async () => {
|
onFinish={async () => {
|
||||||
await waitTime(1000);
|
// await waitTime(1000);
|
||||||
message.success('提交成功');
|
// message.success('提交成功');
|
||||||
}}
|
}}
|
||||||
formProps={{
|
formProps={{
|
||||||
validateMessages: {
|
validateMessages: {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { EllipsisOutlined, PlusOutlined } from '@ant-design/icons';
|
import { EllipsisOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
import type { ActionType, ProColumns } from '@ant-design/pro-components';
|
import type { ActionType, ProColumns } from '@ant-design/pro-components';
|
||||||
|
import { useNavigate } from '@umijs/max';
|
||||||
import type { MenuProps, TabsProps } from 'antd';
|
import type { MenuProps, TabsProps } from 'antd';
|
||||||
import { Button, Dropdown, Space, Tabs } from 'antd';
|
import { Button, Dropdown, Space, Tabs } from 'antd';
|
||||||
import { useCallback, useRef, useState } from 'react';
|
import { useCallback, useRef, useState } from 'react';
|
||||||
@@ -25,6 +26,7 @@ const ProdList = () => {
|
|||||||
const [type, setType] = useState<'create' | 'update' | 'test'>('create');
|
const [type, setType] = useState<'create' | 'update' | 'test'>('create');
|
||||||
const [status, setStatus] = useState<number>();
|
const [status, setStatus] = useState<number>();
|
||||||
const detailRef = useRef<ConfigurableDrawerFormRef>(null);
|
const detailRef = useRef<ConfigurableDrawerFormRef>(null);
|
||||||
|
const navigator = useNavigate();
|
||||||
// const editRef = useRef<ConfigurableDrawerFormRef>(null);
|
// const editRef = useRef<ConfigurableDrawerFormRef>(null);
|
||||||
const [id, setId] = useState<number>(0);
|
const [id, setId] = useState<number>(0);
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
@@ -89,6 +91,12 @@ const ProdList = () => {
|
|||||||
[id, type],
|
[id, type],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleLink = (key: string, row: Prod) => {
|
||||||
|
if (key === 'sku') {
|
||||||
|
// navigator(`/prod/list/sku/${row.prodId}`);
|
||||||
|
}
|
||||||
|
console.log(key, row);
|
||||||
|
};
|
||||||
// const renderDetailFooter = () => {
|
// const renderDetailFooter = () => {
|
||||||
// if (type === "update") {
|
// if (type === "update") {
|
||||||
// return (
|
// return (
|
||||||
@@ -118,7 +126,11 @@ const ProdList = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'sku',
|
key: 'sku',
|
||||||
label: <Button type="link">SKU管理</Button>,
|
label: (
|
||||||
|
<Button type="link" onClick={() => handleLink('sku', row)}>
|
||||||
|
SKU管理
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'rules-service',
|
key: 'rules-service',
|
||||||
@@ -155,7 +167,9 @@ const ProdList = () => {
|
|||||||
<Button type="link" onClick={() => handleEdit(record)}>
|
<Button type="link" onClick={() => handleEdit(record)}>
|
||||||
商品编辑
|
商品编辑
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="link">SKU管理</Button>
|
<Button type="link" onClick={() => handleLink('sku', record)}>
|
||||||
|
SKU管理
|
||||||
|
</Button>
|
||||||
</Space.Compact>
|
</Space.Compact>
|
||||||
<Space.Compact direction="vertical" block key="2">
|
<Space.Compact direction="vertical" block key="2">
|
||||||
<Button type="link">服务规则</Button>
|
<Button type="link">服务规则</Button>
|
||||||
|
|||||||
20
src/pages/trade/order/components/createSales.module.less
Normal file
20
src/pages/trade/order/components/createSales.module.less
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
.sales {
|
||||||
|
:global {
|
||||||
|
.ant-pro-card-body {
|
||||||
|
padding: 16px !important;
|
||||||
|
}
|
||||||
|
.ant-pro-card-size-small {
|
||||||
|
.ant-pro-card-header {
|
||||||
|
background: rgba(0, 0, 0, 0.02);
|
||||||
|
min-height: 41px;
|
||||||
|
}
|
||||||
|
.ant-form-item {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.ant-pro-card-col {
|
||||||
|
flex: 1 auto;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
762
src/pages/trade/order/components/createSalesModal copy.tsx
Normal file
762
src/pages/trade/order/components/createSalesModal copy.tsx
Normal file
@@ -0,0 +1,762 @@
|
|||||||
|
//新建售后
|
||||||
|
|
||||||
|
import {
|
||||||
|
BetaSchemaForm,
|
||||||
|
type EditableFormInstance,
|
||||||
|
EditableProTable,
|
||||||
|
ProCard,
|
||||||
|
type ProColumns,
|
||||||
|
ProForm,
|
||||||
|
type ProFormColumnsType,
|
||||||
|
ProFormDependency,
|
||||||
|
ProFormDigit,
|
||||||
|
ProFormRadio,
|
||||||
|
ProFormTextArea,
|
||||||
|
} from '@ant-design/pro-components';
|
||||||
|
import {
|
||||||
|
Badge,
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
Empty,
|
||||||
|
Form,
|
||||||
|
type FormInstance,
|
||||||
|
Input,
|
||||||
|
InputNumber,
|
||||||
|
Space,
|
||||||
|
Spin,
|
||||||
|
Statistic,
|
||||||
|
Table,
|
||||||
|
Typography,
|
||||||
|
} from 'antd';
|
||||||
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import EditableProTableCom from '@/components/EditableProTable';
|
||||||
|
import CommonModal from '@/components/ModalCom/CommonModal';
|
||||||
|
import UploadImages from '@/components/Upload/UploadImages';
|
||||||
|
import styles from './createSales.module.less';
|
||||||
|
|
||||||
|
const { Title, Text } = Typography;
|
||||||
|
|
||||||
|
interface DataType {
|
||||||
|
key: string;
|
||||||
|
name: string;
|
||||||
|
age: number;
|
||||||
|
address: string;
|
||||||
|
}
|
||||||
|
const originData = Array.from({ length: 100 }).map<DataType>((_, i) => ({
|
||||||
|
key: i.toString(),
|
||||||
|
name: `Edward ${i}`,
|
||||||
|
age: 32,
|
||||||
|
address: `London Park no. ${i}`,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const waitTime = (time: number = 100) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve(true);
|
||||||
|
}, time);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const CreateSalesModal = () => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const formRefSales = useRef<FormInstance>(null);
|
||||||
|
const formRef = useRef<FormInstance>(null);
|
||||||
|
|
||||||
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
const handleOk = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const values1 = formRef.current?.getFieldsValue();
|
||||||
|
const values2 = formRefSales.current?.getFieldsValue();
|
||||||
|
|
||||||
|
console.log({ ...values1, values2 });
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [open]);
|
||||||
|
|
||||||
|
const onOpen = useCallback(
|
||||||
|
(flag: boolean) => {
|
||||||
|
setOpen(flag);
|
||||||
|
},
|
||||||
|
[open],
|
||||||
|
);
|
||||||
|
const salesColumns: ProFormColumnsType[] = [
|
||||||
|
{
|
||||||
|
title: '售后类型',
|
||||||
|
dataIndex: 'cancelReason',
|
||||||
|
valueType: 'radioButton',
|
||||||
|
formItemProps: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
fieldProps: {
|
||||||
|
options: [{ value: '仅退款', label: '仅退款' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '退款类型',
|
||||||
|
dataIndex: 'cancelRemark',
|
||||||
|
valueType: 'radioButton',
|
||||||
|
formItemProps: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
fieldProps: {
|
||||||
|
options: [
|
||||||
|
{ value: '整单退款(通用)', label: '整单退款' },
|
||||||
|
{ value: '选项二', label: '商品退款' },
|
||||||
|
{ value: '选项三', label: '扩展服务退款' },
|
||||||
|
{ value: '服务附加费退款', label: '服务附加费退款' },
|
||||||
|
{ value: '选项五', label: '退运费' },
|
||||||
|
{ value: '其他', label: '其他' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// title: "",
|
||||||
|
// dependencies: ["cancelRemark"],
|
||||||
|
// dataIndex: "cancelRemark1",
|
||||||
|
// valueType: "textarea",
|
||||||
|
// formItemProps: {
|
||||||
|
// rules: [
|
||||||
|
// {
|
||||||
|
// required: true,
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// fieldProps: {
|
||||||
|
// placeholder: "请输入",
|
||||||
|
// },
|
||||||
|
// renderFormItem: (schema, config, form) => {
|
||||||
|
// const grade = form.getFieldValue("cancelRemark");
|
||||||
|
// if (grade === "其他") {
|
||||||
|
// return <Input.TextArea placeholder="请输入" rows={4} />;
|
||||||
|
// }
|
||||||
|
// if (grade === "服务附加费退款") {
|
||||||
|
// return (
|
||||||
|
// <Form.Item>
|
||||||
|
// <ExtendRefunds />
|
||||||
|
// </Form.Item>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// return <></>;
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
];
|
||||||
|
const applyColumns: ProFormColumnsType[] = [
|
||||||
|
{
|
||||||
|
title: '售后类型',
|
||||||
|
dataIndex: 'cancelReason1',
|
||||||
|
valueType: 'radio',
|
||||||
|
formItemProps: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
fieldProps: {
|
||||||
|
options: [
|
||||||
|
{ value: '用户主动取消', label: '用户主动取消' },
|
||||||
|
{ value: '选项二', label: '选项二' },
|
||||||
|
{ value: '选项三', label: '选项三' },
|
||||||
|
{ value: '选项四', label: '选项四' },
|
||||||
|
{ value: '选项五', label: '选项五' },
|
||||||
|
{ value: '选项六', label: '选项六' },
|
||||||
|
{ value: '选项七', label: '选项七' },
|
||||||
|
],
|
||||||
|
style: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '8px',
|
||||||
|
marginTop: '6px',
|
||||||
|
background: '#f0f0f0',
|
||||||
|
padding: '8px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '问题描述',
|
||||||
|
dataIndex: 'cancelRemark22',
|
||||||
|
valueType: 'textarea',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '问题凭证',
|
||||||
|
dataIndex: 'cancelRemark23',
|
||||||
|
formItemProps: {
|
||||||
|
extra: '请上传问题凭证,支持.jpg .png 格式,最多 4 张。',
|
||||||
|
},
|
||||||
|
renderFormItem: () => {
|
||||||
|
return <UploadImages multiple={true} maxCount={4} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: '商家备注',
|
||||||
|
dataIndex: 'cancelRemark2311',
|
||||||
|
valueType: 'textarea',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button onClick={() => onOpen(true)}>新建售后</Button>
|
||||||
|
<CommonModal
|
||||||
|
title="新建售后单"
|
||||||
|
open={true}
|
||||||
|
onOk={handleOk}
|
||||||
|
onCancel={() => onOpen(false)}
|
||||||
|
confirmLoading={loading}
|
||||||
|
>
|
||||||
|
<Space
|
||||||
|
direction="vertical"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
size={16}
|
||||||
|
className={styles.sales}
|
||||||
|
>
|
||||||
|
<ProCard.Group direction="row">
|
||||||
|
<ProCard layout="center">
|
||||||
|
<Statistic title="实付金额" value={1000} precision={2} />
|
||||||
|
</ProCard>
|
||||||
|
<ProCard layout="center">
|
||||||
|
<Statistic title="累计退款金额" value={200} precision={2} />
|
||||||
|
</ProCard>
|
||||||
|
<ProCard layout="center">
|
||||||
|
<Statistic title="可退款金额" value={800} precision={2} />
|
||||||
|
</ProCard>
|
||||||
|
</ProCard.Group>
|
||||||
|
<ProCard title="售后选项">
|
||||||
|
<ProForm
|
||||||
|
formRef={formRefSales}
|
||||||
|
layout="horizontal"
|
||||||
|
submitter={false}
|
||||||
|
labelCol={{ style: { width: '80px' } }}
|
||||||
|
labelAlign="right"
|
||||||
|
>
|
||||||
|
<ProFormRadio.Group
|
||||||
|
name="cancelReason"
|
||||||
|
label="售后类型"
|
||||||
|
rules={[{ required: true, message: '请选择售后类型' }]}
|
||||||
|
options={[{ value: '仅退款', label: '仅退款' }]}
|
||||||
|
radioType="button"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ProFormRadio.Group
|
||||||
|
name="cancelRemark"
|
||||||
|
label="退款类型"
|
||||||
|
rules={[{ required: true, message: '请选择退款类型' }]}
|
||||||
|
options={[
|
||||||
|
{ value: '整单退款(通用)', label: '整单退款' },
|
||||||
|
{ value: '选项二', label: '商品退款' },
|
||||||
|
{ value: '选项三', label: '扩展服务退款' },
|
||||||
|
{ value: '服务附加费退款', label: '服务附加费退款' },
|
||||||
|
{ value: '选项五', label: '退运费' },
|
||||||
|
{ value: '其他', label: '其他' },
|
||||||
|
]}
|
||||||
|
radioType="button"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ConditionalFields />
|
||||||
|
</ProForm>
|
||||||
|
</ProCard>
|
||||||
|
|
||||||
|
<ProCard title="申请信息(通用表单样式,根据当前订单状态调用不同的申请原因表单)">
|
||||||
|
<BetaSchemaForm
|
||||||
|
layoutType="Form"
|
||||||
|
formRef={formRef}
|
||||||
|
columns={applyColumns || []}
|
||||||
|
layout="horizontal"
|
||||||
|
submitter={false}
|
||||||
|
labelCol={{ style: { width: '80px' } }}
|
||||||
|
labelAlign="right"
|
||||||
|
/>
|
||||||
|
</ProCard>
|
||||||
|
<ProCard split="vertical" headerBordered title="预估退款明细">
|
||||||
|
<ProCard
|
||||||
|
colSpan={'50%'}
|
||||||
|
headerBordered
|
||||||
|
title="预估退款明细"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<Space align="center" style={{ marginBottom: 16 }}>
|
||||||
|
<div>
|
||||||
|
<Text type="danger">最高退款金额</Text>
|
||||||
|
<Text type="secondary">(不含虚拟资产)</Text>
|
||||||
|
</div>
|
||||||
|
<ProFormDigit
|
||||||
|
name="input-number"
|
||||||
|
min={1}
|
||||||
|
max={10}
|
||||||
|
fieldProps={{
|
||||||
|
precision: 0,
|
||||||
|
prefix: '¥',
|
||||||
|
style: { width: '100px', margin: 0 },
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary">
|
||||||
|
退款金额上限 <Text type="danger">¥700</Text>
|
||||||
|
,金额不支持修改,优惠券、积分、余额等虚拟资产不支持修改,虚拟资产将随退款而原路退回,具体金额以实际情况为准。
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<Divider />
|
||||||
|
<Title level={5}>预估退款项</Title>
|
||||||
|
<Space direction="vertical" size={8} style={{ width: '100%' }}>
|
||||||
|
<Badge
|
||||||
|
status="processing"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
text={
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '90%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>商品/服务</Text>
|
||||||
|
<Text>合计¥500</Text>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Badge
|
||||||
|
status="processing"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
text={
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '90%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>增值/扩展服务</Text>
|
||||||
|
<Text>合计¥500</Text>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Badge
|
||||||
|
status="processing"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
text={
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '90%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>运费</Text>
|
||||||
|
<Text>合计¥500</Text>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</ProCard>
|
||||||
|
<ProCard
|
||||||
|
size="small"
|
||||||
|
headerBordered
|
||||||
|
title="预估退款至"
|
||||||
|
colSpan={'50%'}
|
||||||
|
>
|
||||||
|
<Title level={5}>
|
||||||
|
预估退款项
|
||||||
|
<Text type="secondary">(退款方式:原支付方式返还)</Text>
|
||||||
|
</Title>
|
||||||
|
<Space direction="vertical" size={8} style={{ width: '100%' }}>
|
||||||
|
<Badge
|
||||||
|
status="processing"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
text={
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '90%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>
|
||||||
|
微信支付
|
||||||
|
<Text type="secondary">(建设银行 尾号1626)</Text>
|
||||||
|
</Text>
|
||||||
|
<Text>¥500</Text>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Badge
|
||||||
|
status="processing"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
text={
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '90%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>
|
||||||
|
钱包余额
|
||||||
|
<Text type="secondary">(个人钱包 编号1090)</Text>
|
||||||
|
</Text>
|
||||||
|
<Text>¥500</Text>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Badge
|
||||||
|
status="processing"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
text={
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '90%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>宠豆豆(积分)</Text>
|
||||||
|
<Text>¥500</Text>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Badge
|
||||||
|
status="processing"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
text={
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '90%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>红包</Text>
|
||||||
|
<Text>¥500</Text>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
<Divider />
|
||||||
|
<Title level={5}>预估扣除项</Title>
|
||||||
|
<Text type="secondary">
|
||||||
|
如果后续有设计退款时扣除比例的设定才会存在此字段。目前暂时不做这个业务逻辑,仅为布局占位。
|
||||||
|
</Text>
|
||||||
|
<Space direction="vertical" size={8} style={{ width: '100%' }}>
|
||||||
|
<Badge
|
||||||
|
status="processing"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
text={
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '90%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>
|
||||||
|
违约金
|
||||||
|
<Text type="secondary">(服务开始前60分钟内退款)</Text>
|
||||||
|
</Text>
|
||||||
|
<Text>¥500</Text>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</ProCard>
|
||||||
|
</ProCard>
|
||||||
|
</Space>
|
||||||
|
</CommonModal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
type DataSourceType = {
|
||||||
|
id: React.Key;
|
||||||
|
prod?: string;
|
||||||
|
num?: number;
|
||||||
|
state?: string;
|
||||||
|
created_at?: number;
|
||||||
|
children?: DataSourceType[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const columnsSale: ProColumns<DataSourceType>[] = [
|
||||||
|
{
|
||||||
|
title: '商品',
|
||||||
|
dataIndex: 'prod',
|
||||||
|
editable: () => false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '退款信息',
|
||||||
|
key: 'refund',
|
||||||
|
editable: () => false,
|
||||||
|
dataIndex: 'state',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '退款数量',
|
||||||
|
dataIndex: 'num',
|
||||||
|
valueType: 'digit',
|
||||||
|
editable: () => true,
|
||||||
|
formItemProps: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入退款数量',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const defaultData: DataSourceType[] = new Array(20).fill(1).map((_, index) => {
|
||||||
|
return {
|
||||||
|
id: (Date.now() + index).toString(),
|
||||||
|
prod: `活动名称${index}`,
|
||||||
|
num: index,
|
||||||
|
state: 'open',
|
||||||
|
created_at: 1590486176000,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const ExtendRefunds = (props: {
|
||||||
|
onChange?: (value: readonly DataSourceType[]) => void;
|
||||||
|
}) => {
|
||||||
|
const [data, setData] = useState<readonly DataSourceType[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const editorFormRef = useRef<EditableFormInstance<DataSourceType>>(null);
|
||||||
|
const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
|
||||||
|
const [formformSales] = Form.useForm();
|
||||||
|
const [formExtend] = Form.useForm();
|
||||||
|
const [formServeExtFee] = Form.useForm();
|
||||||
|
const [formShippingFee] = Form.useForm();
|
||||||
|
const fetchData = async () => {
|
||||||
|
await waitTime();
|
||||||
|
setData(defaultData);
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
useEffect(() => {
|
||||||
|
props.onChange?.(data);
|
||||||
|
}, [data, props.onChange]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setEditableRowKeys(data!.map((item) => item.id));
|
||||||
|
}, [data]);
|
||||||
|
console.log(editableKeys);
|
||||||
|
return (
|
||||||
|
<EditableProTable
|
||||||
|
value={defaultData}
|
||||||
|
columns={columnsSale}
|
||||||
|
rowKey="id"
|
||||||
|
recordCreatorProps={false}
|
||||||
|
editable={{
|
||||||
|
// form: formformSales,
|
||||||
|
editableKeys: editableKeys,
|
||||||
|
onChange: setEditableRowKeys,
|
||||||
|
onValuesChange: (record, recordList) => {
|
||||||
|
setData(recordList);
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
// <EditableProTableCom<DataSourceType>
|
||||||
|
// columns={columnsSale}
|
||||||
|
// value={defaultData}
|
||||||
|
// rowKey="id"
|
||||||
|
// rowSelection={{
|
||||||
|
// selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],
|
||||||
|
// }}
|
||||||
|
// editable={{
|
||||||
|
// form: formformSales,
|
||||||
|
// editableKeys: editableKeys,
|
||||||
|
// onChange: setEditableRowKeys,
|
||||||
|
// onValuesChange: (record, recordList) => {
|
||||||
|
// setData(recordList);
|
||||||
|
// },
|
||||||
|
// }}
|
||||||
|
// />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
// const ExtendRefunds = (props: {
|
||||||
|
// onChange?: (value: readonly DataSourceType[]) => void;
|
||||||
|
// }) => {
|
||||||
|
// const [data, setData] = useState<readonly DataSourceType[]>([]);
|
||||||
|
// const [loading, setLoading] = useState(true);
|
||||||
|
// const editorFormRef = useRef<EditableFormInstance<DataSourceType>>(null);
|
||||||
|
// const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
|
||||||
|
// const [formformSales] = Form.useForm();
|
||||||
|
// const [formExtend] = Form.useForm();
|
||||||
|
// const [formServeExtFee] = Form.useForm();
|
||||||
|
// const [formShippingFee] = Form.useForm();
|
||||||
|
|
||||||
|
// const fetchData = async () => {
|
||||||
|
// await waitTime();
|
||||||
|
// setData(defaultData);
|
||||||
|
// setLoading(false);
|
||||||
|
// };
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// fetchData();
|
||||||
|
// }, []);
|
||||||
|
// useEffect(() => {
|
||||||
|
// props.onChange?.(data);
|
||||||
|
// }, [data]);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// setEditableRowKeys(data!.map((item) => item.id));
|
||||||
|
// console.log(data);
|
||||||
|
// }, [data]);
|
||||||
|
|
||||||
|
// const renderContent = useCallback(() => {
|
||||||
|
// // if (data.length === 0 && !loading) {
|
||||||
|
// // return (
|
||||||
|
// // <div>
|
||||||
|
// // <Empty
|
||||||
|
// // description="订单无此退款项/类型"
|
||||||
|
// // style={{
|
||||||
|
// // background: "#fff",
|
||||||
|
// // padding: "16px",
|
||||||
|
// // margin: 0,
|
||||||
|
// // borderRadius: "4px",
|
||||||
|
// // }}
|
||||||
|
// // />
|
||||||
|
// // </div>);
|
||||||
|
// // }
|
||||||
|
// return (
|
||||||
|
// <Spin spinning={loading} >
|
||||||
|
// <EditableProTable<DataSourceType>
|
||||||
|
// columns={columnsSale}
|
||||||
|
// rowKey="id"
|
||||||
|
// rowSelection={{
|
||||||
|
// selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],
|
||||||
|
// }}
|
||||||
|
// value={data}
|
||||||
|
// editable={{
|
||||||
|
// form: formformSales,
|
||||||
|
// editableKeys: data.map((item) => item.id),
|
||||||
|
// onChange: setEditableRowKeys,
|
||||||
|
// onValuesChange: (record, recordList) => {
|
||||||
|
// setData(recordList);
|
||||||
|
// },
|
||||||
|
// }}
|
||||||
|
// />
|
||||||
|
// {/* <EditableProTableCom<DataSourceType>
|
||||||
|
// columns={columnsSale}
|
||||||
|
// rowKey="id"
|
||||||
|
// value={data}
|
||||||
|
// key={"sale"}
|
||||||
|
// // onChange={onTableChange}
|
||||||
|
// rowSelection={{
|
||||||
|
// selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],
|
||||||
|
// }}
|
||||||
|
// editable={{
|
||||||
|
// form: formformSales,
|
||||||
|
// editableKeys: data.map((item) => item.id),
|
||||||
|
// onChange: setEditableRowKeys,
|
||||||
|
// // onValuesChange: (record, recordList) => {
|
||||||
|
// // setData(recordList);
|
||||||
|
// // },
|
||||||
|
// }}
|
||||||
|
// />
|
||||||
|
// </ProCard>
|
||||||
|
// <ProCard title="退款项">
|
||||||
|
// <EditableProTableCom<DataSourceType>
|
||||||
|
// columns={columnsSale}
|
||||||
|
// rowKey="id"
|
||||||
|
// value={data}
|
||||||
|
// // onChange={onTableChange}
|
||||||
|
// rowSelection={{
|
||||||
|
// selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],
|
||||||
|
// }}
|
||||||
|
// key={"sale1"}
|
||||||
|
// editable={{
|
||||||
|
// form: formExtend,
|
||||||
|
// editableKeys,
|
||||||
|
// onChange: setEditableRowKeys,
|
||||||
|
// // onValuesChange: (record, recordList) => {
|
||||||
|
// // setData(recordList);
|
||||||
|
// // },
|
||||||
|
// }}
|
||||||
|
// />
|
||||||
|
// </ProCard>
|
||||||
|
// <ProCard>
|
||||||
|
// <EditableProTableCom<DataSourceType>
|
||||||
|
// columns={columns}
|
||||||
|
// rowKey="id"
|
||||||
|
// value={data}
|
||||||
|
// // onChange={onTableChange}
|
||||||
|
// rowSelection={{
|
||||||
|
// selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],
|
||||||
|
// }}
|
||||||
|
// key={"sale2"}
|
||||||
|
// editable={{
|
||||||
|
// form: formServeExtFee,
|
||||||
|
// editableKeys,
|
||||||
|
// onChange: setEditableRowKeys,
|
||||||
|
// // onValuesChange: (record, recordList) => {
|
||||||
|
// // setData(recordList);
|
||||||
|
// // },
|
||||||
|
// }}
|
||||||
|
// />
|
||||||
|
// </ProCard>
|
||||||
|
// <ProCard>
|
||||||
|
// <EditableProTableCom<DataSourceType>
|
||||||
|
// columns={columns}
|
||||||
|
// rowKey="id"
|
||||||
|
// value={data}
|
||||||
|
// key={"sale4"}
|
||||||
|
// onChange={onTableChange}
|
||||||
|
// rowSelection={{
|
||||||
|
// selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],
|
||||||
|
// }}
|
||||||
|
// editable={{
|
||||||
|
// form: formShippingFee,
|
||||||
|
// editableKeys,
|
||||||
|
// onChange: setEditableRowKeys,
|
||||||
|
// // onValuesChange: (record, recordList) => {
|
||||||
|
// // setData(recordList);
|
||||||
|
// // },
|
||||||
|
// }}
|
||||||
|
// />
|
||||||
|
// </ProCard> */}
|
||||||
|
// </Spin>
|
||||||
|
// );
|
||||||
|
// }, [loading, data]);
|
||||||
|
// return <div key="salcessw"> {renderContent()}</div>;
|
||||||
|
// };
|
||||||
|
|
||||||
|
export default CreateSalesModal;
|
||||||
|
|
||||||
|
const ConditionalFields: React.FC = () => {
|
||||||
|
const cancelRemark = Form.useWatch('cancelRemark', { preserve: true });
|
||||||
|
|
||||||
|
if (cancelRemark === '其他') {
|
||||||
|
return (
|
||||||
|
<ProFormTextArea
|
||||||
|
name="cancelRemarkText"
|
||||||
|
label=""
|
||||||
|
placeholder="请输入"
|
||||||
|
rules={[{ required: true, message: '请输入内容' }]}
|
||||||
|
fieldProps={{
|
||||||
|
rows: 4,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cancelRemark === '服务附加费退款') {
|
||||||
|
return (
|
||||||
|
<ProForm.Item name="extendRefunds" label="">
|
||||||
|
<ExtendRefunds />
|
||||||
|
</ProForm.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
609
src/pages/trade/order/components/createSalesModal.tsx
Normal file
609
src/pages/trade/order/components/createSalesModal.tsx
Normal file
@@ -0,0 +1,609 @@
|
|||||||
|
import {
|
||||||
|
BetaSchemaForm,
|
||||||
|
ProCard,
|
||||||
|
type ProFormColumnsType,
|
||||||
|
} from '@ant-design/pro-components';
|
||||||
|
import {
|
||||||
|
Badge,
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
InputNumber,
|
||||||
|
message,
|
||||||
|
Radio,
|
||||||
|
Space,
|
||||||
|
Statistic,
|
||||||
|
Table,
|
||||||
|
Typography,
|
||||||
|
} from 'antd';
|
||||||
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
import CommonModal from '@/components/ModalCom/CommonModal';
|
||||||
|
import styles from './createSales.module.less';
|
||||||
|
|
||||||
|
const { Title, Text } = Typography;
|
||||||
|
|
||||||
|
type DataSourceType = {
|
||||||
|
id: React.Key;
|
||||||
|
prod: string;
|
||||||
|
num: number;
|
||||||
|
state: string;
|
||||||
|
availableNum: number; // 新增:可退款数量
|
||||||
|
};
|
||||||
|
|
||||||
|
const CreateSalesModal = () => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [formSales] = Form.useForm();
|
||||||
|
const [formApply] = Form.useForm();
|
||||||
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
|
const [cancelRemarkType, setCancelRemarkType] = useState<string>('');
|
||||||
|
|
||||||
|
const handleOk = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
const values1 = await formApply.validateFields();
|
||||||
|
const values2 = await formSales.validateFields();
|
||||||
|
|
||||||
|
console.log('申请信息:', values1);
|
||||||
|
console.log('售后信息:', values2);
|
||||||
|
console.log('表格数据:', values2.extendRefunds);
|
||||||
|
|
||||||
|
message.success('提交成功');
|
||||||
|
onOpen(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('表单验证失败:', error);
|
||||||
|
message.error('请完善表单信息');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [formApply, formSales]);
|
||||||
|
|
||||||
|
const onOpen = useCallback(
|
||||||
|
(flag: boolean) => {
|
||||||
|
setOpen(flag);
|
||||||
|
if (!flag) {
|
||||||
|
setCancelRemarkType('');
|
||||||
|
formSales.resetFields();
|
||||||
|
formApply.resetFields();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[formSales, formApply],
|
||||||
|
);
|
||||||
|
|
||||||
|
const applyColumns: ProFormColumnsType[] = [
|
||||||
|
{
|
||||||
|
title: '售后类型',
|
||||||
|
dataIndex: 'cancelReason1',
|
||||||
|
valueType: 'radio',
|
||||||
|
formItemProps: {
|
||||||
|
rules: [{ required: true }],
|
||||||
|
},
|
||||||
|
fieldProps: {
|
||||||
|
options: [
|
||||||
|
{ value: '用户主动取消', label: '用户主动取消' },
|
||||||
|
{ value: '选项二', label: '选项二' },
|
||||||
|
{ value: '选项三', label: '选项三' },
|
||||||
|
{ value: '选项四', label: '选项四' },
|
||||||
|
{ value: '选项五', label: '选项五' },
|
||||||
|
{ value: '选项六', label: '选项六' },
|
||||||
|
{ value: '选项七', label: '选项七' },
|
||||||
|
],
|
||||||
|
style: {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '8px',
|
||||||
|
marginTop: '6px',
|
||||||
|
background: '#f0f0f0',
|
||||||
|
padding: '8px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '问题描述',
|
||||||
|
dataIndex: 'cancelRemark22',
|
||||||
|
valueType: 'textarea',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '商家备注',
|
||||||
|
dataIndex: 'cancelRemark2311',
|
||||||
|
valueType: 'textarea',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button onClick={() => onOpen(true)}>新建售后</Button>
|
||||||
|
<CommonModal
|
||||||
|
title="新建售后单"
|
||||||
|
open={open}
|
||||||
|
onOk={handleOk}
|
||||||
|
onCancel={() => onOpen(false)}
|
||||||
|
confirmLoading={loading}
|
||||||
|
width={1000}
|
||||||
|
>
|
||||||
|
<Space
|
||||||
|
direction="vertical"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
size={16}
|
||||||
|
className={styles.sales}
|
||||||
|
>
|
||||||
|
{/* 统计卡片 */}
|
||||||
|
<ProCard.Group direction="row">
|
||||||
|
<ProCard layout="center">
|
||||||
|
<Statistic title="实付金额" value={1000} precision={2} />
|
||||||
|
</ProCard>
|
||||||
|
<ProCard layout="center">
|
||||||
|
<Statistic title="累计退款金额" value={200} precision={2} />
|
||||||
|
</ProCard>
|
||||||
|
<ProCard layout="center">
|
||||||
|
<Statistic title="可退款金额" value={800} precision={2} />
|
||||||
|
</ProCard>
|
||||||
|
</ProCard.Group>
|
||||||
|
|
||||||
|
{/* 售后选项 */}
|
||||||
|
<ProCard title="售后选项">
|
||||||
|
<Form
|
||||||
|
form={formSales}
|
||||||
|
layout="horizontal"
|
||||||
|
labelCol={{ style: { width: '80px' } }}
|
||||||
|
labelAlign="right"
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="cancelReason"
|
||||||
|
label="售后类型"
|
||||||
|
rules={[{ required: true, message: '请选择售后类型' }]}
|
||||||
|
>
|
||||||
|
<Radio.Group buttonStyle="solid">
|
||||||
|
<Radio.Button value="仅退款">仅退款</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
name="cancelRemark"
|
||||||
|
label="退款类型"
|
||||||
|
rules={[{ required: true, message: '请选择退款类型' }]}
|
||||||
|
>
|
||||||
|
<Radio.Group
|
||||||
|
buttonStyle="solid"
|
||||||
|
onChange={(e) => {
|
||||||
|
setCancelRemarkType(e.target.value);
|
||||||
|
// 切换类型时清空相关字段
|
||||||
|
formSales.setFieldsValue({
|
||||||
|
cancelRemarkText: undefined,
|
||||||
|
extendRefunds: undefined,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Radio.Button value="整单退款(通用)">整单退款</Radio.Button>
|
||||||
|
<Radio.Button value="选项二">商品退款</Radio.Button>
|
||||||
|
<Radio.Button value="选项三">扩展服务退款</Radio.Button>
|
||||||
|
<Radio.Button value="服务附加费退款">
|
||||||
|
服务附加费退款
|
||||||
|
</Radio.Button>
|
||||||
|
<Radio.Button value="选项五">退运费</Radio.Button>
|
||||||
|
<Radio.Button value="其他">其他</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
{cancelRemarkType === '其他' && (
|
||||||
|
<Form.Item
|
||||||
|
name="cancelRemarkText"
|
||||||
|
label=""
|
||||||
|
rules={[{ required: true, message: '请输入内容' }]}
|
||||||
|
labelCol={{ style: { width: '80px' } }}
|
||||||
|
>
|
||||||
|
<Input.TextArea placeholder="请输入" rows={4} />
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{cancelRemarkType === '服务附加费退款' && (
|
||||||
|
<Form.Item
|
||||||
|
name="extendRefunds"
|
||||||
|
label=""
|
||||||
|
labelCol={{ style: { width: '80px' } }}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
validator: (_, value) => {
|
||||||
|
if (!value || value.length === 0) {
|
||||||
|
return Promise.reject(
|
||||||
|
new Error('请至少选择一项服务附加费'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<ExtendRefundsTable />
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
</Form>
|
||||||
|
</ProCard>
|
||||||
|
|
||||||
|
{/* 申请信息 */}
|
||||||
|
<ProCard title="申请信息(通用表单样式,根据当前订单状态调用不同的申请原因表单)">
|
||||||
|
<BetaSchemaForm
|
||||||
|
layoutType="Form"
|
||||||
|
form={formApply}
|
||||||
|
columns={applyColumns}
|
||||||
|
layout="horizontal"
|
||||||
|
submitter={false}
|
||||||
|
labelCol={{ style: { width: '80px' } }}
|
||||||
|
labelAlign="right"
|
||||||
|
/>
|
||||||
|
</ProCard>
|
||||||
|
|
||||||
|
{/* 预估退款明细 */}
|
||||||
|
<ProCard split="vertical" headerBordered title="预估退款明细">
|
||||||
|
<ProCard
|
||||||
|
colSpan={'50%'}
|
||||||
|
headerBordered
|
||||||
|
title="预估退款明细"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<Space align="center" style={{ marginBottom: 16 }}>
|
||||||
|
<div>
|
||||||
|
<Text type="danger">最高退款金额</Text>
|
||||||
|
<Text type="secondary">(不含虚拟资产)</Text>
|
||||||
|
</div>
|
||||||
|
<InputNumber
|
||||||
|
min={1}
|
||||||
|
max={10000}
|
||||||
|
precision={0}
|
||||||
|
prefix="¥"
|
||||||
|
style={{ width: '120px' }}
|
||||||
|
defaultValue={700}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary">
|
||||||
|
退款金额上限 <Text type="danger">¥700</Text>
|
||||||
|
,金额不支持修改,优惠券、积分、余额等虚拟资产不支持修改,虚拟资产将随退款而原路退回,具体金额以实际情况为准。
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<Divider />
|
||||||
|
<Title level={5}>预估退款项</Title>
|
||||||
|
<Space direction="vertical" size={8} style={{ width: '100%' }}>
|
||||||
|
<Badge
|
||||||
|
status="processing"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
text={
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '90%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>商品/服务</Text>
|
||||||
|
<Text>合计¥500</Text>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Badge
|
||||||
|
status="processing"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
text={
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '90%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>增值/扩展服务</Text>
|
||||||
|
<Text>合计¥500</Text>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Badge
|
||||||
|
status="processing"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
text={
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '90%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>运费</Text>
|
||||||
|
<Text>合计¥500</Text>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</ProCard>
|
||||||
|
<ProCard
|
||||||
|
size="small"
|
||||||
|
headerBordered
|
||||||
|
title="预估退款至"
|
||||||
|
colSpan={'50%'}
|
||||||
|
>
|
||||||
|
<Title level={5}>
|
||||||
|
预估退款项
|
||||||
|
<Text type="secondary">(退款方式:原支付方式返还)</Text>
|
||||||
|
</Title>
|
||||||
|
<Space direction="vertical" size={8} style={{ width: '100%' }}>
|
||||||
|
<Badge
|
||||||
|
status="processing"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
text={
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '90%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>
|
||||||
|
微信支付
|
||||||
|
<Text type="secondary">(建设银行 尾号1626)</Text>
|
||||||
|
</Text>
|
||||||
|
<Text>¥500</Text>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Badge
|
||||||
|
status="processing"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
text={
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '90%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>
|
||||||
|
钱包余额
|
||||||
|
<Text type="secondary">(个人钱包 编号1090)</Text>
|
||||||
|
</Text>
|
||||||
|
<Text>¥500</Text>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Badge
|
||||||
|
status="processing"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
text={
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '90%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>宠豆豆(积分)</Text>
|
||||||
|
<Text>¥500</Text>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Badge
|
||||||
|
status="processing"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
text={
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '90%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>红包</Text>
|
||||||
|
<Text>¥500</Text>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
<Divider />
|
||||||
|
<Title level={5}>预估扣除项</Title>
|
||||||
|
<Text type="secondary">
|
||||||
|
如果后续有设计退款时扣除比例的设定才会存在此字段。目前暂时不做这个业务逻辑,仅为布局占位。
|
||||||
|
</Text>
|
||||||
|
<Space direction="vertical" size={8} style={{ width: '100%' }}>
|
||||||
|
<Badge
|
||||||
|
status="processing"
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
text={
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
width: '90%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>
|
||||||
|
违约金
|
||||||
|
<Text type="secondary">(服务开始前60分钟内退款)</Text>
|
||||||
|
</Text>
|
||||||
|
<Text>¥500</Text>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</ProCard>
|
||||||
|
</ProCard>
|
||||||
|
</Space>
|
||||||
|
</CommonModal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ExtendRefundsTable: React.FC<{
|
||||||
|
value?: DataSourceType[];
|
||||||
|
onChange?: (value: DataSourceType[]) => void;
|
||||||
|
}> = ({ value, onChange }) => {
|
||||||
|
const [dataSource, setDataSource] = useState<DataSourceType[]>([]);
|
||||||
|
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||||
|
|
||||||
|
// 初始化数据 - 默认值为 1,并设置可退款数量
|
||||||
|
useEffect(() => {
|
||||||
|
const initData: DataSourceType[] = Array.from({ length: 10 }).map(
|
||||||
|
(_, index) => ({
|
||||||
|
id: `service_${index}`,
|
||||||
|
prod: `服务附加费项目${index + 1}`,
|
||||||
|
num: 1,
|
||||||
|
state: '待退款',
|
||||||
|
availableNum: index % 3 === 0 ? 0 : Math.floor(Math.random() * 10) + 1, // 模拟数据,每3个有一个为0
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
setDataSource(initData);
|
||||||
|
// 初始化时通知父组件,只选中可退款数量 > 0 的项
|
||||||
|
const validItems = initData.filter((item) => item.availableNum > 0);
|
||||||
|
const validKeys = validItems.map((item) => item.id);
|
||||||
|
notifyChange(initData, validKeys);
|
||||||
|
setSelectedRowKeys(validKeys);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 当外部 value 变化时更新
|
||||||
|
useEffect(() => {
|
||||||
|
if (value && value.length > 0) {
|
||||||
|
// 更新选中状态,只保留可退款数量 > 0 的项
|
||||||
|
setSelectedRowKeys((prev) =>
|
||||||
|
prev.filter((key) => {
|
||||||
|
const item = value.find((v) => v.id === key);
|
||||||
|
return item && item.availableNum > 0;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
// 通知父组件数据变化(包含选中信息)
|
||||||
|
const notifyChange = (data: DataSourceType[], selected: React.Key[]) => {
|
||||||
|
// 只返回选中的项
|
||||||
|
const selectedData = data.filter((item) => selected.includes(item.id));
|
||||||
|
onChange?.(selectedData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNumChange = (id: React.Key, num: number | null) => {
|
||||||
|
const record = dataSource.find((item) => item.id === id);
|
||||||
|
if (!record) return;
|
||||||
|
|
||||||
|
// 如果可退款数量为0,不允许修改
|
||||||
|
if (record.availableNum <= 0) return;
|
||||||
|
|
||||||
|
const finalNum = num ?? 1;
|
||||||
|
// 不能超过可退款数量
|
||||||
|
const validNum = Math.min(finalNum, record.availableNum);
|
||||||
|
|
||||||
|
const newData = dataSource.map((item) =>
|
||||||
|
item.id === id ? { ...item, num: validNum } : item,
|
||||||
|
);
|
||||||
|
setDataSource(newData);
|
||||||
|
|
||||||
|
// 如果数量变为 0 或负数,取消选中
|
||||||
|
let newSelectedKeys = selectedRowKeys;
|
||||||
|
if (validNum <= 0) {
|
||||||
|
newSelectedKeys = selectedRowKeys.filter((key) => key !== id);
|
||||||
|
setSelectedRowKeys(newSelectedKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyChange(newData, newSelectedKeys);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 判断是否有错误
|
||||||
|
const isInvalid = (num: number | undefined | null): boolean => {
|
||||||
|
if (num === undefined || num === null) return true;
|
||||||
|
if (typeof num !== 'number') return true;
|
||||||
|
if (num <= 0) return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '服务项目',
|
||||||
|
dataIndex: 'prod',
|
||||||
|
key: 'prod',
|
||||||
|
width: '30%',
|
||||||
|
ellipsis: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '退款信息',
|
||||||
|
dataIndex: 'state',
|
||||||
|
key: 'state',
|
||||||
|
width: '20%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '可退款数量',
|
||||||
|
dataIndex: 'availableNum',
|
||||||
|
key: 'availableNum',
|
||||||
|
width: '20%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: <span style={{ color: 'red' }}>* 退款数量</span>,
|
||||||
|
dataIndex: 'num',
|
||||||
|
key: 'num',
|
||||||
|
width: '30%',
|
||||||
|
render: (value: number, record: DataSourceType) => {
|
||||||
|
const hasError = isInvalid(value);
|
||||||
|
const isDisabled = record.availableNum <= 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<InputNumber
|
||||||
|
min={1}
|
||||||
|
max={record.availableNum} // 最大值为可退款数量
|
||||||
|
value={value}
|
||||||
|
onChange={(val) => handleNumChange(record.id, val)}
|
||||||
|
disabled={isDisabled}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
}}
|
||||||
|
placeholder={isDisabled ? '不可退款' : '请输入退款数量'}
|
||||||
|
precision={0}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 行选择配置
|
||||||
|
const rowSelection = {
|
||||||
|
selectedRowKeys,
|
||||||
|
onChange: (newSelectedRowKeys: React.Key[]) => {
|
||||||
|
setSelectedRowKeys(newSelectedRowKeys);
|
||||||
|
notifyChange(dataSource, newSelectedRowKeys);
|
||||||
|
},
|
||||||
|
getCheckboxProps: (record: DataSourceType) => ({
|
||||||
|
disabled: record.availableNum <= 0, // 可退款数量为 0 时禁用选中
|
||||||
|
name: record.prod,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginBottom: 8,
|
||||||
|
fontWeight: 500,
|
||||||
|
fontSize: 14,
|
||||||
|
color: '#000',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>服务附加费退款明细</span>
|
||||||
|
<span style={{ fontSize: 12, fontWeight: 'normal', color: '#666' }}>
|
||||||
|
已选择 {selectedRowKeys.length} 项
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Table
|
||||||
|
rowKey="id"
|
||||||
|
dataSource={dataSource}
|
||||||
|
columns={columns}
|
||||||
|
rowSelection={rowSelection}
|
||||||
|
size="small"
|
||||||
|
bordered
|
||||||
|
scroll={{ y: 300 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default CreateSalesModal;
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Tabs, type TabsProps } from 'antd';
|
import { Tabs, type TabsProps } from 'antd';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { TradeOrderPageRespVO } from '@/services/trade/order';
|
import type { TradeOrderPageRespVO } from '@/services/trade/order';
|
||||||
|
import { SalesTable } from '../../sales/list';
|
||||||
import OrderInfo from './order-info';
|
import OrderInfo from './order-info';
|
||||||
|
import SalesRefunds from './src/sales-refunds';
|
||||||
|
|
||||||
const DetailCom: React.FC<{ data?: TradeOrderPageRespVO }> = (props) => {
|
const DetailCom: React.FC<{ data?: TradeOrderPageRespVO }> = (props) => {
|
||||||
const items: TabsProps['items'] = [
|
const items: TabsProps['items'] = [
|
||||||
@@ -20,6 +22,11 @@ const DetailCom: React.FC<{ data?: TradeOrderPageRespVO }> = (props) => {
|
|||||||
label: '商品配送',
|
label: '商品配送',
|
||||||
children: '商品配送',
|
children: '商品配送',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: '4',
|
||||||
|
label: '售后与退款',
|
||||||
|
children: <SalesRefunds orderStatus={props.data?.orderStatus} />,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
return <Tabs defaultActiveKey="1" items={items} destroyOnHidden />;
|
return <Tabs defaultActiveKey="1" items={items} destroyOnHidden />;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -39,12 +39,12 @@ const OrderDetail: React.FC<{ data?: TradeOrderPageRespVO }> = (props) => {
|
|||||||
return (
|
return (
|
||||||
<Space direction="vertical" size={24} style={{ width: '100%' }}>
|
<Space direction="vertical" size={24} style={{ width: '100%' }}>
|
||||||
<BasicInfo data={detais} loading={loading} />
|
<BasicInfo data={detais} loading={loading} />
|
||||||
<ProdInfo data={detais?.items} />
|
<ProdInfo data={detais?.items} loading={loading} />
|
||||||
{detais?.tradeServeInfo && (
|
{detais?.tradeServeInfo && (
|
||||||
<ServiceInfo
|
<ServiceInfo
|
||||||
data={detais.tradeServeInfo}
|
data={detais.tradeServeInfo}
|
||||||
orderCategoryId={detais?.orderCategoryId}
|
orderCategoryId={detais?.orderCategoryId}
|
||||||
id={detais?.id}
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{detais?.tradeExtendServeInfo && (
|
{detais?.tradeExtendServeInfo && (
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const sharedOnCell = (_: DeptVO, index?: number) => {
|
|||||||
export const surchargeInfoColumns: ProColumns<DeptVO>[] = [
|
export const surchargeInfoColumns: ProColumns<DeptVO>[] = [
|
||||||
{
|
{
|
||||||
title: '服务附加费',
|
title: '服务附加费',
|
||||||
dataIndex: 'name',
|
dataIndex: 'serveExtFee',
|
||||||
width: '33.33%',
|
width: '33.33%',
|
||||||
render: (_, record, index) => {
|
render: (_, record, index) => {
|
||||||
if (index === 3) {
|
if (index === 3) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ProCard } from '@ant-design/pro-components';
|
import { ProCard } from '@ant-design/pro-components';
|
||||||
import { Button, Card, Image, Modal, Space, Tag, Typography } from 'antd';
|
import { Button, Card, Image, Modal, Space, Spin, Tag, Typography } from 'antd';
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import AdvancedImageGallery from '@/components/AdvancedImageGallery';
|
import AdvancedImageGallery from '@/components/AdvancedImageGallery';
|
||||||
import DangerouslySetInnerHTML from '@/components/DangerouslySetInnerHTML';
|
import DangerouslySetInnerHTML from '@/components/DangerouslySetInnerHTML';
|
||||||
@@ -42,7 +42,7 @@ const ProdInfo: React.FC<ItemConfig<Item[]>> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles['order-info']}>
|
<div className={styles['order-info']}>
|
||||||
<Card title="商品信息">
|
<Card title="商品信息" loading={props.loading}>
|
||||||
{data?.map((item) => (
|
{data?.map((item) => (
|
||||||
<ProCard
|
<ProCard
|
||||||
key={item.id}
|
key={item.id}
|
||||||
@@ -175,14 +175,20 @@ const PhotoModal: React.FC<{
|
|||||||
item?: Item;
|
item?: Item;
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
const [photo, setPhoto] = useState<TradeOrderFastPhotoRespVo>();
|
const [photo, setPhoto] = useState<TradeOrderFastPhotoRespVo>();
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const { item } = props;
|
const { item } = props;
|
||||||
console.log(item, 'item');
|
console.log(item, 'item');
|
||||||
const onPhoto = useCallback(async () => {
|
const onPhoto = useCallback(async () => {
|
||||||
const res = await getfastPhoto({
|
try {
|
||||||
itemId: item?.id as number,
|
setLoading(true);
|
||||||
spuId: item?.spuId as number,
|
const res = await getfastPhoto({
|
||||||
});
|
itemId: item?.id as number,
|
||||||
setPhoto(res);
|
spuId: item?.spuId as number,
|
||||||
|
});
|
||||||
|
setPhoto(res);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
}, [item]);
|
}, [item]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.open && item) {
|
if (props.open && item) {
|
||||||
@@ -198,53 +204,55 @@ const PhotoModal: React.FC<{
|
|||||||
footer={null}
|
footer={null}
|
||||||
width={820}
|
width={820}
|
||||||
>
|
>
|
||||||
<div
|
<Spin spinning={loading}>
|
||||||
style={{
|
|
||||||
height: '480px',
|
|
||||||
width: '100%',
|
|
||||||
overflow: 'hidden',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
gap: '16px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="left" style={{ width: '386px', height: '100%' }}>
|
|
||||||
<AdvancedImageGallery images={photo?.imgs?.split(',') || []} />
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
className="right"
|
style={{
|
||||||
style={{ height: '100%', overflow: 'auto', width: '386px' }}
|
height: '480px',
|
||||||
|
width: '100%',
|
||||||
|
overflow: 'hidden',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
gap: '16px',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Title level={4}>{photo?.spuName}</Title>
|
<div className="left" style={{ width: '386px', height: '100%' }}>
|
||||||
<Paragraph>
|
<AdvancedImageGallery images={photo?.imgs?.split(',') || []} />
|
||||||
<Text type="secondary">{photo?.brief}</Text>
|
|
||||||
</Paragraph>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
marginBottom: '16px',
|
|
||||||
background: '#00000005',
|
|
||||||
padding: '8px',
|
|
||||||
borderRadius: '8px',
|
|
||||||
fontSize: '12px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
当前页面为订单快照,包含订单创建时的商品描述和下单信息,买卖双方和平台在发生交易争议时,将作为判断依据。
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style={{
|
className="right"
|
||||||
marginBottom: '16px',
|
style={{ height: '100%', overflow: 'auto', width: '386px' }}
|
||||||
background: '#00000005',
|
|
||||||
padding: '8px',
|
|
||||||
borderRadius: '8px',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<strong>已选:{photo?.skuName}(已选的SKU规格)</strong>
|
<Title level={4}>{photo?.spuName}</Title>
|
||||||
|
<Paragraph>
|
||||||
|
<Text type="secondary">{photo?.brief}</Text>
|
||||||
|
</Paragraph>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginBottom: '16px',
|
||||||
|
background: '#00000005',
|
||||||
|
padding: '8px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
fontSize: '12px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
当前页面为订单快照,包含订单创建时的商品描述和下单信息,买卖双方和平台在发生交易争议时,将作为判断依据。
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginBottom: '16px',
|
||||||
|
background: '#00000005',
|
||||||
|
padding: '8px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<strong>已选:{photo?.skuName}(已选的SKU规格)</strong>
|
||||||
|
</div>
|
||||||
|
<DangerouslySetInnerHTML content={photo?.content} />
|
||||||
</div>
|
</div>
|
||||||
<DangerouslySetInnerHTML content={photo?.content} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Spin>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,13 @@ import ServicePetUI from './uis/pets/service';
|
|||||||
|
|
||||||
const ServiceInfo: React.FC<ItemConfig<TradeServeInfo>> = (props) => {
|
const ServiceInfo: React.FC<ItemConfig<TradeServeInfo>> = (props) => {
|
||||||
const { data = {}, orderCategoryId, id } = props;
|
const { data = {}, orderCategoryId, id } = props;
|
||||||
return <>{orderCategoryId === 1 && <ServicePetUI data={data} id={id} />}</>; //宠物服务ui
|
return (
|
||||||
|
<>
|
||||||
|
{orderCategoryId === 1 && (
|
||||||
|
<ServicePetUI data={data} id={id} loading={props.loading} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(ServiceInfo);
|
export default React.memo(ServiceInfo);
|
||||||
|
|||||||
22
src/pages/trade/order/detail/src/sales-refunds.tsx
Normal file
22
src/pages/trade/order/detail/src/sales-refunds.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Button, Space, Typography } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { SalesTable } from '@/pages/trade/sales/list';
|
||||||
|
import CreateSalesModal from '../../components/createSalesModal';
|
||||||
|
|
||||||
|
const { Title } = Typography;
|
||||||
|
const SalesRefunds: React.FC<{ orderStatus?: number }> = (props) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Space align="center" style={{ marginBottom: 16 }} size={16}>
|
||||||
|
<Title level={3} style={{ margin: 0 }}>
|
||||||
|
售后与退款
|
||||||
|
</Title>
|
||||||
|
<CreateSalesModal />
|
||||||
|
</Space>
|
||||||
|
|
||||||
|
<SalesTable orderStatus={props?.orderStatus} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(SalesRefunds);
|
||||||
@@ -67,7 +67,7 @@ const OrderListItem: React.FC<{ orderStatus: number }> = (props) => {
|
|||||||
) => {
|
) => {
|
||||||
const data = await getTradeOrderPage({
|
const data = await getTradeOrderPage({
|
||||||
...params,
|
...params,
|
||||||
orderStatus,
|
orderStatus: props?.orderStatus ? props?.orderStatus : undefined,
|
||||||
pageNo: params.current,
|
pageNo: params.current,
|
||||||
pageSize: params.pageSize,
|
pageSize: params.pageSize,
|
||||||
});
|
});
|
||||||
|
|||||||
157
src/pages/trade/sales/config.tsx
Normal file
157
src/pages/trade/sales/config.tsx
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
import type { ProColumns } from '@ant-design/pro-components';
|
||||||
|
import { Badge, Button, Image, Space, Tag, Typography } from 'antd';
|
||||||
|
import type { TradeOrderPageRespVO } from '@/services/trade/order';
|
||||||
|
|
||||||
|
const { Text, Paragraph } = Typography;
|
||||||
|
export const baseOrderColumns: ProColumns<TradeOrderPageRespVO>[] = [
|
||||||
|
{
|
||||||
|
title: '售后服务单',
|
||||||
|
dataIndex: 'items',
|
||||||
|
hideInSearch: true,
|
||||||
|
ellipsis: true,
|
||||||
|
render: (_, record) => (
|
||||||
|
<Space direction="vertical">
|
||||||
|
<Badge status="error" text="等待退款" />
|
||||||
|
<div>
|
||||||
|
<Text type="secondary">售后类型:</Text>
|
||||||
|
<Text>{record?.payPrice || '-'}</Text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary">退款类型:</Text>
|
||||||
|
<Text>{record.payType || '-'}</Text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary">申请原因:</Text>
|
||||||
|
<Text>{record.financeStatus || '-'}</Text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary">申请人员:</Text>
|
||||||
|
<Text>{record.financeStatus || '-'}</Text>
|
||||||
|
</div>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: '售后商品',
|
||||||
|
dataIndex: 'serveAddress',
|
||||||
|
hideInSearch: true,
|
||||||
|
ellipsis: true,
|
||||||
|
render: (_, record) => {
|
||||||
|
if (!record.items) {
|
||||||
|
return _;
|
||||||
|
}
|
||||||
|
return record.items.map((item, index) => (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
style={{ width: '100%', display: 'flex', gap: '8px' }}
|
||||||
|
key={`${index}${Math.random()}`}
|
||||||
|
>
|
||||||
|
<Image src={item.picUrl} width={64} height={64} />
|
||||||
|
<div style={{ flex: '1', overflow: 'hidden' }}>
|
||||||
|
<Paragraph ellipsis style={{ width: '100%', marginBottom: 0 }}>
|
||||||
|
{item.spuName}测试商品名称测试商品名称测试商品名称测试商品名称
|
||||||
|
</Paragraph>
|
||||||
|
<div>{item.skuName}</div>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary">申请数量:</Text>
|
||||||
|
<Text>{item.count || 0}</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button size="small" style={{ marginTop: 10, textAlign: 'right' }}>
|
||||||
|
售后详情
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '申请信息',
|
||||||
|
dataIndex: 'price',
|
||||||
|
hideInSearch: true,
|
||||||
|
render: (_, record) => (
|
||||||
|
<Space direction="vertical">
|
||||||
|
<div>
|
||||||
|
<Text type="secondary">售后类型:</Text>
|
||||||
|
<Text>{record?.payPrice || '-'}</Text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary">退款类型:</Text>
|
||||||
|
<Text>{record.payType || '-'}</Text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary">申请原因:</Text>
|
||||||
|
<Text>{record.financeStatus || '-'}</Text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary">申请人员:</Text>
|
||||||
|
<Text>{record.financeStatus || '-'}</Text>
|
||||||
|
</div>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '退款信息',
|
||||||
|
dataIndex: 'merchantName',
|
||||||
|
hideInSearch: true,
|
||||||
|
render: (_, record) => (
|
||||||
|
<Space direction="vertical">
|
||||||
|
<Badge status="default" text="无退款" />
|
||||||
|
<div>
|
||||||
|
<Text type="secondary">退款金额:</Text>
|
||||||
|
<Text>{record?.payPrice || '-'}</Text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Text type="secondary">退款方式:</Text>
|
||||||
|
<Text>{record.payType || '-'}</Text>
|
||||||
|
</div>
|
||||||
|
<Space>
|
||||||
|
<Button size="small" type="primary">
|
||||||
|
重试退款
|
||||||
|
</Button>
|
||||||
|
<Button size="small" type="primary">
|
||||||
|
审核处理
|
||||||
|
</Button>
|
||||||
|
<Button size="small">转人工退款</Button>
|
||||||
|
<Button size="small">取消申请</Button>
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '申请人姓名、手机或ID',
|
||||||
|
dataIndex: 'userSearch',
|
||||||
|
hideInTable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '售后类型',
|
||||||
|
dataIndex: 'orderCategoryId',
|
||||||
|
valueType: 'select',
|
||||||
|
hideInTable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '退款类型',
|
||||||
|
dataIndex: 'orderTerminal',
|
||||||
|
valueType: 'select',
|
||||||
|
hideInTable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '退款状态',
|
||||||
|
dataIndex: 'financeStatus',
|
||||||
|
valueType: 'select',
|
||||||
|
hideInTable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '申请时间',
|
||||||
|
dataIndex: 'createTime',
|
||||||
|
valueType: 'dateRange',
|
||||||
|
hideInTable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '退款时间',
|
||||||
|
dataIndex: 'subTime',
|
||||||
|
valueType: 'dateRange',
|
||||||
|
hideInTable: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
13
src/pages/trade/sales/index.module.less
Normal file
13
src/pages/trade/sales/index.module.less
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
.sales {
|
||||||
|
:global {
|
||||||
|
.ant-pro-card-header {
|
||||||
|
background: rgba(0, 0, 0, 0.02);
|
||||||
|
min-height: 41px;
|
||||||
|
}
|
||||||
|
.ant-pro-card-col {
|
||||||
|
flex: 1 auto;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,6 +1,24 @@
|
|||||||
// 售后列表
|
// 售后列表
|
||||||
const TradeSales: React.FC = () => {
|
import type { TabsProps } from 'antd';
|
||||||
return 111;
|
import { Tabs } from 'antd';
|
||||||
|
import React from 'react';
|
||||||
|
import { SalesStatusLableMap } from '@/constants/trade';
|
||||||
|
import SalesListItem from './list';
|
||||||
|
|
||||||
|
const TradeSalesList: React.FC = () => {
|
||||||
|
const items: TabsProps['items'] = Object.entries(SalesStatusLableMap).map(
|
||||||
|
([value, label]) => ({
|
||||||
|
key: value,
|
||||||
|
label: label,
|
||||||
|
children: <SalesListItem orderStatus={Number(value)} />,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="page-container">
|
||||||
|
<Tabs defaultActiveKey="1" items={items} destroyOnHidden />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TradeSales;
|
export default TradeSalesList;
|
||||||
|
|||||||
133
src/pages/trade/sales/list.tsx
Normal file
133
src/pages/trade/sales/list.tsx
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import { type ActionType, ProCard } from '@ant-design/pro-components';
|
||||||
|
import { Button, Input, Space, Statistic } from 'antd';
|
||||||
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import EnhancedProTable from '@/components/EnhancedProTable';
|
||||||
|
import { baseOrderColumns } from './config';
|
||||||
|
|
||||||
|
const { Search } = Input;
|
||||||
|
|
||||||
|
import { DownOutlined, UpOutlined, UserOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getTradeOrderPage,
|
||||||
|
getTradeSummary,
|
||||||
|
type TradeOrderPageRespVO,
|
||||||
|
type TradeReq,
|
||||||
|
type TradeSummaryRespVO,
|
||||||
|
} from '@/services/trade/order';
|
||||||
|
|
||||||
|
const OrderListItem: React.FC<{ orderStatus: number }> = (props) => {
|
||||||
|
const { orderStatus } = props;
|
||||||
|
const tableRef = useRef<ActionType>(null);
|
||||||
|
const [isShowTotal, setIsShowTotal] = useState<boolean>(false);
|
||||||
|
const [summary, setSummary] = useState<TradeSummaryRespVO>();
|
||||||
|
const fetchSummary = async () => {
|
||||||
|
const res = await getTradeSummary();
|
||||||
|
setSummary(res);
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
fetchSummary();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleIsTotal = useCallback(() => {
|
||||||
|
setIsShowTotal(!isShowTotal);
|
||||||
|
}, [isShowTotal]);
|
||||||
|
|
||||||
|
const handleSearch = useCallback((value: string) => {
|
||||||
|
console.log('搜索', value);
|
||||||
|
tableRef.current?.reload();
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Space
|
||||||
|
style={{
|
||||||
|
padding: '18px',
|
||||||
|
background: '#fff',
|
||||||
|
marginBottom: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Search
|
||||||
|
placeholder="输入售后单号/关联订单号搜索"
|
||||||
|
enterButton
|
||||||
|
onSearch={handleSearch}
|
||||||
|
style={{ width: 300 }}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon={isShowTotal ? <DownOutlined /> : <UpOutlined />}
|
||||||
|
onClick={handleIsTotal}
|
||||||
|
>
|
||||||
|
统计
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
{isShowTotal && (
|
||||||
|
<ProCard.Group direction="row" style={{ marginBottom: 18 }}>
|
||||||
|
<ProCard layout="center" style={{ background: '#f5f5f5' }}>
|
||||||
|
<Statistic
|
||||||
|
title="售后单数量"
|
||||||
|
value={summary?.orderCount}
|
||||||
|
precision={2}
|
||||||
|
/>
|
||||||
|
</ProCard>
|
||||||
|
<ProCard layout="center" style={{ background: '#f5f5f5' }}>
|
||||||
|
<Statistic
|
||||||
|
title="待退款金额"
|
||||||
|
value={summary?.payPrice}
|
||||||
|
precision={2}
|
||||||
|
/>
|
||||||
|
</ProCard>
|
||||||
|
<ProCard layout="center" style={{ background: '#f5f5f5' }}>
|
||||||
|
<Statistic
|
||||||
|
title="累计退款金额"
|
||||||
|
value={summary?.livePrice}
|
||||||
|
precision={2}
|
||||||
|
/>
|
||||||
|
</ProCard>
|
||||||
|
</ProCard.Group>
|
||||||
|
)}
|
||||||
|
<SalesTable orderStatus={orderStatus} search={true} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SalesTable = (props: {
|
||||||
|
orderStatus?: number;
|
||||||
|
search?: boolean;
|
||||||
|
}) => {
|
||||||
|
const tableRef = useRef<ActionType>(null);
|
||||||
|
const onFetch = async (
|
||||||
|
params: TradeReq & {
|
||||||
|
pageSize: number;
|
||||||
|
current: number;
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
const data = await getTradeOrderPage({
|
||||||
|
...params,
|
||||||
|
orderStatus: props?.orderStatus ? props?.orderStatus : undefined,
|
||||||
|
pageNo: params.current,
|
||||||
|
pageSize: params.pageSize,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: data.list,
|
||||||
|
success: true,
|
||||||
|
total: data.total,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const tabelConfig = {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EnhancedProTable<TradeOrderPageRespVO>
|
||||||
|
ref={tableRef}
|
||||||
|
columns={baseOrderColumns}
|
||||||
|
request={onFetch}
|
||||||
|
headerTitle="售后列表"
|
||||||
|
showIndex={false}
|
||||||
|
showSelection={false}
|
||||||
|
search={props.search ? { defaultCollapsed: true } : false}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(OrderListItem);
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import { getTenantIdByName, login } from "@/services/login";
|
|
||||||
import {
|
import {
|
||||||
AlipayOutlined,
|
AlipayOutlined,
|
||||||
LockOutlined,
|
LockOutlined,
|
||||||
@@ -6,34 +5,36 @@ import {
|
|||||||
TaobaoOutlined,
|
TaobaoOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
WeiboOutlined,
|
WeiboOutlined,
|
||||||
} from "@ant-design/icons";
|
} from '@ant-design/icons';
|
||||||
import {
|
import {
|
||||||
LoginFormPage,
|
LoginFormPage,
|
||||||
ProConfigProvider,
|
ProConfigProvider,
|
||||||
ProFormCaptcha,
|
ProFormCaptcha,
|
||||||
ProFormCheckbox,
|
ProFormCheckbox,
|
||||||
ProFormText,
|
ProFormText,
|
||||||
} from "@ant-design/pro-components";
|
} from '@ant-design/pro-components';
|
||||||
import { history, useModel, useNavigate } from "@umijs/max";
|
import { history, useModel, useNavigate } from '@umijs/max';
|
||||||
import { Button, Divider, Space, Tabs, theme, message } from "antd";
|
import { Button, Divider, message, Space, Tabs, theme } from 'antd';
|
||||||
import type { CSSProperties } from "react";
|
import type { CSSProperties } from 'react';
|
||||||
import { useState } from "react";
|
import { useState } from 'react';
|
||||||
import * as authUtil from "@/utils/auth";
|
import { flushSync } from 'react-dom';
|
||||||
import { flushSync } from "react-dom";
|
import { getTenantIdByName, login } from '@/services/login';
|
||||||
type LoginType = "phone" | "account";
|
import * as authUtil from '@/utils/auth';
|
||||||
|
|
||||||
|
type LoginType = 'phone' | 'account';
|
||||||
|
|
||||||
const iconStyles: CSSProperties = {
|
const iconStyles: CSSProperties = {
|
||||||
color: "rgba(0, 0, 0, 0.2)",
|
color: 'rgba(0, 0, 0, 0.2)',
|
||||||
fontSize: "18px",
|
fontSize: '18px',
|
||||||
verticalAlign: "middle",
|
verticalAlign: 'middle',
|
||||||
cursor: "pointer",
|
cursor: 'pointer',
|
||||||
};
|
};
|
||||||
|
|
||||||
const Page = () => {
|
const Page = () => {
|
||||||
const [loginType, setLoginType] = useState<LoginType>("account");
|
const [loginType, setLoginType] = useState<LoginType>('account');
|
||||||
const { token } = theme.useToken();
|
const { token } = theme.useToken();
|
||||||
const [messageApi, contextHolder] = message.useMessage();
|
const [messageApi, contextHolder] = message.useMessage();
|
||||||
const { initialState, setInitialState } = useModel("@@initialState");
|
const { initialState, setInitialState } = useModel('@@initialState');
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
// 获取租户 ID
|
// 获取租户 ID
|
||||||
const getTenantId = async (name: string) => {
|
const getTenantId = async (name: string) => {
|
||||||
@@ -57,7 +58,7 @@ const Page = () => {
|
|||||||
try {
|
try {
|
||||||
// 根据登录类型处理不同的参数
|
// 根据登录类型处理不同的参数
|
||||||
const params = {
|
const params = {
|
||||||
tenantName: "芋道源码",
|
tenantName: '芋道源码',
|
||||||
...values,
|
...values,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -71,21 +72,22 @@ const Page = () => {
|
|||||||
// 调用登录接口
|
// 调用登录接口
|
||||||
const data = await login(params);
|
const data = await login(params);
|
||||||
// 登录成功
|
// 登录成功
|
||||||
messageApi.success("登录成功!");
|
messageApi.success('登录成功!');
|
||||||
// 跳转到首页
|
// 跳转到首页
|
||||||
authUtil.setToken(data);
|
authUtil.setToken(data);
|
||||||
await fetchUserInfo();
|
await fetchUserInfo();
|
||||||
const urlParams = new URL(window.location.href).searchParams;
|
const urlParams = new URL(window.location.href).searchParams;
|
||||||
navigate(urlParams.get("redirect") || "/");
|
// navigate(urlParams.get("redirect") || "/");
|
||||||
|
window.location.href = urlParams.get('redirect') || '/';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
messageApi.error("登录失败,请检查网络或稍后重试!");
|
messageApi.error('登录失败,请检查网络或稍后重试!');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: "white",
|
backgroundColor: 'white',
|
||||||
height: "100vh",
|
height: '100vh',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{contextHolder}
|
{contextHolder}
|
||||||
@@ -95,8 +97,8 @@ const Page = () => {
|
|||||||
backgroundVideoUrl="https://gw.alipayobjects.com/v/huamei_gcee1x/afts/video/jXRBRK_VAwoAAAAAAAAAAAAAK4eUAQBr"
|
backgroundVideoUrl="https://gw.alipayobjects.com/v/huamei_gcee1x/afts/video/jXRBRK_VAwoAAAAAAAAAAAAAK4eUAQBr"
|
||||||
// title="BY"
|
// title="BY"
|
||||||
containerStyle={{
|
containerStyle={{
|
||||||
backgroundColor: "rgb(0 0 0 / 51%)",
|
backgroundColor: 'rgb(0 0 0 / 51%)',
|
||||||
backdropFilter: "blur(4px)",
|
backdropFilter: 'blur(4px)',
|
||||||
}}
|
}}
|
||||||
onFinish={handleSubmit}
|
onFinish={handleSubmit}
|
||||||
subTitle="百业到家云控台"
|
subTitle="百业到家云控台"
|
||||||
@@ -127,17 +129,17 @@ const Page = () => {
|
|||||||
actions={
|
actions={
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Divider plain>
|
<Divider plain>
|
||||||
<span
|
<span
|
||||||
style={{
|
style={{
|
||||||
color: token.colorTextPlaceholder,
|
color: token.colorTextPlaceholder,
|
||||||
fontWeight: "normal",
|
fontWeight: 'normal',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -147,48 +149,48 @@ const Page = () => {
|
|||||||
<Space align="center" size={24}>
|
<Space align="center" size={24}>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
height: 40,
|
height: 40,
|
||||||
width: 40,
|
width: 40,
|
||||||
border: "1px solid " + token.colorPrimaryBorder,
|
border: '1px solid ' + token.colorPrimaryBorder,
|
||||||
background: token.colorBgContainer,
|
background: token.colorBgContainer,
|
||||||
borderRadius: "50%",
|
borderRadius: '50%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AlipayOutlined style={{ ...iconStyles, color: "#1677FF" }} />
|
<AlipayOutlined style={{ ...iconStyles, color: '#1677FF' }} />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
height: 40,
|
height: 40,
|
||||||
width: 40,
|
width: 40,
|
||||||
border: "1px solid " + token.colorPrimaryBorder,
|
border: '1px solid ' + token.colorPrimaryBorder,
|
||||||
background: token.colorBgContainer,
|
background: token.colorBgContainer,
|
||||||
borderRadius: "50%",
|
borderRadius: '50%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TaobaoOutlined style={{ ...iconStyles, color: "#FF6A10" }} />
|
<TaobaoOutlined style={{ ...iconStyles, color: '#FF6A10' }} />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: 'flex',
|
||||||
justifyContent: "center",
|
justifyContent: 'center',
|
||||||
alignItems: "center",
|
alignItems: 'center',
|
||||||
flexDirection: "column",
|
flexDirection: 'column',
|
||||||
height: 40,
|
height: 40,
|
||||||
width: 40,
|
width: 40,
|
||||||
background: token.colorBgContainer,
|
background: token.colorBgContainer,
|
||||||
border: "1px solid " + token.colorPrimaryBorder,
|
border: '1px solid ' + token.colorPrimaryBorder,
|
||||||
borderRadius: "50%",
|
borderRadius: '50%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<WeiboOutlined style={{ ...iconStyles, color: "#1890ff" }} />
|
<WeiboOutlined style={{ ...iconStyles, color: '#1890ff' }} />
|
||||||
</div>
|
</div>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
@@ -199,125 +201,125 @@ const Page = () => {
|
|||||||
activeKey={loginType}
|
activeKey={loginType}
|
||||||
onChange={(activeKey) => setLoginType(activeKey as LoginType)}
|
onChange={(activeKey) => setLoginType(activeKey as LoginType)}
|
||||||
>
|
>
|
||||||
<Tabs.TabPane key={"account"} tab={"账号密码登录"} />
|
<Tabs.TabPane key={'account'} tab={'账号密码登录'} />
|
||||||
<Tabs.TabPane key={"phone"} tab={"手机号登录"} />
|
<Tabs.TabPane key={'phone'} tab={'手机号登录'} />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
{loginType === "account" && (
|
{loginType === 'account' && (
|
||||||
<>
|
<>
|
||||||
<ProFormText
|
<ProFormText
|
||||||
name="username"
|
name="username"
|
||||||
fieldProps={{
|
fieldProps={{
|
||||||
size: "large",
|
size: 'large',
|
||||||
prefix: (
|
prefix: (
|
||||||
<UserOutlined
|
<UserOutlined
|
||||||
style={{
|
style={{
|
||||||
color: token.colorText,
|
color: token.colorText,
|
||||||
}}
|
}}
|
||||||
className={"prefixIcon"}
|
className={'prefixIcon'}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: "rgb(0 0 0 / 77%)",
|
backgroundColor: 'rgb(0 0 0 / 77%)',
|
||||||
color: "white",
|
color: 'white',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
placeholder={"用户名: admin or user"}
|
placeholder={'用户名: admin or user'}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: "请输入用户名!",
|
message: '请输入用户名!',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<ProFormText.Password
|
<ProFormText.Password
|
||||||
name="password"
|
name="password"
|
||||||
fieldProps={{
|
fieldProps={{
|
||||||
size: "large",
|
size: 'large',
|
||||||
prefix: (
|
prefix: (
|
||||||
<LockOutlined
|
<LockOutlined
|
||||||
style={{
|
style={{
|
||||||
color: token.colorText,
|
color: token.colorText,
|
||||||
}}
|
}}
|
||||||
className={"prefixIcon"}
|
className={'prefixIcon'}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: "rgb(0 0 0 / 77%)",
|
backgroundColor: 'rgb(0 0 0 / 77%)',
|
||||||
color: "white",
|
color: 'white',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
placeholder={"密码: ant.design"}
|
placeholder={'密码: ant.design'}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: "请输入密码!",
|
message: '请输入密码!',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{loginType === "phone" && (
|
{loginType === 'phone' && (
|
||||||
<>
|
<>
|
||||||
<ProFormText
|
<ProFormText
|
||||||
fieldProps={{
|
fieldProps={{
|
||||||
size: "large",
|
size: 'large',
|
||||||
prefix: (
|
prefix: (
|
||||||
<MobileOutlined
|
<MobileOutlined
|
||||||
style={{
|
style={{
|
||||||
color: token.colorText,
|
color: token.colorText,
|
||||||
}}
|
}}
|
||||||
className={"prefixIcon"}
|
className={'prefixIcon'}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: "rgb(0 0 0 / 77%)",
|
backgroundColor: 'rgb(0 0 0 / 77%)',
|
||||||
color: "white",
|
color: 'white',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
name="mobile"
|
name="mobile"
|
||||||
placeholder={"手机号"}
|
placeholder={'手机号'}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: "请输入手机号!",
|
message: '请输入手机号!',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: /^1\d{10}$/,
|
pattern: /^1\d{10}$/,
|
||||||
message: "手机号格式错误!",
|
message: '手机号格式错误!',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<ProFormCaptcha
|
<ProFormCaptcha
|
||||||
fieldProps={{
|
fieldProps={{
|
||||||
size: "large",
|
size: 'large',
|
||||||
prefix: (
|
prefix: (
|
||||||
<LockOutlined
|
<LockOutlined
|
||||||
style={{
|
style={{
|
||||||
color: token.colorText,
|
color: token.colorText,
|
||||||
}}
|
}}
|
||||||
className={"prefixIcon"}
|
className={'prefixIcon'}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
}}
|
}}
|
||||||
captchaProps={{
|
captchaProps={{
|
||||||
size: "large",
|
size: 'large',
|
||||||
}}
|
}}
|
||||||
placeholder={"请输入验证码"}
|
placeholder={'请输入验证码'}
|
||||||
captchaTextRender={(timing, count) => {
|
captchaTextRender={(timing, count) => {
|
||||||
if (timing) {
|
if (timing) {
|
||||||
return `${count} ${"获取验证码"}`;
|
return `${count} ${'获取验证码'}`;
|
||||||
}
|
}
|
||||||
return "获取验证码";
|
return '获取验证码';
|
||||||
}}
|
}}
|
||||||
name="captcha"
|
name="captcha"
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: "请输入验证码!",
|
message: '请输入验证码!',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
onGetCaptcha={async () => {
|
onGetCaptcha={async () => {
|
||||||
message.success("获取验证码成功!验证码为:1234");
|
message.success('获取验证码成功!验证码为:1234');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@@ -332,7 +334,7 @@ const Page = () => {
|
|||||||
</ProFormCheckbox>
|
</ProFormCheckbox>
|
||||||
<a
|
<a
|
||||||
style={{
|
style={{
|
||||||
float: "right",
|
float: 'right',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
忘记密码
|
忘记密码
|
||||||
|
|||||||
@@ -100,9 +100,6 @@ export const errorConfig: RequestConfig = {
|
|||||||
},
|
},
|
||||||
// 错误接收及处理
|
// 错误接收及处理
|
||||||
errorHandler: async (error: any, opts: any) => {
|
errorHandler: async (error: any, opts: any) => {
|
||||||
if (opts?.skipErrorHandler) throw error;
|
|
||||||
// 我们的 errorThrower 抛出的错误。
|
|
||||||
console.log('errorHandler', error);
|
|
||||||
const errorInfo: ResponseStructure | undefined = error.info;
|
const errorInfo: ResponseStructure | undefined = error.info;
|
||||||
if (error.name === 'BizError') {
|
if (error.name === 'BizError') {
|
||||||
if (errorInfo) {
|
if (errorInfo) {
|
||||||
@@ -118,6 +115,7 @@ export const errorConfig: RequestConfig = {
|
|||||||
} else {
|
} else {
|
||||||
message.error(`发送请求时出了点问题:${error.msg}`);
|
message.error(`发送请求时出了点问题:${error.msg}`);
|
||||||
}
|
}
|
||||||
|
throw error;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -136,23 +134,20 @@ export const errorConfig: RequestConfig = {
|
|||||||
const { data } = response as unknown as ResponseStructure;
|
const { data } = response as unknown as ResponseStructure;
|
||||||
const config = response.config;
|
const config = response.config;
|
||||||
const { code } = data;
|
const { code } = data;
|
||||||
// if (!data) {
|
|
||||||
// // 返回“[HTTP]请求没有返回值”;
|
if (!data) {
|
||||||
// throw new Error();
|
// 返回“[HTTP]请求没有返回值”;
|
||||||
// }
|
throw new Error();
|
||||||
// 未设置状态码则默认成功状态
|
}
|
||||||
// 二进制数据则直接返回,例如说 Excel 导出
|
|
||||||
// if (
|
if (
|
||||||
// response.request.responseType === "blob" ||
|
response.request.responseType === 'blob' ||
|
||||||
// response.request.responseType === "arraybuffer"
|
response.request.responseType === 'arraybuffer'
|
||||||
// ) {
|
) {
|
||||||
// // 注意:如果导出的响应为 json,说明可能失败了,不直接返回进行下载
|
return response;
|
||||||
// // if (response.data.type !== "application/json") {
|
// data = await new Response(data).json();
|
||||||
// // return response.data;
|
}
|
||||||
// // }
|
// 获取错误信息
|
||||||
// data = await new Response(data).json();
|
|
||||||
// }
|
|
||||||
// // 获取错误信息
|
|
||||||
// const msg = data.msg || errorCode[code] || errorCode["default"];
|
// const msg = data.msg || errorCode[code] || errorCode["default"];
|
||||||
// if (ignoreMsgs.indexOf(msg) !== -1) {
|
// if (ignoreMsgs.indexOf(msg) !== -1) {
|
||||||
// // 如果是忽略的错误码,直接返回 msg 异常
|
// // 如果是忽略的错误码,直接返回 msg 异常
|
||||||
|
|||||||
74
src/services/ai/model/index.tsx
Normal file
74
src/services/ai/model/index.tsx
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
* 返回数据
|
||||||
|
*
|
||||||
|
* AiModelRespVO
|
||||||
|
*/
|
||||||
|
export interface AiModelRespVO {
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
createTime?: string;
|
||||||
|
/**
|
||||||
|
* 版本描述
|
||||||
|
*/
|
||||||
|
description?: string;
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
id?: number;
|
||||||
|
/**
|
||||||
|
* 负载
|
||||||
|
*/
|
||||||
|
loadPercentage?: number;
|
||||||
|
/**
|
||||||
|
* 模型名称
|
||||||
|
*/
|
||||||
|
modelName?: string;
|
||||||
|
/**
|
||||||
|
* 状态(0-禁用 1-启用 2-测试中 3-已废弃)
|
||||||
|
*/
|
||||||
|
status?: number;
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
updateTime?: string;
|
||||||
|
/**
|
||||||
|
* 版本号
|
||||||
|
*/
|
||||||
|
version?: string;
|
||||||
|
}
|
||||||
|
import { request } from "@umijs/max";
|
||||||
|
export const getModelList = async (params: PageParam) => {
|
||||||
|
return request("/ai/model/page", {
|
||||||
|
method: "GET",
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createModel = async (params: AiModelRespVO) => {
|
||||||
|
return request("/ai/model/create", {
|
||||||
|
method: "POST",
|
||||||
|
data: params,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateModel = async (params: AiModelRespVO) => {
|
||||||
|
return request("/ai/model/update", {
|
||||||
|
method: "PUT",
|
||||||
|
data: params,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const delModel = async (id: number) => {
|
||||||
|
return request("/ai/model/delete", {
|
||||||
|
method: "DELETE",
|
||||||
|
params: { id },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateModelStatus = async (params: AiModelRespVO) => {
|
||||||
|
return request("/ai/model/update-status", {
|
||||||
|
method: "PUT",
|
||||||
|
params: params,
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { request } from "@umijs/max";
|
import { request } from "@umijs/max";
|
||||||
|
import { message } from "antd";
|
||||||
|
|
||||||
export interface SampleVo {
|
export interface SampleVo {
|
||||||
/**
|
/**
|
||||||
@@ -85,9 +86,12 @@ export interface AiSampleRespVO {
|
|||||||
* 样本时长
|
* 样本时长
|
||||||
*/
|
*/
|
||||||
sampleTime?: string;
|
sampleTime?: string;
|
||||||
|
tags?: { tagName?: string; id?: number }[];
|
||||||
|
enumTags?: { tagName?: string; id?: number; enumValue?: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SampleReqVo extends PageParam {
|
export interface SampleReqVo extends PageParam {
|
||||||
|
current: number | undefined;
|
||||||
name?: string;
|
name?: string;
|
||||||
status?: number;
|
status?: number;
|
||||||
}
|
}
|
||||||
@@ -244,3 +248,80 @@ export const updateSampleTag = async (params: {
|
|||||||
data: params,
|
data: params,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 下载
|
||||||
|
export const downloadSample = async (ids: number[]) => {
|
||||||
|
return request("/ai/sample/download", {
|
||||||
|
method: "GET",
|
||||||
|
params: { ids },
|
||||||
|
responseType: "blob",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getGroupListByYagId = async (id: number) => {
|
||||||
|
return request("/ai/sampleTag/group-list-by-tag-id", {
|
||||||
|
method: "GET",
|
||||||
|
params: { tagId: id },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function downloadZipFile(ids: number[]) {
|
||||||
|
try {
|
||||||
|
const response = await downloadSample(ids);
|
||||||
|
|
||||||
|
const blob = response; // Blob {size: 3164203, type: 'application/zip'}
|
||||||
|
|
||||||
|
// 验证文件类型
|
||||||
|
if (blob.type !== "application/zip" && !blob.type.includes("zip")) {
|
||||||
|
console.warn("返回的可能不是 ZIP 文件:", blob.type);
|
||||||
|
const downloadUrl = window.URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = downloadUrl;
|
||||||
|
link.download = `音频文件_${new Date().getTime()}.wav`; // 设置文件名和后缀
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
// 清理
|
||||||
|
document.body.removeChild(link);
|
||||||
|
window.URL.revokeObjectURL(downloadUrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从响应头获取文件名
|
||||||
|
let fileName = "音频文件";
|
||||||
|
const contentDisposition =
|
||||||
|
response.response?.headers?.["content-disposition"];
|
||||||
|
if (!fileName && contentDisposition) {
|
||||||
|
const match = contentDisposition.match(
|
||||||
|
/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/
|
||||||
|
);
|
||||||
|
if (match?.[1]) {
|
||||||
|
fileName = decodeURIComponent(match[1].replace(/['"]/g, ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认文件名
|
||||||
|
fileName = fileName || `archive_${new Date().getTime()}.zip`;
|
||||||
|
|
||||||
|
// 创建下载链接
|
||||||
|
const downloadUrl = window.URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = downloadUrl;
|
||||||
|
link.download = fileName;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
|
||||||
|
// 清理
|
||||||
|
document.body.removeChild(link);
|
||||||
|
window.URL.revokeObjectURL(downloadUrl);
|
||||||
|
|
||||||
|
message.success(
|
||||||
|
`下载成功,文件大小:${(blob.size / 1024 / 1024).toFixed(2)} MB`
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("下载失败:", error);
|
||||||
|
message.error("下载失败,请稍后重试");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import { Spin } from 'antd';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { MenuVO } from '@/services/system/menu';
|
import type { MenuVO } from '@/services/system/menu';
|
||||||
|
|
||||||
export const loopMenuItem = (menus: MenuVO[], pId: number | string): any[] => {
|
export const loopMenuItem = (
|
||||||
|
menus: MenuVO[],
|
||||||
|
pId: number | string = '/',
|
||||||
|
): any[] => {
|
||||||
return menus.map((item) => {
|
return menus.map((item) => {
|
||||||
let Component: React.ComponentType<any> | null = null;
|
let Component: React.ComponentType<any> | null = null;
|
||||||
if (item.component && item.component.trim().length > 0) {
|
if (item.component && item.component.trim().length > 0) {
|
||||||
@@ -34,85 +37,11 @@ export const loopMenuItem = (menus: MenuVO[], pId: number | string): any[] => {
|
|||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
);
|
);
|
||||||
} else if (item.children && item.children.length > 0) {
|
} else if (item.children && item.children.length > 0) {
|
||||||
// routeItem.redirect = "/prod/list";
|
|
||||||
// // 只有当没有 Component 但有子菜单时,才添加重定向
|
|
||||||
// const firstLeafPath = getFirstLeafPath(item.children);
|
|
||||||
// // 确保 firstLeafPath 存在,且不是一个会导致循环的路径
|
|
||||||
// if (
|
|
||||||
// firstLeafPath &&
|
|
||||||
// firstLeafPath !== item.path &&
|
|
||||||
// firstLeafPath.length > 0
|
|
||||||
// ) {
|
|
||||||
// // 在 UmiJS 中,路径是相对的,不需要构建完整路径
|
|
||||||
// const separator =
|
|
||||||
// item.path.endsWith("/") || firstLeafPath.startsWith("/") ? "" : "/";
|
|
||||||
// const fullPath = `${item.path}${separator}${firstLeafPath}`;
|
|
||||||
// console.log(`Redirecting from ${item.path} to ${fullPath}`);
|
|
||||||
// routeItem.element = <Navigate to={fullPath} replace={true} />;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
// 处理子菜单
|
// 处理子菜单
|
||||||
if (item.children && item.children.length > 0) {
|
if (item.children && item.children.length > 0) {
|
||||||
routeItem.children = loopMenuItem(item.children, item.id);
|
routeItem.children = loopMenuItem(item.children, item.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return routeItem;
|
return routeItem;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// return menus.flatMap((item) => {
|
|
||||||
// let Component: React.ComponentType<any> | null = null;
|
|
||||||
// if (item.component && item.component.length > 0) {
|
|
||||||
// // 防止配置了路由,但本地暂未添加对应的页面,产生的错误
|
|
||||||
// Component = React.lazy(() => {
|
|
||||||
// const importComponent = () => import(`@/pages/${item.component}`);
|
|
||||||
// const import404 = () => import("@/pages/404");
|
|
||||||
// return importComponent().catch(import404);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// if (item.children && item.children.length > 0) {
|
|
||||||
// return [
|
|
||||||
// {
|
|
||||||
// path: item.path,
|
|
||||||
// hideInMenu: false,
|
|
||||||
// parentId: pId,
|
|
||||||
// id: item.id,
|
|
||||||
// children: [...loopMenuItem(item.children, item.id)], // 添加缺失的 children 属性
|
|
||||||
// },
|
|
||||||
// ];
|
|
||||||
// } else {
|
|
||||||
// return [
|
|
||||||
// {
|
|
||||||
// path: item.path,
|
|
||||||
// name: item.name,
|
|
||||||
// // icon: item.icon,
|
|
||||||
// id: item.id,
|
|
||||||
// parentId: pId,
|
|
||||||
// hideInMenu: !item.visible,
|
|
||||||
// element: (
|
|
||||||
// <React.Suspense
|
|
||||||
// fallback={<Spin style={{ width: "100%", height: "100%" }} />}
|
|
||||||
// >
|
|
||||||
// {Component && <Component />}
|
|
||||||
// </React.Suspense>
|
|
||||||
// ),
|
|
||||||
// },
|
|
||||||
// ];
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// return [];
|
|
||||||
// };
|
|
||||||
|
|
||||||
// function getFirstLeafPath(menus: any[], parentPath: string): string {
|
|
||||||
// const firstMenu = menus[0];
|
|
||||||
// const currentPath = `${parentPath}/${firstMenu.path}`;
|
|
||||||
// if (firstMenu.children && firstMenu.children.length > 0) {
|
|
||||||
// if (!firstMenu.hideInMenu) {
|
|
||||||
// return getFirstLeafPath(firstMenu.children, currentPath);
|
|
||||||
// } else {
|
|
||||||
// return getFirstLeafPath(firstMenu.children, parentPath);
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// return currentPath;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|||||||
Reference in New Issue
Block a user