Commit b99602c7 authored by Bill's avatar Bill

feat: 渠道品类导航页

parent f6cce542
......@@ -8,6 +8,7 @@
*/
import { RouterChild } from '../utils/index';
const ShopRoute: RouterChild = {
path: '/memberCenter/channelAbility',
name: '渠道能力',
......@@ -33,6 +34,13 @@ const ShopRoute: RouterChild = {
component: '@/pages/channel/templateDetail',
},
{
path: `/memberCenter/channelAbility/template/categoryNavigation`,
name: '渠道品类导航页装修',
hideInMenu: true,
noLayout: true,
component: '@/pages/mobileTemplate/categoryNavigation',
},
{
path: '/memberCenter/channelAbility/channelSeo',
name: '店铺SEO设置',
component: '@/pages/channel/channelSeo',
......
......@@ -34,5 +34,8 @@ export interface RouterChild {
* @memberof RouterChild
*/
noMargin?: boolean
/** 是否使用上级路由布局 */
noLayout?: boolean
}
......@@ -59,3 +59,16 @@
.fileEmpty {
margin-bottom: 0;
}
.uploadContainer {
& > span {
width: 100%;
display: flex;
:global {
.ant-upload {
width: 100%;
}
}
}
}
import React, { useEffect, useState } from 'react';
import cx from 'classnames';
import { UploadProps, UploadChangeParam, UploadFile } from 'antd/lib/upload/interface';
import { UPLOAD_TYPE } from '@/constants';
import { Upload, Progress, Button, message } from 'antd';
import { CloudUploadOutlined, DeleteOutlined } from '@ant-design/icons';
import styles from './UploadFiles.less';
import { UPLOAD_TYPE } from '@/constants';
import pdfIcon from '@/assets/imgs/pdf_icon.png';
import { getAuth } from '@/utils/auth';
import styles from './UploadFiles.less';
type PickProps = "headers" | "action" | "accept" | "beforeUpload" | "onChange" | "fileList"
......@@ -24,7 +24,15 @@ interface PickUploadProps extends Pick<UploadProps, PickProps> {
customizeItemRender?: ((files: UploadFile[], handleRemove: (fileItem: UploadFile) => void) => React.ReactNode) | null,
onRemove?: ((fileItem: UploadFile) => void) | null,
/** 是否显示文件 */
showFiles?: boolean
showFiles?: boolean,
/**
* 上传最大数
*/
maxCount?: number,
/**
* 自定义渲染child
*/
renderUploadChild?: (fileList: any[]) => React.ReactNode,
}
const UploadFiles: React.FC<PickUploadProps> = (props: PickUploadProps) => {
......@@ -43,9 +51,12 @@ const UploadFiles: React.FC<PickUploadProps> = (props: PickUploadProps) => {
mode,
buttonText,
fileContainerClassName,
showFiles
showFiles,
renderUploadChild,
maxCount,
} = props;
const hasFileListProps = "fileList" in props;
const hasMaxCount = typeof maxCount !== 'undefined' ? { maxCount } : {};
const auth = getAuth();
const [files, setFiles] = useState<UploadFile[]>(() => props.fileList || []);
// const renderFiles = hasFileListProps ? props.fileList : files;
......@@ -61,23 +72,22 @@ const UploadFiles: React.FC<PickUploadProps> = (props: PickUploadProps) => {
}, styles.renderFileContainer, fileContainerClassName);
const uploadProps = {
disabled: disable,
name: 'file',
fileList: files,
accept: accept,
action: action,
headers: { ...headers, token: auth.token },
headers: { ...headers, token: auth?.token },
data: {
fileType: UPLOAD_TYPE
},
disabled: disable,
// disabled: loading || disabled,
showUploadList: false,
onChange(info: UploadChangeParam) {
console.log(info.file);
if (info.file.status === 'error' || (info.file.status === 'done' && info.file.response?.code !== 1000)) {
message.error(info.file.response?.message || "上传失败, 请检查上传接口");
return;
}
console.log(123123);
// 如果不存在fileList, 只存在onChange 那么也要改变组件的file
if (!("fileList" in props)) {
const fileList = info.fileList;
......@@ -97,9 +107,9 @@ const UploadFiles: React.FC<PickUploadProps> = (props: PickUploadProps) => {
if (onChange) {
onChange(info);
}
},
beforeUpload
beforeUpload,
...hasMaxCount
};
const handleRemove = (fileItem: UploadFile) => {
......@@ -168,10 +178,11 @@ const UploadFiles: React.FC<PickUploadProps> = (props: PickUploadProps) => {
}
{
!disable && (
<div style={{ order: uploadOrder }}>
<div style={{ order: uploadOrder }} className={styles.uploadContainer}>
<Upload {...uploadProps} >
{renderUploadChild?.(files)}
{
children || (
typeof children !== 'undefined' ? children : (
<Button type={mode} icon={<CloudUploadOutlined />}>
{buttonText}
</Button>
......@@ -201,7 +212,7 @@ UploadFiles.defaultProps = {
}
return true;
},
onChange: (file: UploadChangeParam) => { },
onChange: (file: UploadChangeParam) => {},
customizeItemRender: null,
onRemove: null,
disable: false,
......
......@@ -84,6 +84,15 @@ const TemplateDetail: React.FC<TemplateDetailPropsType> = (props) => {
}
}
const handleJump = () => {
if (detailInfo?.environment !== 4) {
message.info("暂不支持该类型模板预览")
return;
}
window.location.href = `/memberCenter/channelAbility/template/categoryNavigation?id=${detailInfo.id}&template=${detailInfo.fileName}&type=${shopType}&shopId=${detailInfo.shopId}`
}
return (
<DetailPage
title="查看模板"
......@@ -127,7 +136,7 @@ const TemplateDetail: React.FC<TemplateDetailPropsType> = (props) => {
</div>
{
detailInfo?.environment === 4 && (
<div className={cx(styles.btn, styles.fit)}>
<div className={cx(styles.btn, styles.fit)} onClick={handleJump}>
<LayoutOutlined />
<label>渠道品类导航装修</label>
</div>
......
......@@ -18,6 +18,7 @@ const getData = async () => {
function useGetLayout() {
const [info, setInfo] = useState<any>(null);
const { id } = usePageStatus();
const [dataSourceFromRequest, setDataSourceFromRequest] = useState<any>(null);
useEffect(() => {
if(!id) {
......@@ -29,9 +30,7 @@ function useGetLayout() {
templateId: id
});
if(code === 1000) {
// setInfo(data);
setInfo({});
console.log(data);
setInfo(data);
}
}
fetchData();
......@@ -40,7 +39,7 @@ function useGetLayout() {
if(info === null) {
return;
}
const dataFromRequest = {};
const { categoryAdornContent } = info;
const { category = [] } = categoryAdornContent || {};
const startKey = 7;
......@@ -66,6 +65,7 @@ function useGetLayout() {
category.forEach((_item, _index) => {
const tabName = "CustomizeTabs.TabItem";
const configKey = startKey + _index;
dataFromRequest[_item.id] = _item;
tabKeys.push(configKey.toString());
// const children = Object.keys(_item.children);
// console.log(children);
......@@ -123,11 +123,12 @@ function useGetLayout() {
...cloneconfig,
...config
};
setDataSourceFromRequest(dataFromRequest);
console.log("newConfig", newConfig);
updatePageConfig(newConfig);
}, [info]);
return { info };
return { info, dataSourceFromRequest };
}
export default useGetLayout;
......@@ -18,6 +18,7 @@ import FormilyUpload from '@/components/UploadFiles/FormilyUploadFiles';
import { useAsyncSelect } from '@/formSchema/effects/useAsyncSelect';
import { PublicApi } from '@/services/api';
import { getAuth } from '@/utils/auth';
type SettingPanelType = {
selectedInfo: SelectedInfoType,
......@@ -41,6 +42,7 @@ const ComponentSchema = {
const formActions = createFormActions();
const { onFieldInputChange$ } = FormEffectHooks;
const EditPanel = () => {
const userAuth = getAuth();
const fixtureContext = useContext(context);
const { selectedInfo, pageConfig, activeKey, domKey } = useSelector<SettingPanelType, STATE_PROPS | "activeKey" | "domKey">(['selectedInfo', 'pageConfig', 'activeKey', "domKey"]);
const {state: visible, toggle: setVisible } = useToggle(true);
......@@ -72,7 +74,7 @@ const EditPanel = () => {
secondaryItem: {
secondary: selectedInfo?.props?.id,
title: selectedInfo?.props?.name,
icon: [{ name:selectedInfo?.props?.name, url: selectedInfo?.props?.icon }],
icon: [{ name:selectedInfo?.props?.name, url: selectedInfo?.props?.icon }].filter((_item) => _item.url !== ''),
},
flashSale: {
blockTitle: selectedInfo?.props.title
......@@ -148,6 +150,7 @@ const EditPanel = () => {
});
const renderUploadChild = (value) => {
console.log(value);
const target = value[0];
return (
<div className={styles.image}>
......@@ -165,10 +168,11 @@ const EditPanel = () => {
const handleSubmit = (values) => {
/** 如果是tab 类型修改 */
const key = activeKey === null ? domKey : selectedInfo?.selectedKey || domKey;
const componentType: keyof typeof ComponentSchema = activeKey === null ? 'TabItem' : (selectedInfo as any)?.otherProps?.type;
const componentType: keyof typeof ComponentSchema = activeKey === null ? 'tabItem' : (selectedInfo as any)?.otherProps?.type;
console.log(values);
const formValueToProps = {
TabItem: {
tabItem: {
name: values.title,
id: values.primary
},
......@@ -211,7 +215,7 @@ const EditPanel = () => {
changeProps({
treeKey: key,
props: currentProps,
title: values?.title || values?.name || values?.productName || values.branchName,
title: values?.title || values?.name || values?.rankProduct?.name || values.productName || values?.brand?.name,
});
setVisible(false);
......@@ -220,7 +224,8 @@ const EditPanel = () => {
const fetchPrimaryOption = async () => {
const { data, code } = await PublicApi.getSearchChannelCommodityTemplateGetFirstCategoryListByMemberId({
shopId: fixtureContext!.shopId?.toString()
shopId: fixtureContext!.shopId?.toString(),
memberId: userAuth.memberId,
});
return data;
};
......@@ -233,6 +238,7 @@ const EditPanel = () => {
const { data, code } = await PublicApi.getSearchChannelCommodityTemplateGetSecondCategoryListByMemberId({
shopId: fixtureContext!.shopId?.toString(),
categoryId: activeKey!.toString(),
memberId: userAuth.memberId,
});
if (code === 1000) {
const source = data.map((_item) => ({label: _item.name, value: _item.id}));
......@@ -263,9 +269,11 @@ const EditPanel = () => {
effects={($, actions) => {
useAsyncSelect('primary', fetchPrimaryOption, ["name", "id"]);
onFieldInputChange$('*(primary, secondary)').subscribe(fieldState => {
const { originAsyncData } = fieldState;
const target = originAsyncData.filter((_item) => _item.id === fieldState.value)[0];
actions.setFieldValue('title', target?.name);
console.log(originAsyncData)
const target = originAsyncData.filter((_item) => (_item.id === fieldState.value) || _item.value === fieldState.value)[0];
actions.setFieldValue('title', target?.name || target?.label);
});
}}
......
......@@ -71,7 +71,7 @@ const BranchList = (props) => {
console.log(values);
fetchData({
shopId: fixtureContext?.shopId.toString(),
categoryId: categoryId.toString,
customerCategoryId: categoryId.toString(),
current: current.toString(),
pageSize: pageSize.toString(),
});
......@@ -84,7 +84,8 @@ const BranchList = (props) => {
};
const fetchData = async (params: any) => {
const { data, code } = await PublicApi.getSearchCommodityTemplateGetBrandList(params);
console.log(params);
const { data, code } = await PublicApi.getSearchChannelCommodityTemplateGetBrandList(params);
if (code === 1000) {
setDataSource(data.data);
setTotal(data.totalCount);
......@@ -95,9 +96,10 @@ const BranchList = (props) => {
if (!visible) {
return;
}
console.log(categoryId);
fetchData({
shopId: fixtureContext?.shopId.toString(),
categoryId: categoryId.toString,
customerCategoryId: categoryId.toString(),
current: current.toString(),
pageSize: pageSize.toString(),
});
......
......@@ -4,6 +4,7 @@ import { useSelector } from '@lingxi-disign/react';
import { context } from '../../common/context/context';
import { Product } from '@/pages/transaction/marketingAbility/marketingActivitiesManagement/activePage/fixtures/components/ProductPanel';
import CommodityDrawer from '../CommodityDrawer';
import { getAuth } from '@/utils/auth';
interface Iprops {
......@@ -32,6 +33,7 @@ interface Iprops {
/** 普通商品 */
const FormilyCommodity: React.FC<Iprops> & { isFieldComponent: boolean } = (props: Iprops) => {
const { value, mutators } = props;
const userAuth = getAuth();
/** 1 级分类 id */
const { activeKey } = useSelector<any, "activeKey" >(['activeKey']);
const fixtureContext = useContext(context);
......@@ -67,6 +69,8 @@ const FormilyCommodity: React.FC<Iprops> & { isFieldComponent: boolean } = (prop
return {
shopId: fixtureContext?.shopId.toString(),
categoryId: activeKey,
memberId: userAuth.memberId,
memberRoleId: userAuth.memberRoleId,
};
}, [fixtureContext?.shopId.toString(), activeKey]);
......
......@@ -8,7 +8,7 @@ const ProductContainer = (props) => {
marginRight: '-8px',
flexWrap: 'wrap',
alignItems: 'stretch',
minHeight: '20px'
};
const itemStyle = {
paddingRight: '8px',
......@@ -20,7 +20,7 @@ const ProductContainer = (props) => {
};
return (
<Container card={false} listStyle={listStyle as any} itemStyle={itemStyle as any} {...divProps}>
<Container card={false} listStyle={listStyle as any} itemStyle={itemStyle as any} {...divProps} tooltipTitle="精选商品">
{children}
</Container>
);
......
import React, { CSSProperties } from 'react';
import cs from 'classnames';
import { Card } from 'antd';
import { Card, Tooltip } from 'antd';
import styles from './index.less';
import CustomizeCard from '../Card';
......@@ -12,11 +12,12 @@ interface Iprops {
card?: boolean,
cardProps?: CardProps,
listStyle?: CSSProperties,
itemStyle?: CSSProperties
itemStyle?: CSSProperties,
tooltipTitle?: string,
}
const Container: React.FC<Iprops> = (props: Iprops) => {
const { children, card = true, cardProps = {}, listStyle = {}, itemStyle = {} } = props;
const { children, card = true, cardProps = {}, listStyle = {}, itemStyle = {}, tooltipTitle } = props;
const { onClick, onDrag, onDragEnd, onDragEnter, onDragStart, onMouseOver, getOperateState, className, ...rest} = props as any;
const divProps = {
onClick, onDrag, onDragEnd, onDragEnter, onDragStart, onMouseOver
......@@ -45,21 +46,24 @@ const Container: React.FC<Iprops> = (props: Iprops) => {
</div>
</CustomizeCard>
) || (
<div className={styles.list} style={listStyle}>
{
React.Children.map(children, (_child, _index) => {
if(_child === null) {
return null;
}
const node = React.cloneElement(_child);
return (
<div key={_index} className={styles.item} style={itemStyle}>
{node}
</div>
);
})
}
</div>
<Tooltip title={tooltipTitle}>
<div className={styles.list} style={listStyle}>
{
React.Children.map(children, (_child, _index) => {
if(_child === null) {
return null;
}
const node = React.cloneElement(_child);
return (
<div key={_index} className={styles.item} style={itemStyle}>
{node}
</div>
);
})
}
</div>
</Tooltip>
)
}
</div>
......
......@@ -17,9 +17,21 @@
margin-bottom: 12px;
.content {
width: 40px;
height: 40px;
display: flex;
flex-direction: column;
// background-color: red;
.image {
width: 40px;
height: 40px;
margin-bottom: 4px;
}
.name {
text-align: center;
color: '#601314'
}
}
.empty {
......@@ -28,6 +40,8 @@
flex-direction: row;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
}
}
......
......@@ -64,8 +64,11 @@ const Item = (props) => {
return (
<div className={styles.item} >
<div {...divProps} className={wrapClass}>
<img src={icon} style={{width: '100%', height: '100%'}} />
<div className={wrapClass}>
<div {...divProps} style={{width: '40px'}}>
<img src={icon} className={styles.image} />
<div className={styles.name}>{name}</div>
</div>
</div>
</div>
);
......
......@@ -7,6 +7,7 @@ import { history } from 'umi';
import styles from './index.less';
import { context } from '../../../common/context/context';
import { PublicApi } from '@/services/api';
import { getAuth } from '@/utils/auth';
const { TabPane } = Tabs;
......@@ -73,6 +74,7 @@ const getData = async (data: any) => {
const CustomizeTabs: React.FC<Iprops> & { TabItem: typeof TabItem } = (props: Iprops) => {
const { children } = props;
const auth = getAuth();
const { pageConfig, shopId } = useSelector(['pageConfig', 'shopId']);
const [activeKey, setActiveKey] = useState<string>("2");
const [hasRequestTabKey, setHasRequestTabKey] = useState<string[]>([]);
......@@ -100,6 +102,8 @@ const CustomizeTabs: React.FC<Iprops> & { TabItem: typeof TabItem } = (props: Ip
shopId: shopId,
current: 1,
pageSize: ids?.length,
memberId: auth.memberId,
memberRoleId: auth.memberRoleId,
} as any);
};
......@@ -112,8 +116,9 @@ const CustomizeTabs: React.FC<Iprops> & { TabItem: typeof TabItem } = (props: Ip
shopId: shopId,
current: 1,
pageSize: ids.length,
memberId: auth.memberId,
};
return PublicApi.getSearchCommodityTemplateGetBrandList(postData as any);
return PublicApi.getSearchChannelCommodityTemplateGetBrandList(postData as any);
};
/** 并行请求 */
......@@ -197,7 +202,9 @@ const CustomizeTabs: React.FC<Iprops> & { TabItem: typeof TabItem } = (props: Ip
const { postData, dataSourceMap, domKeyMap2Type } = createPostData(matches![2]);
const resultData: any = await getAnyData(postData);
setHasRequestTabKey((prev) => prev.concat(activeKey));
// setHasRequestTabKey((prev) => prev.concat(activeKey));
const concatActiveKey = hasRequestTabKey.concat(activeKey);
setHasRequestTabKey(concatActiveKey);
const finalData = {};
const keyToValue = {
suggestProduct: 'label',
......@@ -215,7 +222,6 @@ const CustomizeTabs: React.FC<Iprops> & { TabItem: typeof TabItem } = (props: Ip
secondary: secondaryData,
...resultData
};
console.log(resultDataWithSecondary);
const cloneDeepPageConfig = cloneDeep(pageConfig);
Object.keys(resultDataWithSecondary).forEach((_item) => {
const parentKey = domKeyMap2Type.get(_item);
......@@ -255,7 +261,7 @@ const CustomizeTabs: React.FC<Iprops> & { TabItem: typeof TabItem } = (props: Ip
...cloneDeepPageConfig,
...finalData,
};
createActions({ type: 'onChangeTabKey', payload: { activeKey: matches?.[1] === 'undefined' ? null : matches?.[1] , domKey: matches?.[2], pageConfig: newConfigData } });
createActions({ type: 'onChangeTabKey', payload: { activeKey: matches?.[1] === 'undefined' ? null : matches?.[1] , domKey: matches?.[2], pageConfig: newConfigData, hasRequestTabKey: concatActiveKey } });
setActiveKey(activeKey);
};
......
......@@ -8,17 +8,18 @@ type SettingPanelType = {
}
interface Iprops {
onSubmit?: null | ((pageConfig:PageConfigType ) => void),
onSubmit?: null | ((pageConfig:PageConfigType, others: any ) => void),
children: React.ReactNode,
loading: boolean
loading: boolean,
dataConfig?: string[],
}
const ToolbarSubmit: React.FC<Iprops> = (props: Iprops) => {
const { pageConfig } = useSelector<SettingPanelType, STATE_PROPS>(['pageConfig']);
const { onSubmit = null, children, loading } = props;
const { onSubmit = null, children, loading, dataConfig = ["pageConfig"] } = props;
const { pageConfig, ...rest } = useSelector<SettingPanelType, STATE_PROPS>(dataConfig as any);
const handleCilck = () => {
onSubmit?.(pageConfig);
onSubmit?.(pageConfig, rest);
};
return (
......
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import { Spin, message } from 'antd';
import { BrickProvider, createActions, ModuleTree } from '@lingxi-disign/react';
import { history } from 'umi';
......@@ -54,9 +54,19 @@ const keyFunc = {
}
};
const TITLE_MAP = {
secondary: "二级品类",
flashSale: '限时抢购',
saleRanking: '销量排行',
brand: '品牌精选',
suggestProduct: '精选商品'
};
const CategoryNavigation = () => {
const { info } = useGetLayout();
const { info, dataSourceFromRequest } = useGetLayout();
const { shopId, id } = usePageStatus();
const [loading, setLoading] = useState<boolean>(false);
useEffect(() => {
if (!info) {
return;
......@@ -64,14 +74,20 @@ const CategoryNavigation = () => {
createActions({ type: 'onChangeShopId', payload: { shopId: shopId } });
}, [info]);
const onSave = async (pageConfig) => {
const onSave = async (pageConfig, rest) => {
const hasRequestTabKey = rest.hasRequestTabKey.map((_item: string) => _item.match(/id_(.*)\/\.\$(\d+)/)?.[1]);
/** domKey 从7开始都是tab 的值 */
const tabChildren = pageConfig[4].childNodes.slice(1);
setLoading(true);
const result = tabChildren.map((_nodeKey) => {
if (!pageConfig[_nodeKey]) {
return ;
}
const { id, name, visible = true } = pageConfig[_nodeKey].props;
if (!hasRequestTabKey.includes(id.toString())) {
return dataSourceFromRequest[id];
}
const tabProps = {
id,
name,
......@@ -85,7 +101,7 @@ const CategoryNavigation = () => {
const rest = type === 'suggestProduct' ? { type: props.type, num: props.num } : {};
tabItemData[type] = {
title: props?.title || '标题',
title: props?.title || TITLE_MAP[type],
...rest,
children: childNodes.map((_son) => {
const sonData = pageConfig[_son];
......@@ -98,12 +114,12 @@ const CategoryNavigation = () => {
tabProps['children'] = tabItemData;
return tabProps;
});
console.log(result);
const postData = { style: 0, category: result };
const { data, code } = await PublicApi.postTemplateAdornAppChannelSave({
templateId: Number(id),
categoryAdornContent: postData as any,
});
setLoading(false);
if (code === 1000) {
history.goBack();
}
......@@ -120,7 +136,7 @@ const CategoryNavigation = () => {
customReducer={customReducer}
>
<div className={styles['wrapper']}>
<Toolbar title="正在编辑:品类导航页" extra={<ToolbarSubmit loading={false} onSubmit={onSave}>保存</ToolbarSubmit>} />
<Toolbar title="正在编辑:品类导航页" extra={<ToolbarSubmit loading={loading} dataConfig={['pageConfig', 'hasRequestTabKey']} onSubmit={onSave}>保存</ToolbarSubmit>} />
<div className={styles['content']}>
<div className={styles.tree}>
<ModuleTree />
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment