提交 3018428e 作者: 龚洪江

Merge branch 'develop'

......@@ -14,4 +14,4 @@ patches:
images:
- name: REGISTRY/NAMESPACE/IMAGE:TAG
newName: mmc-registry.cn-shenzhen.cr.aliyuncs.com/sharefly-dev/admin
newTag: 076c71a473e24a5f127df294af6ea3eb20b5858c
newTag: 063cc3527bbd3e1199f9aec0d902b1c76c712a0a
......@@ -72,3 +72,62 @@ export type categoryDetailsType = InterFunction<
>;
//分类-关联列表
export type categoryRelevantType = InterFunction<{ id: number; type: number }, any>;
//分类管理-分类列表(新)
type categoryItemType = {
id: number;
name: string;
createTime: string;
icon: string;
description: string;
subDTOList: (Omit<categoryItemType, 'createTime' | 'icon' | 'subDTOList' | 'sort'> & {
categoryPrimaryId: number;
})[];
sort: number;
};
export type categoryListRespType = InterItemFunction<{ id?: number }, categoryItemType[]>;
//分类管理-1级分类新增(新)
export type addPrimaryCategoryType = InterFunction<
{
description: string;
icon: string;
name: string;
},
any
>;
//分类管理-1级分类编辑(新)
export type updatePrimaryCategoryType = InterFunction<
{
description: string;
icon: string;
name: string;
id?: number;
},
any
>;
//分类管理-1级分类删除(新)
export type deletePrimaryCategoryType = InterFunction<{ id: number }, any>;
//分类管理-1级分类交换顺序(新)
export type exchangeType = InterFunction<{ id: number; sort: number }[], any>;
//分类管理-2级分类新增(新)
export type addSubCategoryType = InterFunction<
{
categoryPrimaryId: number;
description: string;
name: string;
},
any
>;
//分类管理-2级分类编辑(新)
export type updateSubCategoryType = InterFunction<
{
categoryPrimaryId: number;
description: string;
name: string;
id?: number;
},
any
>;
//分类管理-2级分类删除(新)
export type deleteSubCategoryType = InterFunction<{ id: number }, any>;
......@@ -11,6 +11,7 @@ export type BackEndLoginType = InterFunction<
phoneNum: string;
userName: string;
nickName: string;
appUserAccountId?: number;
companyInfoVO: {
id: number;
companyType: number;
......@@ -23,6 +24,7 @@ export type BackEndLoginType = InterFunction<
companyUserName: string;
phoneNum: string;
remark: string;
leader: number;
};
roleInfo: {
id: number;
......@@ -157,3 +159,13 @@ export type batchRemoveBannerInfo = InterFunction<any, NonNullable<unknown>>;
export type removeBannerInfo = InterFunction<{ id: number }, NonNullable<unknown>>;
// V1.0.1-banner-排序交换
export type exchangeBannerInfo = InterFunction<Array<any>, NonNullable<unknown>>;
//手机号筛选小程序用户
export type getUserAccountByPhoneNumType = InterFunction<
{ phoneNum: string },
{ id: number; nickName: string; userName: string; phoneNum: string; uid: string }[]
>;
//获取小程序二维码
export type appletQRCodeType = InterFunction<{ page: string; scene: string }, string>;
//登录信息-获取
export type getLoginInfoType = InterFunction<{ randomLoginCode: string }, any>;
......@@ -99,7 +99,171 @@ export type otherServiceType = InterFunction<any, { id: number; saleServiceName:
export type skuUnitType = InterFunction<any, { id: number; unitName: string }[]>;
//商品-批量上下架
export type batchOnShelfOrTakeDownType = InterFunction<{ goodsIds: number[]; status: number }, any>;
//商品-单个上下架(新)
export type upOrDownShelfType = InterFunction<{ id: number; status: number }, any>;
//商品批量删除
export type batchRemoveWareInfoType = InterFunction<number[], any>;
//商品-单个删除
export type removeMallGoodsType = InterFunction<{ id: number }, any>;
//商品-上下移动
export type exchangeGoodsInfoType = InterFunction<{ firstId: number; secondId: number }, any>;
//商品-新增(新)
export type addMallGoodsType = InterFunction<
{
categoryPrimaryId: number;
categorySubId: number;
description: string;
goodsDetails: string;
goodsLabel: string;
goodsSpecList: {
chooseType: number;
goodsSpecValuesList: {
channelPrice?: number;
id: any;
partNo: string;
salePrice: number;
showPrice: number;
specValueImage: string;
specValueName: string;
stock?: number;
goodsSpecId?: number;
}[];
id: any;
mallGoodsId: number;
must: number;
skuUnitId: number;
specName: string;
}[];
labelShow: number;
resourcesList: {
id: number;
type: number;
url: string;
}[];
shelfStatus: number;
tradeName: string;
},
any
>;
//商品-编辑(新)
export type editMallGoodsType = InterFunction<
{
categoryPrimaryId: number;
categorySubId: number;
description: string;
goodsDetails: string;
goodsLabel: string;
goodsSpecList: {
chooseType: number;
goodsSpecValuesList: {
channelPrice?: number;
id: any;
partNo: string;
salePrice: number;
showPrice: number;
specValueImage: string;
specValueName: string;
stock?: number;
goodsSpecId: number;
}[];
id: any;
mallGoodsId: number;
must: number;
skuUnitId: number;
specName: string;
}[];
id: number;
labelShow: number;
resourcesList: {
id: number;
type: number;
url: string;
}[];
shelfStatus: number;
tradeName: string;
},
any
>;
//商品-列表(新)
export type listPageGoodsInfoType = InterItemFunction<
{ categoryPrimaryId?: number; tradeName?: string; shelfStatus?: number },
{
categoryPrimaryId: number;
categorySubId: number;
createTime: string;
description: string;
goodsDetails: string;
goodsLabel: string;
goodsSpecList: {
chooseType: number;
goodsSpecValuesList: {
channelPrice: number;
goodsSpecId: number;
id: number;
partNo: string;
salePrice: number;
showPrice: number;
specValueImage: string;
specValueName: string;
stock: number;
}[];
id: number;
mallGoodsId: number;
must: number;
skuUnitId: number;
specName: string;
}[];
id: number;
labelShow: number;
resourcesList: {
id: number;
type: number;
url: string;
}[];
shelfStatus: number;
tradeName: string;
userAccountId: number;
}[]
>;
//商城-详情(新)
export type mallGoodsDetailsType = InterFunction<
{ id: number },
{
categoryPrimaryId: number;
categorySubId: number;
createTime: string;
description: string;
goodsDetails: string;
goodsLabel: string;
goodsSpecList: {
chooseType: number;
goodsSpecValuesList: {
channelPrice: number;
goodsSpecId: number;
id: number;
partNo: string;
salePrice: number;
showPrice: number;
specValueImage: string;
specValueName: string;
stock: number;
}[];
id: number;
mallGoodsId: number;
must: number;
skuUnitId: number;
specName: string;
}[];
id: number;
labelShow: number;
resourcesList: {
id: number;
type: number;
url: string;
}[];
shelfStatus: number;
tradeName: string;
userAccountId: number;
}
>;
......@@ -130,6 +130,8 @@ export type listCompanyPage = InterListFunction<
phoneNum: string;
province: string;
remark: string;
lat: number;
lon: number;
}
>;
// 单位-新增
......@@ -147,6 +149,8 @@ export type listCompanyAdd = InterFunction<
province?: string;
remark?: string;
area?: string[];
lat: number;
lon: number;
},
NonNullable<unknown>
>;
......@@ -165,6 +169,8 @@ export type listCompanyUpdate = InterFunction<
province?: string;
remark?: string;
area?: string[];
lat: number;
lon: number;
},
NonNullable<unknown>
>;
......@@ -184,6 +190,82 @@ export type listCompanyRemove = InterFunction<
},
NonNullable<unknown>
>;
//单位-详情
export type getCompanyInfoByIdType = InterFunction<
{ id: number },
{
address: string;
city: string;
companyName: string;
companyType: number;
companyUserName: string;
district: string;
fullName: string;
id: number;
phoneNum: string;
province: string;
remark: string;
}
>;
//单位-成员列表
export type listCompanyMembersType = InterItemFunction<
{ companyInfoId: number },
{
accountStatus: number;
accountType: number;
companyAuthStatus: number;
companyName: string;
cooperationTagVOS: {
createTime: string;
id: number;
tagDescription: string;
tagImg: string;
tagName: string;
tagRequire: string;
}[];
createTime: string;
deleted: number;
email: string;
id: number;
inviteCount: number;
leader: number;
nickName: string;
phoneNum: string;
portType: number;
realNameAuthStatus: number;
remark: string;
source: number;
uid: string;
userImg: string;
userName: string;
userRcdVO: {
createTime: string;
id: number;
rcdNickname: string;
rcdUserId: number;
rcdUserName: string;
remark: string;
userAccountId: number;
};
userSex: number;
}[]
>;
//单位-成员绑定
export type bindingCompanyMemberType = InterFunction<
{ companyInfoId: number; userAccountId: number },
any
>;
//单位-成员解绑
export type unbindCompanyMemberType = InterFunction<
{ companyInfoId: number; userAccountId: number },
any
>;
//单位-管理员转让
export type transferLeaderType = InterFunction<
{ companyInfoId: number; fromUserAccountId: number; toUserAccountId: number },
any
>;
//账号权限-列表
export type listRoleInfoPageType = InterItemFunction<
{ numberOrName?: string },
......
import axios from '../request';
import {
addPrimaryCategoryType,
addSubCategoryType,
categoryDetailsType,
categoryListRespType,
categoryListType,
categoryRelevantType,
deletePrimaryCategoryType,
deleteSubCategoryType,
directoryListType,
directoryPageListType,
exchangeType,
updatePrimaryCategoryType,
updateSubCategoryType,
} from '~/api/interface/categoryManage';
export class CategoryManageAPI {
......@@ -97,4 +105,28 @@ export class CategoryManageAPI {
static getRelevantBusiness: categoryRelevantType = (params) => {
return axios.get('/pms/classify/queryRelevantBusiness', { params });
};
// 分类管理-分类列表(新)
static getCategoryRespList: categoryListRespType = (data) =>
axios.post('/pms/category/categoryList', data);
// 分类管理-1级分类新增(新)
static addPrimaryCategory: addPrimaryCategoryType = (data) =>
axios.post('/pms/category/addPrimaryCategory', data);
// 分类管理-1级分类编辑(新)
static updatePrimaryCategory: updatePrimaryCategoryType = (data) =>
axios.post('/pms/category/updatePrimaryCategory', data);
// 分类管理-1级分类删除(新)
static deletePrimaryCategory: deletePrimaryCategoryType = (params) =>
axios.get('/pms/category/deletePrimaryCategory', { params });
//分类管理-1级分类交换顺序
static exchangeCategory: exchangeType = (data) => axios.post('/pms/category/exchange', data);
// 分类管理-2级分类新增(新)
static addSubCategory: addSubCategoryType = (data) =>
axios.post('/pms/category/addSubCategory', data);
// 分类管理-2级分类编辑(新)
static updateSubCategory: updateSubCategoryType = (data) =>
axios.post('/pms/category/updateSubCategory', data);
// 分类管理-2级分类删除(新)
static deleteSubCategory: deleteSubCategoryType = (params) =>
axios.get('/pms/category/deleteSubCategory', { params });
}
import axios from '../request';
import {
appletQRCodeType,
BackEndLoginType,
batchRemoveBannerInfo,
exchangeBannerInfo,
getLoginInfoType,
getUserAccountByPhoneNumType,
insertBannerInfo,
insertModuleInfo,
listBannerInfoPage,
......@@ -70,4 +73,15 @@ export class CommonAPI {
// V1.0.1-banner-排序交换
static exchangeBannerInfo: exchangeBannerInfo = (params) =>
axios.post('/release/module/exchangeBannerInfo', params);
//手机号筛选小程序用户
static getUserAccountByPhoneNum: getUserAccountByPhoneNumType = (params) =>
axios.get('/userapp/user-account/getUserAccountByPhoneNum', { params });
//获取小程序二维码
static getAppletQRCode: appletQRCodeType = (params) => {
return axios.get('/userapp/wx/getAppletQRCode', { params });
};
//获取登录信息
static getLoginInfo: getLoginInfoType = (params) =>
axios.get('/userapp/temp-auth/getLoginInfo', { params });
}
import {
addGoodsType,
addMallGoodsType,
batchOnShelfOrTakeDownType,
batchRemoveWareInfoType,
detailGoodsType,
editGoodsType,
editMallGoodsType,
exchangeGoodsInfoType,
listGoodsType,
listPageGoodsInfoType,
mallGoodsDetailsType,
otherServiceType,
removeMallGoodsType,
skuUnitType,
upOrDownShelfType,
} from '~/api/interface/goodsType';
import axios from '../request';
......@@ -30,7 +36,7 @@ class GoodsAPI {
};
// 商品-单位
static getSkuUnit: skuUnitType = () => {
return axios.get('/pms/goods/getSkuUnit');
return axios.get('/pms/mall/goods/getSkuUnit');
};
// 商品-其它服务列表
static getOtherServiceList: otherServiceType = () => {
......@@ -40,13 +46,35 @@ class GoodsAPI {
static batchOnShelfOrTakeDown: batchOnShelfOrTakeDownType = (data) => {
return axios.post('/pms/goods/batchOnShelfOrTakeDown', data);
};
//商品-单个上下架(新)
static upOrDownShelf: upOrDownShelfType = (params) =>
axios.get('/pms/mall/goods/upOrDownShelf', { params });
// 商品-批量删除
static batchRemoveWareInfo: batchRemoveWareInfoType = (data) => {
return axios.post('/pms/goods/batchRemoveWareInfo', data);
};
//商品-单个删除
static removeMallGoods: removeMallGoodsType = (params) =>
axios.get('/pms/mall/goods/removeMallGoods', { params });
// 商品-上下移动
static exchangeGoodsInfo: exchangeGoodsInfoType = (params) => {
return axios.get('/pms/goods/exchangeGoodsInfo', { params });
};
//商品-上下移动
static exchange: exchangeGoodsInfoType = (params) =>
axios.get('/pms/mall/goods/exchange', { params });
// 商品-新增(新)
static addMallGoods: addMallGoodsType = (data) =>
axios.post('/pms/mall/goods/addMallGoods', data);
//商品-编辑(新)
static editMallGoods: editMallGoodsType = (data) =>
axios.post('/pms/mall/goods/editMallGoods', data);
// 商品-列表(新)
static getListPageGoodsInfo: listPageGoodsInfoType = (data) =>
axios.post('/pms/mall/goods/listPageGoodsInfo', data);
// 商品-详情(新)
static getMallGoodsDetails: mallGoodsDetailsType = (params) =>
axios.get('/pms/mall/goods/mallGoodsDetails', { params });
}
export default GoodsAPI;
import {
bindingCompanyMemberType,
deleteRoleInfoType,
getCompanyInfoByIdType,
getSecondDistrictInfo,
insertBAccountType,
insertRoleInfoType,
listBAccountPageType,
listCompanyAdd,
listCompanyMembersType,
listCompanyPage,
listCompanyRemove,
listCompanyUpdate,
......@@ -12,6 +15,8 @@ import {
listRoleInfoPageType,
listRoleMenuInfoType,
removeBAccountType,
transferLeaderType,
unbindCompanyMemberType,
updateBAccountType,
updatePasswordType,
updateRoleInfoType,
......@@ -58,10 +63,25 @@ export class SystemManageAPI {
// 单位-删除
static listCompanyRemove: listCompanyRemove = (params) =>
axios.get('/userapp/company/remove', { params });
//单位-详情
static getCompanyInfoById: getCompanyInfoByIdType = (params) =>
axios.get('/userapp/company/getCompanyInfoById', { params });
// 单位-区域
static getSecondDistrictInfo: getSecondDistrictInfo = (params) =>
axios.get('/pms/webDevice/getSecondDistrictInfo', { params });
//单位-成员列表
static getListCompanyMembers: listCompanyMembersType = (params) =>
axios.get('/userapp/company/listCompanyMembers', { params });
//单位-成员绑定
static bindingCompanyMember: bindingCompanyMemberType = (params) =>
axios.get('/userapp/company/bindingCompanyMember', { params });
//单位-成员解除绑定
static unbindCompanyMember: unbindCompanyMemberType = (params) =>
axios.get('/userapp/company/unbindCompanyMember', { params });
//单位-管理员转让
static transferLeader: transferLeaderType = (params) =>
axios.get('/userapp/company/transferLeader', { params });
//账号权限-列表
static getListRoleInfoPage: listRoleInfoPageType = (data) =>
......
......@@ -36,7 +36,7 @@ service.interceptors.response.use(
// 当接口返回正常时执行
if (status === 200) {
// 如果不报错就直接返回数据
if (data.code === '200') {
if (['200', '7002'].includes(data.code)) {
return Promise.resolve(data);
}
// 重新登录?
......
<svg width="50" height="50" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><filter x="-5.7%" y="-8.7%" width="112.4%" height="117.4%" filterUnits="objectBoundingBox" id="a"><feOffset dx="2" in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="7.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" in="shadowBlurOuter1"/></filter><path d="M2 0h327.586a1 1 0 01.707.293l49.414 49.414a1 1 0 01.293.707V268a2 2 0 01-2 2H2a2 2 0 01-2-2V2a2 2 0 012-2z" id="b"/></defs><g transform="translate(-330)" fill="none" fill-rule="evenodd"><rect fill="#FFF" width="380" height="270" rx="2"/><path d="M366.833 10.833v2.334h2.334v-2.334h-2.334zm-7 16.334h2.334V29.5h-2.334v-2.333zm9.334-9.334h2.333v2.334h-2.333v-2.334zm-9.334 4.667h2.334v2.333h-2.334V22.5zm4.667-4.667h2.333v2.334H364.5v-2.334zM351.667 8.5H361c.644 0 1.167.522 1.167 1.167V19c0 .644-.523 1.167-1.167 1.167h-9.333A1.167 1.167 0 01350.5 19V9.667c0-.645.522-1.167 1.167-1.167zm1.166 2.333v7h7v-7h-7zm2.334 2.334h2.333V15.5h-2.333v-2.333zm10.5-4.667h4.666c.645 0 1.167.522 1.167 1.167v4.666c0 .645-.522 1.167-1.167 1.167h-4.666a1.167 1.167 0 01-1.167-1.167V9.667c0-.645.522-1.167 1.167-1.167zm0 14h4.666c.645 0 1.167.522 1.167 1.167v4.666c0 .645-.522 1.167-1.167 1.167h-4.666a1.167 1.167 0 01-1.167-1.167v-4.666c0-.645.522-1.167 1.167-1.167zm1.166 2.333v2.334h2.334v-2.334h-2.334zM351.667 22.5h4.666c.645 0 1.167.522 1.167 1.167v4.666c0 .645-.522 1.167-1.167 1.167h-4.666a1.167 1.167 0 01-1.167-1.167v-4.666c0-.645.522-1.167 1.167-1.167zm1.166 2.333v2.334h2.334v-2.334h-2.334z" opacity=".5" fill="#000" fill-opacity=".9"/><use fill="#000" filter="url(#a)" xlink:href="#b"/><use fill="#FFF" xlink:href="#b"/></g></svg>
\ No newline at end of file
import { Form, InputNumber, Input, Select } from 'antd';
import { Form, InputNumber, Input, Select, Radio } from 'antd';
import React from 'react';
import { Uploader } from '~/components/uploader';
import { UploadOutlined } from '@ant-design/icons';
......@@ -8,7 +8,7 @@ export interface EditableCellProps extends React.HTMLAttributes<HTMLElement> {
editing: boolean;
dataIndex: string;
title: any;
inputType: 'number' | 'text' | 'select' | 'uploader';
inputType: 'number' | 'text' | 'select' | 'uploader' | 'radio' | 'textArea';
record: any;
index: number;
children: React.ReactNode;
......@@ -16,8 +16,10 @@ export interface EditableCellProps extends React.HTMLAttributes<HTMLElement> {
const EditableCell: React.FC<
EditableCellProps & {
selectOption?: { name: string; id: number }[];
radioOption?: { name: string; id: number }[];
uploadSuccess?: (record: any, result: any) => void;
rules?: any;
maxLength?: number;
}
> = ({
editing,
......@@ -27,15 +29,17 @@ const EditableCell: React.FC<
record,
index,
selectOption,
radioOption,
uploadSuccess,
children,
rules,
maxLength,
...restProps
}) => {
const inputNode = () => {
switch (inputType) {
case 'number':
return <InputNumber />;
return <InputNumber placeholder={`请输入${title}`} maxLength={maxLength} />;
case 'select':
return (
<Select placeholder={`请选择${title}`} style={{ textAlign: 'start' }}>
......@@ -58,8 +62,20 @@ const EditableCell: React.FC<
<UploadOutlined />
</Uploader>
);
case 'radio':
return (
<Radio.Group>
{radioOption?.map((v) => (
<Radio value={v.id} key={v.id}>
{v.name}
</Radio>
))}
</Radio.Group>
);
case 'textArea':
return <Input.TextArea placeholder={`请输入${title}`} maxLength={maxLength} showCount />;
default:
return <Input placeholder={`请输入${title}`} />;
return <Input placeholder={`请输入${title}`} maxLength={maxLength} />;
}
};
return (
......
......@@ -113,6 +113,7 @@ $page-background: #f3f6ff;
.logo-img {
width: 32px;
height: 32px;
border-radius: 4px;
}
.logo-text {
......
......@@ -5,7 +5,6 @@ import { Button, Dropdown, MenuProps, Modal } from 'antd';
import Logo from '../../../assets/icon/logo_big.png';
import './index.scss';
import { REMOVE_MENU, REMOVE_MENU_ID, SET_COLLAPSE } from '~/store/module/menu';
import { REMOVE_ROLE_ID } from '~/store/module/userInfo';
import { useDispatch, useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import Cookies from 'js-cookie';
......@@ -42,7 +41,6 @@ export function TitleView() {
Cookies.remove('SHAREFLY-TOKEN');
dispatch(REMOVE_MENU());
dispatch(REMOVE_MENU_ID());
dispatch(REMOVE_ROLE_ID());
},
});
}}
......@@ -62,8 +60,8 @@ export function TitleView() {
}}
>
<div className='layout-logo'>
<img className='logo-img' src={Logo} alt='云享飞' />
<div className='logo-text'>云享飞后台管理</div>
<img src={userInfo.companyInfoVO.brandLogo || Logo} className='logo-img' alt='云享飞' />
<div className='logo-text'>{userInfo.companyInfoVO.brandName || '云享飞管理后台'}</div>
<div onClick={setMenuCollapsed}>
<svg
className={`hamburger ${isActive ? 'is-active' : ''}`}
......
import { Form, Input, message, Modal, ModalProps } from 'antd';
import { FC, useEffect, useState } from 'react';
import { Uploader } from '~/components/uploader';
import { UploadOutlined } from '@ant-design/icons';
import { InterDataType, InterReqType } from '~/api/interface';
import { addPrimaryCategoryType, categoryListRespType } from '~/api/interface/categoryManage';
import { CategoryManageAPI } from '~/api';
//分类列表返回类型
type categoryType = InterDataType<categoryListRespType>['list'];
//新增分类请求参数类型
type addPrimaryCategoryParameter = InterReqType<addPrimaryCategoryType>;
interface selfProps {
onCancel: () => void;
onOK: () => void;
currentCategory: categoryType[0] | undefined;
}
const AddOrEditCategoryModal: FC<ModalProps & selfProps> = ({
open,
onCancel,
onOK,
currentCategory,
}) => {
const [form] = Form.useForm<addPrimaryCategoryParameter>();
const [fileList, setFileList] = useState<
{
id: number;
name: string;
uid: number;
url: string;
}[]
>([]);
//上传成功
const uploadSuccess = (
value: {
id: number;
name: string;
uid: number;
url: string;
}[],
) => {
form.setFieldValue('icon', value[0].url);
setFileList([...value]);
};
//提交
const handleOk = () => {
form.validateFields().then((values: any) => {
CategoryManageAPI[currentCategory ? 'updatePrimaryCategory' : 'addPrimaryCategory']({
...values,
id: currentCategory ? currentCategory.id : undefined,
}).then(({ code }) => {
if (code === '200') {
message.success('新增成功');
form.resetFields();
setFileList([]);
onOK();
}
});
});
};
const handleCancel = () => {
form.resetFields();
setFileList([]);
onCancel();
};
useEffect(() => {
if (currentCategory) {
form.setFieldsValue({
name: currentCategory.name,
icon: currentCategory.icon,
description: currentCategory.description || undefined,
});
setFileList([
{
name: 'categoryImg',
id: Math.random(),
uid: Math.random(),
url: currentCategory.icon,
},
]);
}
}, [currentCategory]);
return (
<Modal
open={open}
title={currentCategory ? '编辑分类' : '新增分类'}
onCancel={handleCancel}
onOk={handleOk}
>
<Form wrapperCol={{ span: 16 }} labelCol={{ span: 4 }} form={form}>
<Form.Item
label='分类名称'
name='name'
rules={[{ required: true, message: '请输入分类名称' }]}
>
<Input placeholder='请输入分类名称' maxLength={15} />
</Form.Item>
<Form.Item
label='分类图标'
name='icon'
rules={[{ required: true, message: '请上传分类图标' }]}
>
<Uploader
fileUpload
listType='picture-card'
onChange={uploadSuccess}
fileLength={1}
fileSize={5}
defaultFileList={fileList}
>
<UploadOutlined />
</Uploader>
</Form.Item>
<Form.Item label='分类描述' name='description'>
<Input.TextArea placeholder='请输入分类描述' rows={4} maxLength={70} showCount />
</Form.Item>
</Form>
</Modal>
);
};
export default AddOrEditCategoryModal;
.category-list{
&-row-bg{
td{
background-color: rgba($color: #000000, $alpha: 0.1) !important;
}
}
}
import SearchBox from '~/components/search-box';
import { Button, Form, Image, message, Modal, Radio, Table, Tooltip } from 'antd';
import {
ArrowDownOutlined,
ArrowUpOutlined,
DownOutlined,
PlusOutlined,
RightOutlined,
} from '@ant-design/icons';
import { categoryListRespType } from '~/api/interface/categoryManage';
import { useEffect, useState } from 'react';
import { InterDataType, PaginationProps } from '~/api/interface';
import { CategoryManageAPI } from '~/api';
import EditableCell from '~/components/EditableCell';
import AddOrEditCategoryModal from './components/addOrEditCategoryModal';
import './index.scss';
import _ from 'lodash';
type EditableTableProps = Parameters<typeof Table>[0];
type ColumnTypes = Exclude<EditableTableProps['columns'], undefined>;
//分类列表返回类型
type categoryType = InterDataType<categoryListRespType>['list'];
const CategoryList = () => {
const [categoryTableForm] = Form.useForm<any>();
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
//新增,编辑分类弹窗
const [addOrEditCategoryModalShow, setAddOrEditCategoryModalShow] = useState<boolean>(false);
const [currentCategory, setCurrentCategory] = useState<categoryType[0]>();
const [loading, setLoading] = useState<boolean>(false);
// 编辑的行
const [editingKey, setEditingKey] = useState<number[]>([]);
//展开的行
const [expandedRowKeys, setExpandedRowKeys] = useState<number[]>([]);
const tableColumns: (ColumnTypes[number] & {
editable?: boolean;
dataIndex?: string;
rules?: any;
maxLength?: number;
inputType?: string;
})[] = [
{
title: '分类名称',
dataIndex: 'name',
editable: true,
width: '20%',
ellipsis: true,
onHeaderCell: () => ({
style: {
textAlign: 'center',
},
}),
rules: [{ required: true, message: '请输入分类名称' }],
maxLength: 15,
render: (text: string, record: any) => (
<span
style={{
marginLeft: record.subDTOList && record.subDTOList.length != 0 ? '10px' : '24px',
}}
>
{text}
</span>
),
},
{
title: '图片',
align: 'center',
dataIndex: 'icon',
render: (url: string) => (url ? <Image src={url} width={40} height={40} /> : ''),
},
{
title: '描述',
align: 'center',
dataIndex: 'description',
editable: true,
width: '20%',
ellipsis: true,
maxLength: 70,
inputType: 'textArea',
render: (text: string) => (
<Tooltip placement='topLeft' title={<span>{text}</span>}>
<span>{text}</span>
</Tooltip>
),
},
{
title: '创建时间',
align: 'center',
dataIndex: 'createTime',
},
{
title: '操作',
onHeaderCell: () => ({
style: {
textAlign: 'center',
},
}),
onCell: () => ({
style: {
textAlign: 'right',
},
}),
render: (_text: string, record: any) =>
editingKey.some((id: number) => id === record.id) ? (
<>
<Button type='link' onClick={() => addChildCategorySubmit(record)}>
确定
</Button>
<Button type='link' onClick={() => addChildCategoryCancel(record)}>
取消
</Button>
</>
) : (
<>
{record.subDTOList ? (
<Button type='link' onClick={() => addChildrenCategory(record)}>
新增子分类
</Button>
) : (
''
)}
<Button type='link' onClick={() => addOrEditCategoryModalShowClick(record)}>
编辑
</Button>
<Button type='link' danger onClick={() => deleteCategory(record)}>
删除
</Button>
</>
),
},
];
const mergedColumns = tableColumns.map((col: any) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record: categoryType[0], index: number) => ({
record,
index,
dataIndex: col.dataIndex,
title: col.title,
editing: editingKey.some((id: number) => id === record.id),
rules: col.rules,
maxLength: col.maxLength,
inputType: col.inputType,
}),
};
});
const [tableData, setTableData] = useState<categoryType>([]);
const [allTableData, setAllTableData] = useState<categoryType>([]);
//分页
const [pagination, setPagination] = useState<PaginationProps & { totalCount: number }>({
pageNo: 1,
pageSize: 10,
totalCount: 0,
});
//获取分类列表
const getCategoryList = (query?: PaginationProps, isAll?: boolean) => {
setLoading(true);
CategoryManageAPI.getCategoryRespList({
pageNo: pagination.pageNo,
pageSize: pagination.pageSize,
...query,
}).then(({ result }) => {
setLoading(false);
if (isAll) {
setAllTableData(result.list ? [...result.list] : []);
} else {
setTableData(result.list ? [...result.list] : []);
pagination.totalCount = result.totalCount;
setPagination(pagination);
}
});
};
const paginationChange = (pageNo: number, pageSize: number) => {
pagination.pageNo = pageNo;
pagination.pageSize = pageSize;
getCategoryList();
};
// 排序选择
const onSelectChange = (record: categoryType[0]) => {
setSelectedRowKeys([record.id]);
};
//新增、编辑分类弹窗
const addOrEditCategoryModalShowClick = (record?: categoryType[0] | any) => {
if (record && !record.subDTOList) {
setEditingKey([...editingKey, record.id]);
const defaultFormVal = Object.getOwnPropertyNames(record).reduce((pre: any, cur: string) => {
if (['name', 'description'].includes(cur)) {
pre[cur + record.id] = record[cur];
}
return pre;
}, {});
categoryTableForm.setFieldsValue(defaultFormVal);
} else {
setAddOrEditCategoryModalShow(true);
setCurrentCategory(record ? { ...record } : undefined);
}
};
const addOrEditCategoryModalCancel = () => {
setAddOrEditCategoryModalShow(false);
};
const addOrEditCategoryModalOk = () => {
setAddOrEditCategoryModalShow(false);
getCategoryList();
};
//删除分类
const deleteCategory = (record: categoryType[0]) => {
Modal.confirm({
title: '分类删除',
content: '确认删除这条分类?',
onOk: () => {
console.log('当前数据-->', record);
CategoryManageAPI[record.subDTOList ? 'deletePrimaryCategory' : 'deleteSubCategory']({
id: record.id,
}).then(({ code }) => {
if (code === '200') {
message.success('删除成功');
if (pagination.pageNo !== 1 && tableData.length === 1) {
pagination.pageNo -= 1;
}
getCategoryList();
getCategoryList({ pageNo: 1, pageSize: 99999 }, true);
}
});
},
});
};
//新增子分类
const addChildrenCategory = (record: categoryType[0]) => {
const categoryItemIndex = tableData.findIndex((v) => v.id === record.id);
if (categoryItemIndex !== -1) {
const selfChildId: number = Math.random();
tableData[categoryItemIndex].subDTOList = [
...tableData[categoryItemIndex].subDTOList,
{ id: selfChildId, name: '', description: '', categoryPrimaryId: record.id },
];
tableData.splice(categoryItemIndex, 1, tableData[categoryItemIndex]);
setTableData([...tableData]);
setEditingKey([...editingKey, selfChildId]);
}
const isExpend: boolean = expandedRowKeys.some((key: number) => key === record.id);
if (!isExpend) {
setExpandedRowKeys([...expandedRowKeys, record.id]);
}
};
// 展开监听
const onExpand = (_expanded: boolean, record: categoryType[0]) => {
const index = expandedRowKeys.findIndex((item: any) => item === record.id);
if (index !== -1) {
expandedRowKeys.splice(index, 1);
} else {
expandedRowKeys.push(record.id);
}
setExpandedRowKeys([...expandedRowKeys]);
};
//提交新增子分类
const addChildCategorySubmit = (record: categoryType[0]['subDTOList'][0]) => {
categoryTableForm
.validateFields(['description' + record.id, 'name' + record.id])
.then((values) => {
CategoryManageAPI[record.name ? 'updateSubCategory' : 'addSubCategory']({
categoryPrimaryId: record.categoryPrimaryId,
name: values['name' + record.id],
description: values['description' + record.id],
id: record.name ? record.id : undefined,
}).then(({ code }) => {
if (code === '200') {
message.success(record.name ? '编辑子分类成功' : '新增子分类成功');
const editingKeyIndex = editingKey.findIndex((id) => id === record.id);
if (editingKeyIndex !== -1) {
editingKey.splice(editingKeyIndex, 1);
setEditingKey([...editingKey]);
getCategoryList();
}
}
});
});
};
//取消新增子分类
const addChildCategoryCancel = (record: categoryType[0]['subDTOList'][0]) => {
const tableIndex: number = tableData.findIndex((v) => v.id === record.categoryPrimaryId);
if (tableIndex !== -1) {
const subDTOIndex: number = tableData[tableIndex].subDTOList.findIndex(
(v) => v.id === record.id,
);
if (subDTOIndex !== -1) {
//新增下的取消
if (!record.name) {
tableData[tableIndex].subDTOList.splice(subDTOIndex, 1);
tableData.splice(tableIndex, 1, tableData[tableIndex]);
setTableData([...tableData]);
}
//新增、编辑下的取消
const editKeyIndex = editingKey.findIndex((id) => id === record.id);
editingKey.splice(editKeyIndex, 1);
setEditingKey([...editingKey]);
//新增下的取消
if (tableData[tableIndex].subDTOList.length === 0 && !record.name) {
const expandedRowKeysIndex = expandedRowKeys.findIndex(
(id) => id === record.categoryPrimaryId,
);
expandedRowKeys.splice(expandedRowKeysIndex, 1);
setExpandedRowKeys([...expandedRowKeys]);
}
}
}
};
// 切换排序
const handleSort = _.debounce(async (from: 'up' | 'down') => {
if (selectedRowKeys.length === 0) {
message.warning('请选择需要排序的数据').then();
return;
}
// 当前选项
const id = selectedRowKeys.at(-1);
// 当前索引
const index = allTableData.findIndex((i) => i.id === id);
// 当前表格中的索引
const tableIndex = tableData.findIndex((i) => i.id === id);
// 第一条数据不能上移
if (index === 0 && from === 'up') {
message.warning('已是第一条数据').then();
return;
}
// 最后一条数据不能下移
if (index === allTableData.length - 1 && from === 'down') {
message.warning('已是最后一条数据').then();
return;
}
// 转换位置
const res = await CategoryManageAPI.exchangeCategory(
from === 'up'
? [
{ id: allTableData[index - 1].id, sort: allTableData[index - 1].sort },
{ id: allTableData[index].id, sort: allTableData[index].sort },
]
: [
{ id: allTableData[index + 1].id, sort: allTableData[index + 1].sort },
{ id: allTableData[index].id, sort: allTableData[index].sort },
],
);
if (res && res.code === '200') {
message.success('操作成功').then();
// 如果是当前页的第一条数据
if (tableIndex === 0 && from === 'up') {
paginationChange(pagination.pageNo - 1, pagination.pageSize);
getCategoryList({ pageNo: 1, pageSize: 9999 }, true);
return;
}
// 如果是当前页的最后一条数据
if (tableIndex === tableData.length - 1 && from === 'down') {
paginationChange(pagination.pageNo + 1, pagination.pageSize);
getCategoryList({ pageNo: 1, pageSize: 9999 }, true);
return;
}
paginationChange(pagination.pageNo, pagination.pageSize);
getCategoryList({ pageNo: 1, pageSize: 9999 }, true);
}
}, 500);
//防抖
// const debounce:<T extends ()=>T>= (func: T, delay: number)=> {
// let timeout: NodeJS.Timeout;
//
// return function (this: ThisParameterType<T>, ...args: Parameters<T>): ReturnType<T> {
// clearTimeout(timeout);
//
// timeout = setTimeout(() => {
// func.apply(this, args);
// }, delay);
// } as T;
// }
useEffect(() => {
getCategoryList();
getCategoryList({ pageNo: 1, pageSize: 99999 }, true);
}, []);
return (
<div className='category-list'>
<SearchBox
child={
<>
<Button
icon={<PlusOutlined />}
type='primary'
onClick={() => addOrEditCategoryModalShowClick()}
>
新增分类
</Button>
<Button
icon={<ArrowUpOutlined />}
type='primary'
onClick={() => handleSort('up')}
></Button>
<Button
icon={<ArrowDownOutlined />}
type='primary'
onClick={() => handleSort('down')}
></Button>
</>
}
/>
<Form form={categoryTableForm} component={false}>
<Table
bordered
rowSelection={{
hideSelectAll: true,
selectedRowKeys,
renderCell: (checked: boolean, record: categoryType[0]) => {
console.log('选中-->', checked);
return (
<>
{record.subDTOList ? (
<Radio onChange={() => onSelectChange(record)} checked={checked} />
) : (
''
)}
</>
);
},
}}
loading={loading}
rowKey='id'
columns={mergedColumns}
dataSource={tableData}
rowClassName={(record: categoryType[0]) => {
if (record.subDTOList) {
return '';
}
return 'category-list-row-bg';
}}
pagination={{
total: pagination.totalCount,
pageSize: pagination.pageSize,
current: pagination.pageNo,
showSizeChanger: true,
showQuickJumper: true,
onChange: (page: number, pageSize: number) => paginationChange(page, pageSize),
showTotal: (total, range) => `当前 ${range[0]}-${range[1]} 条记录 / 共 ${total} 条数据`,
}}
components={{
body: {
cell: EditableCell,
},
}}
expandedRowKeys={expandedRowKeys}
onExpand={(expanded: boolean, record: categoryType[0]) => onExpand(expanded, record)}
expandable={{
rowExpandable: (record) => {
if (record.subDTOList && record.subDTOList.length != 0) {
return true;
}
return false;
},
expandIcon: ({ expanded, onExpand, record }) => {
if (expanded && record.subDTOList && record.subDTOList.length != 0) {
return <DownOutlined onClick={(e) => onExpand(record, e)} />;
}
if (record.subDTOList && record.subDTOList.length != 0) {
return <RightOutlined onClick={(e) => onExpand(record, e)} />;
}
return <></>;
},
}}
childrenColumnName='subDTOList'
/>
</Form>
{/*新增,编辑分类*/}
<AddOrEditCategoryModal
open={addOrEditCategoryModalShow}
onCancel={addOrEditCategoryModalCancel}
onOK={addOrEditCategoryModalOk}
currentCategory={currentCategory}
/>
</div>
);
};
export default CategoryList;
......@@ -17,8 +17,16 @@ body {
box-shadow: 0px 20px 30px rgba(112, 158, 254, 0.45);
display: flex;
flex-wrap: wrap;
position: relative;
.login-flex {
flex: 1;
position: relative;
.qr-code-wrap{
position: absolute;
top:50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.login-content {
text-align: center;
......@@ -37,6 +45,7 @@ body {
color: rgba(0, 0, 0, 0.5);
}
}
.login-form {
.login-image {
margin: 62px auto 46px;
......@@ -79,5 +88,16 @@ body {
box-shadow: 3px 3px 30px #dddddd;
}
}
.qrCode-icon{
position: absolute;
top: 0;
right: 0;
}
.account-login-operate{
position: absolute;
top: 10px;
right: 10px;
}
}
}
import { Button, Checkbox, Form, Input, message } from 'antd';
import { useEffect } from 'react';
import { Button, Checkbox, Form, Image, Input, message, QRCode } from 'antd';
import { useEffect, useState } from 'react';
import { LockOutlined, UserOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
import Cookies from 'js-cookie';
......@@ -10,18 +10,27 @@ import { CommonAPI } from '~/api';
import { useDispatch } from 'react-redux';
import { SET_USERINFO } from '~/store/module/userInfo';
import { authRouterList } from '~/router';
import qrCodeIcon from '~/assets/image/qr-code-icon.svg';
// 请求的类型
type ReqType = InterReqType<BackEndLoginType>;
//定时器暂存
let time: NodeJS.Timer;
function LoginView() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars,@typescript-eslint/ban-ts-comment
// @ts-ignore
const navigate = useNavigate();
// redux
const dispatch = useDispatch();
// 表单钩子
const [form] = Form.useForm<ReqType>();
//是否二维码登录
const [isLoginCode, setIsLoginCode] = useState<boolean>(false);
//获取小程序二维码唯一标识
const [randomLoginCode] = useState<string>(parseInt(String(Math.random() * 10001), 10) + '');
//小程序二维码地址
const [qrCodeUrl, setQrCodeUrl] = useState<string>('');
// 提交数据
const onFinish = async (values: ReqType) => {
const res = await CommonAPI.BackEndLogin({
......@@ -29,6 +38,7 @@ function LoginView() {
passWord: values?.passWord,
});
if (res && res.code === '200') {
message.success('登录成功');
const { token } = res.result;
await Cookies.set('SHAREFLY-TOKEN', token);
// 记住密码
......@@ -49,7 +59,50 @@ function LoginView() {
message.error('登录失败,请检查账号信息');
}
};
//显示二维码登录方式
const changeLoginUseEvent = () => {
//第一次二维码
if (!isLoginCode && !qrCodeUrl) {
CommonAPI.getAppletQRCode({
page: 'page-identity/identity-empower/index',
scene: `randomLoginCode=${randomLoginCode}&port=1`,
}).then(({ result }) => {
setQrCodeUrl(result ? `data:image/png;base64,${result}` : '');
time = setInterval(() => {
getLoginInfo();
}, 1000);
});
} else if (!isLoginCode && qrCodeUrl) {
time = setInterval(() => {
getLoginInfo();
}, 1000);
} else {
if (time) {
clearInterval(time);
}
}
setIsLoginCode(!isLoginCode);
};
//二维码登录
const getLoginInfo = () => {
CommonAPI.getLoginInfo({ randomLoginCode }).then(async ({ result, code }) => {
if (code === '200') {
clearInterval(time);
message.success('登录成功');
const { token } = result;
await Cookies.set('SHAREFLY-TOKEN', token);
dispatch(SET_USERINFO(result));
localStorage.setItem('roleId', result.roleInfo.id.toString());
authRouterList().then((value: any) => {
if (value.length) {
navigate({ pathname: value[0].children.find((v: any) => !v.meta.hidden)?.path });
} else {
message.warning('请先配置权限');
}
});
}
});
};
// componentDidMount
useEffect(() => {
// 是否保存密码
......@@ -62,15 +115,27 @@ function LoginView() {
remember: Boolean(Cookies.get('remember')),
});
}, []);
return (
<div className='login-warp'>
<div className='login-view'>
<div className='login-flex login-content'>
<div className='login-title'>欢迎来到</div>
<div className='login-text'>科比特 · 云享飞管理平台</div>
<div className='login-title'></div>
<div className='login-text'>云享飞管理平台</div>
<div className='login-detail'>让天空为世界所用</div>
</div>
<div className='login-flex login-form'>
{isLoginCode ? (
<div className='qr-code-wrap'>
{qrCodeUrl ? (
<Image src={qrCodeUrl} alt='' width={200} height={200} preview={false} />
) : (
<QRCode value='https://ant.design/' status='loading' />
)}
</div>
) : (
<>
<div className='login-image' />
<div className='login-input'>
<Form
......@@ -82,7 +147,12 @@ function LoginView() {
autoComplete='off'
>
<Form.Item name='accountNo' rules={[{ required: true, message: '请输入用户名' }]}>
<Input size='large' prefix={<UserOutlined />} placeholder='请输入账号' allowClear />
<Input
size='large'
prefix={<UserOutlined />}
placeholder='请输入账号'
allowClear
/>
</Form.Item>
<Form.Item
......@@ -120,7 +190,21 @@ function LoginView() {
</Form.Item>
</Form>
</div>
</>
)}
</div>
{isLoginCode ? (
<div className='account-login-operate'>
<Button type='link' onClick={changeLoginUseEvent}>
使用账号登录
</Button>
</div>
) : (
<div className='qrCode-icon'>
<img src={qrCodeIcon} alt='icon' onClick={changeLoginUseEvent} />
</div>
)}
</div>
</div>
);
......
......@@ -239,7 +239,7 @@ function CustomListView() {
label: '手机号',
name: 'phoneNum',
type: 'input',
placeholder: '请输入用户账号',
placeholder: '请输入手机号',
},
{
label: '来源',
......
.goods-video-wrap{
position: relative;
width: 200px;
img{
position: absolute;
right: 0;
top: 0;
transform: translate(50%,-25%);
}
}
import { Button, Cascader, Form, Input, Radio, Select } from 'antd';
import { Uploader } from '~/components/uploader';
import type { RadioChangeEvent } from 'antd';
import { UploadOutlined } from '@ant-design/icons';
import React, { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
import { InterDataType } from '~/api/interface';
import { categoryListRespType } from '~/api/interface/categoryManage';
import { CategoryManageAPI } from '~/api';
import deletePng from '~/assets/image/delete.png';
import './index.scss';
//分类返回类型
type categoryType = InterDataType<categoryListRespType>['list'];
interface selfProps {
ref: any;
}
//基本信息表单类型
export type baseInfoType = {
tradeName: string;
description: string;
mainImgList: {
id: number;
name: string;
uid: number;
url: string;
}[];
subImgList: {
id: number;
name: string;
uid: number;
url: string;
}[];
videoList: {
id: number;
name: string;
uid: number;
url: string;
}[];
categoryId: number[];
shelfStatus: number;
labelShow: number;
goodsLabel: string;
};
const BaseInfo = forwardRef<any, selfProps>((_props, ref) => {
const [baseInfoForm] = Form.useForm<baseInfoType>();
const [labelShow, setLabelShow] = useState<boolean>(false);
//分类列表
const [categoryList, setCategoryList] = useState<categoryType>([]);
//主图
const [mainFileList, setMainFileList] = useState<
{
id: number;
name: string;
uid: number;
url: string;
}[]
>([]);
//副图
const [subFileList, setSubFileList] = useState<
{
id: number;
name: string;
uid: number;
url: string;
}[]
>([]);
//视频
const [videoFileList, setVideoFileList] = useState<
{
id: number;
name: string;
uid: number;
url: string;
}[]
>([]);
useImperativeHandle(ref, () => ({
getForm: () => baseInfoForm,
setLabelShow: (value: boolean) => setLabelShow(value),
mediaData: {
setMainFileList,
setSubFileList,
setVideoFileList,
},
}));
//标签选中监听
const labelRadioChange = (e: RadioChangeEvent) => {
setLabelShow(!!e.target.value);
};
//上传结果
const uploadSuccess = (
fileList: {
id: number;
name: string;
uid: number;
url: string;
}[],
type: string,
) => {
switch (type) {
case 'mainImgList':
setMainFileList(fileList);
baseInfoForm.setFieldValue('mainImgList', fileList);
break;
case 'subImgList':
setSubFileList(fileList);
baseInfoForm.setFieldValue('subImgList', fileList);
break;
case 'videoList':
setVideoFileList(fileList);
baseInfoForm.setFieldValue('videoList', fileList);
break;
default:
break;
}
};
//分类列表
const getCategoryList = () => {
CategoryManageAPI.getCategoryRespList({ pageNo: 1, pageSize: 99999 }).then(({ result }) => {
setCategoryList(result.list || []);
});
};
//视频删除
const deleteVideo = () => {
setVideoFileList([]);
baseInfoForm.setFieldValue('videoList', undefined);
};
useEffect(() => {
getCategoryList();
}, []);
return (
<div className='base-info'>
<Form
labelCol={{ span: 2 }}
wrapperCol={{ span: 8 }}
form={baseInfoForm}
initialValues={{ labelShow: 0, shelfStatus: 1 }}
>
<Form.Item
label='商品名称'
name='tradeName'
rules={[{ required: true, message: '请输入商品名称' }]}
>
<Input placeholder='请输入商品名称' maxLength={50} />
</Form.Item>
<Form.Item
label='商品描述'
name='description'
rules={[{ required: true, message: '请输入商品描述' }]}
>
<Input.TextArea rows={4} placeholder='请输入商品描述' maxLength={70} showCount />
</Form.Item>
<Form.Item
label='商品主图'
name='mainImgList'
rules={[{ required: true, message: '请上传商品主图' }]}
>
<Uploader
fileUpload
listType='picture-card'
onChange={(fileList) => uploadSuccess(fileList, 'mainImgList')}
defaultFileList={mainFileList}
>
<UploadOutlined />
</Uploader>
</Form.Item>
<Form.Item label='商品副图' name='subImgList'>
<Uploader
fileUpload
listType='picture-card'
onChange={(fileList) => uploadSuccess(fileList, 'subImgList')}
defaultFileList={subFileList}
fileLength={4}
>
<UploadOutlined />
</Uploader>
</Form.Item>
<Form.Item label='商品视频' name='videoList'>
{videoFileList.length ? (
<div className='goods-video-wrap'>
<video
src={videoFileList[0].url}
style={{ width: '200px', height: '200px' }}
controls
/>
<img src={deletePng} alt='删除' onClick={deleteVideo} />
</div>
) : (
<Uploader
fileUpload
listType='text'
onChange={(fileList) => uploadSuccess(fileList, 'videoList')}
defaultFileList={videoFileList}
fileType={['video/mp4', 'video/avi', 'video/wmv', 'video/rmvb']}
fileSize={50}
>
<Button icon={<UploadOutlined />}>上传视频</Button>
</Uploader>
)}
</Form.Item>
<Form.Item
label='商品分类'
name='categoryId'
rules={[{ required: true, message: '请选择商品分类' }]}
>
<Cascader
placeholder='请选择商品分类'
options={categoryList}
fieldNames={{ label: 'name', value: 'id', children: 'subDTOList' }}
/>
</Form.Item>
<Form.Item
label='商品状态'
name='shelfStatus'
rules={[{ required: true, message: '请选择商品状态' }]}
>
<Select placeholder='请选择商品状态'>
<Select.Option value={1}>上架</Select.Option>
<Select.Option value={0}>下架</Select.Option>
</Select>
</Form.Item>
<Form.Item label='显示标签' name='labelShow'>
<Radio.Group onChange={labelRadioChange}>
<Radio value={1}>显示</Radio>
<Radio value={0}>不显示</Radio>
</Radio.Group>
</Form.Item>
{labelShow ? (
<Form.Item
label='商品标签'
name='goodsLabel'
rules={[{ required: true, message: '请输入商品标签' }]}
>
<Input placeholder='请输入商品标签' maxLength={10} />
</Form.Item>
) : (
''
)}
</Form>
</div>
);
});
export default BaseInfo;
import RichText from '~/components/richText';
import { FC } from 'react';
interface selfProps {
onChange: (html: string) => void;
introduceInfo: string;
}
const IntroduceInfo: FC<selfProps> = ({ onChange, introduceInfo }) => {
//富文本修改
const richTextOnChange = (html?: string) => {
onChange(html || '');
};
return (
<div className='introduce-info'>
<RichText richTextContent={introduceInfo} height={400} onChange={richTextOnChange} />
</div>
);
};
export default IntroduceInfo;
import { Button, Form, Input, Modal, ModalProps, Radio, Select, Table } from 'antd';
import { FC, useEffect, useState } from 'react';
import EditableCell from '~/components/EditableCell';
import { MinusOutlined, PlusOutlined } from '@ant-design/icons';
import { InterDataType, InterReqType } from '~/api/interface';
import { addMallGoodsType, skuUnitType } from '~/api/interface/goodsType';
import { filterObjAttr } from '~/utils';
import { isEmptyBol, regPriceNumber } from '~/utils/validateUtils';
type EditableTableProps = Parameters<typeof Table>[0];
type ColumnTypes = Exclude<EditableTableProps['columns'], undefined>;
//商品sku规格类型
type goodsSpecType = Exclude<InterReqType<addMallGoodsType>, undefined>['goodsSpecList'][0];
//商品sku规格值类型
type goodsSpecValuesType = Exclude<
InterReqType<addMallGoodsType>,
undefined
>['goodsSpecList'][0]['goodsSpecValuesList'][0] & { fileList: any };
//单位返回类型
type unitType = InterDataType<skuUnitType>;
interface selfProps {
onCancel: () => void;
onHandleOk: (value: goodsSpecType) => void;
skuUnitList: unitType;
currentSku: goodsSpecType | undefined;
}
const SkuAddOrEditModal: FC<ModalProps & selfProps> = ({
open,
onCancel,
onHandleOk,
skuUnitList,
currentSku,
}) => {
const [goodsSpecForm] = Form.useForm<Omit<goodsSpecType, 'goodsSpecValuesList'>>();
const [goodsSpecValuesForm] = Form.useForm<goodsSpecValuesType>();
//销售正则价格校验
const salePriceValidator = (_rules: any, value: number) => {
if (!isEmptyBol(value)) {
if (regPriceNumber(value.toString())) {
if (value > 99999999 || value < 0) {
return Promise.reject(new Error('价格最大为99999999且大于0'));
}
return Promise.resolve();
} else {
return Promise.reject(new Error('为整数且最多保留两位小数'));
}
} else {
return Promise.reject(new Error('请输入销售价'));
}
};
//渠道正则价格校验
const channelPriceValidator = (_rules: any, value: number) => {
if (!isEmptyBol(value)) {
if (regPriceNumber(value.toString())) {
if (value > 99999999 || value < 0) {
return Promise.reject(new Error('价格最大为99999999且大于0'));
}
return Promise.resolve();
} else {
return Promise.reject(new Error('为整数且最多保留两位小数'));
}
} else {
return Promise.resolve();
}
};
//库存正则校验
const stockPriceValidator = (_rules: any, value: number) => {
if (!isEmptyBol(value)) {
if (/^[+]{0,1}(\d+)$/.test(value.toString())) {
if (value > 99999999 || value < 0) {
return Promise.reject(new Error('库存最大为99999999且大于0'));
}
return Promise.resolve();
} else {
return Promise.reject(new Error('请输入正整数'));
}
} else {
return Promise.reject(new Error('请输入库存'));
}
};
const [tableData, setTableData] = useState<goodsSpecValuesType[]>([
{
id: Math.random(),
channelPrice: 0,
partNo: '',
salePrice: 0,
showPrice: 1,
specValueImage: '',
specValueName: '',
stock: 0,
fileList: [],
},
]);
const defaultColumns: (ColumnTypes[number] & {
editable?: boolean;
dataIndex?: string;
inputType?: string;
radioOption?: { name: string; id: number }[];
rules?: any;
maxLength?: number;
})[] = [
{
title: '序号',
align: 'center',
width: '5%',
render: (_text: string, _record: any, index: number) => index + 1,
},
{
title: '图片',
align: 'center',
editable: true,
dataIndex: 'specValueImage',
inputType: 'uploader',
},
{
title: '选项名称',
align: 'center',
editable: true,
dataIndex: 'specValueName',
rules: [{ required: true, message: '请输入选项名称' }],
maxLength: 30,
},
{
title: '料号',
align: 'center',
editable: true,
dataIndex: 'partNo',
maxLength: 30,
},
{
title: '销售价',
align: 'center',
editable: true,
dataIndex: 'salePrice',
rules: [{ required: true, validator: salePriceValidator }],
inputType: 'number',
},
{
title: '渠道价',
editable: true,
align: 'center',
dataIndex: 'channelPrice',
rules: [{ required: false, validator: channelPriceValidator }],
inputType: 'number',
},
{
title: '库存',
editable: true,
align: 'center',
dataIndex: 'stock',
rules: [{ required: true, validator: stockPriceValidator }],
inputType: 'number',
},
{
title: '是否显示',
editable: true,
align: 'center',
dataIndex: 'showPrice',
inputType: 'radio',
width: '18%',
radioOption: [
{ id: 1, name: '显示' },
{ id: 0, name: '不显示' },
],
},
{
title: '操作',
align: 'center',
width: '10%',
render: (_text: string, _record: any, index: number) => (
<>
{tableData.length - 1 === index ? (
<Button
type='primary'
icon={<PlusOutlined />}
onClick={addSkuDataRowClick}
style={{ marginRight: '10px' }}
></Button>
) : (
''
)}
{index ? (
<Button
type='primary'
icon={<MinusOutlined />}
onClick={() => removeSkuDataRowClick(index)}
></Button>
) : (
''
)}
</>
),
},
];
const columns = defaultColumns.map((col) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record: any) => ({
record,
dataIndex: col.dataIndex,
title: col.title,
editing: col.editable,
radioOption: col.radioOption,
inputType: col.inputType,
uploadSuccess: col.inputType === 'uploader' ? uploadSuccess : undefined,
rules: col.rules,
}),
};
});
const handleCancel = () => {
clearForm();
onCancel();
};
const clearForm = () => {
goodsSpecForm.resetFields();
goodsSpecValuesForm.resetFields();
const list = [
{
id: Math.random(),
channelPrice: 0,
partNo: '',
salePrice: 0,
showPrice: 1,
specValueImage: '',
specValueName: '',
stock: 0,
fileList: [],
},
];
const obj = list.reduce((pre: any, cur: any) => {
Object.getOwnPropertyNames(cur).forEach((key: string) => {
if (['showPrice'].includes(key)) {
pre[key + cur.id] = cur[key];
}
});
return pre;
}, {});
goodsSpecValuesForm.setFieldsValue(obj);
setTableData(list);
};
//新增一行规格值
const addSkuDataRowClick = () => {
const list = [
...tableData,
{
id: Math.random(),
channelPrice: 0,
partNo: '',
salePrice: 0,
showPrice: 1,
specValueImage: '',
specValueName: '',
stock: 0,
fileList: [],
},
];
setTableData(list);
const obj = list.reduce((pre: any, cur: any) => {
Object.getOwnPropertyNames(cur).forEach((key: string) => {
if (['showPrice'].includes(key)) {
pre[key + cur.id] = cur[key];
}
});
return pre;
}, {});
goodsSpecValuesForm.setFieldsValue(obj);
};
//移除一行规格值
const removeSkuDataRowClick = (index: number) => {
tableData.splice(index, 1);
setTableData([...tableData]);
};
//规格值上传图片返回
const uploadSuccess = (record: goodsSpecValuesType, value: any) => {
const tableIndex: number = tableData.findIndex((v) => v.id === record.id);
goodsSpecValuesForm.setFieldValue(
'specValueImage' + record.id,
value.length ? value[0].url : undefined,
);
if (tableIndex !== -1) {
tableData[tableIndex].fileList = value;
setTableData([...tableData]);
}
};
//提交
const handleOk = () => {
Promise.all([goodsSpecForm.validateFields(), goodsSpecValuesForm.validateFields()]).then(
(values: any) => {
const goodsSpecValuesList: goodsSpecValuesType[] = tableData.map((v) => {
return Object.getOwnPropertyNames(v).reduce((pre: any, cur: string) => {
if (Object.getOwnPropertyNames(values[1]).includes(cur + v.id)) {
pre[cur] = values[1][cur + v.id];
pre.id = v.id;
}
return pre;
}, {});
});
onHandleOk({ ...values[0], goodsSpecValuesList, id: currentSku?.id || Math.random() });
clearForm();
},
);
};
useEffect(() => {
const obj = tableData.reduce((pre: any, cur: any) => {
Object.getOwnPropertyNames(cur).forEach((key: string) => {
if (['showPrice'].includes(key)) {
pre[key + cur.id] = cur[key];
}
});
return pre;
}, {});
goodsSpecValuesForm.setFieldsValue(obj);
if (currentSku) {
goodsSpecForm.setFieldsValue(filterObjAttr(currentSku, ['goodsSpecValuesList']));
setTableData(
currentSku.goodsSpecValuesList.map((v: any) => ({
...v,
fileList: v.specValueImage
? [
{
id: Math.random(),
uid: Math.random(),
url: v.specValueImage,
name: 'specValueImage',
},
]
: [],
})),
);
const goodsSpecValuesObj = currentSku.goodsSpecValuesList.reduce((pre: any, cur: any) => {
Object.getOwnPropertyNames(cur)
.filter((key: string) => key !== 'id')
.forEach((key: string) => {
pre[key + cur.id] = cur[key];
});
return pre;
}, {});
goodsSpecValuesForm.setFieldsValue(goodsSpecValuesObj);
}
}, [currentSku]);
return (
<Modal
open={open}
title={currentSku ? '编辑规格' : '添加规格'}
width={1100}
onCancel={handleCancel}
onOk={handleOk}
>
<Form
labelCol={{ span: 2 }}
wrapperCol={{ span: 22 }}
initialValues={{ chooseType: 0, must: 0, skuUnitId: skuUnitList[0]?.id || undefined }}
form={goodsSpecForm}
>
<Form.Item
label='规格名称'
name='specName'
rules={[{ required: true, message: '请输入规格名称' }]}
>
<Input placeholder='请输入规格名称' maxLength={30} />
</Form.Item>
<Form.Item label='规格值'>
<Form component={false} form={goodsSpecValuesForm}>
<Table
rowKey='id'
columns={columns as ColumnTypes}
components={{
body: {
cell: EditableCell,
},
}}
bordered
dataSource={tableData}
pagination={false}
></Table>
</Form>
</Form.Item>
<Form.Item label='选择方式' name='chooseType'>
<Radio.Group>
<Radio value={0}>单选</Radio>
<Radio value={1}>多选</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label='是否必选' name='must'>
<Radio.Group>
<Radio value={0}>非必选</Radio>
<Radio value={1}>必选</Radio>
</Radio.Group>
</Form.Item>
<Form.Item
label='规格单位'
name='skuUnitId'
rules={[{ required: true, message: '请选择规格单位' }]}
>
<Select placeholder='请选择规格单位'>
{skuUnitList.map((v) => (
<Select.Option value={v.id} key={v.id}>
{v.unitName}
</Select.Option>
))}
</Select>
</Form.Item>
</Form>
</Modal>
);
};
export default SkuAddOrEditModal;
import { Button, Popconfirm, Table, Tag } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import { ColumnsType } from 'antd/es/table';
import { FC, useState } from 'react';
import { InterDataType, InterReqType, PaginationProps } from '~/api/interface';
import { addMallGoodsType, skuUnitType } from '~/api/interface/goodsType';
//商品sku规格类型
type goodsSpecType = Exclude<InterReqType<addMallGoodsType>, undefined>['goodsSpecList'][0];
//单位返回类型
type unitType = InterDataType<skuUnitType>;
interface selfProps {
addOrEditSkuClick: () => void;
skuTableData: goodsSpecType[];
skuUnitList: unitType;
deleteSkuClick: (record: goodsSpecType) => void;
editSkuClick: (record: goodsSpecType) => void;
}
const SkuInfo: FC<selfProps> = ({
addOrEditSkuClick,
skuTableData,
skuUnitList,
deleteSkuClick,
editSkuClick,
}) => {
const tableColumns: ColumnsType<goodsSpecType> = [
{
title: '序号',
align: 'center',
render: (_text: string, _record, index: number) => index + 1,
},
{
title: '规格名称',
align: 'center',
dataIndex: 'specName',
},
{
title: '选择方式',
align: 'center',
dataIndex: 'chooseType',
render: (text: number) => (text ? '多选' : '单选'),
},
{
title: '是否必选',
align: 'center',
dataIndex: 'must',
render: (text: number) => (text ? '必选' : '非必选'),
},
{
title: '规格单位',
align: 'center',
dataIndex: 'skuUnitId',
render: (text: number) => skuUnitList.find((v) => v.id === text)?.unitName || '',
},
{
title: '规格值',
align: 'center',
dataIndex: 'goodsSpecValuesList',
render: (text: goodsSpecType['goodsSpecValuesList']) =>
text.map((v) => (
<Tag key={v.id}>
{v.specValueName}
{v.partNo ? `(${v.partNo})` : ''}
</Tag>
)),
},
{
title: '操作',
align: 'center',
render: (_text: string, record) => (
<>
<Button type='link' onClick={() => editSkuClick(record)}>
编辑
</Button>
<Popconfirm
placement='topLeft'
title={'删除规格'}
description={'确认删除该规格吗?'}
onConfirm={() => deleteSkuClick(record)}
okText='确定'
cancelText='取消'
>
<Button type='link' danger>
删除
</Button>
</Popconfirm>
</>
),
},
];
const [pagination, setPagination] = useState<PaginationProps>({
pageNo: 1,
pageSize: 10,
});
//分页
const paginationChange = (pageNo: number, pageSize: number) => {
pagination.pageNo = pageNo;
pagination.pageSize = pageSize;
setPagination({ ...pagination });
};
return (
<div className='sku-info'>
<div className='sku-info-operate' style={{ margin: ' 20px 0 ' }}>
<Button
type='primary'
icon={<PlusOutlined></PlusOutlined>}
onClick={() => addOrEditSkuClick()}
>
添加规格
</Button>
</div>
<Table
bordered
columns={tableColumns}
dataSource={skuTableData.slice(
(pagination.pageNo - 1) * pagination.pageSize,
pagination.pageNo * pagination.pageSize,
)}
rowKey='id'
pagination={{
total: skuTableData.length,
pageSize: pagination.pageSize,
current: pagination.pageNo,
showSizeChanger: true,
showQuickJumper: true,
onChange: (page: number, pageSize: number) => paginationChange(page, pageSize),
showTotal: (total, range) => `当前 ${range[0]}-${range[1]} 条记录 / 共 ${total} 条数据`,
}}
/>
</div>
);
};
export default SkuInfo;
import BaseInfo from '~/components/goods/commonAddOrEdit/baseInfo';
import StockSku from '~/components/goods/commonAddOrEdit/stockSku';
import OtherInfo from '~/components/goods/commonAddOrEdit/otherInfo';
import GoodsIntroduce from '~/components/goods/commonAddOrEdit/goodsIntroduce';
import AddOrEditSkuModal from '~/components/goods/commonAddOrEdit/addOrEditSkuModal';
import { Button, message } from 'antd';
import { useNavigate, useSearchParams } from 'react-router-dom';
import './index.scss';
import { useEffect, useRef, useState } from 'react';
import { CategoryManageAPI } from '~/api';
import { InterDataType } from '~/api/interface';
import { categoryListType } from '~/api/interface/categoryManage';
import {
customizeEntity,
detailGoodsType,
skuUnitType,
specEntity,
} from '~/api/interface/goodsType';
import goodsAPI from '~/api/modules/goodsAPI';
import { filterObjAttr } from '~/utils';
import GoodsAPI from '~/api/modules/goodsAPI';
import { UploadFile } from 'antd/es/upload/interface';
//分类返回类型
type categoryType = InterDataType<categoryListType>['list'];
//产品-规格单位返回类型
type unitType = InterDataType<skuUnitType>;
//商品返回类型
type goodsDetailType = InterDataType<detailGoodsType>;
const GoodsAddOrEditOrDetail = () => {
const [searchParams] = useSearchParams();
//基本信息ref
const baseInfoRef = useRef<any>();
const navigate = useNavigate();
//当前目录
const [currentDesc, setCurrentDesc] = useState<number>(-1);
//分类
const [categoryList, setCategoryList] = useState<categoryType>([]);
//添加、编辑库存规格弹窗
const [addOrEditSkuModalShow, setAddOrEditSkuModalShow] = useState(false);
//库存规格数据
const [specData, setSpecData] = useState<specEntity[]>([]);
const [goodsSpecCopy, setGoodsSpecCopy] = useState<specEntity[]>([]);
//产品规格-单位
const [skuUnitList, setSkuUnitList] = useState<unitType>([]);
//其它服务
const [otherService, setOtherService] = useState<number[]>([]);
// 当前操作行数据
const [curtRowData, setCurtRowData] = useState<Partial<specEntity>>({});
//商品详情
const [goodsDetail, setGoodsDetail] = useState<goodsDetailType>();
//产品介绍
const [productIntroduce, setProductIntroduce] = useState<string>('');
//是否商品详情
const [isDetail, setIsDetail] = useState<boolean>(false);
//添加、编辑规格
const addOrEditSkuShowEvent = (record?: specEntity) => {
const baseInfoForm = baseInfoRef.current.baseInform;
setCurrentDesc(baseInfoForm.getFieldValue('directoryId') || -1);
if (!baseInfoForm.getFieldValue('directoryId')) {
return message.warning('请先选择目录');
}
if (record) {
setCurtRowData({ ...record });
}
setAddOrEditSkuModalShow(true);
};
//删除规格
const deleteSkuEvent = (record: specEntity) => {
const index = specData.findIndex((v) => v.id === record.id);
specData.splice(index, 1);
setSpecData([...specData]);
};
const addOrEditSkuModalCancel = () => {
setAddOrEditSkuModalShow(false);
setCurtRowData({});
};
const addOrEditSkuModalOk = (data: specEntity) => {
if (Object.keys(curtRowData).length != 0) {
const index: number = specData.findIndex((i) => i.id === data.id);
specData.splice(index, 1, data);
setSpecData([...specData]);
} else {
setSpecData([...specData, data]);
}
addOrEditSkuModalCancel();
};
//根据目录获取分类列表
const getCategoryList = (directoryId: number) => {
CategoryManageAPI.getCategoryList({ directoryId, type: 4, pageSize: 9999, pageNo: 1 }).then(
({ result }) => {
setCategoryList(result.list || []);
},
);
};
//产品-单位
const getSkuUnit = () => {
goodsAPI.getSkuUnit().then(({ result }) => {
setSkuUnitList(result || []);
});
};
//其它服务选择
const otherServiceSelect = (ids: number[]) => {
setOtherService(ids);
};
//获取产品详情
const getRichText = (html?: string) => {
setProductIntroduce(html || '');
};
//商品详情
const getGoodsDetail = (goodsInfoId: number) => {
GoodsAPI.getGoodsDetail({ goodsInfoId, type: 0 }).then(({ result }) => {
setGoodsDetail(result);
getCategoryList(result.directoryId);
const specList: specEntity[] = result.goodsSpec.reduce((pre: any, cur: specEntity) => {
// 自定义
if (cur.flag === 1) {
const cusList: customizeEntity[] =
cur.productSpecList &&
cur.productSpecList.reduce((preProd: any, curProd: customizeEntity, index: number) => {
const obj: UploadFile = {
uid: `img${index}`,
status: 'done',
url: curProd.specImage,
name: 'image',
};
preProd.push({ ...curProd, fileList: [obj] });
return preProd;
}, []);
cur.customizeInfo = cusList;
} else {
const specId: number[] =
result.directoryId === 2
? cur.industrySpecList &&
cur.industrySpecList.map((curIndu: any) => {
return {
mallSpecId: curIndu.industrySpecId,
specName: curIndu.specName,
partNo: curIndu.partNo,
id: curIndu.id,
};
})
: cur.productSpecList &&
cur.productSpecList.map((item: any) => {
return {
mallSpecId: item.productSpec,
specName: item.specName,
partNo: item.partNo,
id: item.id,
};
});
cur.specIds = specId;
}
pre.push({ ...cur, productName: cur.skuName });
return pre;
}, []);
setGoodsSpecCopy(result.goodsSpec);
setOtherService(result.otherService.map((v) => v.saleServiceId));
setSpecData(specList);
});
};
//保存
const saveSubmit = () => {
const baseInfoForm = baseInfoRef.current.baseInform;
baseInfoForm.validateFields().then((values: any) => {
if (specData.length === 0) {
return message.warning('清添加库存规格');
}
//主图
values.images = [
{
imgType: 0,
imgUrl: values.mainImg[0].url,
id: goodsDetail
? goodsDetail.images.some((i) => i.id === values.mainImg[0].id)
? values.mainImg[0].id
: undefined
: undefined,
},
];
//副图
if (values.subImg) {
values.images.push(
...values.subImg.map((v: any) => ({
imgType: 1,
imgUrl: v.url,
id: goodsDetail
? goodsDetail.images.some((i) => i.id === v.id)
? v.id
: undefined
: undefined,
})),
);
}
//分类
values.categoryByOne = values.masterTypeId[0];
values.categoryByTwo = values.masterTypeId[1] || undefined;
// 过滤对象属性
const goodsSpecVO: specEntity[] = specData.reduce((pre: any, cur: specEntity) => {
cur.customizeInfo = cur.customizeInfo?.reduce((cusPre: any, cusCur: customizeEntity) => {
const bol: boolean = goodsSpecCopy.some((i: specEntity) => {
return i.customizeInfo?.some((i: customizeEntity) => i.id === cusCur.id);
});
cusPre = [
...cusPre,
bol ? filterObjAttr(cusCur, ['fileList']) : filterObjAttr(cusCur, ['id', 'fileList']),
];
return cusPre;
}, []);
// 存在对象属性改变!
cur.specIds = cur.specIds?.reduce((preSpec: any, curSpec: any) => {
preSpec = [...preSpec, filterObjAttr(curSpec, ['specName', 'partNo'])];
return preSpec;
}, []);
// 是否新增
const isAdd: boolean = goodsSpecCopy.every((i: specEntity) => i.id != cur.id);
// 是否修改了某一条
const isEdit: boolean = goodsSpecCopy.every(
(i: specEntity) => i.categoryId != cur.categoryId,
);
pre = [
...pre,
!isAdd
? isEdit
? filterObjAttr(cur, ['industrySpecList', 'productSpecList', 'skuName', 'id'])
: filterObjAttr(cur, ['industrySpecList', 'productSpecList', 'skuName'])
: filterObjAttr(cur, ['id', 'skuName']),
];
return pre;
}, []);
goodsAPI[goodsDetail ? 'editGoods' : 'addGoods']({
...filterObjAttr(values, ['mainImg', 'subImg', 'video', 'masterTypeId', 'id', 'goodsDesc']),
productSpec: goodsSpecVO,
goodsType: 0,
goodsDetailVO: { goodsDesc: values.goodsDesc, productDesc: productIntroduce },
otherService: otherService,
id: goodsDetail ? goodsDetail.id : undefined,
}).then(({ code }) => {
if (code === '200') {
message.success(goodsDetail ? '编辑成功' : '新增成功');
navigate(-1);
}
});
});
};
//返回
const backRoute = () => {
navigate(-1);
};
useEffect(() => {
if (searchParams.get('id')) {
getGoodsDetail(Number(searchParams.get('id')));
}
setIsDetail(!!searchParams.get('isDetail'));
getSkuUnit();
}, []);
return (
<div className='goods-info'>
{/* 基本信息*/}
<BaseInfo
ref={baseInfoRef}
categoryList={categoryList}
getCategoryList={getCategoryList}
goodsDetail={goodsDetail}
isDetail={isDetail}
goodsType={0}
/>
{/* 库存规格*/}
<StockSku
addOrEditSku={addOrEditSkuShowEvent}
specData={specData}
skuUnitList={skuUnitList}
deleteSku={deleteSkuEvent}
isDetail={isDetail}
/>
{/*其它信息*/}
<OtherInfo
otherServiceSelect={otherServiceSelect}
goodsDetail={goodsDetail}
isDetail={isDetail}
/>
{/*产品介绍图*/}
<GoodsIntroduce getRichText={getRichText} goodsDetail={goodsDetail} isDetail={isDetail} />
{/*库存规格,添加、编辑弹窗*/}
<AddOrEditSkuModal
currentDesc={currentDesc}
open={addOrEditSkuModalShow}
handleCancel={addOrEditSkuModalCancel}
handleOk={addOrEditSkuModalOk}
skuUnitList={skuUnitList}
curtRowData={curtRowData}
goodsType={0}
/>
<div className='goods-info-operate'>
{!isDetail && (
<Button type='primary' onClick={saveSubmit}>
保存
</Button>
)}
<Button onClick={backRoute}>返回</Button>
</div>
</div>
);
};
export default GoodsAddOrEditOrDetail;
.goods-info {
&-operate {
margin-top: 50px;
display: flex;
justify-content: center;
button {
.goods-operate-wrap{
position: relative;
.next-step{
margin: 20px 0 0 0px;
text-align: center;
button{
&:first-child{
margin-right: 20px;
}
width: 100px;
height: 40px;
&:first-child {
margin-right: 50px;
}
}
.back-btn{
position: absolute;
right: 0;
top: 0;
}
}
import BaseInfo from '~/components/goods/commonAddOrEdit/baseInfo';
import StockSku from '~/components/goods/commonAddOrEdit/stockSku';
import OtherInfo from '~/components/goods/commonAddOrEdit/otherInfo';
import GoodsIntroduce from '~/components/goods/commonAddOrEdit/goodsIntroduce';
import AddOrEditSkuModal from '~/components/goods/commonAddOrEdit/addOrEditSkuModal';
import { Button, message } from 'antd';
import { useNavigate, useSearchParams } from 'react-router-dom';
import './index.scss';
import { Button, message, Tabs, TabsProps } from 'antd';
import { useEffect, useRef, useState } from 'react';
import { CategoryManageAPI } from '~/api';
import { InterDataType } from '~/api/interface';
import { categoryListType } from '~/api/interface/categoryManage';
import {
customizeEntity,
detailGoodsType,
skuUnitType,
specEntity,
} from '~/api/interface/goodsType';
import goodsAPI from '~/api/modules/goodsAPI';
import { filterObjAttr } from '~/utils';
import BaseInfo from './components/baseInfo';
import SkuInfo from './components/skuInfo';
import IntroduceInfo from './components/introduceInfo';
import SkuAddOrEditModal from './components/skuAddOrEditModal';
import './index.scss';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { baseInfoType } from './components/baseInfo';
import { InterDataType, InterReqType } from '~/api/interface';
import { addMallGoodsType, mallGoodsDetailsType, skuUnitType } from '~/api/interface/goodsType';
import GoodsAPI from '~/api/modules/goodsAPI';
import { UploadFile } from 'antd/es/upload/interface';
import { filterObjAttr } from '~/utils';
//分类返回类型
type categoryType = InterDataType<categoryListType>['list'];
//产品-规格单位返回类型
//商品sku规格类型
type goodsSpecType = Exclude<InterReqType<addMallGoodsType>, undefined>['goodsSpecList'][0];
//单位返回类型
type unitType = InterDataType<skuUnitType>;
//商品返回类型
type goodsDetailType = InterDataType<detailGoodsType>;
//商品详情-返回类型
type goodsDetailType = InterDataType<mallGoodsDetailsType>;
const GoodsAddOrEditOrDetail = () => {
const [searchParams] = useSearchParams();
//基本信息ref
const baseInfoRef = useRef<any>();
const navigate = useNavigate();
//当前目录
const [currentDesc, setCurrentDesc] = useState<number>(-1);
//分类
const [categoryList, setCategoryList] = useState<categoryType>([]);
//添加、编辑库存规格弹窗
const [addOrEditSkuModalShow, setAddOrEditSkuModalShow] = useState(false);
//库存规格数据
const [specData, setSpecData] = useState<specEntity[]>([]);
const [goodsSpecCopy, setGoodsSpecCopy] = useState<specEntity[]>([]);
//产品规格-单位
const [searchParams] = useSearchParams();
//单位列表
const [skuUnitList, setSkuUnitList] = useState<unitType>([]);
//其它服务
const [otherService, setOtherService] = useState<number[]>([]);
// 当前操作行数据
const [curtRowData, setCurtRowData] = useState<Partial<specEntity>>({});
const [tabSelectKeys, setTabSelectKeys] = useState<string>('1');
//新增、编辑sku弹窗
const [addOrEditSkuModalShow, setAddOrEditSkuModalShow] = useState<boolean>(false);
//当前编辑sku
const [currentSku, setCurrentSku] = useState<goodsSpecType>();
//skuTable数据
const [skuTable, setSkuTable] = useState<goodsSpecType[]>([]);
//商品介绍详情
const [goodsDetails, setGoodsDetails] = useState<string>('');
//商品id
const [goodsId, setGoodsId] = useState<number>(0);
//商品详情
const [goodsDetail, setGoodsDetail] = useState<goodsDetailType>();
//产品介绍
const [productIntroduce, setProductIntroduce] = useState<string>('');
//是否商品详情
const [isDetail, setIsDetail] = useState<boolean>(false);
const [goodsDetailsInfo, setGoodsDetailsInfo] = useState<goodsDetailType>();
//添加、编辑规格
const addOrEditSkuShowEvent = (record?: specEntity) => {
const baseInfoForm = baseInfoRef.current.baseInform;
setCurrentDesc(baseInfoForm.getFieldValue('directoryId') || -1);
if (!baseInfoForm.getFieldValue('directoryId')) {
return message.warning('请先选择目录');
}
if (record) {
setCurtRowData({ ...record });
}
//新增、编辑sku弹窗显示
const addOrEditSkuClick = (record?: goodsSpecType) => {
setCurrentSku(record ? { ...record } : undefined);
setAddOrEditSkuModalShow(true);
};
//删除规格
const deleteSkuEvent = (record: specEntity) => {
const index = specData.findIndex((v) => v.id === record.id);
specData.splice(index, 1);
setSpecData([...specData]);
};
const addOrEditSkuModalCancel = () => {
setAddOrEditSkuModalShow(false);
setCurtRowData({});
};
const addOrEditSkuModalOk = (data: specEntity) => {
if (Object.keys(curtRowData).length != 0) {
const index: number = specData.findIndex((i) => i.id === data.id);
specData.splice(index, 1, data);
setSpecData([...specData]);
const addOrEditSkuModalOk = (values: goodsSpecType) => {
setAddOrEditSkuModalShow(false);
const skuTableIndex: number = skuTable.findIndex((v) => v.id === values.id);
if (skuTableIndex !== -1) {
skuTable.splice(skuTableIndex, 1, values);
setSkuTable([...skuTable]);
} else {
setSpecData([...specData, data]);
setSkuTable([...skuTable, { ...values }]);
}
addOrEditSkuModalCancel();
};
//根据目录获取分类列表
const getCategoryList = (directoryId: number) => {
CategoryManageAPI.getCategoryList({ directoryId, type: 4, pageSize: 9999, pageNo: 1 }).then(
({ result }) => {
setCategoryList(result.list || []);
},
);
//sku删除
const deleteSkuClick = (record: goodsSpecType) => {
const skuIndex: number = skuTable.findIndex((v) => v.id === record.id);
skuTable.splice(skuIndex, 1);
setSkuTable([...skuTable]);
};
//产品-单位
const getSkuUnit = () => {
goodsAPI.getSkuUnit().then(({ result }) => {
setSkuUnitList(result || []);
});
};
//其它服务选择
const otherServiceSelect = (ids: number[]) => {
setOtherService(ids);
};
//获取产品详情
const getRichText = (html?: string) => {
setProductIntroduce(html || '');
};
//商品详情
const getGoodsDetail = (goodsInfoId: number) => {
GoodsAPI.getGoodsDetail({ goodsInfoId, type: 0 }).then(({ result }) => {
setGoodsDetail(result);
getCategoryList(result.directoryId);
const specList: specEntity[] = result.goodsSpec.reduce((pre: any, cur: specEntity) => {
// 自定义
if (cur.flag === 1) {
const cusList: customizeEntity[] =
cur.productSpecList &&
cur.productSpecList.reduce((preProd: any, curProd: customizeEntity, index: number) => {
const obj: UploadFile = {
uid: `img${index}`,
status: 'done',
url: curProd.specImage,
name: 'image',
//商品详情获取
const getIntroduceInfo = (richText: string) => {
setGoodsDetails(richText);
};
preProd.push({ ...curProd, fileList: [obj] });
return preProd;
}, []);
cur.customizeInfo = cusList;
} else {
const specId: number[] =
result.directoryId === 2
? cur.industrySpecList &&
cur.industrySpecList.map((curIndu: any) => {
return {
mallSpecId: curIndu.industrySpecId,
specName: curIndu.specName,
partNo: curIndu.partNo,
id: curIndu.id,
const tabItems: TabsProps['items'] = [
{
key: '1',
label: `基础信息`,
children: <BaseInfo ref={baseInfoRef} />,
},
{
key: '2',
label: `商品规格`,
children: (
<SkuInfo
addOrEditSkuClick={addOrEditSkuClick}
skuTableData={skuTable}
skuUnitList={skuUnitList}
deleteSkuClick={deleteSkuClick}
editSkuClick={addOrEditSkuClick}
/>
),
},
{
key: '3',
label: `商品详情`,
children: <IntroduceInfo onChange={getIntroduceInfo} introduceInfo={goodsDetails} />,
},
];
//tab 切换
const tabSelectChange = (key: string) => {
setTabSelectKeys(key);
};
//下一步
const toNextStep = () => {
switch (tabSelectKeys) {
case '1':
baseInfoRef.current
.getForm()
.validateFields()
.then(() => {
setTabSelectKeys((Number(tabSelectKeys) + 1).toString());
})
: cur.productSpecList &&
cur.productSpecList.map((item: any) => {
return {
mallSpecId: item.productSpec,
specName: item.specName,
partNo: item.partNo,
id: item.id,
};
.catch((error: any) => {
message.error(error.errorFields[0].errors[0]);
});
cur.specIds = specId;
break;
case '2':
if (skuTable.length) {
setTabSelectKeys((Number(tabSelectKeys) + 1).toString());
} else {
message.warning('请添加规格');
}
pre.push({ ...cur, productName: cur.skuName });
return pre;
}, []);
setGoodsSpecCopy(result.goodsSpec);
setOtherService(result.otherService.map((v) => v.saleServiceId));
setSpecData(specList);
break;
case '3':
break;
default:
break;
}
};
//上一步
const toBackStep = () => {
setTabSelectKeys((Number(tabSelectKeys) - 1).toString());
};
const backRoute = () => {
navigate(-1);
};
//单位列表
const getSkuUnit = () => {
GoodsAPI.getSkuUnit().then(({ result }) => {
setSkuUnitList(result || []);
});
};
//保存
const saveSubmit = () => {
const baseInfoForm = baseInfoRef.current.baseInform;
baseInfoForm.validateFields().then((values: any) => {
if (specData.length === 0) {
return message.warning('清添加库存规格');
}
//主图
values.images = [
{
imgType: 0,
imgUrl: values.mainImg[0].url,
id: goodsDetail
? goodsDetail.images.some((i) => i.id === values.mainImg[0].id)
? values.mainImg[0].id
: undefined
//商品保存
const saveGoods = () => {
baseInfoRef.current
.getForm()
.validateFields()
.then((values: baseInfoType) => {
if (skuTable.length) {
const skuList = skuTable.map((v) => ({
...v,
id: goodsDetailsInfo
? goodsDetailsInfo.goodsSpecList.find((i) => i.id === v.id)?.id
: undefined,
},
];
//副图
if (values.subImg) {
values.images.push(
...values.subImg.map((v: any) => ({
imgType: 1,
imgUrl: v.url,
id: goodsDetail
? goodsDetail.images.some((i) => i.id === v.id)
? v.id
: undefined
goodsSpecValuesList: v.goodsSpecValuesList.map((i) => ({
...i,
id: goodsDetailsInfo
? goodsDetailsInfo.goodsSpecList
.find((j) => j.id === v.id)
?.goodsSpecValuesList.find((k) => k.id === i.id)?.id
: undefined,
goodsSpecId: goodsDetailsInfo
? goodsDetailsInfo.goodsSpecList.find((i) => i.id === v.id)?.id
: undefined,
})),
);
}
//分类
values.categoryByOne = values.masterTypeId[0];
values.categoryByTwo = values.masterTypeId[1] || undefined;
// 过滤对象属性
const goodsSpecVO: specEntity[] = specData.reduce((pre: any, cur: specEntity) => {
cur.customizeInfo = cur.customizeInfo?.reduce((cusPre: any, cusCur: customizeEntity) => {
const bol: boolean = goodsSpecCopy.some((i: specEntity) => {
return i.customizeInfo?.some((i: customizeEntity) => i.id === cusCur.id);
});
cusPre = [
...cusPre,
bol ? filterObjAttr(cusCur, ['fileList']) : filterObjAttr(cusCur, ['id', 'fileList']),
];
return cusPre;
}, []);
// 存在对象属性改变!
cur.specIds = cur.specIds?.reduce((preSpec: any, curSpec: any) => {
preSpec = [...preSpec, filterObjAttr(curSpec, ['specName', 'partNo'])];
return preSpec;
}, []);
// 是否新增
const isAdd: boolean = goodsSpecCopy.every((i: specEntity) => i.id != cur.id);
// 是否修改了某一条
const isEdit: boolean = goodsSpecCopy.every(
(i: specEntity) => i.categoryId != cur.categoryId,
);
pre = [
...pre,
!isAdd
? isEdit
? filterObjAttr(cur, ['industrySpecList', 'productSpecList', 'skuName', 'id'])
: filterObjAttr(cur, ['industrySpecList', 'productSpecList', 'skuName'])
: filterObjAttr(cur, ['id', 'skuName']),
];
return pre;
}, []);
goodsAPI[goodsDetail ? 'editGoods' : 'addGoods']({
...filterObjAttr(values, ['mainImg', 'subImg', 'video', 'masterTypeId', 'id', 'goodsDesc']),
productSpec: goodsSpecVO,
goodsType: 0,
goodsDetailVO: { goodsDesc: values.goodsDesc, productDesc: productIntroduce },
otherService: otherService,
id: goodsDetail ? goodsDetail.id : undefined,
}).then(({ code }) => {
}));
const addGoodsEditReq = {
...filterObjAttr(values, ['mainImgList', 'subImgList', 'videoList', 'categoryId']),
resourcesList: [
...values.mainImgList.map((v) => ({ type: 0, url: v.url })),
...(values.subImgList?.map((v) => ({ type: 1, url: v.url })) || []),
...(values.videoList?.map((v) => ({ type: 2, url: v.url })) || []),
],
categoryPrimaryId: values.categoryId[0],
categorySubId: values.categoryId.length === 2 ? values.categoryId[1] : undefined,
goodsDetails,
goodsSpecList: skuList,
id: goodsId || undefined,
};
GoodsAPI[goodsId ? 'editMallGoods' : 'addMallGoods'](addGoodsEditReq).then(({ code }) => {
if (code === '200') {
message.success(goodsDetail ? '编辑成功' : '新增成功');
message.success(goodsId ? '编辑商城成功' : '新增商品成功');
navigate(-1);
}
});
} else {
message.warning('商品规格未添加');
}
})
.catch((error: any) => {
message.error(error.errorFields[0].errors[0]);
});
};
//返回
const backRoute = () => {
navigate(-1);
//商品详情
const getMallGoodsDetails = (id: number) => {
GoodsAPI.getMallGoodsDetails({ id }).then(({ result }) => {
const mainImgList = result.resourcesList
.filter((v) => v.type === 0)
.map((v) => ({ id: v.id, name: 'mainImg', uid: v.id, url: v.url }));
const subImgList = result.resourcesList
.filter((v) => v.type === 1)
.map((v) => ({ id: v.id, name: 'subImg', uid: v.id, url: v.url }));
const videoList = result.resourcesList
.filter((v) => v.type === 2)
.map((v) => ({ id: v.id, name: 'video', uid: v.id, url: v.url }));
setGoodsDetailsInfo(JSON.parse(JSON.stringify(result)));
setSkuTable(result.goodsSpecList);
setGoodsDetails(result.goodsDetails || '');
baseInfoRef.current.getForm().setFieldsValue({
tradeName: result.tradeName,
description: result.description || undefined,
mainImgList,
subImgList: subImgList.length ? subImgList : undefined,
videoList: videoList.length ? videoList : undefined,
categoryId: result.categorySubId
? [result.categoryPrimaryId, result.categorySubId]
: [result.categoryPrimaryId],
shelfStatus: result.shelfStatus,
labelShow: result.labelShow,
goodsLabel: result.goodsLabel || undefined,
});
baseInfoRef.current.setLabelShow(!!result.labelShow);
baseInfoRef.current.mediaData.setMainFileList(mainImgList);
baseInfoRef.current.mediaData.setSubFileList(subImgList);
baseInfoRef.current.mediaData.setVideoFileList(videoList);
});
};
useEffect(() => {
getSkuUnit();
if (searchParams.get('id')) {
getGoodsDetail(Number(searchParams.get('id')));
setGoodsId(Number(searchParams.get('id')));
getMallGoodsDetails(Number(searchParams.get('id')));
}
setIsDetail(!!searchParams.get('isDetail'));
getSkuUnit();
}, []);
return (
<div className='goods-info'>
{/* 基本信息*/}
<BaseInfo
ref={baseInfoRef}
categoryList={categoryList}
getCategoryList={getCategoryList}
goodsDetail={goodsDetail}
isDetail={isDetail}
goodsType={0}
/>
{/* 库存规格*/}
<StockSku
addOrEditSku={addOrEditSkuShowEvent}
specData={specData}
skuUnitList={skuUnitList}
deleteSku={deleteSkuEvent}
isDetail={isDetail}
/>
{/*其它信息*/}
<OtherInfo
otherServiceSelect={otherServiceSelect}
goodsDetail={goodsDetail}
isDetail={isDetail}
/>
{/*产品介绍图*/}
<GoodsIntroduce getRichText={getRichText} goodsDetail={goodsDetail} isDetail={isDetail} />
{/*库存规格,添加、编辑弹窗*/}
<AddOrEditSkuModal
currentDesc={currentDesc}
open={addOrEditSkuModalShow}
handleCancel={addOrEditSkuModalCancel}
handleOk={addOrEditSkuModalOk}
skuUnitList={skuUnitList}
curtRowData={curtRowData}
goodsType={0}
/>
<div className='goods-info-operate'>
{!isDetail && (
<Button type='primary' onClick={saveSubmit}>
保存
<div className='goods-operate-wrap'>
<Tabs items={tabItems} activeKey={tabSelectKeys} onChange={tabSelectChange}></Tabs>
<div className='next-step'>
{tabSelectKeys !== '1' ? <Button onClick={toBackStep}>上一步</Button> : ''}
{tabSelectKeys !== '3' ? (
<Button type='primary' onClick={toNextStep}>
下一步
</Button>
) : (
''
)}
<Button onClick={backRoute}>返回</Button>
</div>
<div className='back-btn'>
<Button type='primary' onClick={saveGoods} style={{ marginRight: '10px' }}>
保存
</Button>
<Button type='default' onClick={backRoute}>
返回
</Button>
</div>
{/*新增、编辑sku弹窗*/}
<SkuAddOrEditModal
open={addOrEditSkuModalShow}
onCancel={addOrEditSkuModalCancel}
onHandleOk={addOrEditSkuModalOk}
skuUnitList={skuUnitList}
currentSku={currentSku}
/>
</div>
);
};
......
.goods-detail{
position: relative;
&-info{
margin-bottom: 20px;
}
&-sku{
.sku-title{
line-height: 50px;
font-weight: bold;
font-size: 14px;
}
}
&-introduce{
.introduce-title{
line-height: 50px;
font-weight: bold;
font-size: 14px;
}
}
&-operate{
position: absolute;
right: 0;
top: 0;
}
}
import { useSearchParams, useNavigate } from 'react-router-dom';
import { useEffect, useState } from 'react';
import GoodsAPI from '~/api/modules/goodsAPI';
import { InterDataType } from '~/api/interface';
import { mallGoodsDetailsType, skuUnitType } from '~/api/interface/goodsType';
import { Badge, Button, Descriptions, Image, Table, Tag } from 'antd';
import { CategoryManageAPI } from '~/api';
import { categoryListRespType } from '~/api/interface/categoryManage';
import './index.scss';
import { ColumnsType } from 'antd/es/table';
//详情返回类型
type detailType = InterDataType<mallGoodsDetailsType>;
//分类返回类型
type categoryType = InterDataType<categoryListRespType>['list'];
//单位返回类型
type unitType = InterDataType<skuUnitType>;
const GoodsDetails = () => {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
//分类列表
const [categoryList, setCategoryList] = useState<categoryType>([]);
//单位列表
const [skuUnitList, setSkuUnitList] = useState<unitType>([]);
const [goodsInfoDetails, setGoodsInfoDetails] = useState<detailType>();
const tableColumns: ColumnsType<detailType['goodsSpecList'][0]> = [
{
title: '序号',
align: 'center',
render: (_text: string, _record, index: number) => index + 1,
},
{
title: '规格名称',
align: 'center',
dataIndex: 'specName',
},
{
title: '选择方式',
align: 'center',
dataIndex: 'chooseType',
render: (text: number) => (text ? '多选' : '单选'),
},
{
title: '是否必选',
align: 'center',
dataIndex: 'must',
render: (text: number) => (text ? '必选' : '非必选'),
},
{
title: '规格单位',
align: 'center',
dataIndex: 'skuUnitId',
render: (text: number) => skuUnitList.find((v) => v.id === text)?.unitName || '',
},
{
title: '规格值',
align: 'center',
dataIndex: 'goodsSpecValuesList',
render: (text: detailType['goodsSpecList'][0]['goodsSpecValuesList']) =>
text.map((v) => (
<Tag key={v.id}>
{v.specValueName}
{v.partNo ? `(${v.partNo})` : ''}
</Tag>
)),
},
];
const getGoodsDetails = (id: number) => {
GoodsAPI.getMallGoodsDetails({ id }).then(({ result }) => {
setGoodsInfoDetails(result);
});
};
//分类列表
const getCategoryList = () => {
CategoryManageAPI.getCategoryRespList({ pageNo: 1, pageSize: 99999 }).then(({ result }) => {
setCategoryList(result.list || []);
});
};
//单位列表
const getSkuUnit = () => {
GoodsAPI.getSkuUnit().then(({ result }) => {
setSkuUnitList(result || []);
});
};
//返回
const backRoute = () => {
navigate(-1);
};
useEffect(() => {
getGoodsDetails(Number(searchParams.get('id')));
getCategoryList();
getSkuUnit();
}, []);
return (
<div className='goods-detail'>
<div className='goods-detail-info'>
<Descriptions title='基本信息' bordered>
<Descriptions.Item label='商品名称'>{goodsInfoDetails?.tradeName}</Descriptions.Item>
<Descriptions.Item label='商品副图'>
{goodsInfoDetails?.resourcesList
.filter((v) => v.type === 1)
.map((v) => (
<Image src={v.url} width={50} height={50} key={v.id} />
))}
</Descriptions.Item>
<Descriptions.Item label='商品主图'>
{goodsInfoDetails?.resourcesList
.filter((v) => v.type === 0)
.map((v) => (
<Image src={v.url} width={50} height={50} key={v.id} />
))}
</Descriptions.Item>
<Descriptions.Item label='商品标签'>
{goodsInfoDetails?.goodsLabel || '暂无'}
</Descriptions.Item>
<Descriptions.Item label='商品分类'>
{categoryList
.reduce((pre: string[], cur) => {
if (cur.id === goodsInfoDetails?.categoryPrimaryId) {
pre.push(cur.name);
if (goodsInfoDetails?.categorySubId) {
pre.push(
cur.subDTOList.find((v) => v.id === goodsInfoDetails?.categorySubId)?.name ||
'',
);
}
}
return pre;
}, [])
.join('/')}
</Descriptions.Item>
<Descriptions.Item label='商品状态'>
<Badge
status={goodsInfoDetails?.shelfStatus ? 'processing' : 'default'}
text={goodsInfoDetails?.shelfStatus ? '上架中' : '已下架'}
/>
</Descriptions.Item>
<Descriptions.Item label='商品视频' span={1}>
{goodsInfoDetails?.resourcesList
.filter((v) => v.type === 2)
.map((v) => (
<video
src={v.url}
key={v.id}
style={{ width: '200px', height: '200px' }}
controls
/>
))}
</Descriptions.Item>
<Descriptions.Item label='商品描述'>{goodsInfoDetails?.description}</Descriptions.Item>
</Descriptions>
</div>
<div className='goods-detail-sku'>
<div className='sku-title'>商品规格</div>
<Table
bordered
columns={tableColumns}
dataSource={goodsInfoDetails?.goodsSpecList}
rowKey='id'
pagination={false}
></Table>
</div>
<div className='goods-detail-introduce'>
<div className='introduce-title'>商品详情</div>
<div
className='introduce-content'
dangerouslySetInnerHTML={{ __html: goodsInfoDetails?.goodsDetails || '' }}
></div>
</div>
<div className='goods-detail-operate'>
<Button type='primary' onClick={backRoute}>
返回
</Button>
</div>
</div>
);
};
export default GoodsDetails;
......@@ -2,30 +2,32 @@ import SearchBox, { searchColumns } from '~/components/search-box';
import React, { useEffect, useRef, useState } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { Button, Card, Image, message, Modal, Table } from 'antd';
import {
ArrowDownOutlined,
ArrowUpOutlined,
DeleteOutlined,
PlusOutlined,
} from '@ant-design/icons';
import { ArrowDownOutlined, ArrowUpOutlined, PlusOutlined } from '@ant-design/icons';
import { ColumnsType } from 'antd/es/table';
import GoodsAPI from '~/api/modules/goodsAPI';
import { InterDataType, InterReqType, PaginationProps } from '~/api/interface';
import { listGoodsType } from '~/api/interface/goodsType';
import { CategoryManageAPI } from '~/api';
import { listPageGoodsInfoType } from '~/api/interface/goodsType';
import { filterObjAttr } from '~/utils';
import qs from 'query-string';
import { CategoryManageAPI } from '~/api';
import { categoryListRespType } from '~/api/interface/categoryManage';
import { useSelector } from 'react-redux';
import _ from 'lodash';
//商品返回类型
type goodsType = InterDataType<listGoodsType>['list'];
type goodsType = InterDataType<listPageGoodsInfoType>['list'];
//商品列表筛选类型
type goodsSearchParameters = Omit<InterReqType<listGoodsType>, 'goodsType'>;
type goodsSearchParameters = InterReqType<listPageGoodsInfoType>;
//分类返回类型
type categoryType = InterDataType<categoryListRespType>['list'];
const GoodsList = () => {
//筛选ref
const searchRef = useRef();
const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams();
const { userInfo } = useSelector((state: any) => state.UserInfo);
const tabList = [
{
key: '1',
......@@ -41,26 +43,22 @@ const GoodsList = () => {
},
];
const [activeTabKey, setActiveTabKey] = useState<string>('1');
//分类列表
const [categoryList, setCategoryList] = useState<categoryType>([]);
const [searchColumns, setSearchColumns] = useState<searchColumns[]>([
{
label: '商品名称',
placeholder: '请输入商品名称',
name: 'goodsName',
name: 'tradeName',
type: 'input',
},
{
label: '所属目录',
placeholder: '请选择所属目录',
name: 'directoryId',
label: '商品类型',
placeholder: '请选择商品类别',
name: 'categoryPrimaryId',
type: 'select',
options: [],
},
{
label: '创建时间',
placeholder: '请输入选择创建时间',
name: 'time',
type: 'rangePicker',
},
]);
const tableColumns: ColumnsType<goodsType[0]> = [
{
......@@ -72,23 +70,30 @@ const GoodsList = () => {
{
title: '图片',
align: 'center',
dataIndex: 'imgUrl',
render: (text: string) => <Image src={text} width={50} height={50} />,
dataIndex: 'resourcesList',
render: (text: goodsType[0]['resourcesList']) => (
<Image src={text.find((v) => v.type === 0)?.url || ''} width={50} height={50} />
),
},
{ title: '商品名称', align: 'center', dataIndex: 'tradeName', width: '20%', ellipsis: true },
{
title: '商品类别',
align: 'center',
render: (_text: string, record: goodsType[0]) => getCategoryStr(record),
},
{ title: '商品名称', align: 'center', dataIndex: 'goodsName' },
{ title: '所属目录', align: 'center', dataIndex: 'directoryName' },
{ title: '创建时间', align: 'center', dataIndex: 'createTime' },
{
title: '状态',
align: 'center',
dataIndex: 'status',
dataIndex: 'shelfStatus',
render: (text: number) => (text ? '上架' : '下架'),
},
{
title: '操作',
align: 'center',
dataIndex: 'id',
render: (id: number) => (
width: '20%',
render: (id: number, record: goodsType[0]) => (
<>
<Button type='link' onClick={() => toEditGoods(id)}>
编辑
......@@ -96,6 +101,15 @@ const GoodsList = () => {
<Button type='link' onClick={() => toGoodsDetail(id)}>
详情
</Button>
<Button type='link' onClick={() => upOrDownShelf(1, id)} disabled={!!record.shelfStatus}>
上架
</Button>
<Button type='link' onClick={() => upOrDownShelf(0, id)} disabled={!record.shelfStatus}>
下架
</Button>
<Button type='link' danger onClick={() => removeMallGoods(id)}>
删除
</Button>
</>
),
},
......@@ -113,20 +127,20 @@ const GoodsList = () => {
totalPage: 1,
});
//筛选
const [query, setQuery] = useState<goodsSearchParameters>({ status: undefined });
const [query, setQuery] = useState<goodsSearchParameters>({ shelfStatus: undefined });
// 表格多选
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const onTabChange = (key: string) => {
pagination.pageNo = 1;
pagination.pageSize = 10;
query.status = key === '1' ? undefined : key === '2' ? 1 : 0;
query.shelfStatus = key === '1' ? undefined : key === '2' ? 1 : 0;
setSearchParams(
qs.stringify({
pageNo: 1,
pageSize: 10,
...query,
status: query.status === undefined ? 'all' : query.status,
shelfStatus: query.shelfStatus === undefined ? 'all' : query.shelfStatus,
}),
);
getGoodsList(query);
......@@ -137,12 +151,13 @@ const GoodsList = () => {
const paginationChange = (pageNo: number, pageSize: number) => {
pagination.pageNo = pageNo;
pagination.pageSize = pageSize;
setSelectedRowKeys([]);
setSearchParams(
qs.stringify({
pageNo: pageNo,
pageSize: pageSize,
...query,
status: query.status === undefined ? 'all' : query.status,
shelfStatus: query.shelfStatus === undefined ? 'all' : query.shelfStatus,
}),
);
getGoodsList(query);
......@@ -152,28 +167,24 @@ const GoodsList = () => {
const searchSuccess = (data: any) => {
pagination.pageNo = 1;
pagination.pageSize = 10;
setQuery({ ...filterObjAttr(data, ['time']), status: query.status });
getGoodsList({ ...filterObjAttr(data, ['time']), status: query.status });
getGoodsList(
{ ...filterObjAttr(data, ['time']), status: query.status, pageSize: 9999, pageNo: 1 },
true,
);
setQuery({ ...data, shelfStatus: query.shelfStatus });
getGoodsList({ ...data, shelfStatus: query.shelfStatus });
getGoodsList({ ...data, shelfStatus: query.shelfStatus, pageSize: 9999, pageNo: 1 }, true);
setSearchParams(
qs.stringify({
pageNo: 1,
pageSize: 10,
...filterObjAttr(data, ['time']),
status: query.status === undefined ? 'all' : query.status,
shelfStatus: query.shelfStatus === undefined ? 'all' : query.shelfStatus,
}),
);
};
//商品列表
const getGoodsList = (query?: goodsSearchParameters, isAll?: boolean) => {
setLoading(true);
GoodsAPI.getGoodsList({
GoodsAPI.getListPageGoodsInfo({
pageNo: pagination.pageNo,
pageSize: pagination.pageSize,
goodsType: 0,
...query,
}).then(({ result }) => {
setLoading(false);
......@@ -202,48 +213,36 @@ const GoodsList = () => {
const toGoodsDetail = (id: number) => {
navigate({
pathname: '/mallManage/mallGoods/detail',
search: `id=${id}&isDetail=1`,
search: `id=${id}`,
});
};
// 表格多选事件
const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
setSelectedRowKeys(newSelectedRowKeys);
};
//获取目录列表
const getDirectoryList = () => {
CategoryManageAPI.getDirectoryListClone({ type: 4 }).then(({ result }) => {
if (result) {
const options = result.map((v) => ({ id: v.id, name: v.directoryName }));
searchColumns[1].options = options;
setSearchColumns([...searchColumns]);
}
});
};
//商品-批量上下架
const batchOnShelfOrTakeDown = (status: number) => {
if (selectedRowKeys.length === 0) {
return message.warning('请先选择商品');
}
GoodsAPI.batchOnShelfOrTakeDown({ goodsIds: selectedRowKeys as number[], status }).then(
({ code }) => {
//商品-单个上下架
const upOrDownShelf = (status: number, id: number) => {
Modal.confirm({
title: '提示',
content: `确认${status ? '上架' : '下架'}该商品?`,
onOk: () => {
GoodsAPI.upOrDownShelf({ id, status }).then(({ code }) => {
if (code === '200') {
message.success(status ? '上架成功' : '下架成功');
getGoodsList(query);
setSelectedRowKeys([]);
}
});
},
);
});
};
//商品-删除
const deleteGoods = () => {
if (selectedRowKeys.length === 0) {
return message.warning('请先选择商品');
}
const removeMallGoods = (id: number) => {
Modal.confirm({
title: '提示',
content: '删除后数据将会丢失,确定删除吗?',
onOk() {
GoodsAPI.batchRemoveWareInfo(selectedRowKeys as number[]).then(({ code }) => {
GoodsAPI.removeMallGoods({ id }).then(({ code }) => {
if (code === '200') {
if (pagination.pageNo !== 1 && tableData.length == 1) {
pagination.pageNo -= 1;
......@@ -257,11 +256,9 @@ const GoodsList = () => {
});
};
//上移
const upGoodsClick = () => {
const upGoodsClick = _.debounce(() => {
if (selectedRowKeys.length === 0) {
message.warning('请选择商品');
} else if (selectedRowKeys.length > 1) {
message.warning('最多选择一个商品');
} else {
const index = tableData.findIndex((v) => v.id === selectedRowKeys[0]);
const allIndex = allGoods.findIndex((v) => v.id === selectedRowKeys[0]);
......@@ -276,7 +273,7 @@ const GoodsList = () => {
: tableData
.filter((_v, i) => index - 1 === i || index === i)
.map((v) => ({ id: v.id }));
GoodsAPI.exchangeGoodsInfo({ firstId: exReqData[0].id, secondId: exReqData[1].id }).then(
GoodsAPI.exchange({ firstId: exReqData[0].id, secondId: exReqData[1].id }).then(
({ code }) => {
if (code === '200') {
message.success('上移成功');
......@@ -287,7 +284,7 @@ const GoodsList = () => {
pageNo: pagination.pageNo,
pageSize: pagination.pageSize,
...query,
status: query.status === undefined ? 'all' : query.status,
shelfStatus: query.shelfStatus === undefined ? 'all' : query.shelfStatus,
}),
);
}
......@@ -298,13 +295,11 @@ const GoodsList = () => {
);
}
}
};
}, 500);
//下移
const downGoodsClick = () => {
const downGoodsClick = _.debounce(() => {
if (selectedRowKeys.length === 0) {
message.warning('请选择商品');
} else if (selectedRowKeys.length > 1) {
message.warning('最多选择一个商品');
} else {
const index = tableData.findIndex((v) => v.id === selectedRowKeys[0]);
const allIndex = allGoods.findIndex((v) => v.id === selectedRowKeys[0]);
......@@ -319,7 +314,7 @@ const GoodsList = () => {
: tableData
.filter((_v, i) => index + 1 === i || index === i)
.map((v) => ({ id: v.id }));
GoodsAPI.exchangeGoodsInfo({ firstId: exReqData[0].id, secondId: exReqData[1].id }).then(
GoodsAPI.exchange({ firstId: exReqData[0].id, secondId: exReqData[1].id }).then(
({ code }) => {
if (code === '200') {
message.success('下移成功');
......@@ -330,7 +325,7 @@ const GoodsList = () => {
pageNo: pagination.pageNo,
pageSize: pagination.pageSize,
...query,
status: query.status === undefined ? 'all' : query.status,
shelfStatus: query.shelfStatus === undefined ? 'all' : query.shelfStatus,
}),
);
}
......@@ -341,48 +336,66 @@ const GoodsList = () => {
);
}
}
}, 500);
//分类列表
const getCategoryList = () => {
CategoryManageAPI.getCategoryRespList({ pageNo: 1, pageSize: 99999 }).then(({ result }) => {
searchColumns[1].options = (result.list || []).map((v) => ({ id: v.id, name: v.name }));
setSearchColumns([...searchColumns]);
setCategoryList(result.list || []);
});
};
//商品列表,分类组合
const getCategoryStr = (record: goodsType[0]) => {
return categoryList
.reduce((pre: string[], cur) => {
if (cur.id === record.categoryPrimaryId) {
pre.push(cur.name);
if (record.categorySubId) {
pre.push(cur.subDTOList.find((v) => v.id === record.categorySubId)?.name || '');
}
}
return pre;
}, [])
.join('/');
};
useEffect(() => {
getDirectoryList();
getCategoryList();
pagination.pageNo = Number(searchParams.get('pageNo') || 1);
pagination.pageSize = Number(searchParams.get('pageSize') || 10);
const queryObj = {
goodsName: searchParams.get('goodsName') || undefined,
directoryId: searchParams.get('directoryId')
? Number(searchParams.get('directoryId'))
: undefined,
startTime: searchParams.get('startTime') || undefined,
endTime: searchParams.get('endTime') || undefined,
status:
searchParams.get('status') === 'all' || searchParams.get('status') === null
tradeName: searchParams.get('goodsName') || undefined,
shelfStatus:
searchParams.get('shelfStatus') === 'all' || searchParams.get('shelfStatus') === null
? undefined
: Number(searchParams.get('status')),
: Number(searchParams.get('shelfStatus')),
};
getGoodsList(queryObj);
getGoodsList({ ...queryObj, pageNo: 1, pageSize: 9999 }, true);
setActiveTabKey(
searchParams.get('status') === 'all' || searchParams.get('status') === null
searchParams.get('shelfStatus') === 'all' || searchParams.get('shelfStatus') === null
? '1'
: Number(searchParams.get('status')) === 1
: Number(searchParams.get('shelfStatus')) === 1
? '2'
: '3',
);
(searchRef.current as any).getForm().setFieldsValue({
goodsName: searchParams.get('goodsName') || undefined,
directoryId: searchParams.get('directoryId')
? Number(searchParams.get('directoryId'))
: undefined,
time: searchParams.get('startTime')
? [searchParams.get('startTime'), searchParams.get('endTime')]
: undefined,
tradeName: searchParams.get('goodsName') || undefined,
});
}, []);
return (
<div className='goods-list'>
<SearchBox
search={searchColumns}
child={
<Button type='primary' icon={<PlusOutlined />} onClick={toAddMallGoods}>
<Button
type='primary'
icon={<PlusOutlined />}
onClick={toAddMallGoods}
disabled={!!userInfo.roleInfo.superAdmin}
>
新增商品
</Button>
}
......@@ -396,6 +409,7 @@ const GoodsList = () => {
style={{ marginRight: '10px' }}
icon={<ArrowUpOutlined />}
onClick={upGoodsClick}
disabled={!!userInfo.roleInfo.superAdmin}
>
上移
</Button>
......@@ -404,33 +418,10 @@ const GoodsList = () => {
style={{ marginRight: '10px' }}
icon={<ArrowDownOutlined />}
onClick={downGoodsClick}
disabled={!!userInfo.roleInfo.superAdmin}
>
下移
</Button>
{activeTabKey !== '2' && (
<Button
type='primary'
style={{ marginRight: '10px' }}
icon={<ArrowUpOutlined />}
onClick={() => batchOnShelfOrTakeDown(1)}
>
上架
</Button>
)}
{activeTabKey !== '3' && (
<Button
type='primary'
style={{ marginRight: '10px' }}
icon={<ArrowDownOutlined />}
onClick={() => batchOnShelfOrTakeDown(0)}
>
下架
</Button>
)}
<Button danger icon={<DeleteOutlined />} onClick={deleteGoods}>
删除
</Button>
</div>
<Table
columns={tableColumns}
......@@ -440,6 +431,8 @@ const GoodsList = () => {
rowSelection={{
selectedRowKeys,
onChange: onSelectChange,
type: 'radio',
hideSelectAll: true,
}}
loading={loading}
pagination={{
......
import { FC, useState } from 'react';
import { Form, message, Modal, ModalProps, Select } from 'antd';
import { CommonAPI, SystemManageAPI } from '~/api';
interface selfProps {
onOk: () => void;
companyInfoId: number;
}
const AddPeopleModal: FC<ModalProps & selfProps> = ({ open, onCancel, onOk, companyInfoId }) => {
const [form] = Form.useForm<{ userAccountId: number }>();
const [options, setOptions] = useState<{ label: string; value: number; key: string }[]>([]);
const handleOk = () => {
form.validateFields().then((values) => {
SystemManageAPI.bindingCompanyMember({ ...values, companyInfoId }).then(({ code }) => {
if (code === '200') {
message.success('绑定成功');
onOk();
form.resetFields();
}
});
});
};
//select 搜索
const selectSearchEvent = (value: string) => {
CommonAPI.getUserAccountByPhoneNum({ phoneNum: value }).then(({ result }) => {
const list = (result || []).map((v) => ({
label: v.phoneNum + `(${v.uid})`,
value: v.id,
key: value,
}));
setOptions(list);
});
};
return (
<Modal open={open} title='添加成员' onOk={handleOk} onCancel={onCancel}>
<Form form={form}>
<Form.Item
label='成员'
name='userAccountId'
rules={[{ required: true, message: '请选择成员' }]}
>
<Select
placeholder='请输入成员手机号码'
showSearch
onSearch={selectSearchEvent}
filterOption={(input, option) =>
(option?.key ?? '').toLowerCase().includes(input.toLowerCase())
}
options={options}
></Select>
</Form.Item>
</Form>
</Modal>
);
};
export default AddPeopleModal;
import { Form, message, Modal, ModalProps, Select } from 'antd';
import { FC, useEffect, useState } from 'react';
import { SystemManageAPI } from '~/api';
import { InterDataType } from '~/api/interface';
import { listCompanyMembersType } from '~/api/interface/systemManageType';
//单位-成员列表返回类型
type companyMembersType = InterDataType<listCompanyMembersType>['list'];
interface selfProps {
onOk: () => void;
companyInfoId: number;
currentCompanyMembers: companyMembersType[0] | undefined;
}
const TransferLeaderModal: FC<ModalProps & selfProps> = ({
open,
onOk,
onCancel,
companyInfoId,
currentCompanyMembers,
}) => {
const [form] = Form.useForm<{ toUserAccountId: number }>();
const [listCompanyMembers, setListCompanyMembers] = useState<companyMembersType>([]);
const handleOk = () => {
form.validateFields().then((values) => {
if (currentCompanyMembers) {
SystemManageAPI.transferLeader({
companyInfoId,
fromUserAccountId: currentCompanyMembers.id,
...values,
}).then(({ code }) => {
if (code === '200') {
message.success('转让成功');
onOk();
}
});
}
});
};
//单位成员列表
const getListCompanyMembers = () => {
SystemManageAPI.getListCompanyMembers({
companyInfoId,
pageNo: 1,
pageSize: 999999,
}).then(({ result }) => {
setListCompanyMembers((result.list || []).filter((v) => !v.leader));
});
};
useEffect(() => {
if (currentCompanyMembers) {
getListCompanyMembers();
}
}, [currentCompanyMembers]);
return (
<Modal open={open} title='转让管理员' onCancel={onCancel} onOk={handleOk}>
<Form form={form}>
<Form.Item label='转让人' name='toUserAccountId'>
<Select
placeholder='请选择受让人'
filterOption={(input, option) =>
(option!.children as unknown as string).toLowerCase().includes(input.toLowerCase())
}
>
{listCompanyMembers.map((v) => (
<Select.Option key={v.id} value={v.id}>
{v.phoneNum}({v.uid})
</Select.Option>
))}
</Select>
</Form.Item>
</Form>
</Modal>
);
};
export default TransferLeaderModal;
.company-detail{
position: relative;
&-info{
margin-bottom: 20px;
}
&-people{
&-title{
line-height: 50px;
font-weight: bold;
font-size: 14px;
}
&-operate{
margin-bottom: 10px;
}
}
&-operate{
position: absolute;
right: 0;
top: 0;
}
}
import { useSearchParams, useNavigate } from 'react-router-dom';
import { useEffect, useState } from 'react';
import { SystemManageAPI } from '~/api';
import { InterDataType, PaginationProps } from '~/api/interface';
import { getCompanyInfoByIdType, listCompanyMembersType } from '~/api/interface/systemManageType';
import { Button, Descriptions, message, Modal, Table } from 'antd';
import './index.scss';
import { ColumnsType } from 'antd/es/table';
import { PlusOutlined } from '@ant-design/icons';
import AddPeopleModal from './components/addPeopleModal';
import { useSelector } from 'react-redux';
import TransferLeaderModal from '~/pages/systemManage/companyManage/companyDetail/components/transferLeaderModal';
//单位详情-返回类型
type companyDetailType = InterDataType<getCompanyInfoByIdType>;
//单位-成员列表返回类型
type companyMembersType = InterDataType<listCompanyMembersType>['list'];
const CompanyDetail = () => {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const { userInfo } = useSelector((state: any) => state.UserInfo);
const tableColumns: ColumnsType<companyMembersType[0]> = [
{
title: 'uid',
align: 'center',
dataIndex: 'uid',
},
{
title: '姓名',
align: 'center',
dataIndex: 'userName',
},
{
title: '角色',
align: 'center',
dataIndex: 'leader',
render: (text: number) => (text ? '单位管理员' : '普通员工'),
},
{
title: '手机号',
align: 'center',
dataIndex: 'phoneNum',
},
{
title: '操作',
width: '15%',
onHeaderCell: () => ({
style: {
textAlign: 'center',
},
}),
render: (_text: string, record) => (
<>
<Button
type='link'
disabled={!userInfo.companyInfoVO.leader || !record.leader}
onClick={() => transferLeaderClick(record)}
>
转让
</Button>
<Button
type='link'
disabled={!userInfo.companyInfoVO.leader || !!record.leader}
onClick={() => unbindCompanyClick(record)}
>
解绑
</Button>
</>
),
},
];
const [companyId, setCompanyId] = useState<number>(-1);
const [companyDetail, setCompanyDetail] = useState<companyDetailType>();
const [pagination, setPagination] = useState<PaginationProps & { totalCount: number }>({
pageNo: 1,
pageSize: 10,
totalCount: 0,
});
const [listCompanyMembers, setListCompanyMembers] = useState<companyMembersType>([]);
const [currentCompanyMembers, setCurrentCompanyMembers] = useState<companyMembersType[0]>();
const [addPeopleModalShow, setAddPeopleModalShow] = useState<boolean>(false);
const [transferLeaderModalShow, setTransferLeaderModalShow] = useState<boolean>(false);
//单位详情
const getCompanyDetailInfo = (id: number) => {
SystemManageAPI.getCompanyInfoById({ id }).then(({ result }) => {
setCompanyDetail(result);
});
};
//单位成员列表
const getListCompanyMembers = (companyInfoId: number) => {
SystemManageAPI.getListCompanyMembers({
companyInfoId,
pageNo: pagination.pageNo,
pageSize: pagination.pageSize,
}).then(({ result }) => {
pagination.totalCount = result.totalCount;
setPagination(pagination);
setListCompanyMembers(result.list || []);
});
};
//分页
const paginationChange = (pageNo: number, pageSize: number) => {
pagination.pageNo = pageNo;
pagination.pageSize = pageSize;
getListCompanyMembers(companyId);
};
//添加成员弹窗
const addPeopleClick = () => {
setAddPeopleModalShow(true);
};
const addPeopleModalOk = () => {
getListCompanyMembers(companyId);
setAddPeopleModalShow(false);
};
const addPeopleModalCancel = () => {
setAddPeopleModalShow(false);
};
//转让管理员弹窗
const transferLeaderClick = (record: companyMembersType[0]) => {
setCurrentCompanyMembers(record);
setTransferLeaderModalShow(true);
};
const transferLeaderModalCancel = () => {
setTransferLeaderModalShow(false);
};
const transferLeaderModalOk = () => {
setTransferLeaderModalShow(false);
getListCompanyMembers(companyId);
};
//接触绑定
const unbindCompanyClick = (record: companyMembersType[0]) => {
Modal.confirm({
title: '提示',
content: '确认解除绑定该成员?',
onOk: () => {
SystemManageAPI.unbindCompanyMember({
userAccountId: record.id,
companyInfoId: companyId,
}).then(({ code }) => {
if (code === '200') {
message.success('解除成功');
if (pagination.pageNo !== 1 && listCompanyMembers?.length === 1) {
pagination.pageNo -= 1;
}
getListCompanyMembers(companyId);
}
});
},
});
};
//返回
const backRoute = () => {
navigate(-1);
};
useEffect(() => {
setCompanyId(Number(searchParams.get('id')));
getCompanyDetailInfo(Number(searchParams.get('id')));
getListCompanyMembers(Number(searchParams.get('id')));
}, []);
return (
<div className='company-detail'>
<div className='company-detail-info'>
<Descriptions title='基本信息' bordered column={4}>
<Descriptions.Item label='单位名称'>{companyDetail?.companyName}</Descriptions.Item>
<Descriptions.Item label='详细地址'>{companyDetail?.address}</Descriptions.Item>
<Descriptions.Item label='联系人'>
{companyDetail?.companyUserName || ''}
</Descriptions.Item>
<Descriptions.Item label='联系电话'>{companyDetail?.phoneNum || ''}</Descriptions.Item>
</Descriptions>
</div>
<div className='company-detail-people'>
<div className='company-detail-people-title'>成员信息</div>
<div className='company-detail-people-operate'>
<Button
type='primary'
icon={<PlusOutlined></PlusOutlined>}
onClick={addPeopleClick}
disabled={!userInfo.companyInfoVO.leader}
>
添加成员
</Button>
</div>
<Table
bordered
columns={tableColumns}
dataSource={listCompanyMembers}
pagination={{
total: pagination.totalCount,
pageSize: pagination.pageSize,
current: pagination.pageNo,
showSizeChanger: true,
showQuickJumper: true,
onChange: (page: number, pageSize: number) => paginationChange(page, pageSize),
showTotal: (total, range) => `当前 ${range[0]}-${range[1]} 条记录 / 共 ${total} 条数据`,
}}
/>
</div>
<div className='company-detail-operate'>
<Button type='primary' onClick={backRoute}>
返回
</Button>
</div>
{/*添加成员*/}
<AddPeopleModal
open={addPeopleModalShow}
onCancel={addPeopleModalCancel}
onOk={addPeopleModalOk}
companyInfoId={companyId}
/>
{/*转让管理员*/}
<TransferLeaderModal
open={transferLeaderModalShow}
onCancel={transferLeaderModalCancel}
onOk={transferLeaderModalOk}
companyInfoId={companyId}
currentCompanyMembers={currentCompanyMembers}
/>
</div>
);
};
export default CompanyDetail;
import React, { useEffect, useState } from 'react';
import { InterListType, InterReqType } from '~/api/interface';
import { listCompanyAdd, listCompanyPage } from '~/api/interface/systemManageType';
import { Cascader, Form, Input, message, Modal } from 'antd';
import { Button, Col, Form, Input, message, Modal, Row } from 'antd';
import { SystemManageAPI } from '~/api';
import DistrictData from '~/assets/json/district.json';
import { EnvironmentOutlined } from '@ant-design/icons';
import SelectMap from '~/components/select-map';
// 列表的类型
type TableType = InterListType<listCompanyPage>;
// 请求的表单类型
type ReqType = InterReqType<listCompanyAdd>;
type ReqType = Exclude<InterReqType<listCompanyAdd>, undefined>;
// 传参类型
interface propType {
title: string;
......@@ -21,14 +22,15 @@ const AddEditModal: React.FC<propType> = (props) => {
const { title, open, closed, data } = props;
/// 表单钩子
const [form] = Form.useForm<ReqType>();
// 地区
const [localList, setLocalList] = useState<
{
childInfo: any[];
id: number;
name: string;
}[]
>();
//选点信息
const [position, setPosition] = useState<{
lat: number;
lon: number;
address: string;
}>();
//地图选点弹窗
const [selectMapShow, setSelectMapShow] = useState<boolean>(false);
// 关闭弹窗
const handleCancel = () => {
form.resetFields();
......@@ -39,13 +41,14 @@ const AddEditModal: React.FC<propType> = (props) => {
form
.validateFields()
.then(async (values) => {
if (position) {
await handleSubmit({
...values,
province: values?.area?.at(0),
city: values?.area?.at(1),
district: values?.area?.at(2),
lat: position.lat,
lon: position.lon,
companyType: data?.id === 1 ? 0 : 1,
});
}
})
.catch((err) => {
message
......@@ -66,22 +69,40 @@ const AddEditModal: React.FC<propType> = (props) => {
handleCancel();
}
};
// 获取地区信息
const getSecondDistrictInfo = async () => {
setLocalList(DistrictData || []);
const selectMapShowClick = () => {
setSelectMapShow(true);
};
const selectMapClose = () => {
setSelectMapShow(false);
};
const selectMapSubmit = (value: { lat: number; lon: number; address: string }) => {
form.setFieldValue('address', value.address);
setPosition(value);
setSelectMapShow(false);
};
const addressInputEvent = (e: any) => {
if (position) {
position.address = e.target.value;
form.setFieldValue('address', e.target.value || undefined);
setPosition({ ...position });
}
};
// componentDidMount
useEffect(() => {
if (!open) return;
getSecondDistrictInfo().then();
if (!data) return;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
form.setFieldsValue({
...data,
area: [data?.province, data?.city, data?.district],
});
// console.log('data --->', data);
setPosition({
address: data.address,
lat: data.lat,
lon: data.lon,
});
}, [open]);
return (
<Modal
......@@ -100,35 +121,55 @@ const AddEditModal: React.FC<propType> = (props) => {
>
<Input placeholder={'请输入单位名称'} maxLength={25} allowClear />
</Form.Item>
<Form.Item
label='单位全称'
rules={[{ required: false, message: '请输入单位全称' }]}
name='fullName'
>
<Input placeholder={'请输入单位全称'} maxLength={50} allowClear />
</Form.Item>
<Form.Item
label='行政区划'
name='area'
rules={[{ required: true, message: '请选择行政区划' }]}
>
<Cascader
options={localList}
placeholder='请选择行政区划'
allowClear
fieldNames={{
label: 'name',
value: 'name',
children: 'childInfo',
}}
/>
</Form.Item>
{/*<Form.Item*/}
{/* label='单位全称'*/}
{/* rules={[{ required: false, message: '请输入单位全称' }]}*/}
{/* name='fullName'*/}
{/*>*/}
{/* <Input placeholder={'请输入单位全称'} maxLength={50} allowClear />*/}
{/*</Form.Item>*/}
{/*<Form.Item*/}
{/* label='行政区划'*/}
{/* name='area'*/}
{/* rules={[{ required: true, message: '请选择行政区划' }]}*/}
{/*>*/}
{/* <Cascader*/}
{/* options={localList}*/}
{/* placeholder='请选择行政区划'*/}
{/* allowClear*/}
{/* fieldNames={{*/}
{/* label: 'name',*/}
{/* value: 'name',*/}
{/* children: 'childInfo',*/}
{/* }}*/}
{/* />*/}
{/*</Form.Item>*/}
<Form.Item
label='详细地址'
name='address'
rules={[{ required: true, message: '请输入详细地址' }]}
>
<Input.TextArea placeholder='请输入详细地址' maxLength={50} showCount />
{/*<Input.TextArea placeholder='请输入详细地址' maxLength={50} showCount />*/}
<Row justify='space-between'>
{position ? (
<Col span={21}>
<Input
placeholder='请输入详细地址'
value={position.address}
onChange={addressInputEvent}
/>
</Col>
) : (
''
)}
<Col span={2}>
<Button
icon={<EnvironmentOutlined />}
type='primary'
onClick={selectMapShowClick}
></Button>
</Col>
</Row>
</Form.Item>
<Form.Item label='联系人' name='companyUserName'>
<Input placeholder='请输入联系人' maxLength={15} />
......@@ -152,6 +193,7 @@ const AddEditModal: React.FC<propType> = (props) => {
<Form.Item label='备注' name='remark'>
<Input.TextArea placeholder='请输入备注' maxLength={50} showCount />
</Form.Item>
<SelectMap open={selectMapShow} closed={selectMapClose} submit={selectMapSubmit} />
</Form>
</Modal>
);
......
......@@ -6,6 +6,7 @@ import { InterListType, InterReqType } from '~/api/interface';
import { ColumnsType } from 'antd/es/table';
import { SystemManageAPI } from '~/api';
import { listCompanyPage } from '~/api/interface/systemManageType';
import { useNavigate } from 'react-router-dom';
import AddEditModal from './comp/addEditModal';
// 列表的数据类型
......@@ -16,6 +17,7 @@ type ReqType = InterReqType<listCompanyPage>;
let query: ReqType = {};
const CompanyManageView = () => {
const navigate = useNavigate();
const { confirm } = Modal;
// 新增编辑弹窗是否开启
const [addEditModalVisible, setAddEditModalVisible] = useState(false);
......@@ -63,7 +65,7 @@ const CompanyManageView = () => {
// 删除单位
const handleDelete = (value: TableType[0]) => {
confirm({
title: '提示',
title: '提示(删除会造成该单位下的商品全部删除)',
content: '是否删除该单位?',
onOk: async () => {
const res = await SystemManageAPI.listCompanyRemove({ id: value.id });
......@@ -77,6 +79,14 @@ const CompanyManageView = () => {
},
});
};
//跳转单位详情
const toCompanyDetail = (record: TableType[0]) => {
navigate({
pathname: '/systemManage/companyDetail',
search: `id=${record.id}`,
});
};
// componentDidMount
useEffect(() => {
query = {};
......@@ -88,17 +98,11 @@ const CompanyManageView = () => {
title: '单位名称',
dataIndex: 'companyName',
align: 'center',
width: '150px',
width: '20%',
fixed: 'left',
ellipsis: true,
},
{
title: '行政区划',
dataIndex: 'province',
align: 'center',
render: (_value, record) => `${record.province} / ${record.city} / ${record.district}`,
},
{
title: '详细地址',
dataIndex: 'address',
align: 'center',
......@@ -117,7 +121,7 @@ const CompanyManageView = () => {
},
{
title: '操作',
width: '100px',
width: '20%',
fixed: 'right',
align: 'center',
render: (_text, record) => (
......@@ -131,6 +135,9 @@ const CompanyManageView = () => {
>
变更
</Button>
<Button type='link' onClick={() => toCompanyDetail(record)}>
详情
</Button>
<Button
type='link'
danger
......
......@@ -3,17 +3,15 @@ import { RouteObjectType, routerList } from '~/router/router';
import { InterDataType } from '~/api/interface';
import { listMenuInfoType } from '~/api/interface/systemManageType';
import { SystemManageAPI } from '~/api';
import Cookies from 'js-cookie';
import { store } from '~/store';
import { SET_MENU } from '~/store/module/menu';
//菜单类型
type menuType = InterDataType<listMenuInfoType>;
// 缓存路由列表
let routerListStore: any[] = [];
// 获取用户权限路由列表
export const authRouterList = async () => {
if (localStorage.getItem('roleId') && Cookies.get('SHAREFLY-TOKEN')) {
// 如果缓存中没有数据
if (routerListStore.length === 0) {
if (store.getState().Menu.menuList.length === 0) {
// 加载路由数据
const { result } = await SystemManageAPI.getListRoleMenuInfo({
roleId: Number(localStorage.getItem('roleId')),
......@@ -33,14 +31,12 @@ export const authRouterList = async () => {
};
const arr = [...getRouteList(routerList)];
// 将路由数据存到store中
routerListStore = arr;
store.dispatch(SET_MENU(arr));
// 完成后返回路由数据
return Promise.resolve(arr);
} else {
return Promise.resolve(routerListStore);
return Promise.resolve(store.getState().Menu.menuList);
}
}
return Promise.resolve([]);
};
//获取全部节点
const getAllKeys = (data: menuType[]) => {
......
......@@ -14,23 +14,29 @@ function PrivateRouter() {
// TODO: 判断是否登录 (需要改为实时获取地址栏的路由)
const path = location.pathname;
const token = Cookies.get('SHAREFLY-TOKEN');
const roleId = localStorage.getItem('roleId');
if (!token && path !== '/login') {
navigate('/login', { replace: true });
return;
}
};
useEffect(() => {
beforeEach();
if (roleId && token) {
// 整合路由数据
authRouterList().then((value) => {
if (value.length) {
const routes = [...value, ...whiteRouterList];
setRouter(routes); //不同账号登录时,重新更新路由(有瑕疵)
} else if (localStorage.getItem('roleId')) {
setRouter(routes);
if (path === '/') {
navigate({ pathname: value[0].children?.find((v: any) => !v.meta.hidden)?.path });
}
} else {
message.warning('该账号暂无权限');
navigate('/login', { replace: true });
}
});
}
};
useEffect(() => {
beforeEach();
}, [location.pathname]);
return useRoutes(router);
......
......@@ -91,13 +91,16 @@ const RentAddOrEditOrDetailView = React.lazy(
const MallGoodsView = React.lazy(() => import('~/pages/mallManage/mallGoods/goodsList')); //商城商品
const MallAddOrEditOrDetailView = React.lazy(
() => import('~/pages/mallManage/mallGoods/goodsAddOrEditOrDetail'),
); //商城商品新增、编辑、详情
); //商城商品新增、编辑、租赁商品详情
const MallGoodsDetailsView = React.lazy(() => import('~/pages/mallManage/mallGoods/goodsDetails')); //商城商品(新)
const ProduceListView = React.lazy(() => import('~/pages/mallManage/produceManage/produceList')); //产品列表
const ProduceDetailView = React.lazy(
() => import('~/pages/mallManage/produceManage/produceDetail'),
); //产品详情
const MakeListView = React.lazy(() => import('~/pages/mallManage/makeManage/makeList'));
// 分类管理
const CategoryListView = React.lazy(() => import('~/pages/categoryManage/categoryList'));
const CategoryManage = React.lazy(() => import('~/pages/categoryManage/category'));
const CategoryDetail = React.lazy(() => import('~/pages/categoryManage/category/detail'));
// 目录管理
......@@ -119,7 +122,10 @@ import TenderManageDetail from '~/pages/resourceManage/tenderManage/detail';
import TenderManageFeedback from '~/pages/resourceManage/tenderManage/feedback';
import BusinessCaseManage from '~/pages/resourceManage/businessCaseManage';
import CustomIdentityView from '~/pages/customManage/customIdentity';
import CompanyManageView from '~/pages/systemManage/companyManage';
import CompanyListView from '~/pages/systemManage/companyManage/companyList'; //单位列表
const CompanyDetailView = React.lazy(
() => import('~/pages/systemManage/companyManage/companyDetail'),
);
import AccountLimit from '~/pages/systemManage/limitManage/roleList'; //账号权限
import LimitInfo from '~/pages/systemManage/limitManage/limitInfo';
import CustomListDetail from '~/pages/customManage/customList/detail'; //权限信息
......@@ -467,16 +473,6 @@ export const routerList: Array<RouteObjectType> = [
},
children: [
{
path: '/mallManage/courseManage',
element: withLoadingComponent(<CourseManageView />),
errorElement: <ErrorPage />,
meta: {
id: 1010,
icon: <BookOutlined />,
title: '课程管理',
},
},
{
path: '/mallManage/serviceList',
element: withLoadingComponent(<ServiceListView />),
errorElement: <ErrorPage />,
......@@ -585,7 +581,7 @@ export const routerList: Array<RouteObjectType> = [
},
{
path: '/mallManage/mallGoods/detail',
element: withLoadingComponent(<MallAddOrEditOrDetailView />),
element: withLoadingComponent(<MallGoodsDetailsView />),
errorElement: <ErrorPage />,
meta: {
id: 10146,
......@@ -687,8 +683,8 @@ export const routerList: Array<RouteObjectType> = [
},
},
{
path: '/categoryManage/jobServicesCategory/4',
element: withLoadingComponent(<CategoryManage />),
path: '/categoryManage/categoryList',
element: withLoadingComponent(<CategoryListView />),
errorElement: <ErrorPage />,
meta: {
id: 1240,
......@@ -696,6 +692,16 @@ export const routerList: Array<RouteObjectType> = [
icon: <AppstoreOutlined />,
},
},
// {
// path: '/categoryManage/jobServicesCategory/4',
// element: withLoadingComponent(<CategoryManage />),
// errorElement: <ErrorPage />,
// meta: {
// id: 1240,
// title: '产品商城分类',
// icon: <AppstoreOutlined />,
// },
// },
{
path: '/categoryManage/jobServicesCategory/0',
element: withLoadingComponent(<CategoryManage />),
......@@ -903,7 +909,7 @@ export const routerList: Array<RouteObjectType> = [
meta: {
id: 1600,
icon: <BankOutlined />,
title: '飞手培训',
title: '执照培训',
},
children: [
{
......@@ -938,6 +944,16 @@ export const routerList: Array<RouteObjectType> = [
hidden: true,
},
},
{
path: '/pilotTraining/courseManage',
element: withLoadingComponent(<CourseManageView />),
errorElement: <ErrorPage />,
meta: {
id: 1010,
icon: <BookOutlined />,
title: '课程管理',
},
},
],
},
{
......@@ -982,13 +998,24 @@ export const routerList: Array<RouteObjectType> = [
},
},
{
path: '/systemManage/companyManage',
element: withLoadingComponent(<CompanyManageView />),
path: '/systemManage/companyList',
element: withLoadingComponent(<CompanyListView />),
errorElement: <ErrorPage />,
meta: {
id: 1430,
title: '单位管理',
title: '单位列表',
icon: <BankOutlined />,
},
},
{
path: '/systemManage/companyDetail',
element: withLoadingComponent(<CompanyDetailView />),
errorElement: <ErrorPage />,
meta: {
id: 1440,
title: '单位详情',
icon: <BankOutlined />,
hidden: true,
},
},
],
......
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
menuList: JSON.parse(localStorage.getItem('SXTB-ADMIN-MENU-LIST') as string) || [],
menuList: [],
collapsedActive: false,
menuId: JSON.parse(localStorage.getItem('SXTB-ADMIN-MENU-ID') as string) || null,
roleId: localStorage.getItem('roleId') ? Number(localStorage.getItem('roleId')) : -1,
};
export const menuSlice = createSlice({
......@@ -12,22 +12,19 @@ export const menuSlice = createSlice({
reducers: {
SET_MENU(state, action) {
state.menuList = action.payload;
localStorage.setItem('SXTB-ADMIN-MENU-LIST', JSON.stringify(action.payload));
},
SET_COLLAPSE(state, action) {
state.collapsedActive = action.payload;
},
REMOVE_MENU(state) {
localStorage.setItem('SXTB-ADMIN-MENU-LIST', JSON.stringify([]));
state.menuList = [];
},
SET_MENU_ID(state, action) {
localStorage.setItem('SXTB-ADMIN-MENU-ID', JSON.stringify(action.payload));
state.menuId = action.payload;
state.roleId = action.payload;
localStorage.setItem('roleId', action.payload);
},
REMOVE_MENU_ID(state) {
localStorage.setItem('SXTB-ADMIN-MENU-ID', JSON.stringify(null));
state.menuId = null;
REMOVE_MENU_ID() {
localStorage.removeItem('roleId');
},
},
});
......
......@@ -2,8 +2,6 @@ import { createSlice } from '@reduxjs/toolkit';
const initialState = {
userInfo: JSON.parse(localStorage.getItem('SXTB-ADMIN-USER-INFO') as string) || [],
roleId: -1,
roleList: [],
};
export const userInfoSlice = createSlice({
......@@ -14,24 +12,9 @@ export const userInfoSlice = createSlice({
state.userInfo = action.payload;
localStorage.setItem('SXTB-ADMIN-USER-INFO', JSON.stringify(action.payload));
},
SET_ROLE_ID(state, action) {
state.roleId = action.payload;
localStorage.setItem('roleId', action.payload);
},
REMOVE_ROLE_ID() {
localStorage.removeItem('roleId');
},
SET_ROLE_LIST(state, action) {
state.roleList = action.payload;
localStorage.setItem('roleObj', JSON.stringify(action.payload));
},
REMOVE_ROLE_LIST() {
localStorage.removeItem('roleObj');
},
},
});
export const { SET_USERINFO, SET_ROLE_ID, SET_ROLE_LIST, REMOVE_ROLE_LIST, REMOVE_ROLE_ID } =
userInfoSlice.actions;
export const { SET_USERINFO } = userInfoSlice.actions;
export const UserInfo = userInfoSlice.reducer;
import _ from 'lodash';
// 不能输入数字,其他可以输入
export const exceptNumber = (val: any) => {
val.target.value = val.target.value.replace(/1?(\d|([1-9]\d+))(.\d+)?$/g, '').replace(/\s/g, '');
......@@ -105,3 +107,13 @@ export const maxLength6 = (val: any) => {
val.target.value = val.target.value.replace(/\D/g, '');
}
};
//通用价格,且保留两位小数正则
export const regPriceNumber = (value: string): boolean => {
const reg = /^\d+(\.\d{1,2})?$/;
return reg.test(value);
};
//判断空值
export const isEmptyBol = (value: any): boolean => {
return _.isNull(value) || _.isNaN(value) || _.isNull(value) || _.isUndefined(value);
};
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论