feat: 内容管理

This commit is contained in:
2025-10-29 17:10:53 +08:00
parent 476ee7a754
commit 4c628fee22
67 changed files with 8210 additions and 4494 deletions

View File

@@ -12,7 +12,7 @@
"!**/mock",
"!**/dist",
"!**/server",
"!**/public/**/*",
"!**/public",
"!**/coverage",
"!**/node_modules",
"!biome.json"

View File

@@ -20,6 +20,7 @@ const Settings: ProLayoutProps & {
logo: '/logo.svg',
iconfontUrl: '',
splitMenus: true,
token: {
// 参见ts声明demo 见文档通过token 修改样式
// 设置内容区域的边距

View File

@@ -13,13 +13,10 @@ export default {
// 如果需要自定义本地开发服务器 请取消注释按需调整
dev: {
// localhost:8000/api/** -> https://preview.pro.ant.design/api/**
"/admin-api/": {
// 要代理的地址
'/admin-api/': {
// http://192.168.1.231:48080 伟强
// https://petshy.tashowz.com/
target: "https://petshy.tashowz.com/",
// 配置了这个可以从 http 代理到 https
// 依赖 origin 的功能可能需要这个,比如 cookie
target: 'https://petshy.tashowz.com',
changeOrigin: true,
},
},
@@ -29,17 +26,17 @@ export default {
*/
test: {
// localhost:8000/api/** -> https://preview.pro.ant.design/api/**
"/api/": {
target: "https://petshy.tashowz.com/",
'/api/': {
target: 'https://petshy.tashowz.com/',
changeOrigin: true,
pathRewrite: { "^": "" },
pathRewrite: { '^': '' },
},
},
pre: {
"/api/": {
target: "your pre url",
'/api/': {
target: 'https://petshy.tashowz.com/',
changeOrigin: true,
pathRewrite: { "^": "" },
pathRewrite: { '^': '' },
},
},
};

View File

@@ -34,35 +34,26 @@ export default [
path: '/',
redirect: '/welcome',
},
{
path: '/trade',
name: '交易管理',
routes: [
{
name: '订单管理',
path: '/trade/order',
component: './trade/order/index',
},
],
},
// {
// path: "/trade",
// name: "交易管理",
// routes: [
// {
// name: "订单管理",
// path: "/trade/order",
// component: "./trade/order/index",
// },
// ],
// },
{
path: '/prod1',
name: '商品管理1',
path: '/ai1',
name: 'AI1',
routes: [
{
path: '', // 空路径,匹配 /prod
redirect: 'list', // 相对路径重定向
},
{
name: '商品1',
path: '/prod1/list',
component: './prod/list/index',
},
{
name: '类目管理',
path: '/prod1/category',
component: './prod/category/index',
name: 'ai样本',
path: '/ai1/tag',
component: './ai/sample-tag',
},
],
},
@@ -77,6 +68,26 @@ export default [
// },
// ],
// },
// {
// path: "/prod1",
// name: "商品管理2",
// routes: [
// // { index: true, component: "./prod/list/index" },
// {
// name: "商品管理",
// path: "list",
// component: "./prod/list/index",
// routes: [
// {
// name: "类目管理",
// path: "detail",
// component: "./prod/category/index",
// hideInMenu: true,
// },
// ],
// },
// ],
// },
{
component: '404',
layout: false,

3128
mock/dict.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -5,193 +5,223 @@ const getOrderPage = (_req: Request, res: Response) => {
list: [
{
id: 1,
orderNum: "BZ000548787",
createTime: "2025-10-1 14:00:00",
orderCategoryName: "宠物殡葬", // 订单类目
orderTerminal: 0, // 订单来源
userId: 0, // 用户编号
userName: "", // 用户姓名
userNickName: "张宁清", // 用户昵称
userAvatar: "", // 用户头像
userMobile: "18634556151", // 用户手机号
orderStatus: 10, // 订单状态
picUrl: "", // 商品图片
spuName:
"商品主标题商品主标题商品主标题商品主标题商品主题商品主标题商品主标题商品主", // 商品名称
skuName: "标准,单独火化,基础清洁", // 商品规格
count: 1, // 购买商品数量
price: 100, // 单价
handedPrice: 80, // 到手价
payPrice: "500", // 应付金额
unit: "", // 单位
subTime: 1760585499000, // 预约时间
serveAddress: "张宁清,18659156151,福州 台江区 鳌峰路 万达中心甲",
userRemark: "你好呀你好元", // 用户备注
payType: "wx", // 微信
financeStatus: "1", // 财务状态
},
orderNum: "123333333333",
orderType: 1,
orderTerminal: 1,
orderStatus: 10,
userId: 1,
userName: "李老师",
userNickName: null,
userAvatar: null,
userMobile: "17609087876",
userRemark: "测试用户备注",
createTime: "2025-10-16 11:31:39",
finishTime: "2025-10-16 11:28:55",
items: [
{
id: 2,
orderNum: "3214421412",
createTime: "",
orderCategoryName: "宠物殡葬",
orderTerminal: 0,
userId: 0,
userName: "",
userNickName: "",
userAvatar: "",
userMobile: "",
orderStatus: 20,
picUrl: "",
spuName: "",
skuName: "",
subTime: 1760585499000, // 预约时间
count: 0,
price: 0,
picUrl:
"https://inews.gtimg.com/news_bt/OBkbmPLeWLy4IM4oUDGvOIqSDSZ9lYOtW3qSXCYh78KXcAA/1000",
spuName: "测试商品名称",
skuName: "规格名称",
count: 1,
price: 1,
discountPrice: 1,
handedPrice: 0,
payPrice: 0,
unit: "",
orderTime: "",
serveAddress: "",
userRemark: "",
payType: "",
financeStatus: "",
payPrice: 1,
unit: "",
subTime: "2025-10-16 11:34:10",
serveAddress: "11232",
},
{
id: 3,
orderNum: "3214421412",
createTime: "",
orderCategoryName: "宠物殡葬",
orderTerminal: 0,
userId: 0,
userName: "",
subTime: 1760585499000, // 预约时间
userNickName: "",
userAvatar: "",
userMobile: "",
orderStatus: 30,
picUrl: "",
spuName: "",
skuName: "",
count: 0,
price: 0,
handedPrice: 0,
payPrice: 0,
unit: "",
orderTime: "",
serveAddress: "",
userRemark: "",
payType: "",
financeStatus: "",
},
{
id: 4,
orderNum: "3214421412",
createTime: "",
orderCategoryName: "宠物殡葬",
orderTerminal: 0,
userId: 0,
userName: "",
subTime: 1760585499000, // 预约时间
userNickName: "",
userAvatar: "",
userMobile: "",
orderStatus: 40,
picUrl: "",
spuName: "",
skuName: "",
count: 0,
price: 0,
handedPrice: 0,
payPrice: 0,
unit: "",
orderTime: "",
serveAddress: "",
userRemark: "",
payType: "",
financeStatus: "",
},
{
id: 5,
orderNum: "3214421412",
createTime: "",
orderCategoryName: "宠物殡葬",
orderTerminal: 0,
userId: 0,
userName: "",
subTime: 1760585499000, // 预约时间
userNickName: "",
userAvatar: "",
userMobile: "",
orderStatus: 50,
picUrl: "",
spuName: "",
skuName: "",
count: 0,
price: 0,
handedPrice: 0,
payPrice: 0,
unit: "",
orderTime: "",
serveAddress: "",
userRemark: "",
payType: "",
financeStatus: "",
},
{
id: 6,
orderNum: "3214421412",
createTime: "",
orderCategoryName: "宠物殡葬",
orderTerminal: 0,
userId: 0,
userName: "",
subTime: 1760585499000, // 预约时间
userNickName: "",
userAvatar: "",
userMobile: "",
orderStatus: 60,
picUrl: "",
spuName: "",
skuName: "",
count: 0,
price: 0,
handedPrice: 0,
payPrice: 0,
unit: "",
orderTime: "",
serveAddress: "",
userRemark: "",
payType: "",
financeStatus: "",
},
{
id: 7,
orderNum: "3214421412",
createTime: "",
orderCategoryName: "宠物殡葬",
orderTerminal: 0,
userId: 0,
userName: "",
subTime: 1760585499000, // 预约时间
userNickName: "",
userAvatar: "",
userMobile: "",
orderStatus: 70,
picUrl: "",
spuName: "",
skuName: "",
count: 0,
price: 0,
handedPrice: 0,
payPrice: 0,
unit: "",
orderTime: "",
serveAddress: "",
userRemark: "",
payType: "",
financeStatus: "",
],
shopName: null,
shopLogo: null,
payLastTime: null,
},
// {
// orderTerminal: 0, // 订单来源
// userId: 0, // 用户编号
// userName: "", // 用户姓名
// userNickName: "张宁清", // 用户昵称
// userAvatar: "", // 用户头像
// userMobile: "18634556151", // 用户手机号
// orderStatus: 10, // 订单状态
// picUrl: "", // 商品图片
// spuName:
// "商品主标题商品主标题商品主标题商品主标题商品主题商品主标题商品主标题商品主", // 商品名称
// skuName: "标准,单独火化,基础清洁", // 商品规格
// count: 1, // 购买商品数量
// price: 100, // 单价
// handedPrice: 80, // 到手价
// payPrice: "500", // 应付金额
// unit: "", // 单位
// subTime: 1760585499000, // 预约时间
// serveAddress: "张宁清,18659156151,福州 台江区 鳌峰路 万达中心甲",
// userRemark: "你好呀你好元", // 用户备注
// payType: "wx", // 微信
// financeStatus: "1", // 财务状态
// },
// {
// id: 2,
// orderNum: "3214421412",
// createTime: "",
// orderCategoryName: "宠物殡葬",
// orderTerminal: 0,
// userId: 0,
// userName: "",
// userNickName: "",
// userAvatar: "",
// userMobile: "",
// orderStatus: 20,
// picUrl: "",
// spuName: "",
// skuName: "",
// subTime: 1760585499000, // 预约时间
// count: 0,
// price: 0,
// handedPrice: 0,
// payPrice: 0,
// unit: "",
// orderTime: "",
// serveAddress: "",
// userRemark: "",
// payType: "",
// financeStatus: "",
// },
// {
// id: 3,
// orderNum: "3214421412",
// createTime: "",
// orderCategoryName: "宠物殡葬",
// orderTerminal: 0,
// userId: 0,
// userName: "",
// subTime: 1760585499000, // 预约时间
// userNickName: "",
// userAvatar: "",
// userMobile: "",
// orderStatus: 30,
// picUrl: "",
// spuName: "",
// skuName: "",
// count: 0,
// price: 0,
// handedPrice: 0,
// payPrice: 0,
// unit: "",
// orderTime: "",
// serveAddress: "",
// userRemark: "",
// payType: "",
// financeStatus: "",
// },
// {
// id: 4,
// orderNum: "3214421412",
// createTime: "",
// orderCategoryName: "宠物殡葬",
// orderTerminal: 0,
// userId: 0,
// userName: "",
// subTime: 1760585499000, // 预约时间
// userNickName: "",
// userAvatar: "",
// userMobile: "",
// orderStatus: 40,
// picUrl: "",
// spuName: "",
// skuName: "",
// count: 0,
// price: 0,
// handedPrice: 0,
// payPrice: 0,
// unit: "",
// orderTime: "",
// serveAddress: "",
// userRemark: "",
// payType: "",
// financeStatus: "",
// },
// {
// id: 5,
// orderNum: "3214421412",
// createTime: "",
// orderCategoryName: "宠物殡葬",
// orderTerminal: 0,
// userId: 0,
// userName: "",
// subTime: 1760585499000, // 预约时间
// userNickName: "",
// userAvatar: "",
// userMobile: "",
// orderStatus: 50,
// picUrl: "",
// spuName: "",
// skuName: "",
// count: 0,
// price: 0,
// handedPrice: 0,
// payPrice: 0,
// unit: "",
// orderTime: "",
// serveAddress: "",
// userRemark: "",
// payType: "",
// financeStatus: "",
// },
// {
// id: 6,
// orderNum: "3214421412",
// createTime: "",
// orderCategoryName: "宠物殡葬",
// orderTerminal: 0,
// userId: 0,
// userName: "",
// subTime: 1760585499000, // 预约时间
// userNickName: "",
// userAvatar: "",
// userMobile: "",
// orderStatus: 60,
// picUrl: "",
// spuName: "",
// skuName: "",
// count: 0,
// price: 0,
// handedPrice: 0,
// payPrice: 0,
// unit: "",
// orderTime: "",
// serveAddress: "",
// userRemark: "",
// payType: "",
// financeStatus: "",
// },
// {
// id: 7,
// orderNum: "3214421412",
// createTime: "",
// orderCategoryName: "宠物殡葬",
// orderTerminal: 0,
// userId: 0,
// userName: "",
// subTime: 1760585499000, // 预约时间
// userNickName: "",
// userAvatar: "",
// userMobile: "",
// orderStatus: 70,
// picUrl: "",
// spuName: "",
// skuName: "",
// count: 0,
// price: 0,
// handedPrice: 0,
// payPrice: 0,
// unit: "",
// orderTime: "",
// serveAddress: "",
// userRemark: "",
// payType: "",
// financeStatus: "",
// },
],
total: 0,
},
@@ -202,70 +232,254 @@ const getOrderPage = (_req: Request, res: Response) => {
const getOrderDetail = (_req: Request, res: Response) => {
res.json({
data: {
tradeOrderInfoBase: {
id: "7655265",
orderNo: "BZ000548787",
orderStatus: "50",
orderType: "1",
orderCategoryName: "宠物殡葬", // 订单类目
orderCategoryId: "3232321",
orderTerminal: "微信小程序",
userInfo: null,
id: 1,
orderNum: "123333333333",
orderType: 1,
orderTerminal: 1,
orderStatus: 10,
userId: 1,
userName: "李老师",
userNickName: null,
userAvatar: null,
cancelTime: "2025-10-16T11:28:57",
userMobile: "17609087876",
userRemark: "测试用户备注",
createTime: "2025-10-16 11:31:39",
finishTime: "2025-10-16 11:28:55",
orderCategoryId: 1,
orderCategoryName: "222",
cancelReason: "非常",
merchantRemark: "测试商家备注",
refundTime: null,
propertyTime: null,
propertyStatus: null,
price: null,
discountPrice: null,
payPrice: null,
discountPrice: null,
refundPrice: null,
livePrice: null,
payType: "1",
payChannelCode: "1",
payType: 1,
payChannelCode: 1,
financeStatus: null,
payOrderId: "12323123123",
payTime: 1760585391000,
createTime: 1760585499000,
finishTime: 1760585335000,
payTime: "2025-10-16 11:29:51",
cancelTime: "2025-10-16 11:28:57",
statusList: [
{
id: 1,
orderId: 1,
beforeStatus: 10,
afterStatus: 20,
operateType: 1,
content: "1",
createTime: 1760952084000,
},
tradeProductInfo: {
shopLogoUrl: "",
shopName: "",
spuId: 0,
skuId: 0,
skuPicUrl: "",
spuName: "",
skuName: "",
count: 0,
price: 0,
unit: "",
handedPrice: 0,
expensePrice: 0,
properties: [""],
serveContent: "",
{
id: 2,
orderId: 1,
beforeStatus: 20,
afterStatus: 30,
operateType: 1,
content: "测试内容",
createTime: null,
},
],
items: [
{
id: 1,
shopName: "22",
shopLogo: null,
spuId: 1,
skuId: 1,
picUrl:
"https://inews.gtimg.com/news_bt/OBkbmPLeWLy4IM4oUDGvOIqSDSZ9lYOtW3qSXCYh78KXcAA/1000",
spuName: "测试商品名称",
skuName: "规格名称",
orderCategoryId: 1,
orderCategoryName: "222",
count: 1,
price: 1,
unit: "件",
totalPrice: 0,
discountPrice: 0,
payPrice: 0,
refundPrice: 0,
refundCount: 0,
discountPrice: 1,
payPrice: 1,
refundPrice: null,
refundCount: null,
handedPrice: 0,
expensePrice: 1,
properties: null,
serveContent: null,
},
],
tradeServeInfo: {
properties: {
"": {},
changeRule: "bbbb",
userRemark: "bbbb",
subOrder: "bbbb",
pickUpAddress: "bbbb",
subType: "bbbb",
merchantRemark: "bbbb",
sendAddress: "bbbb",
petName: "aaa",
boneUrl: "aaa",
petType: "aaa",
diedTime: "aaa",
weight: "aaa",
diedReason: "aaa",
},
tradeExtendServeInfo: [
{
refundCount: "ccc",
totalPrice: "ccc",
payPrice: "ccc",
discountPrice: "ccc",
refundMoney: "ccc",
serve: {
serveName: "ccc",
serveUrl: "ccc",
price: "ccc",
serveDesc: "ccc",
handPrice: "ccc",
count: "ccc",
},
tempType: 1,
},
{
refundCount: "ccc",
totalPrice: "ccc",
payPrice: "ccc",
discountPrice: "ccc",
refundMoney: "ccc",
serve: [
{
serveName: "ccc",
serveUrl: "ccc",
price: "ccc",
serveDesc: "ccc",
handPrice: "ccc",
count: "ccc",
riteAddress: "ccc",
parentActive: "ccc",
},
],
tempType: 2,
},
{
refundCount: "ccc",
deliveryInfo: {
address: "收货地址",
userMobile: "手机号码",
deliveryType: "配送方式",
user: "收货人/提货人",
},
serveTitle: "骨灰处理",
totalPrice: "ccc",
payPrice: "ccc",
serveType: "处理方式",
discountPrice: "ccc",
refundMoney: "ccc",
serve: [
{
serveName: "ccc",
serveUrl: "ccc",
price: "ccc",
serveDesc: "ccc",
handPrice: "ccc",
count: "ccc",
},
{
serveName: "ccc",
serveUrl: "ccc",
price: "ccc",
serveDesc: "ccc",
handPrice: "ccc",
count: "ccc",
},
],
tempType: 3,
},
{
refundCount: "ccc",
deliveryInfo: {
address: "收货地址",
userMobile: "手机号码",
deliveryType: "配送方式",
user: "收货人/提货人",
},
totalPrice: "ccc",
payPrice: "ccc",
discountPrice: "ccc",
refundMoney: "ccc",
serve: {
parentActivity: "ccc",
prodUrl: "ccc",
price: "ccc",
handPrice: "ccc",
count: "ccc",
prodName: "ccc",
prodType: "ccc",
prodDesc: "ccc",
},
tempType: 4,
},
],
tradeExtendCostInfo: [
{
costDetail: {
costName: "超区域服务费",
targetArea: "目标区域",
chargeType: "收费方式",
serveArea: "可服务区域",
},
serveExtFee: "",
refundPrice: "11",
payInfo: {
totalPrice: "11",
payPrice: "333",
discountPrice: "222",
},
},
tradeExtendServeInfo: {
properties: {
"": {},
{
costDetail: {
respMode: "响应模式",
respTime: "响应时间",
chargeType: "收费方式",
},
serveExtFee: "",
refundPrice: "11",
payInfo: {
totalPrice: "11",
payPrice: "333",
discountPrice: "222",
},
},
tradeExtendCostInfo: {
properties: {
"": {},
{
costDetail: {
costName: "超区域服务费",
chargeType: "收费方式",
weight: "体型/体重",
},
serveExtFee: "",
refundPrice: "11",
payInfo: {
totalPrice: "11",
payPrice: "333",
discountPrice: "222",
},
},
{
costDetail: {
costName: "超区域服务费",
chargeTime: "收费时段",
chargeType: "收费方式",
},
serveExtFee: "",
refundPrice: "11",
payInfo: {
totalPrice: "11",
payPrice: "333",
discountPrice: "222",
},
},
],
},
code: 0,
});

View File

@@ -39,9 +39,13 @@
"@ant-design/icons": "^5.6.1",
"@ant-design/pro-components": "^2.8.9",
"@ant-design/v5-patch-for-react-19": "^1.0.3",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@tinymce/tinymce-react": "^6.3.0",
"antd": "^5.26.4",
"antd": "^5.27.3",
"antd-style": "^3.7.0",
"browser-id3-writer": "^6.3.1",
"classnames": "^2.5.1",
"dayjs": "^1.11.13",
"jsencrypt": "^3.5.4",
@@ -49,6 +53,7 @@
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-infinite-scroll-component": "^6.1.0",
"react-video-thumbnail": "^0.1.3",
"tinymce": "^8.1.2",
"web-storage-cache": "^1.1.1"
},

40
pnpm-lock.yaml generated
View File

@@ -17,15 +17,27 @@ importers:
'@ant-design/v5-patch-for-react-19':
specifier: ^1.0.3
version: 1.0.3(antd@5.27.3(date-fns@2.30.0)(moment@2.30.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@dnd-kit/core':
specifier: ^6.3.1
version: 6.3.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@dnd-kit/sortable':
specifier: ^10.0.0
version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)
'@dnd-kit/utilities':
specifier: ^3.2.2
version: 3.2.2(react@19.1.1)
'@tinymce/tinymce-react':
specifier: ^6.3.0
version: 6.3.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(tinymce@8.1.2)
antd:
specifier: ^5.26.4
specifier: ^5.27.3
version: 5.27.3(date-fns@2.30.0)(moment@2.30.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
antd-style:
specifier: ^3.7.0
version: 3.7.1(@types/react@19.1.12)(antd@5.27.3(date-fns@2.30.0)(moment@2.30.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
browser-id3-writer:
specifier: ^6.3.1
version: 6.3.1
classnames:
specifier: ^2.5.1
version: 2.5.1
@@ -47,6 +59,9 @@ importers:
react-infinite-scroll-component:
specifier: ^6.1.0
version: 6.1.0(react@19.1.1)
react-video-thumbnail:
specifier: ^0.1.3
version: 0.1.3
tinymce:
specifier: ^8.1.2
version: 8.1.2
@@ -1410,6 +1425,12 @@ packages:
'@dnd-kit/core': ^6.0.6
react: '>=16.8.0'
'@dnd-kit/sortable@10.0.0':
resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==}
peerDependencies:
'@dnd-kit/core': ^6.3.0
react: '>=16.8.0'
'@dnd-kit/sortable@7.0.2':
resolution: {integrity: sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==}
peerDependencies:
@@ -3850,6 +3871,9 @@ packages:
brorand@1.1.0:
resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==}
browser-id3-writer@6.3.1:
resolution: {integrity: sha512-sRA4Uq9Q3NsmXiVpLvIDxzomtgCdbw6SY85A6fw7dUQGRVoOBg1/buFv6spPhYiSo6FlVtN5OJQTvvhbmfx9rQ==}
browserify-aes@1.2.0:
resolution: {integrity: sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==}
@@ -9148,6 +9172,9 @@ packages:
react-tween-state@0.1.5:
resolution: {integrity: sha512-sJQpjsdn0wjlDIUpfpb7jQGnOG8hAEW2e8k0KPA+xmf5KFa6Xat2JldbmxBhaqP0S/uIXhVE5ymKyH/b9X8nYA==}
react-video-thumbnail@0.1.3:
resolution: {integrity: sha512-ZakEVv9RNFcziAnHHtwod5tf0yMVv4aLZ8/QsCxt1HTnKRHrlylvolPKv1hr63GCwnPu6tgPjUTDKRqfl30lOQ==}
react@18.3.1:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'}
@@ -13394,6 +13421,13 @@ snapshots:
react: 19.1.1
tslib: 2.8.1
'@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)':
dependencies:
'@dnd-kit/core': 6.3.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@dnd-kit/utilities': 3.2.2(react@19.1.1)
react: 19.1.1
tslib: 2.8.1
'@dnd-kit/sortable@7.0.2(@dnd-kit/core@6.3.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)':
dependencies:
'@dnd-kit/core': 6.3.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
@@ -16921,6 +16955,8 @@ snapshots:
brorand@1.1.0: {}
browser-id3-writer@6.3.1: {}
browserify-aes@1.2.0:
dependencies:
buffer-xor: 1.0.3
@@ -23468,6 +23504,8 @@ snapshots:
raf: 3.4.1
tween-functions: 1.2.0
react-video-thumbnail@0.1.3: {}
react@18.3.1:
dependencies:
loose-envify: 1.4.0

View File

@@ -1,28 +1,28 @@
import type { Settings as LayoutSettings } from "@ant-design/pro-components";
import { SettingDrawer } from "@ant-design/pro-components";
import type { RequestConfig, RunTimeLayoutConfig } from "@umijs/max";
import { history } from "@umijs/max";
import type { Settings as LayoutSettings } from '@ant-design/pro-components';
import { SettingDrawer } from '@ant-design/pro-components';
import type { RequestConfig, RunTimeLayoutConfig } from '@umijs/max';
import { history } from '@umijs/max';
import {
AvatarDropdown,
AvatarName,
Footer,
Question,
SelectLang,
} from "@/components";
import { getInfo } from "@/services/login";
import type { UserInfoVO } from "@/services/login/types";
import defaultSettings from "../config/defaultSettings";
import { errorConfig } from "./requestErrorConfig";
import "@ant-design/v5-patch-for-react-19";
import { useDictStore } from "@/hooks/stores/dict";
import { getAccessToken, getTenantId } from "@/utils/auth";
import { CACHE_KEY, useCache } from "./hooks/web/useCache";
import type { MenuVO } from "./services/system/menu";
import { loopMenuItem } from "./utils/menuUtils";
} from '@/components';
import { getInfo } from '@/services/login';
import type { UserInfoVO } from '@/services/login/types';
import defaultSettings from '../config/defaultSettings';
import { errorConfig } from './requestErrorConfig';
import '@ant-design/v5-patch-for-react-19';
import { useDictStore } from '@/hooks/stores/dict';
import { getAccessToken, getTenantId } from '@/utils/auth';
import { CACHE_KEY, useCache } from './hooks/web/useCache';
import type { MenuVO } from './services/system/menu';
import { loopMenuItem } from './utils/menuUtils';
const isDev = process.env.NODE_ENV === "development";
const isDev = process.env.NODE_ENV === 'development';
const isDevOrTest = isDev || process.env.CI;
const loginPath = "/user/login";
const loginPath = '/user/login';
// 标记是否已添加动态路由
/**
@@ -42,7 +42,7 @@ export async function getInitialState(): Promise<{
try {
const token = getAccessToken();
if (!token) {
throw new Error("No token found");
throw new Error('No token found');
}
const data = await getInfo();
wsCache.set(CACHE_KEY.USER, data);
@@ -64,8 +64,8 @@ export async function getInitialState(): Promise<{
const { location } = history;
if (
![loginPath, "/user/register", "/user/register-result"].includes(
location.pathname
![loginPath, '/user/register', '/user/register-result'].includes(
location.pathname,
)
) {
const currentUser = wsCache.get(CACHE_KEY.USER);
@@ -122,24 +122,29 @@ export const layout: RunTimeLayoutConfig = ({
},
bgLayoutImgList: [
{
src: "https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/D2LWSqNny4sAAAAAAAAAAAAAFl94AQBr",
src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/D2LWSqNny4sAAAAAAAAAAAAAFl94AQBr',
left: 85,
bottom: 100,
height: "303px",
height: '303px',
},
{
src: "https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/C2TWRpJpiC0AAAAAAAAAAAAAFl94AQBr",
src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/C2TWRpJpiC0AAAAAAAAAAAAAFl94AQBr',
bottom: -68,
right: -45,
height: "303px",
height: '303px',
},
{
src: "https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/F6vSTbj8KpYAAAAAAAAAAAAAFl94AQBr",
src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/F6vSTbj8KpYAAAAAAAAAAAAAFl94AQBr',
bottom: 0,
left: 0,
width: "331px",
width: '331px',
},
],
// 面包屑配置
breadcrumb: {
enable: true,
useRoutes: true,
},
menuHeaderRender: undefined,
// 自定义 403 页面
unAccessible: <div>unAccessible</div>,
@@ -175,57 +180,30 @@ export const layout: RunTimeLayoutConfig = ({
* @doc https://umijs.org/docs/max/request#配置
*/
export const request: RequestConfig = {
baseURL: isDev ? "" : "https://proapi.azurewebsites.net",
baseURL: isDev ? '' : 'https://proapi.azurewebsites.net',
...errorConfig,
// 添加请求拦截器
requestInterceptors: [
(url, options) => {
// 为所有请求添加 API 前缀
if (url && !url.startsWith(process.env.API_PREFIX || "/admin-api")) {
url = (process.env.API_PREFIX || "/admin-api") + url;
if (url && !url.startsWith(process.env.API_PREFIX || '/admin-api')) {
url = (process.env.API_PREFIX || '/admin-api') + url;
}
// 获取存储在本地的 token 和 tenantId
const token = getAccessToken();
const tenantId = getTenantId(); // 默认租户ID为1
const contentType: string = options.headers?.["Content-Type"] as string;
// 设置统一的请求头
const headers: Record<string, string> = {
...options.headers,
Accept: "*",
"tenant-id": tenantId,
Accept: '*',
'tenant-id': tenantId,
};
// 如果有token则添加Authorization头
if (token) {
headers.Authorization = `Bearer ${getAccessToken()}`;
headers["Content-Type"] = contentType || "application/json";
}
return { url, options: { ...options, headers } };
},
// requestInterceptors: [
// (url, options) => {
// // 为所有请求添加 API 前缀
// if (url && !url.startsWith(process.env.API_PREFIX || "/admin-api")) {
// url = (process.env.API_PREFIX || "/admin-api") + url;
// }
// // 获取存储在本地的 token 和 tenantId
// const token = getAccessToken();
// const tenantId = getTenantId(); // 默认租户ID为1
// console.log("request", options);
// // 设置统一的请求头
// const contentType: string = options.headers?.["Content-Type"] as string;
// const headers: Record<string, string> = {
// ...options.headers,
// Accept: "*",
// "Content-Type": contentType || "application/json",
// "tenant-id": tenantId,
// };
// // 如果有token则添加Authorization头
// if (token) {
// headers.Authorization = `Bearer ${getAccessToken()}`;
// }
// console.log("headers", headers);
// return { url, options: { ...options, ...headers } };
// },
],
// 添加参数序列化配置
@@ -234,7 +212,7 @@ export const request: RequestConfig = {
const appendParams = (key: string, value: any) => {
if (Array.isArray(value)) {
// 特殊处理 createTime 数组,转换为 createTime[0] 和 createTime[1] 格式
if (key === "createTime") {
if (key === 'createTime') {
value.forEach((val, index) => {
searchParams.append(`${key}[${index}]`, val);
});
@@ -272,7 +250,7 @@ export async function patchClientRoutes({ routes }: any) {
wsCache.set(CACHE_KEY.ROLE_ROUTERS, data.menus);
menus = data.menus;
} catch (error) {
console.error("获取菜单失败:", error);
console.error('获取菜单失败:', error);
return;
}
}
@@ -280,7 +258,7 @@ export async function patchClientRoutes({ routes }: any) {
if (!menus || menus.length === 0) {
return;
}
const routerIndex = routes.findIndex((item: any) => item.path === "/");
const routerIndex = routes.findIndex((item: any) => item.path === '/');
const parentId = routes[routerIndex].id;
if (menus) {

View File

@@ -0,0 +1,61 @@
.drag-overlay-tag {
cursor: grabbing;
.overlay-tag {
display: inline-flex;
align-items: center;
padding: 8px 12px;
font-size: 14px;
user-select: none;
border-radius: 6px;
background: #fff;
border: 2px solid #1890ff;
box-shadow: 0 8px 24px rgba(24, 144, 255, 0.4);
.tag-content {
display: flex;
align-items: center;
gap: 8px;
.drag-handle {
display: flex;
align-items: center;
color: #1890ff;
font-size: 16px;
}
.tag-label {
font-weight: 600;
color: #262626;
display: flex;
align-items: center;
gap: 6px;
.sort-badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 20px;
height: 20px;
padding: 0 6px;
background: #1890ff;
color: #fff;
border-radius: 10px;
font-size: 12px;
font-weight: bold;
}
}
}
}
}
@keyframes float {
0% {
transform: scale(1);
opacity: 0.8;
}
100% {
transform: scale(1.1);
opacity: 1;
}
}

View File

@@ -0,0 +1,30 @@
import { DragOutlined } from '@ant-design/icons';
import { Tag } from 'antd';
import React, { memo } from 'react';
import type { TagItem } from '../types';
import './index.less';
interface DragOverlayTagProps {
tag: TagItem;
}
const DragOverlayTag: React.FC<DragOverlayTagProps> = memo(({ tag }) => {
return (
<div className="drag-overlay-tag">
<Tag className="overlay-tag">
<div className="tag-content">
<span className="drag-handle">
<DragOutlined />
</span>
<span className="tag-label">
{tag.label}
<span className="sort-badge">{tag.sort}</span>
</span>
</div>
</Tag>
</div>
);
});
DragOverlayTag.displayName = 'DragOverlayTag';
export default DragOverlayTag;

View File

@@ -0,0 +1,179 @@
.draggable-tag-list {
padding: 24px;
background: #fff;
border-radius: 8px;
.tag-container {
display: flex;
flex-wrap: wrap;
gap: 12px;
padding: 20px;
background: #fafafa;
border-radius: 6px;
min-height: 120px;
border: 2px dashed #d9d9d9;
transition: border-color 0.3s;
&:hover {
border-color: #40a9ff;
}
}
.sortable-tag {
display: inline-block;
// 使用 will-change 优化动画性能
will-change: opacity, transform;
// 拖拽中的标签 - 原位置透明度降低
&.active {
opacity: 0.3;
transition: opacity 0.2s ease;
.custom-tag {
border-color: #1890ff;
background: #e6f7ff;
}
}
// 经过的目标标签 - 高亮显示
&.over {
.custom-tag {
border-color: #52c41a;
background: #f6ffed;
box-shadow: 0 0 0 4px rgba(82, 196, 26, 0.2);
transform: scale(1.08);
// 使用 transform 而不是复杂动画
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
}
// 禁用状态
&.disabled {
.custom-tag {
background: #f5f5f5;
border-color: #d9d9d9;
color: rgba(0, 0, 0, 0.45);
cursor: not-allowed;
.drag-handle {
display: none;
}
}
}
.custom-tag {
display: inline-flex;
align-items: center;
padding: 8px 12px;
font-size: 14px;
user-select: none;
border-radius: 6px;
// 优化过渡效果
transition: border-color 0.2s ease, box-shadow 0.2s ease;
background: #fff;
border: 2px solid #d9d9d9;
// 使用 GPU 加速
transform: translateZ(0);
&:hover:not(.disabled) {
border-color: #40a9ff;
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.15);
}
.tag-content {
display: flex;
align-items: center;
gap: 8px;
.drag-handle {
display: flex;
align-items: center;
cursor: grab;
color: #8c8c8c;
transition: color 0.2s;
font-size: 16px;
&:hover {
color: #1890ff;
}
&:active {
cursor: grabbing;
}
}
.tag-label {
flex: 1;
font-weight: 500;
display: flex;
align-items: center;
gap: 6px;
.sort-badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 20px;
height: 20px;
padding: 0 6px;
background: #1890ff;
color: #fff;
border-radius: 10px;
font-size: 12px;
font-weight: bold;
}
}
.edit-icon {
cursor: pointer;
color: #8c8c8c;
transition: color 0.2s;
font-size: 14px;
&:hover {
color: #1890ff;
}
}
.disabled-badge {
padding: 2px 8px;
background: #ff4d4f;
color: #fff;
border-radius: 4px;
font-size: 12px;
margin-left: 4px;
font-weight: normal;
}
}
}
}
.sort-info {
padding: 16px;
background: #f0f2f5;
border-radius: 6px;
border: 1px solid #d9d9d9;
h4 {
margin: 0 0 12px 0;
font-size: 14px;
font-weight: 600;
color: #262626;
}
.sort-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
.sort-item {
padding: 4px 12px;
background: #fff;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 13px;
color: #595959;
}
}
}
}

View File

@@ -0,0 +1,318 @@
import { PlusOutlined } from '@ant-design/icons';
import {
DndContext,
type DragEndEvent,
type DragOverEvent,
DragOverlay,
type DragStartEvent,
PointerSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import { rectSortingStrategy, SortableContext } from '@dnd-kit/sortable';
import { Button, Form, Input, Modal, message, Space, Switch } from 'antd';
import React, { useCallback, useMemo, useState } from 'react';
import DragOverlayTag from '../DragOverlayTag';
import { SortableTag } from '../SortableTag';
import type { TagItem } from '../types';
import './index.less';
const DraggableTagList: React.FC = () => {
const [tags, setTags] = useState<TagItem[]>([
{ id: '1', label: '标签1', disabled: false, sort: 1 },
{ id: '2', label: '标签2', disabled: false, sort: 2 },
{ id: '3', label: '标签3', disabled: true, sort: 3 },
{ id: '4', label: '标签4', disabled: false, sort: 4 },
{ id: '5', label: '标签5', disabled: false, sort: 5 },
]);
const [isModalVisible, setIsModalVisible] = useState(false);
const [editingTag, setEditingTag] = useState<TagItem | null>(null);
const [activeId, setActiveId] = useState<string | null>(null);
const [overId, setOverId] = useState<string | null>(null);
const [form] = Form.useForm();
// 优化传感器配置
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
distance: 8,
},
}),
);
// 创建标签映射,避免重复查找
const tagMap = useMemo(() => {
const map = new Map<string, TagItem>();
tags.forEach((tag) => {
map.set(tag.id, tag);
});
return map;
}, [tags]);
// 拖拽开始 - 使用 useCallback 避免重复创建
const handleDragStart = useCallback(
(event: DragStartEvent) => {
const { active } = event;
const activeTag = tagMap.get(active.id as string);
if (activeTag?.disabled) {
return;
}
setActiveId(active.id as string);
},
[tagMap],
);
// 拖拽取消
const handleDragCancel = useCallback(() => {
setActiveId(null);
setOverId(null);
}, []);
// 优化拖拽经过 - 减少不必要的状态更新
const handleDragOver = useCallback(
(event: DragOverEvent) => {
const { over, active } = event;
// 如果没有 over 或 active清除 overId
if (!over || !active) {
if (overId !== null) {
setOverId(null);
}
return;
}
const newOverId = over.id as string;
// 如果 overId 没有变化,不更新状态
if (newOverId === overId) {
return;
}
const activeTag = tagMap.get(active.id as string);
const overTag = tagMap.get(newOverId);
// 检查是否可以高亮
if (
!activeTag ||
activeTag.disabled ||
!overTag ||
overTag.disabled ||
newOverId === active.id
) {
if (overId !== null) {
setOverId(null);
}
return;
}
setOverId(newOverId);
},
[overId, tagMap],
);
// 拖拽结束 - 使用 useCallback
const handleDragEnd = useCallback((event: DragEndEvent) => {
const { active, over } = event;
setActiveId(null);
setOverId(null);
if (!over || active.id === over.id) {
return;
}
setTags((items) => {
const activeIndex = items.findIndex((item) => item.id === active.id);
const overIndex = items.findIndex((item) => item.id === over.id);
if (activeIndex === -1 || overIndex === -1) {
return items;
}
if (items[activeIndex].disabled) {
message.warning('禁用的标签不可拖拽');
return items;
}
if (items[overIndex].disabled) {
message.warning('不能与禁用的标签交换位置');
return items;
}
const newItems = [...items];
// 交换sort值
const tempSort = newItems[activeIndex].sort;
newItems[activeIndex] = {
...newItems[activeIndex],
sort: newItems[overIndex].sort,
};
newItems[overIndex] = {
...newItems[overIndex],
sort: tempSort,
};
// 交换位置
[newItems[activeIndex], newItems[overIndex]] = [
newItems[overIndex],
newItems[activeIndex],
];
message.success('交换成功');
return newItems;
});
}, []);
// 添加标签
const handleAddTag = useCallback(() => {
setEditingTag(null);
form.resetFields();
setIsModalVisible(true);
}, [form]);
// 编辑标签
const handleEditTag = useCallback(
(tag: TagItem) => {
setEditingTag(tag);
form.setFieldsValue(tag);
setIsModalVisible(true);
},
[form],
);
// 删除标签
const handleDeleteTag = useCallback((id: string) => {
setTags((prevTags) => prevTags.filter((tag) => tag.id !== id));
message.success('删除成功');
}, []);
// 保存标签
const handleSaveTag = useCallback(async () => {
try {
const values = await form.validateFields();
if (editingTag) {
setTags((prevTags) =>
prevTags.map((tag) =>
tag.id === editingTag.id ? { ...tag, ...values } : tag,
),
);
message.success('编辑成功');
} else {
setTags((prevTags) => {
const maxSort = Math.max(...prevTags.map((t) => t.sort), 0);
const newTag: TagItem = {
id: Date.now().toString(),
disabled: false,
sort: maxSort + 1,
...values,
};
return [...prevTags, newTag];
});
message.success('添加成功');
}
setIsModalVisible(false);
form.resetFields();
} catch (error) {
console.error('验证失败:', error);
}
}, [editingTag, form]);
// 获取当前拖拽的标签
const activeTag = useMemo(
() => (activeId ? tagMap.get(activeId) : null),
[activeId, tagMap],
);
// 生成 sortable items
const sortableItems = useMemo(() => tags.map((tag) => tag.id), [tags]);
return (
<div className="draggable-tag-list">
<Space direction="vertical" style={{ width: '100%' }} size="large">
<Button type="dashed" icon={<PlusOutlined />} onClick={handleAddTag}>
</Button>
<DndContext
sensors={sensors}
onDragStart={handleDragStart}
onDragOver={handleDragOver}
onDragEnd={handleDragEnd}
onDragCancel={handleDragCancel}
>
<SortableContext items={sortableItems} strategy={rectSortingStrategy}>
<div className="tag-container">
{tags.map((tag) => (
<SortableTag
key={tag.id}
tag={tag}
isActive={activeId === tag.id}
isOver={overId === tag.id}
onEdit={handleEditTag}
onDelete={handleDeleteTag}
/>
))}
</div>
</SortableContext>
<DragOverlay dropAnimation={null}>
{activeTag ? <DragOverlayTag tag={activeTag} /> : null}
</DragOverlay>
</DndContext>
<div className="sort-info">
<h4></h4>
<div className="sort-list">
{tags.map((tag) => (
<span key={tag.id} className="sort-item">
{tag.label} (sort: {tag.sort})
</span>
))}
</div>
</div>
</Space>
<Modal
title={editingTag ? '编辑标签' : '添加标签'}
open={isModalVisible}
onOk={handleSaveTag}
onCancel={() => {
setIsModalVisible(false);
form.resetFields();
}}
okText="确定"
cancelText="取消"
>
<Form form={form} layout="vertical">
<Form.Item
name="label"
label="标签名称"
rules={[
{ required: true, message: '请输入标签名称' },
{ max: 20, message: '标签名称不能超过20个字符' },
]}
>
<Input placeholder="请输入标签名称" maxLength={20} />
</Form.Item>
<Form.Item
name="disabled"
label="禁用状态"
valuePropName="checked"
initialValue={false}
tooltip="禁用后的标签不可拖拽、不可删除、不可作为交换目标"
>
<Switch checkedChildren="禁用" unCheckedChildren="启用" />
</Form.Item>
</Form>
</Modal>
</div>
);
};
export default DraggableTagList;

View File

@@ -0,0 +1,86 @@
import { DragOutlined, EditOutlined } from '@ant-design/icons';
import { useSortable } from '@dnd-kit/sortable';
import { Tag } from 'antd';
import React, { memo } from 'react';
import type { TagItem } from '../types';
interface SortableTagProps {
tag: TagItem;
isActive: boolean;
isOver: boolean;
onEdit: (tag: TagItem) => void;
onDelete: (id: string) => void;
}
export const SortableTag: React.FC<SortableTagProps> = memo(
({ tag, isActive, isOver, onEdit, onDelete }) => {
const { attributes, listeners, setNodeRef } = useSortable({
id: tag.id,
disabled: tag.disabled,
});
// 计算样式类名
const className = [
'sortable-tag',
tag.disabled && 'disabled',
isActive && 'active',
isOver && !tag.disabled && 'over',
]
.filter(Boolean)
.join(' ');
// 处理删除
const handleDelete = (e: React.MouseEvent) => {
e.preventDefault();
if (!tag.disabled) {
onDelete(tag.id);
}
};
// 处理编辑
const handleEdit = (e: React.MouseEvent) => {
e.stopPropagation();
onEdit(tag);
};
return (
<div ref={setNodeRef} className={className}>
<Tag
className="custom-tag"
closable={!tag.disabled}
onClose={handleDelete}
>
<div className="tag-content">
{!tag.disabled && (
<span className="drag-handle" {...attributes} {...listeners}>
<DragOutlined />
</span>
)}
<span className="tag-label">
{tag.label}
<span className="sort-badge">{tag.sort}</span>
</span>
<EditOutlined className="edit-icon" onClick={handleEdit} />
{tag.disabled && <span className="disabled-badge"></span>}
</div>
</Tag>
</div>
);
},
// 自定义比较函数,只在必要时重新渲染
(prevProps, nextProps) => {
return (
prevProps.tag.id === nextProps.tag.id &&
prevProps.tag.label === nextProps.tag.label &&
prevProps.tag.disabled === nextProps.tag.disabled &&
prevProps.tag.sort === nextProps.tag.sort &&
prevProps.isActive === nextProps.isActive &&
prevProps.isOver === nextProps.isOver
);
},
);
SortableTag.displayName = 'SortableTag';

View File

@@ -0,0 +1,8 @@
export interface TagItem {
id: string;
label: string;
disabled?: boolean;
sort: number; // 添加排序字段
isExpire?: number; // 是否失效是否失效0否1是
state?: number; // 状态 0正常1禁用
}

View File

@@ -1,13 +1,13 @@
import type { ProFormColumnsType } from "@ant-design/pro-components";
import { BetaSchemaForm } from "@ant-design/pro-components";
import { Button, type ColProps, Drawer, Space } from "antd";
import type { FormInstance } from "antd/lib";
import type { ProFormColumnsType } from '@ant-design/pro-components';
import { BetaSchemaForm } from '@ant-design/pro-components';
import { Button, type ColProps, Drawer, Space } from 'antd';
import type { FormInstance } from 'antd/lib';
import React, {
Children,
cloneElement,
forwardRef,
useImperativeHandle,
} from "react";
} from 'react';
interface ConfigurableDrawerFormProps {
title?: string;
@@ -33,18 +33,18 @@ const ConfigurableDrawerForm = forwardRef<
>(
(
{
title = "表单",
labelCol = { span: 4 },
wrapperCol = { span: 20 },
title = '表单',
labelCol = { span: 3 },
wrapperCol = { span: 21 },
columns,
onSubmit,
initialValues,
width = 600,
width = 700,
footer,
children,
bodyStyle = {},
},
ref
ref,
) => {
const [open, setOpen] = React.useState(false);
const [formData, setFormData] = React.useState(initialValues || {});
@@ -56,7 +56,7 @@ const ConfigurableDrawerForm = forwardRef<
if (data) {
setFormData(data);
}
console.log("open");
console.log('open');
setOpen(true);
},
close: () => setOpen(false),
@@ -66,7 +66,8 @@ const ConfigurableDrawerForm = forwardRef<
if (React.isValidElement(child)) {
// 使用更安全的方式传递属性,避免 TypeScript 错误
return cloneElement(child, {
...formData,
data: formData,
formRef,
} as any);
}
return child;
@@ -96,12 +97,12 @@ const ConfigurableDrawerForm = forwardRef<
title={title}
styles={{
header: {
textAlign: "left",
position: "relative",
textAlign: 'left',
position: 'relative',
},
body: {
background: "var(--ant-background-color)",
padding: "var(--ant-padding-lg)",
background: 'var(--ant-background-color)',
padding: 'var(--ant-padding-lg)',
...bodyStyle,
},
}}
@@ -111,7 +112,7 @@ const ConfigurableDrawerForm = forwardRef<
onClose={() => setOpen(false)}
width={width}
footer={
<Space style={{ width: "100%", justifyContent: "end" }}>
<Space style={{ width: '100%', justifyContent: 'end' }}>
{footer ? (
footer
) : (
@@ -142,7 +143,7 @@ const ConfigurableDrawerForm = forwardRef<
)}
</Drawer>
);
}
},
);
export default ConfigurableDrawerForm;

View File

@@ -0,0 +1,67 @@
import { Button, Modal, Space } from 'antd';
import React, { createContext } from 'react';
const ReachableContext = createContext<string | null>(null);
const UnreachableContext = createContext<string | null>(null);
const config = {
title: 'Use Hook!',
content: (
<>
<ReachableContext.Consumer>
{(name) => `Reachable: ${name}!`}
</ReachableContext.Consumer>
<br />
<UnreachableContext.Consumer>
{(name) => `Unreachable: ${name}!`}
</UnreachableContext.Consumer>
</>
),
};
const ConfirmModal: React.FC = () => {
const [modal, contextHolder] = Modal.useModal();
return (
<ReachableContext.Provider value="Light">
<Space>
<Button
onClick={async () => {
const confirmed = await modal.confirm(config);
console.log('Confirmed: ', confirmed);
}}
>
Confirm
</Button>
<Button
onClick={() => {
modal.warning(config);
}}
>
Warning
</Button>
<Button
onClick={async () => {
modal.info(config);
}}
>
Info
</Button>
<Button
onClick={async () => {
modal.error(config);
}}
>
Error
</Button>
</Space>
{/* `contextHolder` should always be placed under the context you want to access */}
{contextHolder}
{/* Can not access this context since `contextHolder` is not in it */}
<UnreachableContext.Provider value="Bamboo" />
</ReachableContext.Provider>
);
};
export default ConfirmModal;

View File

@@ -64,7 +64,7 @@ const TagEditor: React.FC<TagEditorProps> = ({
const canAddMore = !maxCount || tags.length < maxCount;
return (
<Space wrap>
<Space wrap style={{ width: '100%' }}>
{tags.map((tag) => (
<Tag
key={tag}

View File

@@ -1,8 +1,8 @@
import { uploadImage } from "@/services/infra/media";
import { Editor } from "@tinymce/tinymce-react";
import { message } from "antd";
import React, { useCallback, useState } from "react";
import type { Editor as TinyMCEEditor } from "tinymce";
import { Editor } from '@tinymce/tinymce-react';
import { message } from 'antd';
import React, { useCallback, useState } from 'react';
import type { Editor as TinyMCEEditor } from 'tinymce';
import { uploadImage } from '@/services/infra/media';
export interface RichEditorProps {
value?: string;
onChange?: (content: string) => void;
@@ -22,7 +22,7 @@ export interface RichEditorProps {
console.log(VITE_BASE_URL);
const RichEditor: React.FC<RichEditorProps> = ({
value = "",
value = '',
onChange,
// height = 400,
// placeholder = "请输入内容...",
@@ -31,7 +31,7 @@ const RichEditor: React.FC<RichEditorProps> = ({
// maxWords,
uploadConfig = {
maxSize: 5,
acceptTypes: ["image/jpeg", "image/png", "image/gif", "image/webp"],
acceptTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
},
}) => {
const [uploading, setUploading] = useState<boolean>(false);
@@ -39,13 +39,13 @@ const RichEditor: React.FC<RichEditorProps> = ({
async (file: File): Promise<string> => {
return new Promise((resolve, reject) => {
const formData = new FormData();
formData.append("file", file);
formData.append('file', file);
try {
uploadImage(formData).then((res) => {
if (res) {
resolve(res);
} else {
reject(new Error("上传失败未返回有效的URL"));
reject(new Error('上传失败未返回有效的URL'));
}
});
} catch (error) {
@@ -53,7 +53,7 @@ const RichEditor: React.FC<RichEditorProps> = ({
}
});
},
[uploadConfig]
[uploadConfig],
);
const handleImageUpload = useCallback(
(blobInfo: any, _: (percent: number) => void): Promise<string> => {
@@ -65,23 +65,23 @@ const RichEditor: React.FC<RichEditorProps> = ({
resolve(url);
});
message.success("图片上传成功");
message.success('图片上传成功');
} catch (error) {
reject(error);
message.error(typeof error === "string" ? error : "上传失败");
message.error(typeof error === 'string' ? error : '上传失败');
} finally {
setUploading(false);
}
});
},
[uploadFile]
[uploadFile],
);
const handleCustomUpload = useCallback(
(editor: TinyMCEEditor) => {
const input = document.createElement("input");
input.type = "file";
input.accept = uploadConfig.acceptTypes?.join(",") || "image/*";
const input = document.createElement('input');
input.type = 'file';
input.accept = uploadConfig.acceptTypes?.join(',') || 'image/*';
input.multiple = true;
const handleFileChange = async (event: Event) => {
@@ -104,7 +104,7 @@ const RichEditor: React.FC<RichEditorProps> = ({
file.size / 1024 / 1024 < (uploadConfig.maxSize || 5);
if (!isValidSize) {
message.error(
`文件 ${file.name} 大小超过 ${uploadConfig.maxSize || 5}MB`
`文件 ${file.name} 大小超过 ${uploadConfig.maxSize || 5}MB`,
);
continue;
}
@@ -123,10 +123,10 @@ const RichEditor: React.FC<RichEditorProps> = ({
}
};
input.addEventListener("change", handleFileChange);
input.addEventListener('change', handleFileChange);
input.click();
},
[uploadConfig, uploadFile]
[uploadConfig, uploadFile],
);
const handleEditorChange = (content: string) => {
@@ -143,18 +143,18 @@ const RichEditor: React.FC<RichEditorProps> = ({
init={{
plugins: [
// Core editing features
"anchor",
"autolink",
"charmap",
"codesample",
"emoticons",
"link",
"lists",
"media",
"searchreplace",
"table",
"visualblocks",
"wordcount",
'anchor',
'autolink',
'charmap',
'codesample',
'emoticons',
'link',
'lists',
'media',
'searchreplace',
'table',
'visualblocks',
'wordcount',
// Your account includes a free trial of TinyMCE premium features
// Try the most popular premium features until Oct 8, 2025:
// "checklist",
@@ -187,44 +187,42 @@ const RichEditor: React.FC<RichEditorProps> = ({
menubar: false,
toolbar:
"undo redo | blocks | bold italic forecolor " +
"alignleft aligncenter | " +
"alignright alignjustify | bullist numlist outdent indent customupload | " +
"removeformat ",
tinycomments_mode: "embedded",
tinycomments_author: "Author name",
toolbar_mode: "wrap" as const, // 改为 wrap 模式,全部展开
'undo redo | blocks | bold italic forecolor ' +
'alignleft aligncenter | ' +
'alignright alignjustify | bullist numlist outdent indent customupload | ' +
'removeformat ',
tinycomments_mode: 'embedded',
tinycomments_author: 'Author name',
toolbar_mode: 'wrap' as const, // 改为 wrap 模式,全部展开
// toolbar_mode: "sliding",
mergetags_list: [
{ value: "First.Name", title: "First Name" },
{ value: "Email", title: "Email" },
{ value: 'First.Name', title: 'First Name' },
{ value: 'Email', title: 'Email' },
],
ai_request: (
_: any,
respondWith: { string: (arg0: () => Promise<never>) => any }
respondWith: { string: (arg0: () => Promise<never>) => any },
) =>
respondWith.string(() =>
Promise.reject("See docs to implement AI Assistant")
Promise.reject('See docs to implement AI Assistant'),
),
language: "zh_CN",
language: 'zh_CN',
// 其他配置
table_class_list: [
{ title: "无样式", value: "" },
{ title: "简单表格", value: "simple-table" },
{ title: "条纹表格", value: "striped-table" },
{ title: "边框表格", value: "bordered-table" },
{ title: '无样式', value: '' },
{ title: '简单表格', value: 'simple-table' },
{ title: '条纹表格', value: 'striped-table' },
{ title: '边框表格', value: 'bordered-table' },
],
convert_urls: false,
remove_script_host: false,
uploadcare_public_key: "0ad3671d77f59c5756dd",
uploadcare_public_key: '0ad3671d77f59c5756dd',
setup: (editor: TinyMCEEditor) => {
// 注册自定义上传按钮
editor.ui.registry.addButton("customupload", {
text: uploading ? "上传中..." : "上传",
icon: "upload",
tooltip: "上传图片(支持多选)",
editor.ui.registry.addButton('customupload', {
text: uploading ? '上传中...' : '上传',
icon: 'upload',
tooltip: '上传图片(支持多选)',
enabled: !disabled && !uploading,
onAction: () => {
handleCustomUpload(editor);
@@ -235,7 +233,9 @@ const RichEditor: React.FC<RichEditorProps> = ({
images_upload_handler: handleImageUpload,
// 性能配置
browser_spellcheck: true,
contextmenu: "link image table",
contextmenu: 'link image table',
promotion: false,
branding: false, // 去除品牌标识
}}
// onBlur={() =>onChange()}

View File

@@ -61,7 +61,6 @@ const AudioUploader: React.FC<AudioUploaderProps> = ({
return true;
};
// 实际的后端接口上传
const uploadToServer = async (file: File) => {
const formData = new FormData();

View File

@@ -0,0 +1,147 @@
import { PlusOutlined } from '@ant-design/icons';
import type { GetProp, UploadFile, UploadProps } from 'antd';
import { Image, message, Spin, Upload } from 'antd';
import React, { useCallback, useEffect, useState } from 'react';
import { uploadImage } from '@/services/infra/media';
type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
const getBase64 = (file: FileType): Promise<string> =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result as string);
reader.onerror = (error) => reject(error);
});
// accept: .doc,.docx,.xml,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document image/*,.pdf
const UploadImages: React.FC<{
value?: string;
onChange?: (value: string | string[]) => void;
multiple?: boolean;
accept?: string;
maxCount?: number;
}> = (props) => {
const {
value,
multiple = false,
maxCount = 1,
accept = 'image/png,image/jpeg',
onChange,
} = props;
const [previewOpen, setPreviewOpen] = useState(false);
const [previewImage, setPreviewImage] = useState('');
const [fileList, setFileList] = useState<UploadFile[]>([]);
const [uploading, setUploading] = useState(false);
useEffect(() => {
if (value) {
setFileList([{ uid: '-1', url: value, status: 'done', name: value }]);
} else {
setFileList([]);
}
}, [value]);
const beforeUpload = (file: FileType) => {
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';
if (!isJpgOrPng) {
message.error('仅支持.jpg .png 格式!');
}
// const isLt2M = file.size / 1024 / 1024 < 2;
// if (!isLt2M) {
// message.error('Image must smaller than 2MB!');
// }
return isJpgOrPng;
};
const handlePreview = async (file: UploadFile) => {
if (!file.url && !file.preview) {
file.preview = await getBase64(file.originFileObj as FileType);
}
setPreviewImage(file.url || (file.preview as string));
setPreviewOpen(true);
};
const handleRemove = (file: UploadFile): boolean => {
const newFileList = fileList.filter((item) => item.uid !== file.uid);
const newUrl = newFileList.map((item) => item.url) as string[];
onChange?.(newUrl[0]);
return true;
};
const uploadButton = (
<button style={{ border: 0, background: 'none' }} type="button">
<PlusOutlined />
<div style={{ marginTop: 8 }}>Upload</div>
</button>
);
const uploadFile = useCallback(async (file: File): Promise<string> => {
return new Promise((resolve, reject) => {
const formData = new FormData();
formData.append('file', file);
try {
uploadImage(formData).then((res) => {
if (res) {
resolve(res);
} else {
reject(new Error('上传失败未返回有效的URL'));
}
});
} catch (error) {
reject(error);
}
});
}, []);
const handleLoadImage: UploadProps['customRequest'] = async (option) => {
const { file, onSuccess, onError, onProgress } = option;
try {
setUploading(true);
// 模拟进度更新
onProgress?.({ percent: 10 });
// 调用后端接口
const url = await uploadFile(file as File);
onProgress?.({ percent: 100 });
if (url) {
onChange?.(url);
onSuccess?.({ url });
message.success('上传成功');
} else {
throw new Error('上传失败');
}
} catch (error) {
console.error('Upload error:', error);
onError?.(error as Error);
message.error(`上传失败: ${(error as Error).message}`);
} finally {
setUploading(false);
}
};
return (
<Spin spinning={uploading}>
<Upload
listType="picture-card"
fileList={fileList}
onPreview={handlePreview}
onRemove={handleRemove}
beforeUpload={beforeUpload}
customRequest={handleLoadImage}
multiple={multiple}
accept={accept}
>
{fileList.length >= maxCount ? null : uploadButton}
</Upload>
{previewImage && (
<Image
wrapperStyle={{ display: 'none' }}
preview={{
visible: previewOpen,
onVisibleChange: (visible) => setPreviewOpen(visible),
afterOpenChange: (visible) => !visible && setPreviewImage(''),
}}
src={previewImage}
/>
)}
</Spin>
);
};
export default UploadImages;

View File

@@ -0,0 +1,192 @@
import { PlusOutlined } from '@ant-design/icons';
import type { GetProp, UploadFile, UploadProps } from 'antd';
import { Modal, message, Spin, Upload } from 'antd';
import React, { useCallback, useEffect, useState } from 'react';
import VideoThumbnail from 'react-video-thumbnail';
import { uploadImage } from '@/services/infra/media';
type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
interface CustomUploadFile extends UploadFile {
thumbUrl?: string;
preview?: string;
}
const getBase64 = (file: FileType): Promise<string> =>
new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result as string);
reader.onerror = (error) => reject(error);
});
// accept: .doc,.docx,.xml,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document image/*,.pdf
const UploadVideo: React.FC<{
value?: string;
onChange?: (value: string | string[]) => void;
multiple?: boolean;
accept?: string;
maxCount?: number;
}> = (props) => {
const {
value,
multiple = false,
maxCount = 1,
accept = 'video/*',
onChange,
} = props;
const [previewOpen, setPreviewOpen] = useState(false);
const [previewVideo, setPreviewVideo] = useState('');
const [fileList, setFileList] = useState<CustomUploadFile[]>([]);
const [uploading, setUploading] = useState(false);
useEffect(() => {
if (value) {
setFileList([
{ uid: '-1', url: value, thumbUrl: value, status: 'done', name: value },
]);
} else {
setFileList([]);
}
}, [value]);
const beforeUpload = (file: FileType) => {
const isJpgOrPng = file.type === 'video/mp4' || file.type === 'video/mov';
if (!isJpgOrPng) {
message.error('仅支持.mp4 .mov 格式!');
}
// const isLt2M = file.size / 1024 / 1024 < 2;
// if (!isLt2M) {
// message.error('Image must smaller than 2MB!');
// }
return isJpgOrPng;
};
// // 生成缩略图组件
// const renderThumbnail = (videoUrl: string) => {
// return (
// <div style={{ width: "100%", height: "100%" }}>
// <VideoThumbnail
// videoUrl={videoUrl}
// width={120}
// height={90}
// snapshotAt={1}
// />
// </div>
// );
// };
const handlePreview = async (file: CustomUploadFile) => {
if (!file.url && !file.preview && file.originFileObj) {
file.preview = await getBase64(file.originFileObj);
}
setPreviewVideo(file.url || file.preview || '');
setPreviewOpen(true);
};
// const customItemRender: UploadProps["itemRender"] = (
// _,
// file: CustomUploadFile
// ) => {
// return (
// <div className="ant-upload-list-item-container">
// <div className="ant-upload-list-item-card">
// <div className="video-thumbnail" onClick={() => handlePreview(file)}>
// {file.thumbUrl ? (
// renderThumbnail("https://petshy.tashowz.com" + file.thumbUrl)
// ) : (
// <div className="video-icon">
// <PlayCircleOutlined
// style={{ fontSize: 48, color: "#1890ff" }}
// />
// </div>
// )}
// </div>
// <div className="ant-upload-list-item-name">{file.name}</div>
// </div>
// </div>
// );
// };
const handleRemove = (file: UploadFile): boolean => {
const newFileList = fileList.filter((item) => item.uid !== file.uid);
const newUrl = newFileList.map((item) => item.url) as string[];
onChange?.(newUrl[0]);
return true;
};
const uploadButton = (
<button style={{ border: 0, background: 'none' }} type="button">
<PlusOutlined />
<div style={{ marginTop: 8 }}></div>
</button>
);
const uploadFile = useCallback(async (file: File): Promise<string> => {
return new Promise((resolve, reject) => {
const formData = new FormData();
formData.append('file', file);
try {
uploadImage(formData).then((res) => {
if (res) {
resolve(res);
} else {
reject(new Error('上传失败未返回有效的URL'));
}
});
} catch (error) {
reject(error);
}
});
}, []);
const handleLoadImage: UploadProps['customRequest'] = async (option) => {
const { file, onSuccess, onError, onProgress } = option;
try {
setUploading(true);
// 模拟进度更新
onProgress?.({ percent: 10 });
// 调用后端接口
const url = await uploadFile(file as File);
onProgress?.({ percent: 100 });
if (url) {
onChange?.(url);
onSuccess?.({ url });
message.success('上传成功');
} else {
throw new Error('上传失败');
}
} catch (error) {
console.error('Upload error:', error);
onError?.(error as Error);
message.error(`上传失败: ${(error as Error).message}`);
} finally {
setUploading(false);
}
};
return (
<Spin spinning={uploading}>
<Upload
listType="picture-card"
fileList={fileList}
onPreview={handlePreview}
// itemRender={customItemRender}
onRemove={handleRemove}
beforeUpload={beforeUpload}
customRequest={handleLoadImage}
multiple={multiple}
accept={accept}
>
{fileList.length >= maxCount ? null : uploadButton}
</Upload>
<Modal
open={previewOpen}
title="视频预览"
footer={null}
onCancel={() => setPreviewOpen(false)}
width={800}
>
<video controls style={{ width: '100%', height: '500px' }} autoPlay>
<source src={previewVideo} type="video/mp4" />
<track kind="captions" src="" srcLang="zh" label="Chinese" />
</video>
</Modal>
</Spin>
);
};
export default UploadVideo;

View File

@@ -0,0 +1,2 @@
export const fallback =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynLv4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4OzsrPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apSAPj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNgAZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWHEnrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//59F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQhLI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2Ibzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydbHjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skdw43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkRYENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECueb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOsgJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZcoEnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg==';

View File

@@ -3,6 +3,9 @@ export const formStatusType: { [key: string]: string } = {
update: '编辑',
test: '测试',
detail: '详情',
sku: 'sku管理',
'extend-rule': '服务规则管理',
'extend-service': '扩展服务管理',
};
export const tenantStatus = [

View File

@@ -0,0 +1,5 @@
const CateGorySelect = () => {
return <div></div>;
};
export default CateGorySelect;

View File

@@ -2,51 +2,54 @@ import type {
ProColumns,
ProCoreActionType,
ProFormColumnsType,
} from "@ant-design/pro-components";
import dayjs from "dayjs";
import TagEditor from "@/components/TagEditor";
import TinyMCEEditor from "@/components/Tinymce";
} from '@ant-design/pro-components';
import { Modal, message, Switch } from 'antd';
import dayjs from 'dayjs';
import TagEditor from '@/components/TagEditor';
import TinyMCEEditor from '@/components/Tinymce';
import UploadImages from '@/components/Upload/UploadImages';
import { dateFormatS } from '@/constants';
import {
getCategoryList,
putCategoryUpdate,
} from "@/services/prodApi/category";
import { Input, message, Modal, Switch } from "antd";
export const baseTenantColumns: ProColumns<API.CategoryDO>[] = [
type Category,
getProdCategoryPage,
updateProdCategory,
} from '@/services/prod/category-manager';
export const baseTenantColumns: ProColumns<Category>[] = [
{
title: "类目名称",
dataIndex: "categoryName",
title: '类目名称',
dataIndex: 'categoryName',
width: 100,
},
{
title: "类目ID",
dataIndex: "categoryId",
title: '类目ID',
dataIndex: 'categoryId',
},
{
title: "类目层级",
dataIndex: "grade",
title: '类目层级',
dataIndex: 'grade',
hideInSearch: true, // 在搜索表单中隐藏
},
{
title: "父级类目",
dataIndex: "parentName",
title: '父级类目',
dataIndex: 'parentName',
hideInSearch: true, // 在搜索表单中隐藏
},
{
title: "排序权重",
dataIndex: "sort",
title: '排序权重',
dataIndex: 'sort',
hideInSearch: true, // 在搜索表单中隐藏
},
{
title: "状态",
dataIndex: "status",
valueType: "switch",
title: '状态',
dataIndex: 'status',
valueType: 'switch',
hideInSearch: true,
render: (
_,
record: API.CategoryDO,
record: Category,
_index: number,
action: ProCoreActionType | undefined
action: ProCoreActionType | undefined,
) => (
<Switch
checked={record.status === 1}
@@ -54,17 +57,17 @@ export const baseTenantColumns: ProColumns<API.CategoryDO>[] = [
unCheckedChildren="禁用"
onChange={(checked) => {
Modal.confirm({
title: "确认操作",
content: `确认要"${checked ? "启用" : "禁用"}${
title: '确认操作',
content: `确认要"${checked ? '启用' : '禁用'}${
record.categoryName
}"类目吗?`,
onOk: async () => {
console.log(checked);
await putCategoryUpdate({
await updateProdCategory({
status: checked ? 1 : 0,
categoryId: record.categoryId,
});
message.success("修改成功");
message.success('修改成功');
action?.reload();
},
});
@@ -73,145 +76,158 @@ export const baseTenantColumns: ProColumns<API.CategoryDO>[] = [
),
},
{
title: "创建时间",
dataIndex: "createTime",
valueType: "dateRange",
title: '创建时间',
dataIndex: 'createTime',
valueType: 'dateRange',
hideInSearch: true, // 在搜索表单中隐藏
render: (_, record: API.CategoryDO) =>
dayjs(record.createTime).format("YYYY-MM-DD HH:mm:ss"),
render: (_, record: Category) =>
dayjs(record.createTime).format('YYYY-MM-DD HH:mm:ss'),
},
];
export const formColumns = (data: {
type: string;
grade: number;
}): ProFormColumnsType[] => [
export const formColumns = (data: { type: string }): ProFormColumnsType[] => {
console.log(data, 'data');
return [
{
title: "类目",
dataIndex: "grade",
valueType: "radio",
title: '类目',
dataIndex: 'grade',
valueType: 'radio',
fieldProps: {
options: [
{ label: "一级类目", value: 1 },
{ label: "二级类目", value: 2 },
{ label: "三级类目", value: 3 },
{ label: '一级类目', value: 1 },
{ label: '二级类目', value: 2 },
{ label: '三级类目', value: 3 },
],
disabled: data.type === "update",
disabled: data.type === 'update',
},
},
{
title: "类目名称",
dataIndex: "categoryName",
title: '类目名称',
dataIndex: 'categoryName',
formItemProps: {
rules: [
{
required: true,
message: "请输入用户名",
message: '请输入用户名',
},
],
},
},
{
title: "排序权重",
dataIndex: "sort",
valueType: "digit",
title: '排序权重',
dataIndex: 'sort',
valueType: 'digit',
},
{
title: "类目描述",
dataIndex: "description",
valueType: "textarea",
title: '类目描述',
dataIndex: 'description',
valueType: 'textarea',
renderFormItem: () => {
return <TinyMCEEditor />;
},
},
{
title: "关联父级",
dataIndex: "parentId",
valueType: "select",
hideInForm: data.grade - 1 <= 0,
title: '关联父级',
dataIndex: 'parentId',
valueType: 'select',
fieldProps: {
fieldNames: { label: "categoryName", value: "categoryId" },
fieldNames: { label: 'categoryName', value: 'categoryId' },
},
request: async () => {
const grade = data.grade ? data.grade - 1 : undefined;
const res = await getCategoryList({ grade });
dependencies: ['grade'],
request: async (params) => {
const res = await getProdCategoryPage({
grade: Number(params.grade) - 1,
});
return res;
},
renderFormItem: (schema, config, form) => {
const grade = form.getFieldValue('grade');
if (Number(grade) === 1) {
return null;
}
return config.defaultRender(schema);
},
},
{
title: "类目icon",
dataIndex: "icon",
title: '类目icon',
dataIndex: 'icon',
renderFormItem: () => {
return <UploadImages multiple={true} />;
},
},
{
title: "类目标签",
dataIndex: "tag",
title: '类目标签',
dataIndex: 'tag',
renderFormItem: () => {
return (
<TagEditor
placeholder="输入标签名称"
maxCount={10}
tagProps={{
color: "blue",
color: 'blue',
}}
/>
);
},
},
{
title: "类目状态",
dataIndex: "status",
hideInForm: data.type === "create",
fieldProps: {
disabled: data.type === "update",
},
title: '类目状态',
dataIndex: 'status',
hideInForm: data.type === 'create',
renderFormItem: (_schema, _config, form) => {
const status = form.getFieldValue("type");
return <Input value={status ? "开启" : "禁用"} disabled />;
const status = form.getFieldValue('status');
return <span>{status ? '启用' : '禁用'}</span>;
},
},
{
title: "类目ID",
dataIndex: "categoryId",
hideInForm: data.type === "create",
fieldProps: {
disabled: data.type === "update",
title: '类目ID',
dataIndex: 'categoryId',
hideInForm: data.type === 'create',
renderFormItem: (_schema, _config, form) => {
const categoryId = form.getFieldValue('categoryId');
return <span>{categoryId}</span>;
},
},
{
title: "创建时间",
dataIndex: "createTime",
valueType: "dateTime",
hideInForm: data.type === "create",
fieldProps: {
disabled: data.type === "update",
title: '创建时间',
dataIndex: 'createTime',
valueType: 'dateTime',
hideInForm: data.type === 'create',
renderFormItem: (_schema, _config, form) => {
const createTime = form.getFieldValue('createTime');
return <span>{dayjs(createTime).format(dateFormatS)}</span>;
},
},
{
title: "创建人",
dataIndex: "creator",
hideInForm: data.type === "create",
fieldProps: {
disabled: data.type === "update",
title: '创建人',
dataIndex: 'creator',
hideInForm: data.type === 'create',
renderFormItem: (_schema, _config, form) => {
const creator = form.getFieldValue('creator');
return <span>{creator}</span>;
},
},
{
title: "更新时间",
dataIndex: "updateTime",
valueType: "dateTime",
hideInForm: data.type === "create",
fieldProps: {
disabled: data.type === "update",
title: '更新时间',
dataIndex: 'updateTime',
valueType: 'dateTime',
hideInForm: data.type === 'create',
renderFormItem: (_schema, _config, form) => {
const dateTime = form.getFieldValue('dateTime');
return <span>{dayjs(dateTime).format(dateFormatS)}</span>;
},
},
{
title: "更新人",
dataIndex: "updator",
hideInForm: data.type === "create",
fieldProps: {
disabled: data.type === "update",
title: '更新人',
dataIndex: 'updator',
hideInForm: data.type === 'create',
renderFormItem: (_schema, _config, form) => {
const updator = form.getFieldValue('updator');
return <span>{updator}</span>;
},
},
];
};
// {
// title: "模板内容",

View File

@@ -1,24 +1,26 @@
import { PlusOutlined } from "@ant-design/icons";
import type { ActionType, ProColumns } from "@ant-design/pro-components";
import type { TabsProps } from "antd";
import { Tabs } from "antd";
import { useCallback, useRef, useState } from "react";
import { PlusOutlined } from '@ant-design/icons';
import type { ActionType, ProColumns } from '@ant-design/pro-components';
import type { TabsProps } from 'antd';
import { Tabs } from 'antd';
import { 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";
} from '@/components/DrawerForm';
import EnhancedProTable from '@/components/EnhancedProTable';
import type { ToolbarAction } from '@/components/EnhancedProTable/types';
import { formStatusType } from '@/constants';
import {
getCategoryList,
postCategoryCreate,
putCategoryUpdate,
} from "@/services/prodApi/category";
import { baseTenantColumns, formColumns } from "./config";
type Category,
type CategoryReq,
createProdCategory,
getProdCategoryPage,
updateProdCategory,
} from '@/services/prod/category-manager';
import { baseTenantColumns, formColumns } from './config';
const ProdCategory = () => {
const tableRef = useRef<ActionType>(null);
const [type, setType] = useState<"create" | "update" | "test">("create");
const [type, setType] = useState<'create' | 'update' | 'test'>('create');
const [grade, setGrade] = useState<number>();
const configurableDrawerRef = useRef<ConfigurableDrawerFormRef>(null);
const [id, setId] = useState<number>(0);
@@ -26,11 +28,11 @@ const ProdCategory = () => {
(key: string) => {
setGrade(Number(key));
},
[grade]
[grade],
);
const onFetch = async (params: API.getProductCategoryCategoryListParams) => {
const data = await getCategoryList({
const onFetch = async (params: CategoryReq) => {
const data = await getProdCategoryPage({
...params,
grade: grade ? Number(grade) : undefined,
});
@@ -41,48 +43,48 @@ const ProdCategory = () => {
};
const handleAdd = () => {
setType("create");
configurableDrawerRef.current?.open({ grade: grade ? grade : 1 });
setType('create');
configurableDrawerRef.current?.open({ grade: 1 });
};
const toolbarActions: ToolbarAction[] = [
{
key: "add",
label: "新建",
type: "primary",
key: 'add',
label: '新建',
type: 'primary',
icon: <PlusOutlined />,
onClick: handleAdd,
},
];
const handleEdit = async (row: API.CategoryDO) => {
setType("update");
const handleEdit = async (row: Category) => {
setType('update');
row.categoryId && setId(row.categoryId);
configurableDrawerRef.current?.open(row);
};
const handleSubmit = useCallback(
async (values: API.CategoryDO) => {
if (type === "create") {
await postCategoryCreate(values);
async (values: Category) => {
if (type === 'create') {
await createProdCategory(values);
} else {
await putCategoryUpdate({
await updateProdCategory({
...values,
categoryId: id,
});
}
tableRef.current?.reload();
tableRef.current?.reload(true);
return true;
},
[id, type]
[id, type],
);
const actionColumns: ProColumns<API.CategoryDO> = {
title: "操作",
dataIndex: "option",
valueType: "option",
fixed: "right",
const actionColumns: ProColumns<Category> = {
title: '操作',
dataIndex: 'option',
valueType: 'option',
fixed: 'right',
width: 120,
render: (_text: React.ReactNode, record: API.CategoryDO, _: number) => [
render: (_text: React.ReactNode, record: Category, _: number) => [
<a key="edit" onClick={() => handleEdit(record)}>
</a>,
@@ -94,7 +96,7 @@ const ProdCategory = () => {
const renderChildren = () => {
return (
<>
<EnhancedProTable<API.CategoryDO>
<EnhancedProTable<Category>
ref={tableRef}
columns={columns}
rowKey="categoryId"
@@ -107,8 +109,8 @@ const ProdCategory = () => {
<ConfigurableDrawerForm
ref={configurableDrawerRef}
title={formStatusType[type]}
// width="50vw"
columns={formColumns({ grade: Number(grade), type })}
width={'60vw'}
columns={formColumns({ type })}
onSubmit={handleSubmit}
footer={undefined}
bodyStyle={{}}
@@ -117,25 +119,25 @@ const ProdCategory = () => {
);
};
const items: TabsProps["items"] = [
const items: TabsProps['items'] = [
{
key: "",
label: "全部分类",
key: '',
label: '全部分类',
children: renderChildren(),
},
{
key: "3",
label: "三级分类",
key: '3',
label: '三级分类',
children: renderChildren(),
},
{
key: "2",
label: "二级分类",
key: '2',
label: '二级分类',
children: renderChildren(),
},
{
key: "1",
label: "一级分类",
key: '1',
label: '一级分类',
children: renderChildren(),
},
];
@@ -143,6 +145,7 @@ const ProdCategory = () => {
<Tabs
defaultActiveKey={grade as unknown as string}
items={items}
destroyOnHidden
onChange={onChange}
/>
);

View File

@@ -0,0 +1,151 @@
import {
ProForm,
ProFormGroup,
ProFormSelect,
ProFormText,
ProFormTextArea,
} from '@ant-design/pro-components';
import { Divider } from 'antd';
import React from 'react';
import TagEditor from '@/components/TagEditor';
import TinymceEditor from '@/components/Tinymce';
import UploadImages from '@/components/Upload/UploadImages';
import UploadVideo from '@/components/Upload/UploadVideo';
import type { Prod } from '@/services/prod/prod-manager';
interface ProdInfoProps<T> {
onRefresh?: (type?: string) => void;
data?: T;
}
const ProdInfo = <T extends Record<string, any>>(props: ProdInfoProps<T>) => {
console.log(props, 'ProdInfo');
return (
<>
<ProFormGroup title="一、基础信息">
<ProFormText
name="prodName"
label="商品名字"
rules={[{ required: true }]}
width="xl"
/>
<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
name="tag"
label="商品标签"
tooltip="设置卖点标签单标签限xx字符最多x个标签"
layout="horizontal"
width="xl"
required={false}
// getValueFromEvent={(e) => e.fileList}
>
<TagEditor
placeholder="输入标签名称"
maxCount={10}
tagProps={{
color: 'blue',
}}
/>
</ProForm.Item>
</ProFormGroup>
<ProFormGroup title="二、内容展示">
<ProForm.Item
style={{ width: '100%' }}
name="pic"
label="主图"
width="xl"
extra="仅支持.jpg .png 格式建议图片比例1:1限1张"
// rules={[{ required: true }]}
// getValueFromEvent={(e) => e.fileList}
>
<UploadImages />
</ProForm.Item>
<ProForm.Item
style={{ width: '100%' }}
name="imgs"
label="轮播图"
layout="horizontal"
width="xl"
extra="仅支持.jpg .png 格式建议图片比例1:1限7张"
// getValueFromEvent={(e) => e.fileList}
>
<UploadImages />
</ProForm.Item>
<ProForm.Item
style={{ width: '100%' }}
name="whiteImg"
label="白底图"
width="xl"
extra="仅支持.jpg .png 格式建议图片比例1:1限1张"
// getValueFromEvent={(e) => e.fileList}
>
<UploadImages />
</ProForm.Item>
<ProForm.Item
name="video"
label="主视频"
width="xl"
extra="仅支持.MP4 .MOV 格式建议比例1:1、16:9限1个"
// getValueFromEvent={(e) => e.fileList}
>
<UploadVideo />
</ProForm.Item>
<ProForm.Item
name="content"
label="图文介绍"
rules={[{ required: true }]}
width="xl"
// getValueFromEvent={(e) => e.fileList}
>
<TinymceEditor />
</ProForm.Item>
</ProFormGroup>
<ProFormGroup title="三、营销与传播">
<ProFormText
name="seoShortName"
label="短标题"
colProps={{
span: 20,
}}
/>
<ProFormText name="seoSearch" label="SEO标题" width={'xl'} />
<ProFormTextArea name="keyword" label="商品关键词" width={'xl'} />
<Divider />
<ProForm.Item
style={{ width: '100%' }}
name="shareImage"
label="分享图"
width="xl"
extra="仅支持.jpg .png 格式限1张"
>
<UploadImages />
</ProForm.Item>
<ProFormTextArea
name="shareContent"
label="分享话术"
placeholder={'请输入分享话术'}
width={'xl'}
/>
</ProFormGroup>
<ProFormGroup>
<ProFormSelect name="status" label="商品状态" readonly />
<ProFormText name="prodId" label="商品ID" readonly />
<ProFormText name="createTime" label="创建时间" readonly />
<ProFormText name="creator" label="创建人" readonly />
<ProFormText name="updateTime" label="更新时间" readonly />
<ProFormText name="updater" label="更新人" readonly />
<ProFormText name="version" label="版本号" readonly />
</ProFormGroup>
</>
);
};
export default React.memo(ProdInfo) as <_T extends Record<string, any>>(
props: ProdInfoProps<Prod>,
) => React.ReactElement;

View File

@@ -0,0 +1,69 @@
import { ProForm, ProFormText } from '@ant-design/pro-components';
const ServiceRule: React.FC = () => {
return (
<ProForm<{
name: string;
company: string;
}>
grid
onFinish={async (values) => {
// await waitTime(2000);
console.log(values);
// message.success('提交成功');
}}
initialValues={{
name: '蚂蚁设计有限公司',
useMode: 'chapter',
}}
>
<ProForm.Group>
<ProFormText
width="md"
name="name"
label="签约客户名称"
tooltip="最长为 24 位"
placeholder="请输入名称"
/>
<ProFormText
width="md"
name="company"
label="我方公司名称"
placeholder="请输入名称"
/>
</ProForm.Group>
<ProFormText width="sm" name="id" label="主合同编号" />
<ProForm.Item
label="数组数据"
name="dataSource"
// initialValue={defaultData}
trigger="onValuesChange"
>
{/* <EditableProTable<DataSourceType>
rowKey="id"
toolBarRender={false}
columns={columns}
recordCreatorProps={{
newRecordType: 'dataSource',
position: 'top',
record: () => ({
id: Date.now(),
addonBefore: 'ccccccc',
decs: 'testdesc',
}),
}}
editable={{
type: 'multiple',
editableKeys,
onChange: setEditableRowKeys,
actionRender: (row, _, dom) => {
return [dom.delete];
},
}} */}
{/* /> */}
</ProForm.Item>
</ProForm>
);
};
export default ServiceRule;

View File

@@ -0,0 +1,31 @@
import { ProForm } from '@ant-design/pro-components';
import React from 'react';
import SkuConfig from './sku-confiig';
import SkuList from './sku-list';
const Sku: React.FC = () => {
return (
<>
<ProForm.Item
style={{ width: '100%' }}
name="prodPropSaveReqVO"
layout="horizontal"
width="xl"
// getValueFromEvent={(e) => e.fileList}
>
<SkuConfig />
</ProForm.Item>
<ProForm.Item
style={{ width: '100%' }}
name="skuList"
layout="horizontal"
width="xl"
// getValueFromEvent={(e) => e.fileList}
>
<SkuList />
</ProForm.Item>
</>
);
};
export default React.memo(Sku);

View File

@@ -0,0 +1,23 @@
import { Typography } from 'antd';
import React from 'react';
import DraggableTagList from '@/components/Draggable/DraggableTagList';
const { Title, Text } = Typography;
const SkuConfig: React.FC<{
value?: API.ProdPropSaveReqVO;
onChange?: (value: API.ProdPropSaveReqVO) => void;
}> = () => {
// const { value, onChange } = props;
return (
<>
<Title level={4}></Title>
<Text type="secondary">
使<a></a>
</Text>
<DraggableTagList />
</>
);
};
export default React.memo(SkuConfig);

View File

@@ -0,0 +1,22 @@
import { Typography } from 'antd';
import React from 'react';
import type { SkuConfig } from '@/services/prod/prod-manager';
const { Title, Text } = Typography;
const SkuList: React.FC<{
value?: SkuConfig;
onChange?: (value: SkuConfig) => void;
}> = (props) => {
const { value } = props;
console.log('value', value);
return (
<>
<Title level={4}>SKU </Title>
<Text type="secondary">
SKU价格与可售数量
</Text>
</>
);
};
export default React.memo(SkuList);

View File

@@ -0,0 +1,90 @@
import type { ProColumns } from '@ant-design/pro-components';
import { Badge, Button, Image, Space, Typography } from 'antd';
import dayjs from 'dayjs';
import type { Prod } from '@/services/prod/prod-manager';
const { Text } = Typography;
export const baseTenantColumns: ProColumns<Prod>[] = [
{
title: '商品',
dataIndex: 'prodName',
render: (_, record) => (
<Space>
<Image width={64} height={64} src={record.pic} />
<div>
<div>
<Text>{record.categoryName}</Text>
</div>
<div>
<Text type="secondary">ID{record.categoryName}</Text>
</div>
<div>
<Text type="secondary">{record.categoryName}</Text>
</div>
</div>
</Space>
),
},
{
title: '服务信息',
dataIndex: 'categoryId',
render: () => (
<Space direction="vertical">
<div>
<Text type="secondary"></Text>
<Text></Text>
</div>
<div>
<Text type="secondary"></Text>
<Text></Text>
</div>
<div>
<Text type="secondary"></Text>
<Text></Text>
</div>
</Space>
),
},
{
title: '状态',
dataIndex: 'status',
valueType: 'switch',
width: 300,
hideInSearch: true,
render: () => (
<Space direction="vertical">
<Space>
<Badge status="success" text="出售中" />
<Badge status="default" text="已置灰" />
<Badge status="warning" text="待审核" />
<Badge status="default" text="仓库中" />
</Space>
<Button size="small" type="dashed">
</Button>
</Space>
),
},
{
title: '时间信息',
dataIndex: 'createTime',
valueType: 'dateRange',
hideInSearch: true, // 在搜索表单中隐藏
render: (_, record) => (
<Space direction="vertical">
<div>
<Image></Image>
</div>
<div>
<Text type="secondary"></Text>
{dayjs(record.createTime).format('YYYY-MM-DD HH:mm:ss')}
</div>
<div>
<Text type="secondary"></Text>
{dayjs(record.updateTime).format('YYYY-MM-DD HH:mm:ss')}
</div>
</Space>
),
},
];

View File

@@ -0,0 +1,84 @@
import type { FormInstance } from '@ant-design/pro-components';
import { ProCard, ProForm, StepsForm } from '@ant-design/pro-components';
import { message } from 'antd';
import { useEffect } from 'react';
import ProdInfo from '@/pages/prod/list/components/prod-info';
import Sku from '@/pages/prod/list/components/sku';
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<{
data?: Prod;
formRef?: React.RefObject<FormInstance<any>>;
}> = (props) => {
const { data, formRef } = props;
console.log(data, 'ProdDetailPage');
useEffect(() => {
if (data) {
formRef?.current?.setFieldsValue(data);
}
}, [data]);
return (
<ProCard>
{data?.prodId ? (
<ProForm
formRef={formRef}
layout="horizontal"
grid={true}
submitter={false}
>
<ProdInfo />
</ProForm>
) : (
<StepsForm<ProdDetail>
current={0}
formRef={formRef}
submitter={false}
onFinish={async () => {
await waitTime(1000);
message.success('提交成功');
}}
formProps={{
validateMessages: {
required: '此项为必填项',
},
}}
>
<StepsForm.StepForm<ProdDetail>
grid={true}
layout="horizontal"
name="info"
title="填写商品信息"
onFinish={async () => {
try {
await formRef?.current?.validateFields();
console.log(formRef?.current?.getFieldsValue());
} catch (error: any) {
console.log(error);
message.error(error.msg);
}
return true;
}}
>
<ProdInfo data={data} />
</StepsForm.StepForm>
<StepsForm.StepForm<SkuConfig> name="sku" title="设置商品规格">
<Sku />
</StepsForm.StepForm>
<StepsForm.StepForm<ProdDetail> name="prod" title="发布商品">
</StepsForm.StepForm>
</StepsForm>
)}
</ProCard>
);
};
export default ProdDetailPage;

View File

@@ -1,5 +1,250 @@
import { EllipsisOutlined, PlusOutlined } from '@ant-design/icons';
import type { ActionType, ProColumns } from '@ant-design/pro-components';
import type { MenuProps, TabsProps } from 'antd';
import { Button, Dropdown, Space, Tabs } from 'antd';
import { 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 ProdDetail from '@/pages/prod/list/detail';
import {
createProd,
getProdDetail,
getProdPage,
type Prod,
type ProdReq,
updateProd,
} from '@/services/prod/prod-manager';
import { baseTenantColumns } from './config';
const ProdList = () => {
return <div>ProdList</div>;
const tableRef = useRef<ActionType>(null);
const [type, setType] = useState<'create' | 'update' | 'test'>('create');
const [status, setStatus] = useState<number>();
const detailRef = useRef<ConfigurableDrawerFormRef>(null);
// const editRef = useRef<ConfigurableDrawerFormRef>(null);
const [id, setId] = useState<number>(0);
const onChange = useCallback(
(key: string) => {
setStatus(Number(key));
},
[status],
);
const onFetch = async (params: ProdReq) => {
const data = await getProdPage({
...params,
status: status ? status : undefined,
});
return {
data: data.list,
success: true,
};
};
const handleAdd = () => {
setType('create');
detailRef.current?.open({});
};
const toolbarActions: ToolbarAction[] = [
{
key: 'add',
label: '新建',
type: 'primary',
icon: <PlusOutlined />,
onClick: handleAdd,
},
];
const handleEdit = async (row: Prod) => {
setType('update');
const res = await getProdDetail({ id: row.prodId as number });
setId(row.prodId as number);
detailRef.current?.open(res);
};
// 商品更新
// const _handleEditSubmit = async (values: Prod) => {
// await updateProd(values);
// tableRef.current?.reload();
// message.success("编辑成功");
// return true;
// };
const handleSubmit = useCallback(
async (values: Prod) => {
if (type === 'create') {
await createProd(values);
} else {
await updateProd({
...values,
categoryId: id,
});
}
tableRef.current?.reload();
return true;
},
[id, type],
);
// const renderDetailFooter = () => {
// if (type === "update") {
// return (
// <Space>
// <Button onClick={() => detailRef.current?.close()}>取消</Button>
// <Button type="primary" onClick={handleEditSubmit}>
// 确定
// </Button>
// </Space>
// );
// } else {
// return (
// <Button type="primary" onClick={() => detailRef.current?.close()}>
// 确定
// </Button>
// );
// }
// };
const itemsMenu = (row: Prod): MenuProps['items'] => [
{
key: 'edit',
label: (
<Button type="link" onClick={() => handleEdit(row)}>
</Button>
),
},
{
key: 'sku',
label: <Button type="link">SKU管理</Button>,
},
{
key: 'rules-service',
label: <Button type="link"></Button>,
},
{
key: 'extend-service',
label: <Button type="link"></Button>,
},
{
key: 'status',
label: <Button type="link"></Button>,
},
{
key: 'delete',
label: (
<Button color="danger" variant="link">
</Button>
),
},
];
const actionColumns: ProColumns<Prod> = {
title: '操作',
dataIndex: 'option',
valueType: 'option',
fixed: 'right',
align: 'center',
width: 200,
render: (_text: React.ReactNode, record: Prod, _: number) => [
<Space style={{ width: '100%' }} wrap size={0} key={record.prodId}>
<Space.Compact direction="vertical" block key="1">
<Button type="link" onClick={() => handleEdit(record)}>
</Button>
<Button type="link">SKU管理</Button>
</Space.Compact>
<Space.Compact direction="vertical" block key="2">
<Button type="link"></Button>
<Button type="link"></Button>
</Space.Compact>
<Space.Compact direction="vertical" block key="3">
<Dropdown menu={{ items: itemsMenu(record) }} placement="bottomLeft">
<Button type="link">
<EllipsisOutlined />
</Button>
</Dropdown>
</Space.Compact>
</Space>,
],
};
const columns = [...baseTenantColumns, actionColumns];
const renderChildren = () => {
return (
<>
<EnhancedProTable<Prod>
ref={tableRef}
columns={columns}
request={onFetch}
toolbarActions={toolbarActions}
headerTitle="短信渠道"
showIndex={false}
rowKey="prodId"
showSelection={false}
/>
<ConfigurableDrawerForm
ref={detailRef}
onSubmit={handleSubmit}
// footer={renderDetailFooter()}
title={formStatusType[type]}
width={'80vw'}
bodyStyle={{
background: '#f5f5f5',
paddingTop: 8,
}}
>
<ProdDetail />
</ConfigurableDrawerForm>
{/* <ConfigurableDrawerForm
ref={editRef}
onSubmit={handleEditSubmit}
footer={renderDetailFooter()}
width={"80vw"}
bodyStyle={{
background: "#f5f5f5",
paddingTop: 8,
}}
>
<ProdDetail />
</ConfigurableDrawerForm> */}
</>
);
};
const items: TabsProps['items'] = [
{
key: '',
label: '全部商品',
children: renderChildren(),
},
{
key: '1',
label: '出售中的商品',
children: renderChildren(),
},
{
key: '0',
label: '仓库中的商品',
children: renderChildren(),
},
{
key: '2',
label: '待审核的商品',
children: renderChildren(),
},
];
return (
<Tabs
defaultActiveKey={status as unknown as string}
items={items}
onChange={onChange}
/>
);
};
export default ProdList;

View File

@@ -1,6 +1,10 @@
// src/pages/system/menu/index.tsx
import { PlusOutlined, ReloadOutlined } from '@ant-design/icons';
import {
ExclamationCircleOutlined,
PlusOutlined,
ReloadOutlined,
} from '@ant-design/icons';
import type { ActionType, ProColumns } from '@ant-design/pro-components';
import { useModel } from '@umijs/max';
import { Modal, Popconfirm } from 'antd';
@@ -11,8 +15,6 @@ import ConfigurableDrawerForm, {
import EnhancedProTable from '@/components/EnhancedProTable';
import type { ToolbarAction } from '@/components/EnhancedProTable/types';
import { formStatusType } from '@/constants';
import { useMessage } from '@/hooks/antd/useMessage';
import { CACHE_KEY, useCache } from '@/hooks/web/useCache';
import {
createMenu,
deleteMenu,
@@ -27,11 +29,10 @@ import { baseMenuColumns, formColumns } from './config';
const SystemMenu = () => {
const configurableDrawerRef = useRef<ConfigurableDrawerFormRef>(null);
const tableRef = useRef<ActionType>(null);
const { wsCache } = useCache();
const message = useMessage(); // 消息弹窗
const [modal, contextHolder] = Modal.useModal();
const [type, setType] = useState<'create' | 'update'>('create');
const [id, setId] = useState<number>(0);
const { initialState, setInitialState } = useModel('@@initialState');
const { initialState } = useModel('@@initialState');
const handleEdit = (record: MenuVO) => {
setType('update');
setId(record.id);
@@ -56,13 +57,18 @@ const SystemMenu = () => {
const handleReload = async () => {
try {
await message.confirm('即将更新缓存刷新浏览器!', '刷新菜单缓存');
// 清空,从而触发刷新
// wsCache.delete(CACHE_KEY.USER);
// wsCache.delete(CACHE_KEY.ROLE_ROUTERS);
await modal.confirm({
title: '刷新菜单缓存',
icon: <ExclamationCircleOutlined />,
content: '即将更新缓存刷新浏览器!',
okText: '确认',
cancelText: '取消',
onOk: async () => {
await initialState?.fetchUserInfo?.();
// 刷新浏览器
location.reload();
},
});
} catch {}
};
@@ -126,6 +132,7 @@ const SystemMenu = () => {
const columns = [...baseMenuColumns, actionColumns];
return (
<>
{contextHolder}
<EnhancedProTable<MenuVO>
ref={tableRef}
columns={columns}

View File

@@ -1,43 +1,52 @@
import type { ProColumns } from '@ant-design/pro-components';
import { Button, Space, Tag, Typography } from 'antd';
import dayjs from 'dayjs';
import { dateFormat, dateFormatS } from '@/constants';
import { Image, Space, Tag, Typography } from 'antd';
import type { TradeOrderPageRespVO } from '@/services/trade/order';
const { Text, Paragraph } = Typography;
export const baseOrderColumns: ProColumns<TradeOrderPageRespVO>[] = [
{
title: '商品',
dataIndex: 'spuName',
dataIndex: 'items',
hideInSearch: true,
width: '100%',
ellipsis: true,
render: (_, record) => (
<Paragraph>
<Paragraph ellipsis style={{ marginBottom: 0 }}>
{record.spuName}
render: (_, record) => {
if (!record.items) {
return _;
}
return record.items.map((item) => (
<div
style={{ width: '100%', display: 'flex', gap: '8px' }}
key={item.id}
>
<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>{record.skuName}</div>
<div>{item.skuName}</div>
<Space>
<div>
<Text type="secondary"></Text>
<Text>{record.count}</Text>
<Text>{item.count || 0}</Text>
</div>
<div>
<Text type="secondary"></Text>
<Text>
{record.price}/{record.unit}
{item.price || 0}/{item.unit || '-'}
</Text>
</div>
<div>
<Text type="secondary"></Text>
<Text>
{record.handedPrice}/{record.unit}
{item.handedPrice || 0}/{item.unit || '-'}
</Text>
</div>
</Space>
</Paragraph>
),
</div>
</div>
));
},
},
{
@@ -50,7 +59,7 @@ export const baseOrderColumns: ProColumns<TradeOrderPageRespVO>[] = [
<Space direction="vertical" style={{ width: '100%' }}>
<Paragraph ellipsis style={{ marginBottom: 0 }}>
<Text type="secondary"></Text>
<Text>{dayjs(record.subTime).format(dateFormatS)}</Text>
<Text>{record?.subTime || '-'}</Text>
<Tag style={{ marginLeft: 10 }}></Tag>
<Tag color="error" style={{ marginLeft: 10 }}>
@@ -58,14 +67,11 @@ export const baseOrderColumns: ProColumns<TradeOrderPageRespVO>[] = [
</Paragraph>
<Paragraph ellipsis style={{ marginBottom: 0 }}>
<Text type="secondary"></Text>
<Text>
{record.serveAddress}
</Text>
<Text>{record.serveAddress || '-'}</Text>
</Paragraph>
<div>
<Text type="secondary"></Text>
<Text>{record.userRemark}</Text>
<Text>{record.userRemark || '-'}</Text>
</div>
</Space>
),
@@ -74,20 +80,20 @@ export const baseOrderColumns: ProColumns<TradeOrderPageRespVO>[] = [
title: '财务',
dataIndex: 'price',
hideInSearch: true,
width: '100',
width: '100%',
render: (_, record) => (
<Space direction="vertical">
<div>
<Text type="secondary"></Text>
<Text>{record.payPrice}</Text>
<Text>{record?.payPrice || '-'}</Text>
</div>
<div>
<Text type="secondary"></Text>
<Text>{record.payType}</Text>
<Text>{record.payType || '-'}</Text>
</div>
<div>
<Text type="secondary"></Text>
<Text>{record.financeStatus}</Text>
<Text>{record.financeStatus || '-'}</Text>
</div>
</Space>
),

View File

@@ -1,23 +1,36 @@
import { ProCard } from '@ant-design/pro-components';
import { Avatar, Button, Card, Empty, Space, Steps, Typography } from 'antd';
import {
Avatar,
Button,
Card,
Divider,
Empty,
Space,
Steps,
Typography,
} from 'antd';
import dayjs from 'dayjs';
import RcResizeObserver from 'rc-resize-observer';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { OrderStatus, OrderStatusLabels } from '@/constants/trade';
import type { TradeOrderBaseInfo } from '@/services/trade/order';
import type {
TradeOrderDetailRespVO,
TradeOrderStatusRespVo,
} from '@/services/trade/order/detail';
import type { ItemConfig } from '../../order-info';
import { type BtnType, renderBaseInfoOrder } from './config';
import styles from './index.module.less';
const { Title, Text } = Typography;
const BasicInfo: React.FC<{ data?: TradeOrderBaseInfo; id: number }> = (
props,
) => {
const { data } = props;
const { Text } = Typography;
const BasicInfo: React.FC<ItemConfig<TradeOrderDetailRespVO>> = (props) => {
const { data, loading } = props;
const [orderStatus, setOrderStatus] = useState<number>(0);
const [statusList, setStatusList] = useState<TradeOrderStatusRespVo[]>([]);
useEffect(() => {
if (data) {
setOrderStatus(Number(data.orderStatus));
setStatusList(data?.statusList || []);
}
}, [data]);
@@ -27,22 +40,24 @@ const BasicInfo: React.FC<{ data?: TradeOrderBaseInfo; id: number }> = (
<Space size={16}>
<div>
<Text type="secondary"></Text>
<Text copyable>{data?.orderNo}</Text>
<Text copyable>{data?.orderNum}</Text>
</div>
<div>
<Text type="secondary"></Text>
<Text>{data?.orderCategoryId}</Text>
<Text>{data?.orderCategoryName}</Text>
<Text>ID:</Text>
<Text copyable>{data?.id}</Text>
<Divider type="vertical" />
<Text copyable>{data?.orderCategoryId}</Text>
</div>
<div>
<Text type="secondary"></Text>
<Text>{data?.orderCategoryName}</Text>
<Text>{data?.orderTerminal}</Text>
</div>
<div>
<Space>
<Avatar size={20} src={data?.userAvatar} />
<Text>{data?.userInfo}</Text>
</div>
<Text>{data?.userName || data?.userNickName}</Text>
<Text>{data?.userMobile}</Text>
</Space>
</Space>
);
}, [data]);
@@ -65,7 +80,8 @@ const BasicInfo: React.FC<{ data?: TradeOrderBaseInfo; id: number }> = (
return (
<div className={styles['order-info']}>
<Card title="基本信息">
<Card title="基本信息" loading={loading}>
<Space direction="vertical" size={16} style={{ width: '100%' }}>
{data ? (
<ProCard
size="small"
@@ -98,7 +114,7 @@ const BasicInfo: React.FC<{ data?: TradeOrderBaseInfo; id: number }> = (
size="small"
progressDot
style={{ width: '100%' }}
current={orderStatus / 10}
current={statusList.length}
direction={responsive ? 'vertical' : 'horizontal'}
items={[
{
@@ -127,7 +143,8 @@ const BasicInfo: React.FC<{ data?: TradeOrderBaseInfo; id: number }> = (
},
{
title: `${
OrderStatusLabels[OrderStatus.PendingConfirmation].label
OrderStatusLabels[OrderStatus.PendingConfirmation]
.label
} ${
orderStatus > OrderStatus.PendingConfirmation
? '(已确定)'
@@ -187,6 +204,60 @@ const BasicInfo: React.FC<{ data?: TradeOrderBaseInfo; id: number }> = (
) : (
<Empty />
)}
<ProCard
size="small"
bordered
headerBordered
title="订单付款信息"
gutter={8}
extra={<Button size="small">:{data?.financeStatus}</Button>}
>
<ProCard>
<Space direction="vertical">
<div>
<Text type="secondary"></Text>
<Text>{data?.price}</Text>
</div>
<div>
<Text type="secondary"></Text>
<Text>{data?.discountPrice}</Text>
</div>
<div>
<Text type="secondary"></Text>
<Text>{data?.payPrice}</Text>
</div>
<div>
<Text type="secondary">退</Text>
<Text>{data?.refundPrice}</Text>
</div>
<div>
<Text type="secondary"></Text>
<Text>{data?.livePrice}</Text>
</div>
</Space>
</ProCard>
<ProCard>
<Space direction="vertical">
<div>
<Text type="secondary"></Text>
<Text>{data?.payType}</Text>
</div>
<div>
<Text type="secondary"></Text>
<Text>{data?.payChannelCode}</Text>
</div>
<div>
<Text type="secondary"></Text>
<Text>{data?.payTime}</Text>
</div>
<div>
<Text type="secondary"></Text>
<Text>{data?.payOrderId}</Text>
</div>
</Space>
</ProCard>
</ProCard>
</Space>
</Card>
</div>
);

View File

@@ -9,30 +9,10 @@ import {
OrderStatusLabels,
} from '@/constants/trade';
import type { DeptVO } from '@/services/system/dept';
import type { TradeOrderBaseInfo } from '@/services/trade/order';
import type { TradeOrderDetailRespVO } from '@/services/trade/order';
const { Title, Text, Paragraph } = Typography;
export const baseOrderColumns: ProColumns<DeptVO>[] = [
{
title: '商品',
dataIndex: 'name',
width: '33.33%',
render: (_, record) => (
<Tag color={record.status === 1 ? 'green' : 'red'}>{record.name}</Tag>
),
},
{
title: '服务',
dataIndex: 'leaderUserId',
hideInSearch: true,
},
{
title: '财务',
dataIndex: 'sort',
hideInSearch: true,
},
];
const sharedOnCell = (_: DeptVO, index?: number) => {
if (index === 3) {
return { colSpan: 0 };
@@ -77,17 +57,17 @@ export const surchargeInfoColumns: ProColumns<DeptVO>[] = [
export type BtnType = 'sales' | 'confirm';
export const renderBaseInfoOrder = (
data: TradeOrderBaseInfo,
data: TradeOrderDetailRespVO,
handleClick: (type: BtnType) => void,
): React.ReactNode => {
const orderStatusObj =
OrderStatusLabels[data.orderStatus as unknown as OrderStatus];
const orderStatusObj = data.orderStatus
? OrderStatusLabels[data.orderStatus as unknown as OrderStatus]
: { color: 'default', label: '未知' };
console.log(orderStatusObj, data);
return (
<>
<Title
type={(orderStatusObj.color as BaseType) || ''}
type={(orderStatusObj?.color as BaseType) || ''}
level={4}
style={{ margin: 0 }}
>
@@ -101,7 +81,7 @@ export const renderBaseInfoOrder = (
Number(data.orderStatus) === OrderStatus.Refunded && (
<Paragraph>
<Text type="secondary">退</Text>
<Text>退</Text>
<Text>{data.refundTime}</Text>
</Paragraph>
)}
{data.orderStatus &&

View File

@@ -0,0 +1,20 @@
import { Card } from 'antd';
import React from 'react';
import type { TradeExtendCostInfo } from '@/services/trade/order/detail';
import type { ItemConfig } from '../../order-info';
import styles from './index.module.less';
import ExtendCost from './uis/pets/extend-cost';
const EntendCostInfo: React.FC<ItemConfig<TradeExtendCostInfo[]>> = (props) => {
const { data = [], orderCategoryId } = props;
return (
<div className={styles['order-info']}>
<Card title="服务信息">
{orderCategoryId === 1 && <ExtendCost data={data} />}
</Card>
</div>
);
};
export default React.memo(EntendCostInfo);

View File

@@ -0,0 +1,18 @@
import React from 'react';
import type { TradeExtendServeInfo } from '@/services/trade/order/detail';
import type { ItemConfig } from '../../order-info';
import styles from './index.module.less';
import ExtendServicePets from './uis/pets/extend-service';
const ExtendService: React.FC<ItemConfig<TradeExtendServeInfo[][]>> = (
props,
) => {
const { data = [], orderCategoryId } = props;
return (
<div className={styles['order-info']}>
{orderCategoryId === 1 && <ExtendServicePets data={data} />}
</div>
);
};
export default React.memo(ExtendService);

View File

@@ -2,6 +2,7 @@
:global {
.ant-pro-card-header {
background: rgba(0, 0, 0, 0.02);
min-height: 41px;
}
.ant-pro-card-col {
flex: 1 auto;
@@ -21,10 +22,13 @@
.ant-pro-card-body {
padding: 0px;
}
.ant-steps-dot {
.ant-steps-item-icon {
height: 8px !important;
line-height: 8px !important;
}
}
.ant-steps-item-active {
.ant-steps-icon-dot {
height: 10px !important;

View File

@@ -1,24 +1,25 @@
import { ProCard } from '@ant-design/pro-components';
import { Button, Card, Image, Space, Tag, Typography } from 'antd';
import React, { useCallback } from 'react';
import type { TradeProductInfo } from '@/services/trade/order';
import { fallback } from '@/constants/antd/image';
import type { Item } from '@/services/trade/order/detail';
import type { ItemConfig } from '../../order-info';
import styles from './index.module.less';
const { Text, Paragraph } = Typography;
const ProdInfo: React.FC<{ data?: TradeProductInfo; id: number }> = (props) => {
const { data = {}, id } = props;
const renderTitle = useCallback(() => {
const ProdInfo: React.FC<ItemConfig<Item[]>> = (props) => {
const { data = [] } = props;
const renderTitle = useCallback((item: Item) => {
return (
<Space style={{ height: '100%' }} size={16}>
<Paragraph className="order-paragraph"></Paragraph>
<Paragraph className="order-paragraph">{item.shopName}</Paragraph>
<Paragraph className="order-paragraph">
<Text type="secondary"> ID</Text>
<Text copyable>8877777</Text>
<Text copyable>{item.spuId}</Text>
</Paragraph>
<Paragraph className="order-paragraph">
<Text type="secondary">SKU ID</Text>
<Text copyable>8877777</Text>
<Text copyable>{item.skuId}</Text>
</Paragraph>
</Space>
);
@@ -26,9 +27,11 @@ const ProdInfo: React.FC<{ data?: TradeProductInfo; id: number }> = (props) => {
return (
<div className={styles['order-info']}>
<Card title="商品信息">
{data?.map((item) => (
<ProCard
key={item.id}
size="small"
title={renderTitle()}
title={renderTitle(item)}
bordered
headerBordered
gutter={8}
@@ -40,15 +43,12 @@ const ProdInfo: React.FC<{ data?: TradeProductInfo; id: number }> = (props) => {
style={{ borderRight: '1px solid rgba(5,5,5,0.06)' }}
>
<Space>
<Image
width={64}
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
/>
<Image width={64} src={item.shopLogo} fallback={fallback} />
<div>
<Text></Text>
<Text>{item.shopName}</Text>
<Paragraph className="order-paragraph">
<Text></Text>
<Text type="secondary"> ,,</Text>
<Text type="secondary"> {item.skuName}</Text>
</Paragraph>
<Space wrap size={0} direction="horizontal">
<Paragraph
@@ -56,30 +56,40 @@ const ProdInfo: React.FC<{ data?: TradeProductInfo; id: number }> = (props) => {
style={{ marginRight: 16 }}
>
<Text type="secondary"></Text>
<Text> 1</Text>
<Text> {item.count}</Text>
</Paragraph>
<Paragraph
className="order-paragraph"
style={{ marginRight: 16 }}
>
<Text type="secondary"></Text>
<Text> ¥100 </Text>
<Text>
¥{item.price}
{item.unit}
</Text>
</Paragraph>
<Paragraph
className="order-paragraph"
style={{ marginRight: 16 }}
>
<Text type="secondary"></Text>
<Text> ¥80 </Text>
<Text>
{' '}
¥{item.handedPrice} {item.unit}
</Text>
</Paragraph>
<Paragraph className="order-paragraph">
<Text type="secondary"></Text>
<Text> ¥50 /-</Text>
<Text>
{' '}
¥{item.expensePrice} {item.unit}/-
</Text>
</Paragraph>
</Space>
</div>
</Space>
<Paragraph style={{ marginTop: 16 }}>
{item.properties}
<Tag></Tag>
<Tag></Tag>
<Tag></Tag>
@@ -89,7 +99,7 @@ const ProdInfo: React.FC<{ data?: TradeProductInfo; id: number }> = (props) => {
<Paragraph className="order-paragraph">
<Text type="secondary"></Text>
<Paragraph className="order-paragraph">
{item.serveContent}
</Paragraph>
</Paragraph>
</ProCard>
@@ -120,15 +130,16 @@ const ProdInfo: React.FC<{ data?: TradeProductInfo; id: number }> = (props) => {
<Text>退</Text>
<Paragraph className="order-paragraph">
<Text type="secondary">退</Text>
<Text>¥500</Text>
<Text>¥{item.refundPrice}</Text>
</Paragraph>
<Paragraph className="order-paragraph">
<Text type="secondary">退</Text>
<Text>1</Text>
<Text>{item.refundCount}</Text>
</Paragraph>
</Space>
</ProCard>
</ProCard>
))}
</Card>
</div>
);

View File

@@ -1,80 +0,0 @@
import { ProCard } from '@ant-design/pro-components';
import { Card, Space } from 'antd';
import React from 'react';
import EnhancedProTable from '@/components/EnhancedProTable';
import {
type DeptReqVO,
type DeptVO,
getDeptPage,
} from '@/services/system/dept';
import type { TradeExtendCostInfo } from '@/services/trade/order';
import { baseOrderColumns } from '../../../config';
import styles from './index.module.less';
const SelectInfo: React.FC<{ data?: TradeExtendCostInfo; id: number }> = (
props,
) => {
const { data = {}, id } = props;
const onFetch = async (
params: DeptReqVO & {
pageSize?: number;
current?: number;
},
) => {
const data = await getDeptPage({
...params,
pageNo: params.current,
pageSize: params.pageSize,
});
return {
data: data,
success: true,
total: data.total,
};
};
return (
<div className={styles['order-info']}>
<Card title="可选服务">
<Space direction="vertical" style={{ width: '100%' }} size={16}>
<ProCard split="vertical" bordered>
<ProCard size="small" title="服务" headerBordered>
1111
</ProCard>
<ProCard size="small" title="付款信息" headerBordered>
1111
</ProCard>
<ProCard size="small" title="退款信息" headerBordered>
1111
</ProCard>
</ProCard>
<EnhancedProTable<DeptVO>
columns={baseOrderColumns}
request={onFetch}
showIndex={false}
size="small"
search={false}
showActions={false}
showSelection={false}
bordered
pagination={false}
/>
<ProCard split="vertical" bordered>
<ProCard size="small" title="纪念品" headerBordered>
1111
</ProCard>
<ProCard size="small" title="付款信息" headerBordered>
1111
</ProCard>
<ProCard size="small" title="退款摘要" headerBordered>
1111
</ProCard>
</ProCard>
</Space>
</Card>
</div>
);
};
export default React.memo(SelectInfo);

View File

@@ -1,102 +1,11 @@
import { ProCard } from '@ant-design/pro-components';
import { Button, Card, Image, Space, Typography } from 'antd';
import React from 'react';
import type { TradeServeInfo } from '@/services/trade/order';
import styles from './index.module.less';
import type { TradeServeInfo } from '@/services/trade/order/detail';
import type { ItemConfig } from '../../order-info';
import ServicePetUI from './uis/pets/service';
const { Text, Paragraph } = Typography;
const ServiceInfo: React.FC<{ data?: TradeServeInfo; id: number }> = (
props,
) => {
const { data = {}, id } = props;
return (
<div className={styles['order-info']}>
<Card title="服务信息">
<ProCard split="vertical">
<ProCard
title="遗体信息"
extra={<Button size="small"></Button>}
size="small"
colSpan="30%"
headerBordered
>
<Paragraph>
<Text type="secondary"></Text>
<Text></Text>
</Paragraph>
<Paragraph>
<Text type="secondary"></Text>
<Text></Text>
</Paragraph>
<Paragraph>
<Text type="secondary">/</Text>
<Text></Text>
</Paragraph>
<Paragraph>
<Text type="secondary"></Text>
<Text></Text>
</Paragraph>
<Paragraph>
<Text type="secondary"></Text>
<Text></Text>
</Paragraph>
<Paragraph>
<Text type="secondary"></Text>
<Space wrap>
<Image
width={48}
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
/>
<Image
width={48}
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
/>
<Image
width={48}
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
/>
<Image
width={48}
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
/>
<Image
width={48}
src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png"
/>
</Space>
</Paragraph>
</ProCard>
<ProCard
size="small"
title="预约信息"
headerBordered
extra={<Button size="small"></Button>}
>
<Paragraph>
<Text type="secondary"></Text>
<Text></Text>
</Paragraph>
<Paragraph>
<Text type="secondary"></Text>
<Text></Text>
</Paragraph>
<Paragraph>
<Text type="secondary"></Text>
<Text></Text>
</Paragraph>
<Paragraph>
<Text type="secondary"></Text>
<Text></Text>
</Paragraph>
<Paragraph>
<Text type="secondary"></Text>
<Text></Text>
</Paragraph>
</ProCard>
</ProCard>
</Card>
</div>
);
const ServiceInfo: React.FC<ItemConfig<TradeServeInfo>> = (props) => {
const { data = {}, orderCategoryId } = props;
return <>{orderCategoryId === 1 && <ServicePetUI data={data} />}</>; //宠物服务ui
};
export default React.memo(ServiceInfo);

View File

@@ -1,53 +0,0 @@
import { Card } from 'antd';
import React from 'react';
import EnhancedProTable from '@/components/EnhancedProTable';
import {
type DeptReqVO,
type DeptVO,
getDeptPage,
} from '@/services/system/dept';
import type { TradeExtendServeInfo } from '@/services/trade/order';
import { surchargeInfoColumns } from './config';
import styles from './index.module.less';
const SurchargeInfo: React.FC<{ data?: TradeExtendServeInfo; id: number }> = (
props,
) => {
const { data, id } = props;
const onFetch = async (
params: DeptReqVO & {
pageSize?: number;
current?: number;
},
) => {
const data = await getDeptPage({
...params,
pageNo: params.current,
pageSize: params.pageSize,
});
return {
data: data,
success: true,
total: data.total,
};
};
return (
<div className={styles['order-info']}>
<Card title="服务信息">
<EnhancedProTable<DeptVO>
columns={surchargeInfoColumns}
request={onFetch}
showIndex={false}
size="small"
search={false}
showActions={false}
showSelection={false}
bordered
pagination={false}
/>
</Card>
</div>
);
};
export default React.memo(SurchargeInfo);

View File

@@ -0,0 +1,233 @@
import type { ProColumns } from '@ant-design/pro-components';
import { Image, Space, Typography } from 'antd';
import { fallback } from '@/constants/antd/image';
import type {
TradeExtendCostInfo,
TradeExtendServeInfo,
} from '@/services/trade/order/detail';
const { Text } = Typography;
const sharedOnCell = (record: TradeExtendServeInfo) => {
if (record.deliveryType) {
return { colSpan: 0 };
}
return {};
};
export const baseOrderColumns: ProColumns<TradeExtendServeInfo>[] = [
{
title: '服务',
dataIndex: 'serve',
width: '33.33%',
onCell: (record) => {
if (record.deliveryType) {
return {
colSpan: 3,
};
}
return {};
},
render: (_, record) => {
if (record.deliveryType) {
return (
<Space direction="vertical">
<div>
<Text type="secondary"></Text>
<Text>{record.deliveryType}</Text>
</div>
{record.receiveUser && (
<div>
<Text type="secondary"></Text>
<Text>{record.receiveUser}</Text>
</div>
)}
{record.sendUser && (
<div>
<Text type="secondary"></Text>
<Text>{record.sendUser}</Text>
</div>
)}
<div>
<Text type="secondary"></Text>
<Text>{record?.userMobile}</Text>
</div>
<div>
<Text type="secondary"></Text>
<Text>{record.address}</Text>
</div>
</Space>
);
} else if (record.tempType === 3) {
return (
<div>
<Text type="secondary">{record.serveTitle}</Text>
<Text>{record.serveType}</Text>
</div>
);
} else {
return (
<Space size={8} direction="vertical">
{record.serve?.map((serve, index) => (
<Space size={8} key={`${index}-${Math.random()}`}>
<Image
src={serve.serveUrl}
width={50}
height={50}
fallback={fallback}
/>
<div>
<div>{serve.serveName}</div>
<Text type="secondary">{serve.serveDesc}</Text>
<div>
<Text type="secondary"></Text>
<Text>{serve.count}</Text>
<Text type="secondary"></Text>
<Text>{serve.price}</Text>
<Text type="secondary"></Text>
<Text>{serve.handPrice}</Text>
</div>
</div>
</Space>
))}
</Space>
);
}
},
},
{
title: '付款信息',
dataIndex: 'payPrice',
hideInSearch: true,
onCell: sharedOnCell,
render: (_, record) => (
<Space direction="vertical">
<div>
<Text type="secondary"></Text>
<Text>{record.totalPrice}</Text>
</div>
<div>
<Text type="secondary"></Text>
<Text>{record.discountPrice}</Text>
</div>
<div>
<Text type="secondary"></Text>
<Text>{record.payPrice}</Text>
</div>
</Space>
),
},
{
title: '退款信息',
dataIndex: 'refundMoney',
hideInSearch: true,
onCell: sharedOnCell,
render: (_, record) => (
<Space direction="vertical">
<div>
<Text type="secondary">退</Text>
<Text>{record.refundMoney}</Text>
</div>
<div>
<Text type="secondary">退</Text>
<Text>{record.refundCount}</Text>
</div>
</Space>
),
},
];
export const extendCost: ProColumns<TradeExtendCostInfo>[] = [
{
title: '服务附加费',
dataIndex: 'serveExtFee',
},
{
title: '费用详情',
dataIndex: 'leaderUserId',
render: (_, record) => (
<Space direction="vertical">
<div>
<Text type="secondary"></Text>
<Text>{record.costName}</Text>
</div>
{record.serveArea && (
<div>
<Text type="secondary"></Text>
<Text>{record.serveArea}</Text>
</div>
)}
{record.targetArea && (
<div>
<Text type="secondary"></Text>
<Text>{record.targetArea}</Text>
</div>
)}
{record.weight && (
<div>
<Text type="secondary">/</Text>
<Text>{record.weight}</Text>
</div>
)}
{record.respTime && (
<div>
<Text type="secondary"></Text>
<Text>{record.respTime}</Text>
</div>
)}
{record.respMode && (
<div>
<Text type="secondary"></Text>
<Text>{record.respMode || '-'}</Text>
</div>
)}
{record.chargeTime && (
<div>
<Text type="secondary"></Text>
<Text>{record.chargeTime}</Text>
</div>
)}
<div>
<Text type="secondary"></Text>
<Text>{record.chargeType || '-'}</Text>
</div>
</Space>
),
},
{
title: '付款信息',
dataIndex: 'payInfo',
render: (_, record) => (
<Space direction="vertical">
<div>
<Text type="secondary"></Text>
<Text>¥{record.payInfo?.totalPrice || 0}</Text>
</div>
<div>
<Text type="secondary"></Text>
<Text>¥{record.payInfo?.discountPrice || 0}</Text>
</div>
<div>
<Text type="secondary"></Text>
<Text>¥{record.payInfo?.payPrice || 0}</Text>
</div>
</Space>
),
},
{
title: '退款信息',
dataIndex: 'refundPrice',
render: (_, record) => (
<Space direction="vertical">
<div>
<Text type="secondary">退</Text>
<Text>¥{record.refundPrice || 0}</Text>
</div>
</Space>
),
},
];

View File

@@ -0,0 +1,24 @@
import React from 'react';
import EnhancedProTable from '@/components/EnhancedProTable';
import type { TradeExtendCostInfo } from '@/services/trade/order/detail';
import type { ItemConfig } from '../../../../order-info';
import { extendCost } from './config';
const ExtendCost: React.FC<ItemConfig<TradeExtendCostInfo[]>> = (props) => {
const { data } = props;
return (
<EnhancedProTable<TradeExtendCostInfo>
columns={extendCost}
dataSource={data}
showIndex={false}
size="small"
search={false}
showActions={false}
showSelection={false}
bordered
pagination={false}
/>
);
};
export default React.memo(ExtendCost);

View File

@@ -0,0 +1,34 @@
import { Card } from 'antd';
import React from 'react';
import EnhancedProTable from '@/components/EnhancedProTable';
import type { TradeExtendServeInfo } from '@/services/trade/order/detail';
import type { ItemConfig } from '../../../../order-info';
import { baseOrderColumns } from './config';
const ExtendServicePet: React.FC<ItemConfig<TradeExtendServeInfo[][]>> = (
props,
) => {
const { data = [] } = props;
return (
<Card title="可选服务">
<div style={{ display: 'flex', gap: '16px', flexDirection: 'column' }}>
{data.map((item, index) => (
<EnhancedProTable<TradeExtendServeInfo>
key={`${index}-${Math.random()}`}
columns={baseOrderColumns}
dataSource={item}
showIndex={false}
size="small"
search={false}
showActions={false}
showSelection={false}
bordered
pagination={false}
/>
))}
</div>
</Card>
);
};
export default React.memo(ExtendServicePet);

View File

@@ -0,0 +1,106 @@
import { ClockCircleOutlined, EnvironmentOutlined } from '@ant-design/icons';
import { ProCard } from '@ant-design/pro-components';
import { Button, Card, Image, Space, Timeline, Typography } from 'antd';
import React from 'react';
import { fallback } from '@/constants/antd/image';
import type { TradeServeInfo } from '@/services/trade/order/detail';
import type { ItemConfig } from '../../../../order-info';
import styles from '../../index.module.less';
const { Text, Paragraph } = Typography;
const ServicePetUI: React.FC<ItemConfig<TradeServeInfo>> = (props) => {
const { data = {} } = props;
const { boneInfo, subInfo } = data;
return (
<div className={styles['order-info']}>
<Card title="服务信息">
<ProCard split="vertical" bordered>
<ProCard title="遗体信息" size="small" colSpan="30%" headerBordered>
<Paragraph>
<Text type="secondary"></Text>
<Text>{boneInfo?.petName}</Text>
</Paragraph>
<Paragraph>
<Text type="secondary"></Text>
<Text>{boneInfo?.petType}</Text>
</Paragraph>
<Paragraph>
<Text type="secondary">/</Text>
<Text>{boneInfo?.weight}</Text>
</Paragraph>
<Paragraph>
<Text type="secondary"></Text>
<Text>{boneInfo?.diedTime}</Text>
</Paragraph>
<Paragraph>
<Text type="secondary"></Text>
<Text>{boneInfo?.diedReason}</Text>
</Paragraph>
<Paragraph>
<Text type="secondary"></Text>
<Space wrap>
{data.boneInfo?.boneUrl?.map((item: string, index: number) => (
<Image
width={48}
src={item}
key={`${index}-${Math.random()}`}
fallback={fallback}
/>
))}
</Space>
</Paragraph>
</ProCard>
<ProCard
size="small"
title="预约信息"
headerBordered
extra={<Button size="small"></Button>}
>
<Space size={100}>
<div>
<Paragraph>
<Text type="secondary"></Text>
<Text>{subInfo?.subType}</Text>
</Paragraph>
<Paragraph>
<Text type="secondary"></Text>
<Text>{subInfo?.changeRule}</Text>
</Paragraph>
<Paragraph>
<Text type="secondary"></Text>
<Text>{subInfo?.subOrder}</Text>
</Paragraph>
<Paragraph>
<Text type="secondary"></Text>
<Text>{subInfo?.userRemark}</Text>
</Paragraph>
<Paragraph>
<Text type="secondary"></Text>
<Text>{subInfo?.merchantRemark}</Text>
</Paragraph>
</div>
<Timeline
items={[
{
dot: (
<ClockCircleOutlined className="timeline-clock-icon" />
),
color: 'green',
children: subInfo?.pickUpAddress,
},
{
dot: <EnvironmentOutlined />,
children: subInfo?.pickUpAddress,
color: 'red',
},
]}
/>
</Space>
</ProCard>
</ProCard>
</Card>
</div>
);
};
export default React.memo(ServicePetUI);

View File

@@ -3,34 +3,60 @@ import React, { useCallback, useEffect, useState } from 'react';
import {
getTradeOrderDetail,
type TradeOrderDetailRespVO,
type TradeOrderPageRespVO,
} from '@/services/trade/order';
import BasicInfo from './component/info/basic-info'; //基本信息(通版)
import ExtendCostInfo from './component/info/extend-cost'; //服务附加费(殡葬专属字段)
import ExtendService from './component/info/extend-service'; //可选服务(殡葬专属字段)
import ProdInfo from './component/info/prod-info'; //商品信息(通版)
import SelectInfo from './component/info/select-info'; //可选服务(殡葬专属字段)
import ServiceInfo from './component/info/service-info';
import SurchargeInfo from './component/info/surcharge-info'; //服务附加费(殡葬专属字段)
const OrderDetail: React.FC<{ data?: TradeOrderDetailRespVO }> = (props) => {
export interface ItemConfig<T> {
data?: T;
loading?: boolean;
orderCategoryId?: number;
}
const OrderDetail: React.FC<{ data?: TradeOrderPageRespVO }> = (props) => {
const { data } = props;
const [detais, setDetails] = useState<TradeOrderDetailRespVO>();
const [loading, setLoading] = useState<boolean>(false);
const fetch = useCallback(async () => {
try {
setLoading(true);
if (data?.id) {
const res = await getTradeOrderDetail(data.id);
setDetails(res);
}
}, [data]);
} finally {
setLoading(false);
}
}, [data?.id]);
useEffect(() => {
fetch();
}, [data]);
}, [data?.id]);
return (
<Space direction="vertical" size={24} style={{ width: '100%' }}>
<BasicInfo data={detais?.tradeOrderInfoBase} id={data?.id} />
<ProdInfo data={detais?.tradeProductInfo} id={data?.id} />
<ServiceInfo data={detais?.tradeServeInfo} id={data?.id} />
<SelectInfo data={detais?.tradeExtendCostInfo} id={data?.id} />
<SurchargeInfo data={detais?.tradeExtendServeInfo} id={data?.id} />
<BasicInfo data={detais} loading={loading} />
<ProdInfo data={detais?.items} />
{detais?.tradeServeInfo && (
<ServiceInfo
data={detais.tradeServeInfo}
orderCategoryId={detais?.orderCategoryId}
/>
)}
{detais?.tradeExtendServeInfo && (
<ExtendService
data={detais.tradeExtendServeInfo}
orderCategoryId={detais?.orderCategoryId}
/>
)}
{detais?.tradeExtendCostInfo && (
<ExtendCostInfo
data={detais?.tradeExtendCostInfo}
orderCategoryId={detais?.orderCategoryId}
/>
)}
</Space>
);
};

View File

@@ -23,8 +23,6 @@ import { baseOrderColumns } from './config';
const { Search } = Input;
const { Text } = Typography;
import { DownOutlined, UpOutlined, UserOutlined } from '@ant-design/icons';
import PopconfirmForm, {
type PopconfirmFormRef,
@@ -50,8 +48,8 @@ const OrderListItem: React.FC<{ orderStatus: number }> = (props) => {
const popconfirmFormRef = useRef<PopconfirmFormRef>(null);
const onFetch = async (
params: TradeReq & {
pageSize?: number;
current?: number;
pageSize: number;
current: number;
},
) => {
const data = await getTradeOrderPage({
@@ -68,10 +66,13 @@ const OrderListItem: React.FC<{ orderStatus: number }> = (props) => {
};
};
const handleDetail = useCallback((record: TradeOrderPageRespVO) => {
const handleDetail = useCallback(
(record: TradeOrderPageRespVO) => {
setModalData(record);
configurableDrawerRef.current?.open();
}, []);
configurableDrawerRef.current?.open(record);
},
[modalData],
);
const handleOrder = useCallback((id?: number) => {
console.log(id, '取消订单');
@@ -107,10 +108,10 @@ const OrderListItem: React.FC<{ orderStatus: number }> = (props) => {
<a key="cancel" onClick={() => handleOrder(record.id)}>
</a>
<a key="cancel" onClick={() => handleOrder(record.id)}>
<a key="order" onClick={() => handleOrder(record.id)}>
</a>
<a key="cancel" onClick={() => handleOrder(record.id)}>
<a key="sale" onClick={() => handleOrder(record.id)}>
</a>
<a key="detail" onClick={() => handleDetail(record)}>
@@ -186,9 +187,9 @@ const OrderListItem: React.FC<{ orderStatus: number }> = (props) => {
<span>{record.orderNum}</span>
<span> {record.payType}</span>
<Space>
<Avatar icon={<UserOutlined />} />
<Avatar icon={<UserOutlined />} src={record.shopLogo} />
{/* <Image src={record.picUrl} width={64} /> */}
{record.shopName || '-'}
</Space>
<Space>
{record.userAvatar ? (
@@ -211,7 +212,6 @@ const OrderListItem: React.FC<{ orderStatus: number }> = (props) => {
header: {
cell: (props: any) => {
const { children, ...restProps } = props;
console.log(restProps, children);
return <th {...restProps}>{children}</th>;
},
},

View File

@@ -1,8 +1,8 @@
import type { RequestOptions } from "@@/plugin-request/request";
import type { RequestConfig } from "@umijs/max";
import { request } from "@umijs/max";
import { message } from "antd";
import { getAccessToken, getRefreshToken, setToken } from "./utils/auth";
import type { RequestOptions } from '@@/plugin-request/request';
import type { RequestConfig } from '@umijs/max';
import { request } from '@umijs/max';
import { message } from 'antd';
import { getAccessToken, getRefreshToken, setToken } from './utils/auth';
// const tenantEnable = process.env.VITE_APP_TENANT_ENABLE;
// const { result_code, base_url, request_timeout } = config;
@@ -49,8 +49,8 @@ interface ResponseStructure {
*/
const refreshToken = async () => {
return await request("/system/auth/refresh-token", {
method: "POST",
return await request('/system/auth/refresh-token', {
method: 'POST',
params: { refreshToken: getRefreshToken() },
});
};
@@ -93,7 +93,7 @@ export const errorConfig: RequestConfig = {
const { success, data, code, msg } = res as unknown as ResponseStructure;
if (!success) {
const error: any = new Error(msg);
error.name = "BizError";
error.name = 'BizError';
error.info = { code, msg, data };
throw error; // 抛出自制的错误
}
@@ -102,9 +102,9 @@ export const errorConfig: RequestConfig = {
errorHandler: async (error: any, opts: any) => {
if (opts?.skipErrorHandler) throw error;
// 我们的 errorThrower 抛出的错误。
console.log("errorHandler", error);
console.log('errorHandler', error);
const errorInfo: ResponseStructure | undefined = error.info;
if (error.name === "BizError") {
if (error.name === 'BizError') {
if (errorInfo) {
const { msg } = errorInfo;
message.error(msg);
@@ -114,7 +114,7 @@ export const errorConfig: RequestConfig = {
// 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
message.error(`Response status:${error.response.status}`);
} else if (error.request) {
message.error("None response! Please retry.");
message.error('None response! Please retry.');
} else {
message.error(`发送请求时出了点问题:${error.msg}`);
}
@@ -125,7 +125,7 @@ export const errorConfig: RequestConfig = {
requestInterceptors: [
(config: RequestOptions) => {
// 拦截请求配置,进行个性化处理。
console.log("requestInterceptors", config);
console.log('requestInterceptors', config);
return { ...config };
},
],
@@ -133,25 +133,25 @@ export const errorConfig: RequestConfig = {
// 响应拦截器
responseInterceptors: [
async (response) => {
let { data } = response as unknown as ResponseStructure;
const { data } = response as unknown as ResponseStructure;
const config = response.config;
const { code } = data;
if (!data) {
// 返回“[HTTP]请求没有返回值”;
throw new Error();
}
// if (!data) {
// // 返回“[HTTP]请求没有返回值”;
// throw new Error();
// }
// 未设置状态码则默认成功状态
// 二进制数据则直接返回,例如说 Excel 导出
if (
response.request.responseType === "blob" ||
response.request.responseType === "arraybuffer"
) {
// 注意:如果导出的响应为 json说明可能失败了不直接返回进行下载
// if (response.data.type !== "application/json") {
// return response.data;
// if (
// response.request.responseType === "blob" ||
// response.request.responseType === "arraybuffer"
// ) {
// // 注意:如果导出的响应为 json说明可能失败了不直接返回进行下载
// // if (response.data.type !== "application/json") {
// // return response.data;
// // }
// data = await new Response(data).json();
// }
data = await new Response(data).json();
}
// // 获取错误信息
// const msg = data.msg || errorCode[code] || errorCode["default"];
// if (ignoreMsgs.indexOf(msg) !== -1) {
@@ -159,7 +159,7 @@ export const errorConfig: RequestConfig = {
// return Promise.reject(msg);
// }
if (!config.url) {
throw new Error("请求URL不能为空");
throw new Error('请求URL不能为空');
}
// 发送请求时出了点问题
if (code === 401) {
@@ -172,28 +172,21 @@ export const errorConfig: RequestConfig = {
// 2. 进行刷新访问令牌
try {
const refreshTokenRes = await refreshToken();
console.log("刷新成功", refreshTokenRes);
// 2.1 刷新成功,则回放队列的请求 + 当前请求
setToken(refreshTokenRes);
// 发出 token 刷新事件
// 刷新当前路由
// window.location.reload();
// requestEventBus.emit('token-refreshed');
location.reload();
} catch (_) {
// 为什么需要 catch 异常呢?刷新失败时,请求因为 Promise.reject 触发异常。
// 提示是否要登出。即不回放当前请求!不然会形成递归
return handleAuthorized();
} finally {
isRefreshToken = false;
}
return request(config.url, config);
} else {
console.log("刷新令牌失败");
console.log('刷新令牌失败');
//添加到队列,等待刷新获取到新的令牌
return new Promise((resolve) => {
requestList.push(() => {
if (!config.url) {
throw new Error("请求URL不能为空");
throw new Error('请求URL不能为空');
}
if (config.headers)
config.headers.Authorization = `Bearer ${getAccessToken()}`; // 让每个请求携带自定义token 请根据实际情况自行修改
@@ -206,7 +199,7 @@ export const errorConfig: RequestConfig = {
return Promise.reject(data);
}
if (data?.success === false) {
message.error("请求失败!");
message.error('请求失败!');
return Promise.reject(data);
}

View File

@@ -0,0 +1,43 @@
import { request } from "@umijs/max";
export interface Category extends PageParam {
parentId?: number;
categoryName?: string; //类目名称
categoryId?: number; //类目ID
grade?: number; //类目层级
parentName?: string; //父级类目
sort?: number; //排序权重
status?: number; // 1为开启 0 为禁用;
createTime?: Date;
description?: string; //描述
tag?: string[] | string;
}
export interface CategoryReq extends PageParam {
parentName?: string;
categoryName?: string;
status?: number;
grade?: number; //父级类目
}
// 查询分类列表
export const getProdCategoryPage = async (params: CategoryReq) => {
return request("/product/category/categoryList", {
method: "GET",
params,
});
};
// 新增分类
export const createProdCategory = async (data: Category) => {
return request("/product/category/create", {
method: "GET",
data,
});
};
//编辑分类
export const updateProdCategory = async (data: Category) => {
return request<IResponse<boolean>>("/product/category/create", {
method: "PUT",
data,
});
};

View File

@@ -1,3 +1,4 @@
import { request } from "@umijs/max";
export interface Prod extends PageParam {
/**
* 商品简称
@@ -176,10 +177,10 @@ export interface SkuPropValues {
id?: number;
propValue: string;
valueId: number;
state: 0 | 1; // 0禁用1启用
isExist: 0 | 1; //是否新增 0否1是
state: number; // 0禁用1启用
isExist: number; //是否新增 0否1是
sort: number;
isExpire: 0 | 1 /**是否失效0否1是**/;
isExpire: number /**是否失效0否1是**/;
}
export interface SkuConfig {
propName: string; //规格名字
@@ -204,7 +205,209 @@ export interface ProdDetail {
export interface ProdReq extends PageParam {
name?: string;
status?: 0 | 1 | 2; //状态 1正常状态出售中, 0下架仓库中 2待审核
status?: number; //状态 1正常状态出售中, 0下架仓库中 2待审核
prodName?: string; // 商品名称
createTime?: Date; //创建时间
}
// 查询商品管理列表
export const getProdPage = async (params: ProdReq) => {
return request("/product/prod/page", {
method: "GET",
params,
});
};
// 查询商品回收站
export const getProdRecycleBinPageList = async (params: ProdReq) => {
return request("/product/prod/getProdRecycleBinPageList", {
method: "GET",
params,
});
};
// 恢复商品
export const restoreProdList = async (params: { ids: number[] }) => {
return request("/product/prod/restoreProdList", {
method: "POST",
params,
});
};
// 删除商品
export const deleteProd = async (params: { id: number }) => {
return request<PageResult<Prod[]>>("/product/prod/delete", {
method: "DELETE",
params,
});
};
// 批量删除商品
export const deleteSkuList = async (params: { ids: number[] }) => {
return request<PageResult<Prod[]>>("/product/prod/deleteSkuList", {
method: "DELETE",
params,
});
};
// 批量上下架商品
export const updateSkuShelfList = async (params: {
status: number;
ids: number[];
}) => {
return request<PageResult<Prod[]>>("/product/prod/updateSkuShelfList", {
method: "DELETE",
params,
});
};
// 查询商品管理详情
export const getProdDetail = async (params: { id: number }) => {
return request<PageResult<Prod[]>>("/product/prod/getProdInfo", {
method: "GET",
params,
});
};
// 创建商品
export const createProd = async (data: Prod) => {
return request<PageResult<Prod[]>>("/product/prod/create", {
method: "POST",
data,
});
};
// 更新商品详情
export const updateProd = async (data: Prod) => {
return request<Promise<PageResult<Prod[]>>>("/product/prod/update", {
method: "PUT",
data,
});
};
// 获取sku规格
export const getSKuPropList = async (params: Prod) => {
return request("/product/sku/getSKuPropList", {
method: "GET",
params,
});
};
// 更新sku
export const updateProp = async (data: Prod) => {
return request("/product/sku/updateProp", {
method: "PUT",
data,
});
};
//更新单品SKU
export const updateSKu = async (data: Prod) => {
return request("/product/sku/update", {
method: "PUT",
data,
});
};
//更新单品SKU上下架
export const updateSkuShelf = async (params: {
id?: number;
isShelf?: number;
}) => {
return request("/product/sku/updateSkuShelf", {
method: "PUT",
params,
});
};
//删除规格值
export const deletePropSku = async (params: { id?: number }) => {
return request("/product/sku/deleteProp", {
method: "PUT",
params,
});
};
//禁用规格值
export const disablePropSku = async (params: Prod) => {
return request("/product/sku/disableProp", {
method: "PUT",
params,
});
};
//获得SKU分页列表
export const getSkuPageList = async (params: Prod) => {
return request("/product/sku/getSkuPageList", {
method: "GET",
params,
});
};
//删除SKU
export const deleteSku = async (id: number) => {
return request("/product/sku/delete", {
method: "DELETE",
params: { id },
});
};
//获得SKU回收站列表
export const getSkuRecycleBinPageList = async (params: Prod) => {
return request("/product/sku/getSkuRecycleBinPageList", {
method: "GET",
params,
});
};
//获得规格回收站
export const getPropRecycleBinList = async (params: Prod) => {
return request("/product/sku/getPropRecycleBinList", {
method: "GET",
params,
});
};
//恢复规格值
export const restorePropList = async (ids: number[]) => {
return request("/product/sku/restorePropList", {
method: "POST",
params: { ids },
});
};
//SKU回收站恢复
export const restoreSkuList = async (ids: number[]) => {
return request("/product/sku/restoreSkuList", {
method: "POST",
params: { ids },
});
};
// 规格属性修改
export const updateProdProp = async (params: {
id: number;
propName: string;
}) => {
return request("/product/sku/updateProdProp", {
method: "PUT",
params,
});
};
// 规格值修改
export const updatePropValue = async (params: {
id: number;
propValue: string;
}) => {
return request("/product/sku/updatePropValue", {
method: "PUT",
params,
});
};
// /tz/sku/update
// //修改服务配置
// export const getSKuPropList = async (data: Prod) => {
// return await request.post({ url: "/tz/sku/getSKuPropList", data: data });
// };
// /product/prod/uptateProdService

View File

@@ -0,0 +1,565 @@
import { request } from "@umijs/max";
import type { Prod } from ".";
/**
* ProdServiceVO
*/
export interface ProdServiceVO {
/**
* 是否特殊日期节假日周末什么的0关1开
*/
additionalFeeSwitch?: number;
/**
* 是否特殊时段0关1开
*/
additionalSwitch?: number;
/**
* 分类名称
*/
categoryName?: string;
/**
* 创建时间
*/
createTime?: string;
/**
* 新建人
*/
creator?: string;
/**
* 是否紧急响应服务0关1开
*/
emergencySwitch?: number;
/**
* 是否接单上线0关1开
*/
orderLimitSwitch?: number;
/**
* 特殊时段规则配置
*/
prodAdditionalFeeDatesList?: ProdAdditionalFeeDatesDO[];
/**
* 特殊日期规则配置
*/
prodAdditionalFeePeriodsList?: ProdAdditionalFeePeriodsDO[];
/**
* 急响应服务配置
*/
prodEmergencyInfoVO?: ProdEmergencyInfoVO;
/**
* 产品ID
*/
prodId?: number;
/**
* 预约配置
*/
prodReservationConfig?: ProdReservationInfoVO;
/**
* 服务区域配置
*/
prodServiceAreasInfo?: ProdServiceAreasInfoVO;
/**
* 接单上线配置
*/
productOrderLimitVO?: ProductOrderLimitDO;
/**
* 体重配置
*/
prodWeightConfig?: ProdWeightRangePricesSaveInfoVO;
/**
* 是否开启服务区域配置0关1开
*/
regionSwitch?: number;
/**
* 是否预约0关1开
*/
reservationSwitch?: number;
/**
* 修改人
*/
updater?: string;
/**
* 修改时间
*/
updateTime?: string;
/**
* 是否开启体重配置0关1开
*/
weightSwitch?: number;
}
/**
* 特殊日期附加费用规则 DO
*
* ProdAdditionalFeeDatesDO
*/
export interface ProdAdditionalFeeDatesDO {
/**
* 收费方式
*/
chargeMode?: number;
/**
* 创建时间
*/
createTime?: string;
/**
* 创建者,目前使用 SysUser 的 id 编号
*
* 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
*/
creator?: string;
/**
* 自定义日期时间段JSON格式存储
*/
customTimeSlots?: string[];
/**
* 日期类型0'自定义日期范围':1'指定日期':2'法定节假日',3'固定休息日'
*/
dateType?: number;
/**
* 是否删除
*/
deleted?: number;
/**
* 特殊日期规则的唯一标识符
*/
id?: number;
/**
* 是否启用该规则
*/
isEnabled?: number;
/**
* 名称
*/
name?: string;
/**
* 价格或上浮百分比
*/
price?: number;
/**
* 商品id
*/
prodId?: number;
/**
* 类型1特殊日期 2可预约时段黑名单日期 3紧急相应服务黑名单日期
*/
type?: number;
/**
* 更新者,目前使用 SysUser 的 id 编号
*
* 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
*/
updater?: string;
/**
* 最后更新时间
*/
updateTime?: string;
}
/**
* 特殊时段附加费用规则 DO
*
* ProdAdditionalFeePeriodsDO
*/
export interface ProdAdditionalFeePeriodsDO {
/**
* 收费方式0:'固定金额',1:'基准价上浮'
*/
chargeMode?: number;
/**
* 创建时间
*/
createTime?: string;
/**
* 创建者,目前使用 SysUser 的 id 编号
*
* 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
*/
creator?: string;
/**
* 是否删除
*/
deleted?: number;
/**
* 浮动百分比
*/
floatingPercentage?: number;
/**
* 特殊时段规则的唯一标识符
*/
id?: number;
/**
* 名称
*/
name?: string;
/**
* 价格或上浮百分比
*/
price?: number;
/**
* 商品ID
*/
prodId?: number;
/**
* 特殊时段设置JSON格式存储
*/
specialTimeSlots?: string[];
/**
* 更新者,目前使用 SysUser 的 id 编号
*
* 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
*/
updater?: string;
/**
* 最后更新时间
*/
updateTime?: string;
}
/**
* 急响应服务配置
*
* ProdEmergencyInfoVO
*/
export interface ProdEmergencyInfoVO {
/**
* 紧急响应服务配置的唯一标识符
*/
id?: number;
/**
* 紧急响应黑名单日期设置
*/
prodEmergencyResponseBlackList?: ProdAdditionalFeeBlackVO[];
/**
* 紧急响应时间区间设置
*/
prodEmergencyResponseIntervalsList?: ProdEmergencyResponseIntervalsDO[];
/**
* 关联的商品ID
*/
prodId?: number;
/**
* 可响应时间段JSON格式存储
*/
responseTimeSlots?: string[];
}
/**
*
* com.tashow.cloud.productapi.api.product.vo.prodadditionalfeedates.ProdAdditionalFeeBlackVO
*
* ProdAdditionalFeeBlackVO
*/
export interface ProdAdditionalFeeBlackVO {
/**
* 黑名单日期设置
*/
customTimeSlots?: string[];
/**
* 日期类型0'自定义日期范围':1'指定日期':2'法定节假日',3'固定休息日'
*/
dateType?: number;
/**
* 特殊日期规则的唯一标识符
*/
id?: number;
/**
* 是否启用该规则是否启用该规则0关1开
*/
isEnabled?: number;
/**
* 类型1特殊日期 2可预约时段黑名单日期 3紧急相应服务黑名单日期
*/
type?: number;
}
/**
* 紧急响应时间区间设置 DO
*
* ProdEmergencyResponseIntervalsDO
*/
export interface ProdEmergencyResponseIntervalsDO {
/**
* 收费模式0固定收费 1浮动收费
*/
chargeMode?: number;
/**
* 关联的紧急响应服务配置ID
*/
configId?: number;
/**
* 创建时间
*/
createTime?: string;
/**
* 创建者,目前使用 SysUser 的 id 编号
*
* 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
*/
creator?: string;
/**
* 是否删除
*/
deleted?: number;
/**
* 浮动百分比
*/
floatingPercentage?: number;
/**
* 响应时间区间的唯一标识符
*/
id?: number;
/**
* 响应模式名称
*/
modeName?: string;
name?: string;
/**
* 价格或上浮百分比
*/
price?: number;
/**
* 商品ID
*/
prodId?: number;
/**
* 响应时间(小时)
*/
responseHours?: number;
/**
* 更新者,目前使用 SysUser 的 id 编号
*
* 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
*/
updater?: string;
/**
* 最后更新时间
*/
updateTime?: string;
}
/**
* 预约配置
*
* ProdReservationInfoVO
*/
export interface ProdReservationInfoVO {
/**
* 需提前多少小时预约
*/
advanceHours?: number;
/**
* 是否允许更改预约时间 1可以 0不可以
*/
allowChange?: number;
/**
* 更改预约时间的时间规则如服务开始前1小时可更改
*/
changeTimeRule?: number;
/**
* 预约配置的唯一标识符
*/
id?: number;
/**
* 允许更改预约时间的最大次数
*/
maxChangeTimes?: number;
/**
* 关联的商品ID
*/
prodId?: number;
/**
* 预约黑名单日期设置
*/
prodReservationBlackList?: ProdAdditionalFeeBlackVO[];
/**
* 预约日期范围 7天 10天 15天 30天
*/
reservationDateRange?: number;
/**
* 预约时段设置
*/
reservationTimeSlots?: string[];
/**
* 预约时间区间设置
*/
timeBook?: TimeBookVO;
/**
* 时间段
*/
timeSlot?: number;
}
/**
* 预约时间区间设置
*
* TimeBookVO
*/
export interface TimeBookVO {
/**
* 预约时段设置
*/
reservationTimeSlots?: string[];
/**
* 时间段
*/
timeSlot?: number;
}
/**
* 服务区域配置
*
* ProdServiceAreasInfoVO
*/
export interface ProdServiceAreasInfoVO {
/**
* 服务区域地址名称
*/
areaNameList?: string[];
/**
* 超区费用仅在rule_type为accept_with_fee时有效
*/
fee?: number;
/**
* 超区规则的唯一标识符
*/
id?: number;
/**
* 关联的商品ID
*/
prodId?: number;
/**
* 超区规则类型0:拒单、2:接单并收取超区费、3:接单并免超区费)
*/
ruleType?: number;
[property: string]: any;
}
/**
* 体重配置
*
* ProdWeightRangePricesSaveInfoVO
*/
export interface ProdWeightRangePricesSaveInfoVO {
/**
* 体重是否收费0否1是
*/
isWeightCharge?: number;
/**
* 体重配置
*/
prodWeightConfigList?: ProdWeightRangePricesDO[];
}
/**
* 体重区间价格 DO
*
* ProdWeightRangePricesDO
*/
export interface ProdWeightRangePricesDO {
/**
* 创建时间
*/
createTime?: string;
/**
* 创建者,目前使用 SysUser 的 id 编号
*
* 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
*/
creator?: string;
/**
* 是否删除
*/
deleted?: number;
/**
* 体重区间价格的唯一标识符
*/
id?: number;
/**
* 是否启用该规则0否1是
*/
isEnabled?: number;
/**
* 价格
*/
price?: number;
/**
* 关联的体重配置ID
*/
prodId?: number;
/**
* 更新者,目前使用 SysUser 的 id 编号
*
* 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
*/
updater?: string;
/**
* 最后更新时间
*/
updateTime?: string;
/**
* 体重区间
*/
weightRange?: string;
}
/**
* 接单上线配置
*
* ProductOrderLimitDO
*/
export interface ProductOrderLimitDO {
/**
* 创建时间
*/
createTime?: string;
/**
* 创建者,目前使用 SysUser 的 id 编号
*
* 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
*/
creator?: string;
/**
* 是否删除
*/
deleted?: number;
/**
* 接单上限配置的唯一标识符
*/
id?: number;
/**
* 限制单位'0:按自然天',1:'按自然周',2:'按自然月'
*/
limitUnit?: number;
/**
* 上限阈值
*/
maxOrders?: number;
/**
* 关联的商品ID
*/
prodId?: number;
/**
* 更新者,目前使用 SysUser 的 id 编号
*
* 使用 String 类型的原因是,未来可能会存在非数值的情况,留好拓展性。
*/
updater?: string;
/**
* 最后更新时间
*/
updateTime?: string;
}
export const getProdServiceRule = async (params: Prod) => {
return request("/product/prod/getProdService", {
method: "GET",
params,
});
};
export const uptateProdServiceRule = async (data: ProdServiceVO) => {
return request("/product/prod/uptateProdService", {
method: "POST",
data,
});
};

View File

@@ -1,48 +0,0 @@
// @ts-ignore
/// <reference path="./typings.d.ts" />
import { request } from "@umijs/max";
/** 获取菜单页面的表 GET /product/category/categoryList */
export async function getCategoryList(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.getProductCategoryCategoryListParams,
options?: { [key: string]: any }
) {
return request("/product/category/categoryList", {
method: "GET",
params: {
...params,
},
...(options || {}),
});
}
/** 创建产品类目 创建产品类目 POST /product/category/create */
export async function postCategoryCreate(
body: API.CategorySaveReqVO,
options?: { [key: string]: any }
) {
return request<API.CommonResultLong>("/product/category/create", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
data: body,
...(options || {}),
});
}
/** 更新产品类目 更新产品类目 PUT /product/category/update */
export async function putCategoryUpdate(
body: API.CategorySaveReqVO,
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/category/update", {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
data: body,
...(options || {}),
});
}

View File

@@ -1,10 +0,0 @@
// @ts-ignore
/* eslint-disable */
// API 更新时间:
// API 唯一标识:
import * as product from "./product";
import * as category from "./category";
export default {
product,
category,
};

View File

@@ -1,586 +0,0 @@
// @ts-ignore
/// <reference path="./typings.d.ts" />
import { request } from "@umijs/max";
/** 创建商品 创建商品 POST /prod/create */
export async function postProdCreate(
body: API.ProdSaveReqVO,
options?: { [key: string]: any }
) {
return request<API.CommonResultLong>("/prod/create", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
data: body,
...(options || {}),
});
}
/** 创建商品 创建商品 POST /product/prod/create */
export async function postProductProdCreate(
body: API.ProdSaveReqVO,
options?: { [key: string]: any }
) {
return request<API.CommonResultLong>("/product/prod/create", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
data: body,
...(options || {}),
});
}
/** 创建商品服务配置 创建商品服务配置 POST /product/prod/createProdService */
export async function postProductProdCreateProdService(
body: API.ProdServiceVO,
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/prod/createProdService", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
data: body,
...(options || {}),
});
}
/** 删除商品 删除商品 DELETE /product/prod/delete */
export async function deleteProductProdOpenApiDelete(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.deleteProductProd_openAPI_deleteParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/prod/delete", {
method: "DELETE",
params: {
...params,
},
...(options || {}),
});
}
/** 批量删除商品 批量删除商品 DELETE /product/prod/deleteSkuList */
export async function deleteProductProdDeleteSkuList(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.deleteProductProdDeleteSkuListParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/prod/deleteSkuList", {
method: "DELETE",
params: {
...params,
},
...(options || {}),
});
}
/** 获得商品回收站分页列表 获得商品回收站分页列表 GET /product/prod/getProdRecycleBinPageList */
export async function getProductProdGetProdRecycleBinPageList(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.getProductProdGetProdRecycleBinPageListParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultPageResultProdRestoreListVO>(
"/product/prod/getProdRecycleBinPageList",
{
method: "GET",
params: {
...params,
pageNo: params.pageNo ?? "1",
pageSize: params.pageSize ?? "10",
},
...(options || {}),
}
);
}
/** 获得商品服务信息 获得商品服务信息 GET /product/prod/getProdService */
export async function getProductProdGetProdService(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.getProductProdGetProdServiceParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultProdServiceVO>(
"/product/prod/getProdService",
{
method: "GET",
params: {
...params,
},
...(options || {}),
}
);
}
/** 获得商品分页 获得商品分页 GET /product/prod/page */
export async function getProductProdPage(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.getProductProdPageParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultPageResultProdListVO>("/product/prod/page", {
method: "GET",
params: {
...params,
pageNo: params.pageNo ?? "1",
pageSize: params.pageSize ?? "10",
},
...(options || {}),
});
}
/** 恢复商品 恢复商品 POST /product/prod/restoreProdList */
export async function postProductProdRestoreProdList(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.postProductProdRestoreProdListParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/prod/restoreProdList", {
method: "POST",
params: {
...params,
},
...(options || {}),
});
}
/** 更新商品 更新商品 PUT /product/prod/update */
export async function putProductProdUpdate(
body: API.ProdSaveReqVO,
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/prod/update", {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
data: body,
...(options || {}),
});
}
/** 批量上下架 批量上下架 DELETE /product/prod/updateSkuShelfList */
export async function deleteProductProdUpdateSkuShelfList(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.deleteProductProdUpdateSkuShelfListParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/prod/updateSkuShelfList", {
method: "DELETE",
params: {
...params,
},
...(options || {}),
});
}
/** 修改商品服务配置 修改商品服务配置 POST /product/prod/uptateProdService */
export async function postProductProdUptateProdService(
body: API.ProdServiceInfoVO,
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/prod/uptateProdService", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
data: body,
...(options || {}),
});
}
/** 创建sku扩展服务配置 创建sku扩展服务配置 POST /product/sku/createSkuExtend */
export async function postProductSkuCreateSkuExtend(
body: API.SkuExtendVO,
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/sku/createSkuExtend", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
data: body,
...(options || {}),
});
}
/** 删除单品SKU 删除单品SKU DELETE /product/sku/delete */
export async function deleteProductSkuOpenApiDelete(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.deleteProductSku_openAPI_deleteParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/sku/delete", {
method: "DELETE",
params: {
...params,
},
...(options || {}),
});
}
/** 删除规格值 删除规格值 PUT /product/sku/deleteProp */
export async function putProductSkuDeleteProp(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.putProductSkuDeletePropParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/sku/deleteProp", {
method: "PUT",
params: {
...params,
},
...(options || {}),
});
}
/** 批量删除SKU 批量删除SKU DELETE /product/sku/deleteSkuList */
export async function deleteProductSkuDeleteSkuList(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.deleteProductSkuDeleteSkuListParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/sku/deleteSkuList", {
method: "DELETE",
params: {
...params,
},
...(options || {}),
});
}
/** 禁用或者启用规格值 禁用规格值 PUT /product/sku/disableProp */
export async function putProductSkuDisableProp(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.putProductSkuDisablePropParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/sku/disableProp", {
method: "PUT",
params: {
...params,
},
...(options || {}),
});
}
/** 获得单品SKU 获得单品SKU GET /product/sku/get */
export async function getProductSkuGet(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.getProductSkuGetParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultSkuRespVO>("/product/sku/get", {
method: "GET",
params: {
...params,
},
...(options || {}),
});
}
/** 获取规格回收站 获取规格回收站 GET /product/sku/getPropRecycleBinList */
export async function getProductSkuGetPropRecycleBinList(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.getProductSkuGetPropRecycleBinListParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultPageResultProPropRecycleBinVO>(
"/product/sku/getPropRecycleBinList",
{
method: "GET",
params: {
...params,
pageNo: params.pageNo ?? "1",
pageSize: params.pageSize ?? "10",
},
...(options || {}),
}
);
}
/** 获取sku扩展服务配置信息 获取sku扩展服务配置信息 POST /product/sku/getSkuExtend */
export async function postProductSkuGetSkuExtend(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.postProductSkuGetSkuExtendParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultSkuExtendVO>("/product/sku/getSkuExtend", {
method: "POST",
params: {
...params,
},
...(options || {}),
});
}
/** 获得SKU分页列表 获得SKU分页列表 GET /product/sku/getSkuPageList */
export async function getProductSkuGetSkuPageList(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.getProductSkuGetSkuPageListParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultPageResultSkuDO>(
"/product/sku/getSkuPageList",
{
method: "GET",
params: {
...params,
pageNo: params.pageNo ?? "1",
pageSize: params.pageSize ?? "10",
},
...(options || {}),
}
);
}
/** 获取sku规格 获取sku规格 GET /product/sku/getSKuPropList */
export async function getProductSkuGetSKuPropList(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.getProductSkuGetSKuPropListParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultSkuPropInfoVO>("/product/sku/getSKuPropList", {
method: "GET",
params: {
...params,
},
...(options || {}),
});
}
/** 获得SKU回收站分页列表 获得SKU回收站分页列表 GET /product/sku/getSkuRecycleBinPageList */
export async function getProductSkuGetSkuRecycleBinPageList(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.getProductSkuGetSkuRecycleBinPageListParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultPageResultSkuRecycleBinVO>(
"/product/sku/getSkuRecycleBinPageList",
{
method: "GET",
params: {
...params,
pageNo: params.pageNo ?? "1",
pageSize: params.pageSize ?? "10",
},
...(options || {}),
}
);
}
/** 恢复规格 恢复规格 POST /product/sku/restorePropList */
export async function postProductSkuRestorePropList(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.postProductSkuRestorePropListParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/sku/restorePropList", {
method: "POST",
params: {
...params,
},
...(options || {}),
});
}
/** 恢复SKU 恢复SKU POST /product/sku/restoreSkuList */
export async function postProductSkuRestoreSkuList(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.postProductSkuRestoreSkuListParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/sku/restoreSkuList", {
method: "POST",
params: {
...params,
},
...(options || {}),
});
}
/** 更新单品SKU 更新单品SKU PUT /product/sku/update */
export async function putProductSkuUpdate(
body: API.SkuSaveReqVO,
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/sku/update", {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
data: body,
...(options || {}),
});
}
/** 修改配送方式 修改配送方式 POST /product/sku/updateDeliver */
export async function postProductSkuUpdateDeliver(
body: API.SkuServiceDeliverDO[],
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/sku/updateDeliver", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
data: body,
...(options || {}),
});
}
/** 修物料配置 修物料配置 POST /product/sku/updateMaterial */
export async function postProductSkuUpdateMaterial(
body: API.SkuServiceMaterialDO[],
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/sku/updateMaterial", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
data: body,
...(options || {}),
});
}
/** 修改属性规格值 修改属性规格值 PUT /product/sku/updateProdProp */
export async function putProductSkuUpdateProdProp(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.putProductSkuUpdateProdPropParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/sku/updateProdProp", {
method: "PUT",
params: {
...params,
},
...(options || {}),
});
}
/** 新增统一保存sku规格 更新sku规格 PUT /product/sku/updateProp */
export async function putProductSkuUpdateProp(
body: API.SkuPropVO,
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/sku/updateProp", {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
data: body,
...(options || {}),
});
}
/** 修改属性下面规格值 修改属性下面规格值 PUT /product/sku/updatePropValue */
export async function putProductSkuUpdatePropValue(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.putProductSkuUpdatePropValueParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/sku/updatePropValue", {
method: "PUT",
params: {
...params,
},
...(options || {}),
});
}
/** 修改扩展服务信息配置(遗体接运扩展服务,遗体清洁配置,追思告别配置,骨灰处理配置...... 修改扩展服务信息配置(遗体接运扩展服务,遗体清洁配置,追思告别配置,骨灰处理配置...... POST /product/sku/updateServiceDetails */
export async function postProductSkuUpdateServiceDetails(
body: API.SkuServiceDetailsDO[],
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/sku/updateServiceDetails", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
data: body,
...(options || {}),
});
}
/** 修改单品上下架 修改单品上下架 PUT /product/sku/updateSkuShelf */
export async function putProductSkuUpdateSkuShelf(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.putProductSkuUpdateSkuShelfParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/sku/updateSkuShelf", {
method: "PUT",
params: {
...params,
},
...(options || {}),
});
}
/** 修改单品上下架 修改单品上下架 POST /product/sku/updateSkuShelf */
export async function postProductSkuUpdateSkuShelf(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.postProductSkuUpdateSkuShelfParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/sku/updateSkuShelf", {
method: "POST",
params: {
...params,
},
...(options || {}),
});
}
/** 批量上下架 批量上下架 PUT /product/sku/updateSkuShelfList */
export async function putProductSkuUpdateSkuShelfList(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.putProductSkuUpdateSkuShelfListParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/sku/updateSkuShelfList", {
method: "PUT",
params: {
...params,
},
...(options || {}),
});
}
/** 批量上下架 批量上下架 POST /product/sku/updateSkuShelfList */
export async function postProductSkuUpdateSkuShelfList(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: API.postProductSkuUpdateSkuShelfListParams,
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>("/product/sku/updateSkuShelfList", {
method: "POST",
params: {
...params,
},
...(options || {}),
});
}
/** 修改接运地址配置 修改接运地址配置 POST /product/sku/updateTransportAdress */
export async function postProductSkuUpdateTransportAdress(
body: API.SkuServiceTransportDO[],
options?: { [key: string]: any }
) {
return request<API.CommonResultBoolean>(
"/product/sku/updateTransportAdress",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
data: body,
...(options || {}),
}
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,348 @@
export interface TradeServeInfo {
boneInfo?: {
boneUrl?: string[];
diedReason?: string;
diedTime?: string;
petName?: string;
petType?: string;
weight?: string;
};
subInfo?: {
changeRule?: string;
merchantRemark?: string;
pickUpAddress?: string;
sendAddress?: string;
subOrder?: string;
subType?: string;
userRemark?: string;
};
userId?: number;
userMobile?: number;
userName?: string;
userNickName?: string;
userRemark?: string;
}
export interface TradeExtendCostInfo {
chargeType?: string; //收费方式
costName?: string; //超区域服务费
serveArea?: string; //可服务区域
targetArea?: string; //目标区域
weight?: string; //体型/体重
respTime?: string; //响应时间
respMode?: string; //响应模式
chargeTime?: string; //收费时段
payInfo?: {
payPrice?: string;
totalPrice?: string;
discountPrice?: string;
};
refundPrice?: string;
serveExtFee?: string; //服务附加费
}
export interface Serve {
count?: string;
handPrice?: number;
price?: string;
serveDesc?: string;
serveName?: string;
serveUrl?: string;
}
export interface TradeExtendServeInfo {
discountPrice?: string;
payPrice?: number;
refundCount?: string;
refundMoney?: string;
serve?: Serve[];
tempType?: number;
totalPrice?: string;
serveType?: string; //"处理方式"
serveTitle?: string;
address?: string;
deliveryType?: string;
sendUser?: string;
userMobile?: string;
receiveUser?: string;
}
export interface TradeOrderDetailRespVO {
/**
* 取消原因
*/
cancelReason?: string;
/**
* 取消时间
*/
cancelTime?: string;
/**
* 下单时间
*/
createTime?: string;
/**
* 优惠金额
*/
discountPrice?: number;
/**
* 财务状态
*/
financeStatus?: number;
/**
* 订单完成时间
*/
finishTime?: string;
/**
* 订单编号
*/
id?: number;
/**
* 商品列表
*/
items?: Item[];
/**
* 实收金额
*/
livePrice?: number;
/**
* 商家备注
*/
merchantRemark?: string;
/**
* 订单类目id
*/
orderCategoryId?: number;
/**
* 订单类目名称
*/
orderCategoryName?: string;
/**
* 订单编号
*/
orderNum?: string;
/**
* 订单状态
* {@link TradeOrderStatusEnum#getStatus()}
*/
orderStatus?: number;
/**
* 订单来源
* {@link TerminalEnum#getTerminal()}
*/
orderTerminal?: number;
/**
* 订单类型
* {@link TradeOrderTypeEnum#getType()}
*/
orderType?: number;
/**
* 支付渠道 (线上线下)
*/
payChannelCode?: number;
/**
* 交易流水号
*/
payOrderId?: string;
/**
* 实付金额
*/
payPrice?: number;
/**
* 支付时间
*/
payTime?: string;
/**
* 支付方式
*/
payType?: number;
/**
* 订单金额
*/
price?: number;
/**
* 保障状态
*/
propertyStatus?: number;
/**
* 保障时间
*/
propertyTime?: string;
/**
* 退款金额
*/
refundPrice?: number;
/**
* 退款时间
*/
refundTime?: string;
/**
* 订单状态流转记录
*/
statusList?: TradeOrderStatusRespVo[];
/**
* 附加费信息(order_serve_info配置)
*/
tradeExtendCostInfo?: TradeExtendCostInfo[];
/**
* 扩展服务信息(order_serve_info配置)
*/
tradeExtendServeInfo?: TradeExtendServeInfo[][];
/**
* 服务信息(order_serve_info配置)
*/
tradeServeInfo?: TradeServeInfo;
/**
* 用户头像
*/
userAvatar?: string;
/**
* 用户编号
*/
userId?: number;
/**
* 用户手机号
*/
userMobile?: string;
/**
* 用户姓名
*/
userName?: string;
/**
* 用户昵称
*/
userNickName?: string;
/**
* 用户备注 - 必填,示例:你猜
*/
userRemark?: string;
}
/**
* com.tashow.cloud.trade.controller.admin.order.vo.TradeOrderDetailRespVO.Item
*
* Item
*/
export interface Item {
/**
* 购买数量 - 必填示例1
*/
count?: number;
/**
* 商品优惠(总) - 必填示例100
*/
discountPrice?: number;
/**
* 成本价
*/
expensePrice?: number;
/**
* 到手价
*/
handedPrice?: number;
/**
* 店铺名称
*/
id?: number;
/**
* 订单类目id
*/
orderCategoryId?: number;
/**
* 订单类目名称
*/
orderCategoryName?: string;
/**
* 商品实付金额(总) - 必填示例100
*/
payPrice?: number;
/**
* 商品图片 - 必填示例https://www.iocoder.cn/1.png
*/
picUrl?: string;
/**
* 商品原价(单) - 必填示例100
*/
price?: number;
/**
* 属性数组
*/
properties?: string;
/**
* 累计退款数量
*/
refundCount?: number;
/**
* 累计退款金额
*/
refundPrice?: number;
/**
* 服务内容
*/
serveContent?: string;
/**
* 店铺logo
*/
shopLogo?: string;
/**
* 店铺名称
*/
shopName?: string;
/**
* 商品 SKU 编号 - 必填示例1
*/
skuId?: number;
/**
* 商品规格
*/
skuName?: string;
/**
* 商品 SPU 编号 - 必填示例1
*/
spuId?: number;
/**
* 商品 SPU 名称 - 必填,示例:芋道源码
*/
spuName?: string;
/**
* 商品总价
*/
totalPrice?: number;
/**
* 单位
*/
unit?: string;
}
/**
* com.tashow.cloud.trade.controller.admin.order.vo.TradeOrderStatusRespVo
*
* TradeOrderStatusRespVo
*/
export interface TradeOrderStatusRespVo {
/**
* 操作后状态
*/
afterStatus?: number;
/**
* 操作前状态
*/
beforeStatus?: number;
/**
* 订单日志信息
*/
content?: string;
/**
* 订单日志信息
*/
createTime?: string;
id?: number;
/**
* 操作类型
*
* {@link TradeOrderOperateTypeEnum}
*/
operateType?: number;
/**
* 订单号
*
* 关联{@link TradeOrderDO#getId()}
*/
orderId?: number;
}

View File

@@ -1,425 +1,6 @@
import { request } from "@umijs/max";
export interface TradeReq {
/**
* 售后状态示例1
*/
afterSaleStatus?: number;
/**
* 创建时间
*/
createTime?: string[];
/**
* 财务状态示例1
*/
financeStatus?: number;
/**
* 卖家名称
*/
merchantName?: string;
/**
* 订单类目id示例1
*/
orderCategoryId?: number;
/**
* 订单状态示例1
*/
orderStatus?: number;
/**
* 订单来源示例1
*/
orderTerminal?: number;
/**
* 页码,从 1 开始", example = "1
*/
pageNo?: number;
/**
* 每页条数,最大值为 100"
*/
pageSize?: number;
/**
* 聚合检索字段商品名称商品id订单号
*/
prodSearch?: string;
/**
* 创建时间
*/
subTime?: string[];
/**
* 预约类型示例1
*/
subType?: number;
/**
* 聚合检索字段 买家昵称/手机号
*/
userSearch?: string;
}
export interface TradeOrderPageRespVO {
/**
* 购买的商品数量
*/
count?: number;
/**
* 下单时间
*/
createTime?: string;
/**
* 财务状态
*/
financeStatus?: string;
/**
* 到手价
*/
handedPrice?: number;
/**
* 订单编号
*/
id?: number;
/**
* 订单类目
*/
orderCategoryName?: string;
/**
* 订单流水号
*/
orderNum?: string;
/**
* 订单状态
*/
orderStatus?: number;
/**
* 订单来源
*/
orderTerminal?: number;
/**
* 预约时间
*/
subTime?: string;
/**
* 到手价
*/
payPrice?: number;
/**
* 支付方式
*/
payType?: string;
/**
* 商品图片
*/
picUrl?: string;
/**
* 单价
*/
price?: number;
/**
* 服务地址
*/
serveAddress?: string;
/**
* 商品规格
*/
skuName?: string;
/**
* 商品名称
*/
spuName?: string;
/**
* 单位
*/
unit?: string;
/**
* 用户头像
*/
userAvatar?: string;
/**
* 用户编号
*/
userId?: number;
/**
* 用户手机号
*/
userMobile?: string;
/**
* 用户姓名
*/
userName?: string;
/**
* 用户昵称
*/
userNickName?: string;
/**
* 用户备注 - 必填,示例:你猜
*/
userRemark?: string;
}
/**
* 返回数据
*
* TradeOrderDetailRespVO
*/
export interface TradeOrderDetailRespVO {
/**
* 附加费信息
*/
tradeExtendCostInfo?: TradeExtendCostInfo;
/**
* 扩展服务信息
*/
tradeExtendServeInfo?: TradeExtendServeInfo;
/**
* 基本信息
*/
tradeOrderInfoBase?: TradeOrderBaseInfo;
/**
* 商品信息
*/
tradeProductInfo?: TradeProductInfo;
/**
* 服务信息
*/
tradeServeInfo?: TradeServeInfo;
[property: string]: any;
}
/**
* 附加费信息
*
* TradeExtendCostInfo
*/
export interface TradeExtendCostInfo {
/**
* 内部存储动态属性的 Map键为属性名称值为属性值
*/
properties?: MapObject;
[property: string]: any;
}
/**
* 内部存储动态属性的 Map键为属性名称值为属性值
*
* MapObject
*/
export interface MapObject {
key?: { [key: string]: any };
[property: string]: any;
}
/**
* 扩展服务信息
*
* TradeExtendServeInfo
*/
export interface TradeExtendServeInfo {
/**
* 内部存储动态属性的 Map键为属性名称值为属性值
*/
properties?: MapObject;
[property: string]: any;
}
/**
* 基本信息
*
* TradeOrderBaseInfo
*/
export interface TradeOrderBaseInfo {
/**
* 取消原因
*/
cancelReason?: string;
/**
* 取消时间
*/
cancelTime?: string;
/**
* 创建时间
*/
createTime?: string;
/**
* 优惠金额
*/
discountPrice?: string;
/**
* 完成时间
*/
finishTime?: string;
/**
* 订单id
*/
id?: string;
/**
* 实收金额
*/
livePrice?: string;
/**
* 商家备注
*/
merchantRemark?: string;
/**
* 订单类目
*/
orderCategoryId?: string;
orderCategoryName?: string;
/**
* 订单编号
*/
orderNo?: string;
/**
* 订单状态
*/
orderStatus?: string;
/**
* 订单来源
*/
orderTerminal?: string;
/**
* 订单类型
*/
orderType?: string;
/**
* 支付渠道 (线上线下)
*/
payChannelCode?: string;
/**
* 交易流水号
*/
payOrderId?: string;
/**
* 订单金额
*/
payPrice?: number;
/**
* 支付时间
*/
payTime?: string;
/**
* 支付方式(支付宝微信)
*/
payType?: string;
/**
* 订单总价
*/
price?: string;
/**
* 保障状态
*/
propertyStatus?: string;
/**
* 保障时间
*/
propertyTime?: string;
/**
* 退款金额
*/
refundPrice?: string;
/**
* 退款时间
*/
refundTime?: string;
/**
* 用户信息
*/
userInfo?: string;
userAvatar?: string;
}
/**
* 商品信息
*
* TradeProductInfo
*/
export interface TradeProductInfo {
/**
* 购买数量
*/
count?: number;
/**
* 优惠金额
*/
discountPrice?: number;
/**
* 成本价
*/
expensePrice?: number;
/**
* 到手价(单价 - 优惠)
*/
handedPrice?: number;
/**
* 实付金额
*/
payPrice?: number;
/**
* 商品单价
*/
price?: number;
/**
* 保障
*/
properties?: string[];
/**
* 累计退款数量
*/
refundCount?: number;
/**
* 累计退款金额
*/
refundPrice?: number;
/**
* 服务内容
*/
serveContent?: string;
/**
* 店铺log
*/
shopLogoUrl?: string;
/**
* 店铺名称
*/
shopName?: string;
/**
* skuid
*/
skuId?: number;
/**
* 商品规格
*/
skuName?: string;
/**
* 商品规格图
*/
skuPicUrl?: string;
/**
* 商品id
*/
spuId?: number;
/**
* 商品名称
*/
spuName?: string;
/**
* 商品总价
*/
totalPrice?: number;
/**
* 商品单位
*/
unit?: string;
[property: string]: any;
}
/**
* 服务信息
*
* TradeServeInfo
*/
export interface TradeServeInfo {
/**
* 内部存储动态属性的 Map键为属性名称值为属性值
*/
properties?: MapObject;
[property: string]: any;
}
import { TradeOrderPageRespVO, TradeReq } from "./list";
import { TradeOrderDetailRespVO } from "./detail";
export const getTradeOrderPage = async (params: TradeReq) => {
return request<PageResult<TradeOrderPageRespVO[]>>("/trade/order/page", {
@@ -429,8 +10,10 @@ export const getTradeOrderPage = async (params: TradeReq) => {
};
export const getTradeOrderDetail = async (id: number) => {
return request<IResponse<TradeOrderDetailRespVO>>("/trade/order/get-detail", {
return request<TradeOrderDetailRespVO>("/trade/order/get-detail", {
method: "GET",
params: { id },
});
};
export { TradeOrderPageRespVO, TradeOrderDetailRespVO, TradeReq };

View File

@@ -0,0 +1,194 @@
/**
* CommonResultPageResultTradeOrderPageRespVO
*/
/**
* com.tashow.cloud.trade.controller.admin.order.vo.TradeOrderPageRespVO
*
* TradeOrderPageRespVO
*/
export interface TradeOrderPageRespVO {
serveAddress?: string;
subTime?: string;
payPrice?: string;
payType?: string;
financeStatus?: string;
/**
* 下单时间
*/
createTime?: string;
/**
* 订单完成时间
*/
finishTime?: string;
/**
* 订单编号
*/
id?: number;
/**
* 订单项列表
*/
items?: Item[];
/**
* 订单流水号
*/
orderNum?: string;
/**
* 订单状态
*/
orderStatus?: number;
/**
* 订单来源
*/
orderTerminal?: number;
/**
* 订单类型
* {@link TradeOrderTypeEnum#getType()}
*/
orderType?: number;
/**
* 支付剩余时间
*/
payLastTime?: string;
/**
* 店铺log
*/
shopLogo?: string;
/**
* 店铺名称
*/
shopName?: string;
/**
* 用户头像
*/
userAvatar?: string;
/**
* 用户编号
*/
userId?: number;
/**
* 用户手机号
*/
userMobile?: string;
/**
* 用户姓名
*/
userName?: string;
/**
* 用户昵称
*/
userNickName?: string;
/**
* 用户备注 - 必填,示例:你猜
*/
userRemark?: string;
}
/**
* com.tashow.cloud.trade.controller.admin.order.vo.TradeOrderPageRespVO.Item
*
* Item
*/
export interface Item {
id?: number;
/**
*
* 购买数量 - 必填示例1
*/
count?: number;
/**
* 商品优惠(总) - 必填示例100
*/
discountPrice?: number;
/**
* 到手价
*/
handedPrice?: number;
/**
* 商品实付金额(总) - 必填示例100
*/
payPrice?: number;
/**
* 商品图片 - 必填示例https://www.iocoder.cn/1.png
*/
picUrl?: string;
/**
* 商品原价(单) - 必填示例100
*/
price?: number;
/**
* 服务地址
*/
serveAddress?: string;
/**
* 商品规格
*/
skuName?: string;
/**
* 商品 SPU 名称 - 必填,示例:芋道源码
*/
spuName?: string;
/**
* 预约时间
*/
subTime?: string;
/**
* 单位
*/
unit?: string;
}
export interface TradeReq {
/**
* 售后状态示例1
*/
afterSaleStatus?: number;
/**
* 创建时间
*/
createTime?: string[];
/**
* 财务状态示例1
*/
financeStatus?: number;
/**
* 卖家名称
*/
merchantName?: string;
/**
* 订单类目id示例1
*/
orderCategoryId?: number;
/**
* 订单状态示例1
*/
orderStatus?: number;
/**
* 订单来源示例1
*/
orderTerminal?: number;
/**
* 页码,从 1 开始", example = "1
*/
pageNo: number;
/**
* 每页条数,最大值为 100"
*/
pageSize: number;
/**
* 聚合检索字段商品名称商品id订单号
*/
prodSearch?: string;
/**
* 创建时间
*/
subTime?: string[];
/**
* 预约类型示例1
*/
subType?: number;
/**
* 聚合检索字段 买家昵称/手机号
*/
userSearch?: string;
}

1
src/typings.d.ts vendored
View File

@@ -14,3 +14,4 @@ declare module 'omit.js';
declare module 'numeral';
declare module 'mockjs';
declare module 'react-fittext';
declare module 'react-video-thumbnail';

View File

@@ -1,12 +1,11 @@
import { Navigate } from '@umijs/max';
import { Spin } from 'antd';
import React from 'react';
import type { MenuVO } from '@/services/system/menu';
export const loopMenuItem = (menus: any[], pId: number | string): any[] => {
// console.log(menus, "menus");
return menus.flatMap((item) => {
export const loopMenuItem = (menus: MenuVO[], pId: number | string): any[] => {
return menus.map((item) => {
let Component: React.ComponentType<any> | null = null;
if (item.component && item.component.length > 0) {
if (item.component && item.component.trim().length > 0) {
// 防止配置了路由,但本地暂未添加对应的页面,产生的错误
Component = React.lazy(() => {
const importComponent = () => import(`@/pages/${item.component}`);
@@ -14,56 +13,106 @@ export const loopMenuItem = (menus: any[], pId: number | string): any[] => {
return importComponent().catch(import404);
});
}
if (item.children && item.children.length > 0) {
return [
{
const routeItem: any = {
path: item.path,
name: item.name,
// icon: item.icon,
icon: '',
id: item.id,
parentId: pId,
children: [
{
path: item.path,
element: (
<Navigate
to={getFirstLeafPath(item.children, item.path)}
replace
/>
),
},
...loopMenuItem(item.children, item.menuID),
],
},
];
} else {
return [
{
path: item.path,
name: item.name,
// icon: item.icon,
id: item.menuID,
parentId: pId,
element: (
hideInMenu: !item.visible,
children: [],
};
// 只有当 Component 存在时才添加 element 属性
if (Component) {
routeItem.element = (
<React.Suspense
fallback={<Spin style={{ width: '100%', height: '100%' }} />}
>
{Component && <Component />}
<Component />
</React.Suspense>
),
children: [], // 添加缺失的 children 属性
},
];
);
} 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) {
routeItem.children = loopMenuItem(item.children, item.id);
}
return routeItem;
});
};
function getFirstLeafPath(menus: any[], parentPath: string): string {
const firstMenu = menus[0];
const currentPath = `${parentPath}/${firstMenu.path}`;
if (firstMenu.children && firstMenu.children.length > 0) {
return getFirstLeafPath(firstMenu.children, currentPath);
} else {
return currentPath;
}
}
// 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;
// }
// }