Commit 0d3450e8 authored by Bill's avatar Bill

fix: 平台活动页修改

parent b7c021d2
......@@ -20,7 +20,6 @@ const mallLayoutConfig: PageConfigType = {
}
},
"childNodes": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23"]
},
};
......
......@@ -95,7 +95,13 @@ const CommodityList = {
type: "ProductPanel", // 这里说明打开什么样的修改panel
},
}
}
},
"CommodityList.SwapCoupon": {
propsConfig: {}
},
"CommodityList.SwapProduct": {
propsConfig: {}
},
};
const WrapCommodityList = {
......
......@@ -69,7 +69,10 @@ const useGetSameKeys = () => {
if (!element) {
return;
}
const { dataIndex, childNodes } = element;
// const { dataIndex, childNodes, otherProps: { type } } = element;
const { childNodes, otherProps: { type } } = element;
const dataIndex = type;
/** 活动广告图 直接跳过 */
if (dataIndex === 'top') {
return;
......
......@@ -12,10 +12,7 @@
"props": {
"theme":0,
"visible": true,
"childrenData": [
{"id": 4, "type": 1},
{"id": 57, "type": 2}
]
"childrenData": []
}
},
"hot":{
......@@ -23,7 +20,8 @@
"props": {
"theme":0,
"visible": true,
"childrenData": [26]
"title": "活动推荐",
"childrenData": []
}
},
"specialOffer":{
......@@ -201,22 +199,7 @@
"sort":23,
"props": {
"visible":true,
"childrenData":[
{
"title":"应季爆款1",
"theme": 1,
"childrenData": [
{"id": 26, "label":["特价", "满299减30"]}
]
},
{
"title":"应季爆款2",
"theme": 0,
"childrenData": [
{"id": 26, "label":["特价", "满299减30"]}
]
}
]
"childrenData":[]
}
}
}
import React, { useMemo } from 'react';
import { useSelector } from '@lingxi-disign/react';
import { changeProps } from '@lingxi-disign/core';
import ComponentModule from './ComponentModule';
import styles from './index.less';
import attemptImg from '@/asserts/activity/attempt.png';
import bargainImg from '@/asserts/activity/bargain.png';
import buySwapImg from '@/asserts/activity/buySwap.png';
......@@ -21,8 +23,6 @@ import discountImg from '@/asserts/activity/discount.png';
import specialOfferImg from '@/asserts/activity/specialOffer.png';
import giveCouponImg from '@/asserts/activity/giveCoupon.png';
import ComponentModule from './ComponentModule';
import styles from './index.less';
const ACTIVITYS = ["specialOffer", "plummet", "discount", "fullQuantitySub", "fullQuantityDiscount", "fullMoneySub", "fullMoneyDiscount", "giveProduct", "giveCoupon", "morePiece", "combination", "groupPurchase", "bargain", "secKill", "fullSwap", "buySwap", "preSale", "setMeal", "attempt"];
......@@ -40,7 +40,8 @@ const ModuleContainer = () => {
const config = pageConfig;
const res: ModuleType[] = [];
Object.keys(config).forEach((_item) => {
const { props = {}, dataIndex } = config[_item];
const { props = {} } = config[_item];
const dataIndex = config[_item]?.otherProps?.type;
if (ACTIVITYS.includes(dataIndex)) {
const visible = typeof props.visible === 'undefined' ? true : (props as any)?.visible;
res.push({
......
......@@ -191,7 +191,7 @@ const CouponSelect: React.FC<Iprops> = React.forwardRef((props: Iprops, couponRe
formExtra={formExtra}
radioChange
tableProps={{
rowKey: (record) => record.id,
rowKey: (record) => record?.id,
}}
effects={($, actions) => {
useStateFilterSearchLinkageEffect($, actions, 'id', FORM_FILTER_PATH);
......
import React, { useState, useEffect, useMemo } from 'react';
import { changeProps, clearSelectedStatus, SelectedInfoType, STATE_PROPS } from '@lingxi-disign/core';
import { useSelector } from '@lingxi-disign/react';
import { PageConfigType } from '@lingxi-disign/utils';
import cs from 'classnames';
import { CloseOutlined, PlusCircleOutlined } from '@ant-design/icons';
import { useToggle } from '@umijs/hooks';
import { Space, Button, Spin } from 'antd';
import { createFormActions, FormPath } from '@formily/antd';
import styles from './index.less';
import { activityImageSchema, couponSchema, activityProducts, cardSchema } from './schema';
import FormilyCoupon from '../EditPanelFormily/Coupon';
import FormilyProduct from '../EditPanelFormily/FormilyProduct';
import useGetSameKeys from '../../common/hooks/useGetSameKeys';
import NiceForm from '@/components/NiceForm';
import FormilyUpload from '@/components/UploadFiles/FormilyUploadFiles';
type SettingPanelType = {
selectedInfo: SelectedInfoType,
pageConfig: PageConfigType,
}
const formActions = createFormActions();
/** 请求借口type, 请求列表接口需要带上type */
const ACTIVITY_MAP = {
"specialOfferItem": 1,
/** 直降促销 */
"plummetItem": 2,
/** 折扣促销 */
"discountItem": 3,
/** 满量促销--满量减 */
"fullQuantitySubItem": 4,
/** 满量促销--满量折 */
"fullQuantityDiscountItem": 4,
/** 满额促销--满额减 */
"fullMoneySubItem": 5,
/** 满额促销--满额折 */
"fullMoneyDiscountItem": 5,
/** "赠送促销--赠送商品(满额赠+买商品赠) */
"giveProductItem": 6,
/** "赠送促销--赠送优惠劵(满额赠+买商品赠)*/
"giveCouponItem": 6,
/** 多件促销 */
"morePieceItem": 7,
/** 组合促销 */
"combinationItem": 8,
/** 拼团 */
"groupPurchaseItem": 9,
"luckDrawItem": 10,
/** 砍价 */
"bargainItem": 11,
/** 秒杀 */
"secKillItem": 12,
/** 组合促销 */
"fullSwapItem": 13,
/** 换购-买商品换购*/
"buySwapItem": 13,
/** 预售 */
"preSaleItem": 14,
/** 套餐 */
"setMealItem": 15,
/** 适用 */
"attemptItem": 16,
};
const activityListItem = Object.keys(ACTIVITY_MAP);
const activityList = activityListItem.map((_item) => _item.substring(0, _item.length - 4));
const EditPanelForm = () => {
const { selectedInfo, pageConfig } = useSelector<SettingPanelType, STATE_PROPS>(['selectedInfo', 'pageConfig']);
const {state: visible, toggle: setVisible } = useToggle(false);
const activityImage = useMemo(() => pageConfig[1]?.props?.imageUrl, [pageConfig]);
const { sameKeys } = useGetSameKeys();
const [formValue, setFormValue] = useState<any>(null);
const [schema, setSchema] = useState<any>(null);
const className = cs(styles.editPanel, {
[styles.hide]: !visible,
[styles.show]: visible
});
const handleOnClose = () => {
clearSelectedStatus();
setVisible(false);
};
console.log(selectedInfo);
useEffect(() => {
if (selectedInfo === null) {
setVisible(false);
return;
}
const componentType = (selectedInfo as any)?.otherProps?.type;
const propsMapToValue = {
top: {
imageUrl: [{ name: '广告图', url: selectedInfo?.props?.imageUrl }]
},
couponItem: {
coupon: {
...selectedInfo.props
}
},
hotItem: {
product: {
...selectedInfo.props,
}
},
hot: {
title: selectedInfo.props?.title,
},
};
/** 如果是活动子集, 那么现实选择活动商品 */
if(activityListItem.includes(componentType) || componentType === 'hotItem' || componentType === 'suggestProductItem') {
setFormValue({
product: {
...selectedInfo.props,
}
});
setSchema(activityProducts);
} else {
const schemaMap = {
top: activityImageSchema,
couponItem: couponSchema,
};
/** 如果是 suggestProduct, 或者是hot 或者是活动父级,那么直接设置他的卡片名称 */
const tempSchema = activityList.includes(componentType) || componentType === 'hot' || componentType === 'suggestProduct' ? cardSchema : schemaMap?.[componentType];
const tempFormValue = activityList.includes(componentType) || componentType === 'hot' || componentType === 'suggestProduct'
? {
title: selectedInfo.props.title
}
: propsMapToValue?.[componentType];
setSchema(tempSchema);
setFormValue(tempFormValue);
}
/** 16 种活动,请求是需要带上活动类型 */
const activityType = ACTIVITY_MAP[componentType] ? { activityType: ACTIVITY_MAP[componentType] } : {};
const isWithLabels = componentType === 'suggestProductItem' ? { isWithLabels: true } : { isWithLabels: false };
// console.log(hotItem".substring(0, 1));
formActions.setFieldState('product', (fieldState) => {
const [, parentKey] = selectedInfo.parentKey.split('-');
FormPath.setIn(fieldState, 'props.x-component-props', {
activityImage: activityImage,
...activityType,
disabledKeys: componentType === 'suggestProductItem' ? sameKeys[`suggestProduct_${parseInt(parentKey) - 1}`] : sameKeys[`${componentType?.substring(0, componentType.length - 4)}`] || [],
...isWithLabels,
// fetchOptions: fetchMemberOptions,
});
});
setVisible(true);
}, [selectedInfo]);
const handleSubmit = (values) => {
const componentType = (selectedInfo as any)?.otherProps?.type;
const valueMapToProps = {
top: {
imageUrl: values?.imageUrl?.[0].url,
},
couponItem: {
...values.coupon
},
hot: {
title: values.title,
},
suggestProduct: {
title: values.title,
}
};
let currentProps = {};
if (activityListItem.includes(componentType) || componentType === 'suggestProductItem') {
currentProps = values.product;
} else if (activityList.includes(componentType)) {
currentProps = {
title: values.title,
};
} else {
currentProps = valueMapToProps[componentType];
}
changeProps({
treeKey: selectedInfo.selectedKey,
props: {
...selectedInfo.props,
...currentProps
},
title: values?.title || values?.name
});
};
const renderUploadChild = (value) => {
const target = value[0];
return (
<div className={styles.image}>
<div className={styles.uploadImage}>上传图片</div>
<div className={styles.imageIcon}>
<Spin spinning={target?.status === 'uploading'}>
{
target?.url ? <img src={target?.url} style={{width: '100%'}} /> : <PlusCircleOutlined />
}
</Spin>
</div>
</div>
);
};
const renderForm = () => {
const formProps = {
onSubmit: handleSubmit,
expressionScope: {
renderUploadChild,
},
actions: formActions,
components: {FormilyUpload, FormilyCoupon, FormilyProduct},
};
return (
<NiceForm
value={formValue}
{...formProps}
schema={schema}
/>
);
};
return (
<div style={{position: 'relative', width: '400px'}}>
<div className={className}>
<div className={styles.header}>
<span className={styles.title}>内容</span>
<CloseOutlined onClick={handleOnClose} />
</div>
<div className={styles.content}>
{renderForm()}
</div>
<div className={styles.footer}>
<Space>
<Button onClick={handleOnClose}>取消</Button>
<Button type="primary" onClick={() => formActions.submit()}>确认</Button>
</Space>
</div>
</div>
</div>
);
};
export default EditPanelForm;
// .editPanel {
// background-color: #fff;
// height: 100%;
// overflow-y: auto;
// transition: width .25s;
// &.hide {
// width: 0;
// }
// &.show {
// width: 440px;
// }
// .settingTabs {
// :global {
// .ant-tabs-tab {
// margin: 0 32px;
// .ant-tabs-tab-btn {
// color: #909399;
// font-weight: bold;
// font-size: 14px;
// }
// &.ant-tabs-tab-active {
// .ant-tabs-tab-btn {
// color: #303133;
// }
// }
// }
// }
// .panel {
// padding: 0 16px;
// }
// }
// }
.editPanel {
background-color: #fff;
height: 100%;
position: absolute;
bottom: 0;
right: 0;
top: 0;
overflow-y: auto;
transition: width .25s;
display: flex;
flex-direction: column;
&.hide {
width: 0;
}
&.show {
width: 440px;
width: 400px;
}
.header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 16px;
border-bottom: 1px solid #f5f6f7;
.settingTabs {
:global {
.ant-tabs-tab {
margin: 0 32px;
.title {
font-size: 16px;
color: #252d37;
}
}
.ant-tabs-tab-btn {
color: #909399;
font-weight: bold;
font-size: 14px;
}
.content {
padding: 16px;
flex: 1,
}
&.ant-tabs-tab-active {
.ant-tabs-tab-btn {
color: #303133;
}
}
}
.footer {
display: flex;
flex-direction: row-reverse;
padding: 12px;
border-top: 1px solid #f5f6f7;
}
.image {
margin-top: 8px;
width: 100%;
background-color: #fafbfc;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 176px;
position: relative;
cursor: pointer;
overflow: hidden;
.imageIcon {
height: 100%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
overflow: hidden;
}
.panel {
padding: 0 16px;
.uploadImage {
position: absolute;
bottom: -34px;
left: 0;
right: 0;
padding: 4px;
background: rgba(0,0,0,0.3);
z-index: 99;
text-align: center;
color: #fff;
transition: all 0.6s;
}
&:hover {
.uploadImage {
transform: translateY(-34px);
}
}
}
}
import { ISchema } from "@formily/antd";
/** 活动图片广告图 */
export const activityImageSchema: ISchema = {
type: 'object',
properties: {
layout: {
type: 'object',
"x-component": 'mega-layout',
"x-component-props": {
labelAlign: "top"
},
properties: {
imageUrl: {
type: 'string',
"x-component": 'FormilyUpload',
"x-component-props": {
renderUploadChild: '{{renderUploadChild}}',
showFiles: false,
customizeItemRender: null,
children: null,
maxCount: 1,
},
},
}
}
}
};
/** 优惠券 */
export const couponSchema: ISchema = {
type: 'object',
properties: {
coupon: {
type: 'object',
'x-component': 'FormilyCoupon'
}
}
};
/** 卡片容器 */
export const cardSchema: ISchema = {
type: 'object',
properties: {
layout: {
type: 'object',
"x-component": 'mega-layout',
"x-component-props": {
labelAlign: "top"
},
properties: {
title: {
type: 'string',
title: '活动名称'
}
}
}
}
};
/**
* 活动商品
*/
export const activityProducts: ISchema = {
type: 'object',
properties: {
product: {
type: 'object',
'x-component': 'FormilyProduct',
}
}
};
.container {
display: flex;
flex-direction: column;
.header {
padding: 16px;
}
.content {
.info {
margin-top: 16px;
.row {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 16px;
.name {
color: #91959B;
font-size: 12px;
min-width: 72px;
}
.value {
color: #303133;
font-size: 12px;
}
}
}
}
}
/* eslint-disable react/display-name */
import React, { useRef, useState, useCallback, useEffect, useMemo, useContext } from 'react';
import { Button , Radio, Space, message } from 'antd';
import { useToggle } from '@umijs/hooks';
import { unstable_batchedUpdates as batchedUpdates } from 'react-dom';
import moment from 'moment';
import { changeProps, clearSelectedStatus } from '@lingxi-disign/core';
import styles from './index.less';
import CouponSelect from '../../CouponSelect';
import { Context as ShopContext } from '../../../common/context/shopContext';
import useGetSameKeys from '../../../common/hooks/useGetSameKeys';
import { PublicApi } from '@/services/api';
type ColumnType<T> = {
title: string,
dataIndex: string,
render?: (text: string, record: T) => React.ReactNode,
}
interface Iprops {
value?: {
id: number,
name: string
},
mutators: {
change: (data: any) => void
}
}
const PLATFORM = 1;
const BUSINESS = 2;
const format = 'YYYY-MM-DD HH:mm:ss';
const options = [
{ label: '平台', value: PLATFORM },
{ label: '商家', value: BUSINESS },
];
const FormilyCoupon: React.FC<Iprops> & { isFieldComponent: boolean } = (props: Iprops) => {
const { value = null } = props;
const { shopId } = useContext(ShopContext) || {};
const { sameKeys } = useGetSameKeys();
const disabledCouponKeys = useMemo(() => sameKeys['coupon'], [sameKeys]);
const { state: drawerVisible, toggle: setDrawerVisible } = useToggle();
const [radioValue, setRadioValue] = useState(PLATFORM);
const ref = useRef();
const columns: ColumnType<any>[] = [
{
title: '优惠券ID',
dataIndex: 'id'
},
{
title: "优惠券名称",
dataIndex: 'name'
},
{
title: '优惠券类型',
dataIndex: 'typeName'
},
{
title: '领券方式',
dataIndex: 'getWayName'
},
{
title: '面额',
dataIndex: 'denomination'
},
{
title: '使用条件',
dataIndex: 'useConditionMoney'
},
{
title: "有效期",
dataIndex: 'validityTime',
render: (_text, _record) => {
return (
<div>
<span>{_record?.releaseTimeStart && moment(_record?.releaseTimeStar).format(format)}</span>
<span>{_record?.releaseTimeEnd && moment(_record?.releaseTimeEnd).format(format)}</span>
</div>
);
}
}
];
// useEffect(() => {
// if (!visible) {
// return ;
// }
// setInnerValue(value);
// }, [value]);
const onChange = (e) => {
setRadioValue(e.target.value);
const pageInfo = (ref?.current as any)?.getPaginationInfo();
const values = (ref?.current as any)?.formValues(["id", "couponName"]);
(ref?.current as any)?.reload({...values, current: pageInfo?.page || 1, pageSize: pageInfo?.pageSize || 10, radio: e.target.value});
};
const formExtra = (
<div style={{position: 'absolute', top: '4px', right: 0}}>
<Radio.Group
options={options}
value={radioValue}
onChange={onChange}
optionType="button"
/>
</div>
);
const onOk = (selectedKey: string[], selectedRow: any[]) => {
batchedUpdates(() => {
// setInnerValue(selectedRow[0]);
props.mutators.change(selectedRow[0]);
setDrawerVisible(false);
});
};
const fetchData = useCallback(async (params: any) => {
const { radio = PLATFORM, ...rest } = params;
const service = radio === PLATFORM ? PublicApi.getMarketingCouponPlatformActivityPageSelectPage : PublicApi.getMarketingCouponPlatformActivityPageSelectMerchantPage;
/** @tofix shopId */
const { data, code } = await service({...rest, shopId: shopId! });
if (code === 1000) {
return data;
}
return {
totalCount: 0,
data: []
};
}, []);
const selectedValue = useMemo(() => [value], [value]);
const rowSelection = {
getCheckboxProps: (_record) => ({
disabled: disabledCouponKeys.includes(_record.id)
})
};
return (
<div>
<div className={styles.container}>
<div className={styles.content}>
<div className={styles.module}>
<Button onClick={() => setDrawerVisible(true)}>选择优惠券</Button>
</div>
<div className={styles.info}>
{
columns.map((_item) => {
return (
<div key={_item.dataIndex} className={styles.row}>
<span className={styles.name}>{_item.title}</span>
<span className={styles.value}>
{
(_item?.render?.(value?.[_item.dataIndex], value)) || value?.[_item.dataIndex]
}
</span>
</div>
);
})
}
</div>
</div>
</div>
<CouponSelect
visible={drawerVisible}
onCancel={() => setDrawerVisible(false)}
mode="radio"
formExtra={formExtra}
ref={ref as any}
fetchData={fetchData}
onOk={onOk}
value={selectedValue}
rowSelection={rowSelection}
/>
</div>
);
};
FormilyCoupon.isFieldComponent = true;
export default FormilyCoupon;
import React, { useContext, useMemo, useState } from 'react';
import { useToggle } from '@umijs/hooks';
import { useSelector } from '@lingxi-disign/react';
import { Context as ShopContext } from '../../../common/context/shopContext';
import { Product } from '@/pages/marketingManage/marketing/marketingActivitiesManagement/activePage/fixtures/components/ProductPanel';
import ActivityProductDrawer from '@/pages/marketingManage/marketing/marketingActivitiesManagement/activePage/fixtures/components/ActivityAreaSetting/activityProductDrawer';
import { PublicApi } from '@/services/api';
import { GetMarketingAdornPlatformActivityListAdornRequest } from '@/services/MaketingV2Api';
import activityImageSvg from '@/asserts/activity/ActivityImage.svg';
interface Iprops {
value: {
id: number,
productName: string,
activityId: number,
activityList: any[],
productImgUrl: string,
price: number,
label: string[],
},
props: {
['x-component-props']: {
activityType: number,
isWithLabels?: boolean,
activityImage?: string,
/** [`${id}_${activityId}`] */
disabledKeys?: string[]
} & {
[key: string]: any
},
},
mutators: {
change: (params: any) => void
},
}
const FormilyActivityProduct: React.FC<Iprops> & { isFieldComponent: boolean } = (props: Iprops) => {
const { value, mutators } = props;
const { shopId } = useContext(ShopContext) || {};
const componentProps = props.props?.['x-component-props'] || {};
const activityImage = componentProps.activityImage || activityImageSvg;
const disabledKeys = componentProps.disabledKeys || [];
const { state: productVisible, toggle: setProductVisible } = useToggle();
const cacheProductList = useMemo(() => [{ ...value }], [value]);
const productProps = useMemo(() => ({
id: value?.id,
activityId: value?.activityId,
productName: value?.productName,
activityList: value?.activityList,
productImgUrl: value?.productImgUrl,
price: value?.price,
label: value?.label
}), [value]);
const onOk = (data) => {
const first = data[0];
mutators.change(first);
setProductVisible(false);
};
const onLabelChange = (data: { id: number, activityId: number, label: string[] }) => {
// const current = {...innerProducts};
// current["label"] = data.label;
// setInnerProducts(current);
const current = {
...productProps,
label: data.label
};
mutators.change(current);
};
const onEdit = () => {
setProductVisible(true);
};
const fetchData = async (params: GetMarketingAdornPlatformActivityListAdornRequest) => {
const withActivityType = componentProps?.activityType ? { activityType: componentProps?.activityType } : {};
const common = {
...params,
shopId: shopId?.toString(),
...withActivityType
};
const isWithActivityType = common;
return await PublicApi.getMarketingAdornPlatformActivityListAdorn(isWithActivityType as any);
};
return (
<div style={{ position: 'relative' }}>
<Product
onEdit={onEdit}
activityImage={activityImage}
{...productProps}
isWithLabels={componentProps.isWithLabels || false}
onLabelChange={onLabelChange}
/>
<ActivityProductDrawer
activityImage={activityImage}
products={cacheProductList}
onOk={onOk}
fetchData={fetchData}
visible={productVisible}
onCancel={() => setProductVisible(false)}
mode="radio"
disabledList={disabledKeys}
/>
</div>
);
};
FormilyActivityProduct.isFieldComponent = true;
export default FormilyActivityProduct;
......@@ -10,7 +10,7 @@ interface Iprops {
}
const Advertisement: React.FC<Iprops> = (props: Iprops) => {
const { imageUrl, width = "100%", height = "100%", style = {}, className, ...other } = props;
const { imageUrl, width = "100%", height = 176, style = {}, className, ...other } = props;
const cacheWidth = useMemo(() => typeof width === 'number' ? `${width}px` : width, [width]);
const cacheHeight = useMemo(() => typeof height === 'number' ? `${height}px` : height, [height]);
......
......@@ -23,6 +23,7 @@
flex-direction: column;
background: #fff;
padding: 12px;
border-radius: 8px;
.commodityGroupEmpty {
min-height: 430px;
......@@ -49,85 +50,9 @@
background-color: #fff;
}
}
.footer {
margin-top: 12px;
border-top: 1px solid rgba(0, 0, 0, 0.1);
padding: 12px 0 0px 0;
display: flex;
flex-direction: row;
justify-content: space-between;
.price {
display: flex;
flex-direction: row;
align-items: center;
.discount {
color: #F2270E;
font-size: 12px;
.priceInt {
font-size: 16px;
font-weight: 600;
}
}
.originalPrice {
color: #91959B;
font-size: 10px;
text-decoration:line-through;
margin-left: 4px;
}
}
.button {
background-color: #EF3346;
font-size: 14px;
padding: 4px 12px;
border-radius: 50px;
color: #fff;
display: flex;
flex-direction: row;
align-items: center;
}
}
}
.customizeTabBar {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 16px;
.tabbarItem {
color: #91959B;
font-size: 12px;
line-height: 12px;
margin-right: 12px;
}
.activeTabbar {
color: #252D37;
font-size: 16px;
line-height: 24px;
position: relative;
&::after {
content: "";
position: absolute;
bottom: -4px;
left: 50%;
width: 24px;
height: 4px;
background: #EF3346;
border-radius: 2px;
transform: translateX(-12px);
}
}
}
.twoColumns {
display: flex;
......
......@@ -4,7 +4,11 @@ import { Commodity, Progress } from '@lingxi-disign/ui';
import { Tabs } from 'antd';
import { PlusOutlined, TagOutlined } from '@ant-design/icons';
import _omit from 'lodash/omit';
import Tabbar from './tabbar';
import TabFooter from './tabFooter';
import styles from './index.less';
import SwapCoupon from './swapCoupon';
import SwapProduct from './swapProduct';
const { TabPane } = Tabs;
interface Iprops {
......@@ -15,67 +19,73 @@ interface Iprops {
visible: boolean,
}
const CommodityList: React.FC<Iprops> & { Item: typeof CommodityItem, CommodityTab: typeof CommodityTab } = (props: Iprops) => {
const { children, className, title, theme, visible = true, ...other } = props;
const classNameStr = cx(styles.recommand, className, { [styles.hide]: !visible });
const { onClick, onDrag, onDragEnd, onDragEnter, onDragStart, onMouseOver, getOperateState } = other as any;
const CommodityList: React.FC<Iprops> & {
Item: typeof CommodityItem,
CommodityTab: typeof CommodityTab,
SwapCoupon: typeof SwapCoupon,
SwapProduct: typeof SwapProduct
} =
(props: Iprops) => {
const { children, className, title, theme, visible = true, ...other } = props;
const classNameStr = cx(styles.recommand, className, { [styles.hide]: !visible });
const { onClick, onDrag, onDragEnd, onDragEnter, onDragStart, onMouseOver, getOperateState } = other as any;
const divProps = {
onClick, onDrag, onDragEnd, onDragEnter, onDragStart, onMouseOver,
};
const render2Columns = () => {
return (
<div className={styles.twoColumns}>
{
React.Children.map(children, (_child: any) => {
if (_child === null) {
return null;
}
return React.cloneElement(_child, {..._child?.props || {}, customizeClassName: styles.commodityItem, mode: 'vertical'});
})
}
</div>
);
};
const renderGroup = () => {
return (
<div>
{
React.Children.map(children, (_child: any) => {
if (_child === null) {
return null;
}
return React.cloneElement(_child, {..._child.props});
})
}
</div>
);
};
const divProps = {
onClick, onDrag, onDragEnd, onDragEnter, onDragStart, onMouseOver,
};
const render2Columns = () => {
return (
<div className={styles.twoColumns}>
{
React.Children.map(children, (_child: any) => {
if (_child === null) {
return null;
}
return React.cloneElement(_child, {..._child?.props || {}, customizeClassName: styles.commodityItem, mode: 'vertical'});
})
}
</div>
);
};
const renderComponent = () => {
if (theme === 1) {
return render2Columns();
}
if (theme === 2) {
return renderGroup();
}
return children;
};
const renderGroup = () => {
return (
<div>
{
React.Children.map(children, (_child: any) => {
if (_child === null) {
return null;
}
return React.cloneElement(_child, {..._child.props});
})
}
<div className={classNameStr} {...divProps}>
<span className={styles.title}>{title}</span>
<div className={styles.container}>
{
renderComponent()
}
</div>
</div>
);
};
const renderComponent = () => {
if (theme === 1) {
return render2Columns();
}
if (theme === 2) {
return renderGroup();
}
return children;
};
return (
<div className={classNameStr} {...divProps}>
<span className={styles.title}>{title}</span>
<div className={styles.container}>
{
renderComponent()
}
</div>
</div>
);
};
interface Iprops {
className: string,
customizeClassName: string,
......@@ -84,7 +94,7 @@ interface Iprops {
const CommodityItem: React.FC<Iprops> = (props: Iprops) => {
const { className, customizeClassName, ...other } = props;
const classNameStr = cx(className, styles.item, customizeClassName);
const classNameStr = cx(styles.item, customizeClassName);
const rest = _omit(other, ["draggable", "getOperateState", "onClick", "onDrag", "onDragEnd", "onDragEnter", "onDragStart", "onMouseOver"]);
const { onClick, onDrag, onDragEnd, onDragEnter, onDragStart, onMouseOver, getOperateState } = other as any;
......@@ -116,10 +126,13 @@ const CommodityItem: React.FC<Iprops> = (props: Iprops) => {
const commodityProps = mode === 'horizontal' ? horizontalData : verticalData;
return (
<div className={classNameStr} {...divProps}>
<Commodity
{...commodityProps}
/>
<div className={classNameStr} >
<div {...divProps} className={className}>
<Commodity
{...commodityProps}
/>
</div>
</div>
);
};
......@@ -130,36 +143,6 @@ CommodityList.Item = CommodityItem;
const CommodityTab: React.FC<any> = (props) => {
const { isEmpty, id, productName, productImgUrl, price, goodsSubsidiaryGroupList } = props;
const [activeKey, setActiveKey] = useState<string>("1");
console.log("CommodityTab", props);
const renderTabBar = (tabProps) => {
const tabInfo = tabProps.panes.map((_item) => {
const { key, props } = _item;
return {
tab: props.tab,
key,
};
});
return (
<div className={styles.customizeTabBar}>
{
tabInfo?.map((_item) => {
return (
<span
onClick={() => setActiveKey(_item.key)}
key={_item.key}
className={cx(styles.tabbarItem, {
[styles.activeTabbar]: _item.key === tabProps.activeKey,
})}
>
{_item.tab}
</span>
);
})
}
</div>
);
};
const renderEmpty = () => {
return (
......@@ -169,6 +152,10 @@ const CommodityTab: React.FC<any> = (props) => {
);
};
const handleTabChange = (key: string) => {
setActiveKey(key);
};
const renderContent = () => {
return (
<>
......@@ -188,7 +175,7 @@ const CommodityTab: React.FC<any> = (props) => {
/>
</div>
<div className={styles.tab}>
<Tabs activeKey={activeKey} renderTabBar={renderTabBar}>
<Tabs activeKey={activeKey} renderTabBar={(tabProps) => <Tabbar tabProps={tabProps} onChange={handleTabChange} />}>
{
goodsSubsidiaryGroupList?.map((_item, _index) => {
const { groupNo, groupPrice, goodsSubsidiaryGroupDetailsList } = _item;
......@@ -214,18 +201,7 @@ const CommodityTab: React.FC<any> = (props) => {
})
}
</div>
<div className={styles.footer}>
<div className={styles.price}>
<div className={styles.discount}>
<span className={styles.priceInt}>{discountPrice[0]}</span>.
<span>{discountPrice?.[1] || '00'}</span>
</div>
<span className={styles.originalPrice}>{allTotal + price}</span>
</div>
<div className={styles.button}>
<span>立即抢购</span>
</div>
</div>
<TabFooter discountPrice={discountPrice} originalPrice={allTotal + price} />
</TabPane>
);
})
......@@ -266,4 +242,7 @@ const CommodityGroup = (props) => {
CommodityList.CommodityTab = CommodityGroup;
CommodityList.SwapCoupon = SwapCoupon;
CommodityList.SwapProduct = SwapProduct;
export default CommodityList;
.commodityGroupEmpty {
min-height: 430px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 12px;
border: 1px dashed #C8CACD;
}
.section {
padding: 12px;
background: #fff;
border-radius: 8px;
margin-bottom: 8px;
}
.tab {
// margin-bottom: 12px;
background-color: #fff;
.groupPane {
display: flex;
flex-direction: row;
margin-right: -12px;
}
.couponItem {
background-color: #FFF0F2;
padding: 16px 8px;
margin-right: 12px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #EF3346;
border-radius: 8px;
.money {
.num {
font-size: 20px;
}
}
.condition {
margin: 8px 0 16px 0;
}
.couponType {
background-image: linear-gradient(90deg, #F87362 0%, #EF3346 100%);
border-radius: 8px;
padding: 2px 6px;
color: #fff;
}
}
}
import React, { useState } from 'react';
import _omit from 'lodash/omit';
import { Commodity, Progress } from '@lingxi-disign/ui';
import { PlusOutlined, TagOutlined } from '@ant-design/icons';
import { Tabs } from 'antd';
import cs from 'classnames';
import styles from './swapCoupon.less';
import Tabbar from './tabbar';
import TabFooter from './tabFooter';
const { TabPane } = Tabs;
const SwapCoupon = (props) => {
const { className, ...other } = props;
const [activeKey, setActiveKey] = useState<string>("1");
const restProps = _omit(other, ["getOperateState", "onClick", "onDrag", "onDragEnd", "onDragEnter", "onDragStart", "onMouseOver", "draggable"]);
const isEmpty = typeof restProps['productName'] === 'undefined';
const { onClick, onDrag, onDragEnd, onDragEnter, onDragStart, onMouseOver, getOperateState } = other as any;
const divProps = {
onClick, onDrag, onDragEnd, onDragEnter, onDragStart, onMouseOver,
};
const handleTabChange = (key: string) => {
setActiveKey(key);
};
const { productName, productImgUrl, price, giveCouponList } = restProps;
if (isEmpty) {
return (
<div className={cs(styles.commodityGroupEmpty, className)} {...divProps}>
<div><PlusOutlined style={{color: '#C8CACD'}} /></div>
</div>
);
}
return (
<div className={className} {...divProps}>
<div className={styles.section}>
<div className={styles.mainCommodity}>
<Commodity
name={productName}
image={productImgUrl}
mode="horizontal"
discountPrice={price}
tags={["赠优惠券"]}
buyBtn={true}
/>
</div>
<div className={styles.tab}>
<Tabs activeKey={activeKey} renderTabBar={(tabProps) => <Tabbar tabProps={tabProps} onChange={handleTabChange} />}>
{
giveCouponList?.map((_item, _index) => {
const { groupNo, limitValue, list } = _item;
return (
<TabPane key={_item.groupNo.toString()} tab={`满${limitValue}元获赠`}>
<div className={styles.groupPane}>
{
list?.map((_row, _key) => {
return (
<div className={styles.couponItem} key={_key}>
<div className={styles.money}>
<span className={styles.num}>{_row.denomination}</span>
</div>
<div className={styles.condition}>{`满${_row.useConditionMoney}可使用`}</div>
<div className={styles.couponType}>{_row.couponName}</div>
</div>
);
})
}
</div>
{/* <TabFooter discountPrice={60} originalPrice={50} /> */}
</TabPane>
);
})
}
</Tabs>
</div>
</div>
</div>
);
};
export default SwapCoupon;
.commodityGroupEmpty {
min-height: 430px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 12px;
border: 1px dashed #C8CACD;
}
.section {
padding: 12px;
background: #fff;
border-radius: 8px;
margin-bottom: 8px;
}
.tab {
// margin-bottom: 12px;
background-color: #fff;
.groupPane {
display: flex;
flex-direction: row;
margin-right: -12px;
}
.giftItem {
margin-right: 12px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #EF3346;
border-radius: 8px;
.giftImage {
height: 104px;
width: 104px;
position: relative;
margin-bottom: 12px;
img {
width: 100%;
height: 100%;
}
.num {
position: absolute;
bottom: 0;
right: 0;
}
}
.money {
.num {
font-size: 20px;
}
}
.condition {
margin: 8px 0 16px 0;
}
.couponType {
background-image: linear-gradient(90deg, #F87362 0%, #EF3346 100%);
border-radius: 8px;
padding: 2px 6px;
color: #fff;
}
}
}
import React, { useState } from 'react';
import _omit from 'lodash/omit';
import { Commodity, Progress, CustomizeTag } from '@lingxi-disign/ui';
import { PlusOutlined, TagOutlined } from '@ant-design/icons';
import { Tabs } from 'antd';
import cs from 'classnames';
import styles from './swapProduct.less';
import Tabbar from './tabbar';
import TabFooter from './tabFooter';
const { TabPane } = Tabs;
const SwapProduct = (props) => {
const { className, ...other } = props;
const [activeKey, setActiveKey] = useState<string>("1");
const restProps = _omit(other, ["getOperateState", "onClick", "onDrag", "onDragEnd", "onDragEnter", "onDragStart", "onMouseOver", "draggable"]);
const isEmpty = typeof restProps['productName'] === 'undefined';
const { onClick, onDrag, onDragEnd, onDragEnter, onDragStart, onMouseOver, getOperateState } = other as any;
const divProps = {
onClick, onDrag, onDragEnd, onDragEnter, onDragStart, onMouseOver,
};
const handleTabChange = (key: string) => {
setActiveKey(key);
};
const { productName, productImgUrl, price, goodsSubsidiaryGroupList } = restProps;
if (isEmpty) {
return (
<div className={cs(styles.commodityGroupEmpty, className)} {...divProps}>
<div><PlusOutlined style={{color: '#C8CACD'}} /></div>
</div>
);
}
return (
<div className={className} {...divProps}>
<div className={styles.section}>
<div className={styles.mainCommodity}>
<Commodity
name={productName}
image={productImgUrl}
mode="horizontal"
discountPrice={price}
tags={["赠商品"]}
buyBtn={false}
/>
</div>
<div className={styles.tab}>
<Tabs activeKey={activeKey} renderTabBar={(tabProps) => <Tabbar tabProps={tabProps} onChange={handleTabChange} />}>
{
goodsSubsidiaryGroupList?.map((_item, _index) => {
const { groupNo, limitValue, goodsSubsidiaryGroupDetailsList } = _item;
return (
<TabPane key={_item.groupNo.toString()} tab={`满${limitValue}元赠送`}>
<div className={styles.groupPane}>
{
goodsSubsidiaryGroupDetailsList?.map((_row, _key) => {
return (
<div className={styles.giftItem} key={_key}>
<div className={styles.giftImage}>
<img src={_row.productImgUrl} />
<div className={styles.num}>x{_row.num}</div>
</div>
<CustomizeTag>原价{`${_row.price}`}</CustomizeTag>
</div>
);
})
}
</div>
<TabFooter discountPrice={60} originalPrice={50} />
</TabPane>
);
})
}
</Tabs>
</div>
</div>
</div>
);
};
export default SwapProduct;
.footer {
margin-top: 12px;
border-top: 1px solid rgba(0, 0, 0, 0.1);
padding: 12px 0 0px 0;
display: flex;
flex-direction: row;
justify-content: space-between;
.price {
display: flex;
flex-direction: row;
align-items: center;
.discount {
color: #F2270E;
font-size: 12px;
.priceInt {
font-size: 16px;
font-weight: 600;
}
}
.originalPrice {
color: #91959B;
font-size: 10px;
text-decoration:line-through;
margin-left: 4px;
}
}
.button {
background-color: #EF3346;
font-size: 14px;
padding: 4px 12px;
border-radius: 50px;
color: #fff;
display: flex;
flex-direction: row;
align-items: center;
}
}
import React, { useMemo } from 'react';
import styles from './tabFooter.less';
interface Iprops {
originalPrice: number,
discountPrice: number
}
const TabFooter: React.FC<Iprops> = (props: Iprops) => {
const { discountPrice, originalPrice } = props;
const cacheDiscountPrice = useMemo(() => discountPrice?.toString().split("."), [discountPrice])
return (
<div className={styles.footer}>
<div className={styles.price}>
<div className={styles.discount}>
<span className={styles.priceInt}>{cacheDiscountPrice[0]}</span>.
<span>{cacheDiscountPrice?.[1] || '00'}</span>
</div>
<span className={styles.originalPrice}>{originalPrice.toFixed(2)}</span>
</div>
<div className={styles.button}>
<span>立即抢购</span>
</div>
</div>
);
};
export default TabFooter;
.customizeTabBar {
display: flex;
flex-direction: row;
align-items: center;
margin-bottom: 16px;
.tabbarItem {
color: #91959B;
font-size: 12px;
line-height: 12px;
margin-right: 12px;
}
.activeTabbar {
color: #252D37;
font-size: 16px;
line-height: 24px;
position: relative;
&::after {
content: "";
position: absolute;
bottom: -4px;
left: 50%;
width: 24px;
height: 4px;
background: #EF3346;
border-radius: 2px;
transform: translateX(-12px);
}
}
}
import React, { useState } from 'react';
import cx from 'classnames';
import styles from './tabbar.less';
interface Iprops {
tabProps: any,
onChange?: (key: string) => void
}
const Tabbar: React.FC<Iprops> = (props: Iprops) => {
// const [activeKey, setActiveKey] = useState<string>("1");
const { tabProps, onChange } = props;
const tabInfo = tabProps?.panes?.map((_item) => {
const { key, props } = _item;
return {
tab: props.tab,
key,
};
});
const handleChange = (key: string) => {
onChange?.(key);
};
return (
<div className={styles.customizeTabBar}>
{
tabInfo?.map((_item) => {
return (
<span
onClick={() => handleChange(_item.key)}
key={_item.key}
className={cx(styles.tabbarItem, {
[styles.activeTabbar]: _item.key === tabProps.activeKey,
})}
>
{_item.tab}
</span>
);
})
}
</div>
);
};
export default Tabbar;
import React from 'react';
import React, { useMemo } from 'react';
import { MarketingCard } from '@lingxi-disign/ui';
import cx from 'classnames';
import styles from './index.less';
......@@ -51,19 +51,34 @@ interface ItemIprops {
onDragEnter: () => void,
onDragStart: () => void,
onMouseOver: () => void,
draggable?: false
getOperateState: any,
belongName?: string
belongType?: number,
denomination?: number,
getWay?: number,
getWayName?: string,
id?: number
/** 1 => 平台, 2 商家 */
type?: 1 | 2 | number & {},
typeName?: string,
useConditionMoney?: number
}
const CouponItem: React.FC<ItemIprops> = (props: ItemIprops) => {
const { children, className, ...other} = props;
const { onClick, onDrag, onDragEnd, onDragEnter, onDragStart, onMouseOver, getOperateState, ...rest} = other;
const { money, isnull, tag, info, typeName } = rest as any;
const { denomination, tag, useConditionMoney, typeName } = rest as any;
const divProps = {
onClick, onDrag, onDragEnd, onDragEnter, onDragStart, onMouseOver,
};
const isNotNull = useMemo(() => rest?.id && true, [rest]);
return (
<div {...divProps} className={cx(className, styles.item)}>
<CouponsItem money={money} isnull={isnull} typeName={typeName} tag={tag} info={info} className={styles.couponItem} />
<div className={cx(styles.item)}>
<div {...divProps} className={className}>
<CouponsItem money={denomination} isnull={!isNotNull} typeName={typeName} tag={tag} info={`满${useConditionMoney}可用`} className={styles.couponItem} />
</div>
</div>
);
};
......
......@@ -5,41 +5,46 @@ import _get from 'lodash/get';
import _omit from 'lodash/omit';
import _pick from 'lodash/pick';
import { history } from 'umi';
import { usePageStatus } from '@/hooks/usePageStatus';
import { PublicApi } from '@/services/api';
import MobileDesignPanel from './components/MobileDesignPanel';
import componentConfigs from './common/configs';
import Toolbar from './components/Toolbar';
import styles from './index.less';
import EditPanel from './components/EditPanel';
// import EditPanel from './components/EditPanel';
import EditPanel from './components/EditPanel/editPanelForm';
import configs from './common/configs/pageConfigs';
import useGetData from './common/hooks/useGetData';
import useGetLayout from './common/hooks/useGetLayout';
import Module from './components/ComponentTree';
import ToolbarSubmit from './components/Toolbar/toolbarSubmit';
import { RenovationProvider } from './common/context/shopContext';
import { PublicApi } from '@/services/api';
import { usePageStatus } from '@/hooks/usePageStatus';
const { TabPane } = Tabs;
const primaryKey = ["hot", "specialOffer", "plummet", "discount", "fullQuantitySub", "fullQuantityDiscount", "fullMoneySub", "fullMoneyDiscount", "giveProduct", "giveCoupon", "morePiece", "combination", "groupPurchase", "bargain", "secKill", "fullSwap", "buySwap", "preSale", "setMeal", "attempt"];
const Fixtures = () => {
const { id } = usePageStatus();
const { detail, loading } = useGetData(componentConfigs as any);
// const { detail, loading } = useGetData(componentConfigs as any);
const { detail, loading } = useGetLayout();
const [submitLoading, setSubmitLoading] = useState<boolean>(false);
const onSave = async (pageConfig: any) => {
const onSave = async (pageConfig) => {
const childNodes = pageConfig[0].childNodes;
setSubmitLoading(true);
let result: any = {};
childNodes.map((_item, _index) => {
const target = pageConfig[_item];
const { dataIndex, props } = target || {};
const childNodes = target.childNodes;
const { props } = target || {};
const dataIndex = target.otherProps.type;
const sort = _index + 1;
if (dataIndex === 'top') {
const current = { sort: sort, props: _omit(props, 'style') };
result = generaterData(result, dataIndex, current);
} else if(dataIndex === 'coupon') {
const { childNodes } = target;
const childrenData = childNodes.map((_record) => {
const childTargetProps = pageConfig[_record].props;
return {
......@@ -55,8 +60,12 @@ const Fixtures = () => {
}
});
} else if (primaryKey.includes( dataIndex )) {
const { products, ...otherProps } = props || {};
const childrenData = products?.map((_item) => _item.id) || [];
const { ...otherProps } = props || {};
const childrenData = childNodes.map((_record) => {
const childTargetProps = pageConfig[_record].props;
return childTargetProps.id;
});
// const childrenData = products?.map((_item) => _item.id) || [];
result = generaterData(result, dataIndex, {
sort: sort,
props: {
......@@ -65,7 +74,7 @@ const Fixtures = () => {
}
});
} else if (dataIndex === 'suggestProduct') {
const { products, childrenData, ...otherProps } = props || {};
const { ...otherProps } = props || {};
console.log(otherProps, "suggestProduct");
const { childNodes } = target;
const temp = {
......@@ -74,14 +83,15 @@ const Fixtures = () => {
visible: true,
childrenData: childNodes?.filter((_record) => /\d+-\d+/.test(_record)).map((_row) => {
const childrenNodeTarget = pageConfig[_row];
const { products: productList, ...childRestProps } = childrenNodeTarget?.props;
const { ...childRestProps } = childrenNodeTarget?.props;
return {
title: childRestProps.title,
theme: childRestProps.theme || 0,
childrenData: productList?.map((_listItem) => {
childrenData: childrenNodeTarget.childNodes?.map((_listItem) => {
const sonNodeTarget = pageConfig[_listItem];
return {
id: _listItem.id,
label: _listItem?.label || []
id: sonNodeTarget?.props.id,
label: sonNodeTarget?.props?.label || []
};
})
};
......@@ -91,8 +101,6 @@ const Fixtures = () => {
result = generaterData(result, dataIndex, temp);
}
});
console.log(result);
// return;
const { data, code } = await PublicApi.postTemplateWebActivityPageAdorn({
id: id,
adornContent: result
......
......@@ -63,6 +63,7 @@ export function useFilterSameOption() {
});
});
});
console.log(result);
return result;
}, [pageConfig]);
......
......@@ -53,7 +53,7 @@ const EditPanel = () => {
/** 精选商品type */
const [type, setType] = useState<number>(0);
/**
* 当每次点selectInfo 时,都把selectInfo.props 映射到formily 的value 上
* 当每次点selectInfo 时,都把selectInfo.props 映射到formily 的value 上
* 但当一级类型没有被选择的时候,那么必须选择一级导航类型
*/
useEffect(() => {
......
......@@ -408,6 +408,16 @@ export const getIChannelInfo = () => {
return url.split('.').slice(-2).join('.')
}
/** 数组转对象 */
export function arrayToMap<T>(list: T[], primaryKey: keyof T) {
const result: { [props: string]: T } = {};
list.forEach((_item: T) => {
const key = _item[primaryKey];
(result as any)[key] = _item;
});
return result;
}
export default {
isArray,
isObject,
......
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