Commit aaee4865 authored by Bill's avatar Bill

fix: PC平台活动页装修

parent 9ea094b9
...@@ -36,6 +36,7 @@ export default defineConfig({ ...@@ -36,6 +36,7 @@ export default defineConfig({
'process.env.SOCKET_URL': process.env.SOCKET_URL 'process.env.SOCKET_URL': process.env.SOCKET_URL
}, },
esbuild: {}, esbuild: {},
// mfsu: {},
locale: { locale: {
antd: true, antd: true,
// 默认情况下,当前语言环境的识别按照:localStorage 中 umi_locale 值 > 浏览器检测 > default 设置的默认语言 > 中文 // 默认情况下,当前语言环境的识别按照:localStorage 中 umi_locale 值 > 浏览器检测 > default 设置的默认语言 > 中文
......
...@@ -84,14 +84,17 @@ ...@@ -84,14 +84,17 @@
"prettier": "^1.19.1", "prettier": "^1.19.1",
"query-string": "^6.13.1", "query-string": "^6.13.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-color": "^2.19.3",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-sortablejs": "^6.0.0", "react-sortablejs": "^6.0.0",
"sortablejs": "^1.12.0", "sortablejs": "^1.12.0",
"styled-components": "^5.2.1", "styled-components": "^5.2.1",
"umi": "~3.2.28", "umi": "3.5",
"yorkie": "^2.0.0" "yorkie": "^2.0.0"
}, },
"devDependencies": { "devDependencies": {
"@linkseeks/god-upload-scp": "^1.0.0",
"@linkseeks/god-yapi2ts": "^1.0.0",
"@svgr/webpack": "^5.5.0", "@svgr/webpack": "^5.5.0",
"@types/sortablejs": "^1.10.6", "@types/sortablejs": "^1.10.6",
"async": "^3.2.0", "async": "^3.2.0",
...@@ -100,8 +103,6 @@ ...@@ -100,8 +103,6 @@
"connect-history-api-fallback": "^1.6.0", "connect-history-api-fallback": "^1.6.0",
"cross-env": "^7.0.2", "cross-env": "^7.0.2",
"express": "^4.17.1", "express": "^4.17.1",
"@linkseeks/god-upload-scp": "^1.0.0",
"@linkseeks/god-yapi2ts": "^1.0.0",
"gulp": "^4.0.2", "gulp": "^4.0.2",
"http-proxy-middleware": "^1.0.5", "http-proxy-middleware": "^1.0.5",
"json2ts": "^0.0.7", "json2ts": "^0.0.7",
......
...@@ -32,7 +32,7 @@ export interface SiteInfo { ...@@ -32,7 +32,7 @@ export interface SiteInfo {
name: string; name: string;
logo: string; logo: string;
siteUrl: string; siteUrl: string;
symbol: string; symbol?: any;
} }
export interface Global { export interface Global {
......
...@@ -18,18 +18,25 @@ ...@@ -18,18 +18,25 @@
.infoContainer { .infoContainer {
display: flex; display: flex;
flex-direction: row; flex-direction: column;
justify-content: space-between; // justify-content: space-between;
align-items: center; // align-items: center;
flex: 1; flex: 1;
.header {
color: #303133;
font-size: 16px;
line-height: 16px;
margin-bottom: @marginBottom;
display: flex;
flex-direction: row;
}
.info { .info {
.header { display: flex;
color: #303133; flex-direction: row;
font-size: 16px; justify-content: space-between;
line-height: 16px; align-items: center;
margin-bottom: @marginBottom;
}
.tags { .tags {
margin-bottom: @marginBottom; margin-bottom: @marginBottom;
} }
...@@ -46,6 +53,21 @@ ...@@ -46,6 +53,21 @@
margin-right: 24px; margin-right: 24px;
} }
} }
.fixture {
display: flex;
flex-direction: row;
padding: 8px 12px;
justify-content: center;
align-items: center;
background: #F4F5F7;
color: #000;
border-radius: 4px;
img {
width: 16px;
height: 16px;
margin-right: 8px;
}
}
} }
} }
...@@ -53,4 +75,32 @@ ...@@ -53,4 +75,32 @@
margin-left: auto; margin-left: auto;
// margin-right: 104px; // margin-right: 104px;
} }
.delete {
background: #F4F5F7;
color: #000;
border-radius: 4px;
padding: 8px 12px;
}
.copyLink {
height: 32px;
width: 32px;
display: flex;
align-items: center;
justify-content: center;
background: #F4F5F7;
border-radius: 4px;
border: none;
:global {
.ant-typography {
margin-bottom: 0px;
}
.ant-typography-copy {
color: #000;
margin-left: 0px;
}
}
}
} }
import React from 'react'; import React from 'react';
import { DeleteOutlined, EditOutlined, PlayCircleOutlined } from '@ant-design/icons'; import { DeleteOutlined, EditOutlined, PlayCircleOutlined } from '@ant-design/icons';
import { Button, Space, Popconfirm } from 'antd'; import { Button, Space, Popconfirm, Switch, Typography } from 'antd';
import moment from 'moment'; import moment from 'moment';
import { Link } from 'umi'; import { Link } from 'umi';
import styles from './index.less'; import styles from './index.less';
import { enumName } from '@/constants/const/environment'; import { enumName, WEB } from '@/constants/const/environment';
import StatusTag from '@/components/StatusTag'; import StatusTag from '@/components/StatusTag';
import { REQUEST_HEADER, TOP_DOMAIN } from '@/constants';
const { Paragraph } = Typography;
interface Iprops { interface Iprops {
templatePicUrl?: string, templatePicUrl?: string,
title: string, title: string,
...@@ -16,6 +18,8 @@ interface Iprops { ...@@ -16,6 +18,8 @@ interface Iprops {
endTime: number, endTime: number,
statusName: string, statusName: string,
id: number, id: number,
/** 商城子域名 */
url: string,
/** 1.WEB 2.H5 3.小程序 4.APP */ /** 1.WEB 2.H5 3.小程序 4.APP */
environment: 1 | 2 | 3 | 4 | number, environment: 1 | 2 | 3 | 4 | number,
onRemove?: ((id: number) => void )| null, onRemove?: ((id: number) => void )| null,
...@@ -24,17 +28,33 @@ interface Iprops { ...@@ -24,17 +28,33 @@ interface Iprops {
onChangeStatus: ((id: number, status: number) => void )| null onChangeStatus: ((id: number, status: number) => void )| null
} }
const APP_FIXTURE_LINK = `/marketingManage/marketing/activitiesManagement/fixtures`
const WEB_FIXTURE_LINK = `/marketingManage/marketing/activitiesManagement/webFixtures`
const PENDIGN_ONLINE = 1; const PENDIGN_ONLINE = 1;
const ONLINE = 2; const ONLINE = 2;
const IN_PROGRESS = 3; const IN_PROGRESS = 3;
const OFFLINE = 4; const OFFLINE = 4;
const END = 5; const END = 5;
const format = 'YYYY-MM-DD HH:mm:ss'; const format = 'YYYY-MM-DD HH:mm:ss';
const ActiveItem: React.FC<Iprops> = (props: Iprops) => { const ActiveItem: React.FC<Iprops> = (props: Iprops) => {
const { title, templateName, shopName, startTime, endTime, statusName, templatePicUrl, id, onRemove, status, onChangeStatus, environment } = props; const {
title,
templateName,
shopName,
startTime,
endTime,
statusName,
templatePicUrl,
id,
onRemove,
status,
onChangeStatus,
environment,
url
} = props;
const handleRemove = () => { const handleRemove = () => {
onRemove?.(id); onRemove?.(id);
...@@ -55,49 +75,70 @@ const ActiveItem: React.FC<Iprops> = (props: Iprops) => { ...@@ -55,49 +75,70 @@ const ActiveItem: React.FC<Iprops> = (props: Iprops) => {
<div className={styles.section}> <div className={styles.section}>
<img className={styles.image} src={templatePicUrl} /> <img className={styles.image} src={templatePicUrl} />
<div className={styles.infoContainer}> <div className={styles.infoContainer}>
<div className={styles.info}> <div className={styles.header}>
<div className={styles.header}> <Link to={`/marketingManage/marketing/activitiesManagement/view?id=${id}`}>{title}</Link>
<Link to={`/marketingManage/marketing/activitiesManagement/view?id=${id}`}>{title}</Link></div> <div className={styles.status}>
<div className={styles.tags}> <StatusTag type="success" title={statusName} />
<Space>
<StatusTag type="default" title={templateName} />
<StatusTag type="warnning" title={enumName[environment] || ''} />
</Space>
</div> </div>
<div className={styles.mall}> </div>
<span className={styles.label}>适用商城:</span> <div className={styles.info}>
<span>{shopName}</span> <div>
<div className={styles.tags}>
<Space>
<StatusTag type="default" title={templateName} />
<StatusTag type="warnning" title={enumName[environment] || ''} />
</Space>
</div>
<div className={styles.mall}>
<span className={styles.label}>适用商城:</span>
<span>{shopName}</span>
</div>
<div className={styles.time}>
<span className={styles.startTime}>有效期开始:{startTime && moment(startTime).format(format)}</span>
<span>有效期结束:{endTime && moment(endTime).format(format)}</span>
</div>
</div> </div>
<div className={styles.time}> <div>
<span className={styles.startTime}>有效期开始:{startTime && moment(startTime).format(format)}</span> <Space size={16}>
<span>有效期结束:{endTime && moment(endTime).format(format)}</span> {
environment === WEB && status !== END && (
<div className={styles.copyLink}>
<Paragraph copyable={{ text: `${REQUEST_HEADER}${url}.${TOP_DOMAIN}/activity/${id}` }} />
</div>
)
}
{
[PENDIGN_ONLINE, OFFLINE].includes(status) && (
// <Link to={`/memberCenter/marketingAbility/activityPages/management/edit?id=${id}`}>
<Link to={`${environment === WEB ? WEB_FIXTURE_LINK : APP_FIXTURE_LINK}?id=${id}`}>
{/* <Button icon={<EditOutlined />}></Button> */}
<div className={styles.fixture}>
{/* <img src={fixture}/> */}
<div>活动页装修</div>
</div>
</Link>
) || null
}
{
status === PENDIGN_ONLINE && (
<Popconfirm placement="topLeft" title={"是否确认删除?"} onConfirm={handleRemove} okText={"确定"} cancelText={"取消"}>
<div className={styles.delete}>
<DeleteOutlined />
</div>
</Popconfirm>
)
}
{
status !== END && (
<Switch checked={status === ONLINE || status === IN_PROGRESS} onChange={handleChangeStatus} />
// <Button onClick={handleChangeStatus} icon={<PlayCircleOutlined />} type={ status === 1 ? 'primary' : 'default' }>{statusName}</Button>
) || null
}
</Space>
</div> </div>
</div> </div>
<Space size={16}>
<div className={styles.status}>
<StatusTag type="success" title={statusName} />
</div>
{
[PENDIGN_ONLINE, OFFLINE].includes(status) && (
<Link to={`/marketingManage/marketing/activitiesManagement/edit?id=${id}`}>
<Button icon={<EditOutlined />}></Button>
</Link>
) || null
}
{
status === PENDIGN_ONLINE && (
<Popconfirm placement="topLeft" title={"确定删除吗?"} onConfirm={handleRemove} okText="确定" cancelText="取消">
<Button icon={<DeleteOutlined />}></Button>
</Popconfirm>
)
}
{
status !== END && (
<Button onClick={handleChangeStatus} icon={<PlayCircleOutlined />} type={ status === 1 ? 'primary' : 'default' }>{statusName}</Button>
) || null
}
</Space>
</div> </div>
</div> </div>
); );
......
...@@ -4,11 +4,14 @@ import MallLayout from '@/pages/pageCustomized/configs/componentConfigs/LingXiUI ...@@ -4,11 +4,14 @@ import MallLayout from '@/pages/pageCustomized/configs/componentConfigs/LingXiUI
import * as HTML from '@/pages/pageCustomized/configs/componentConfigs/HTML'; import * as HTML from '@/pages/pageCustomized/configs/componentConfigs/HTML';
import schema from './schema'; import schema from './schema';
import CustomLayouts from '../../components/Layouts'; import CustomLayouts from '../../components/Layouts';
import WebLayout from '../../components/WebDesignPanel/components/WebLayout'
// import MobileQuickNav from '@/pages/pageCustomized/configs/componentConfigs/LingXiUI/MobileQuickNav'; // import MobileQuickNav from '@/pages/pageCustomized/configs/componentConfigs/LingXiUI/MobileQuickNav';
import webSchema from './webSchema';
/** 组件注册 */
const componentSchemasMap = { MallLayout, ...schema, ...HTML }; const originalComponents = { ...LxUI, ...CustomLayouts, WebLayout };
const originalComponents = { ...LxUI, ...CustomLayouts }; /** 组件schema 注册 */
const componentSchemasMap = { ...schema, ...HTML, ...webSchema };
// /** // /**
// * 容器组件分类 // * 容器组件分类
......
import { ComponentSchemaType, PROPS_SETTING_TYPES, PROPS_TYPES } from '@linkseeks/design-core';
/**
* * web 装修页布局
*/
const WebLayout = {
propsConfig: {
children: {
type: PROPS_TYPES.string,
}
},
}
/**
* web 装修页广告图
*/
const WebAdvertise = {
propsConfig: {
imageUrl: PROPS_TYPES.string
},
}
/** web 装修页 每日推荐 */
const WebHotCommoditySwiper = {
propsConfig: {
}
}
const WebHotCommodityItem = {
propsConfig: {}
}
/** web 装修页 优惠券容器 */
const WebCouponContainer = {
propsConfig: {
title: PROPS_TYPES.string,
/** 该容器下的children 只能是 WebCoupon 组件*/
children: PROPS_TYPES.objectArray
}
}
/** web 装修页 优惠券 */
const WebCoupon = {
propsConfig: {
}
}
/** web 装修页 活动商品容器,包含 */
const WebCommodityContainer = {
propsConfig: {
title: PROPS_TYPES.string,
/** 该容器下的children 只能是 WebCommodity 组件*/
children: PROPS_TYPES.objectArray
}
}
const WebCommodity = {
propsConfig: {
title: PROPS_TYPES.string,
children: PROPS_TYPES.objectArray
}
}
const WebCustomCommodity = {
propsConfig: {
title: PROPS_TYPES.string,
children: PROPS_TYPES.objectArray
}
}
export default {
WebLayout,
WebAdvertise,
WebHotCommoditySwiper,
WebHotCommodityItem,
WebCouponContainer,
WebCoupon,
WebCommodityContainer,
WebCommodity,
WebCustomCommodity
}
import { Ref, useEffect, useState } from "react";
function useDraggable(el: React.MutableRefObject<HTMLDivElement>) {
const [{ dx, dy }, setOffset] = useState({ dx: 0, dy: 0 });
useEffect(() => {
const handleMouseDown = event => {
const startX = event.pageX
const startY = event.pageY
el.current.style.cursor = 'grabbing';
const { scrollLeft, scrollTop } = el.current
const handleMouseMove = event => {
const newDx = event.pageX - startX;
const newDy = event.pageY - startY;
el.current.scrollLeft = -newDx + scrollLeft;
el.current.scrollTop = -newDy + scrollTop;
// setOffset({ dx: el.current.scrollLeft, dy: newDy });
};
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener(
"mouseup",
() => {
el.current.style.cursor = 'default';
document.removeEventListener("mousemove", handleMouseMove);
},
{ once: true }
);
};
el.current?.addEventListener("mousedown", handleMouseDown);
return () => {
el.current?.removeEventListener("mousedown", handleMouseDown);
};
}, [dx, dy]);
}
export default useDraggable
{
"themeStyle": {
"sort": 0,
"props": {
"color": "#00A98F"
}
},
"top":{
"sort":1,
"props": {
"theme":0,
"visible": true,
"imageUrl": ""
}
},
"hot":{
"sort":3,
"props": {
"theme":0,
"visible": true,
"title": "活动推荐",
"childrenData": []
}
},
"coupon":{
"sort":2,
"props": {
"theme":0,
"visible": true,
"title": "优惠好券",
"childrenData": []
}
},
"specialOffer":{
"sort":5,
"props": {
"theme": 0,
"visible": true,
"title":"特价促销",
"childrenData": []
}
},
"plummet":{
"sort":4,
"props": {
"theme":0,
"visible": true,
"title":"直降促销",
"childrenData": []
}
},
"discount":{
"sort":6,
"props": {
"theme":0,
"visible": true,
"title":"折扣促销",
"childrenData": []
}
},
"fullQuantitySub":{
"sort":7,
"props": {
"theme":1,
"visible": true,
"title":"满量促销--满量减",
"childrenData": []
}
},
"fullQuantityDiscount":{
"sort":8,
"props": {
"theme":1,
"visible": true,
"title":"满量促销--满量折",
"childrenData": []
}
},
"fullMoneySub":{
"sort":9,
"props": {
"theme":1,
"visible": true,
"title":"满额促销--满额减",
"childrenData": []
}
},
"fullMoneyDiscount":{
"sort":10,
"props": {
"theme":1,
"visible": true,
"title":"满额促销--满额折",
"childrenData": []
}
},
"giveProduct":{
"sort":11,
"props": {
"theme":3,
"visible": true,
"title":"赠送促销--赠送商品(满额赠+买商品赠)",
"childrenData":[]
}
},
"giveCoupon":{
"sort":12,
"props": {
"theme": 4,
"visible": true,
"title":"赠送促销--赠送优惠劵(满额赠+买商品赠)",
"childrenData":[]
}
},
"morePiece":{
"sort":13,
"props": {
"theme":0,
"visible": true,
"title":"多件促销",
"childrenData": []
}
},
"combination":{
"sort":14,
"props": {
"theme": 2,
"visible": true,
"title":"组合促销",
"childrenData": []
}
},
"groupPurchase":{
"sort":15,
"props": {
"theme":0,
"visible": true,
"title":"拼团",
"childrenData":[]
}
},
"bargain":{
"sort":16,
"props": {
"theme":0,
"visible": true,
"title":"砍价",
"childrenData":[]
}
},
"secKill":{
"sort":17,
"props": {
"theme":0,
"visible": true,
"title":"秒杀",
"childrenData": []
}
},
"fullSwap":{
"sort":18,
"props": {
"theme":0,
"visible": true,
"title":"换购-满额换购",
"childrenData":[]
}
},
"buySwap":{
"sort":19,
"props": {
"theme":0,
"visible": true,
"title":"换购-买商品换购",
"childrenData":[]
}
},
"preSale":{
"sort": 20,
"props": {
"theme":0,
"visible": true,
"title":"预售",
"childrenData":[]
}
},
"setMeal":{
"sort":21,
"props": {
"theme": 2,
"visible": true,
"title":"套餐",
"childrenData": []
}
},
"attempt":{
"sort":22,
"props": {
"theme":0,
"visible": true,
"title":"试用",
"childrenData":[]
}
},
"suggestProduct":{
"sort":23,
"props": {
"visible":true,
"childrenData":[]
}
}
}
import React, { useMemo } from 'react';
import { useSelector } from '@linkseeks/design-react';
import { addChildComponent, changeProps, deleteComponentByKey } from '@linkseeks/design-core';
import ComponentModule from './ComponentModule';
import { Switch } from 'antd'
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';
import combinationImg from '@/asserts/activity/combination.png';
import fullMoneyDiscountImg from '@/asserts/activity/fullMoneyDiscount.png';
import fullMoneySubImg from '@/asserts/activity/fullMoneySub.png';
import fullQuantityDiscountImg from '@/asserts/activity/fullQuantityDiscount.png';
import fullQuantitySubImg from '@/asserts/activity/fullQuantitySub.png';
import fullSwapImg from '@/asserts/activity/fullSwap.png';
import giveProductImg from '@/asserts/activity/giveProduct.png';
import groupPurchaseImg from '@/asserts/activity/groupPurchase.png';
import morePieceImg from '@/asserts/activity/morePiece.png';
import plummetImg from '@/asserts/activity/plummet.png';
import preSaleImg from '@/asserts/activity/preSale.png';
import secKillImg from '@/asserts/activity/secKill.png';
import setMealImg from '@/asserts/activity/setMeal.png';
import discountImg from '@/asserts/activity/discount.png';
import specialOfferImg from '@/asserts/activity/specialOffer.png';
import giveCouponImg from '@/asserts/activity/giveCoupon.png';
const ACTIVITYS = [
"specialOffer",
"plummet",
"discount",
"fullQuantitySub",
"fullQuantityDiscount",
"fullMoneySub",
"fullMoneyDiscount",
"giveProduct",
"giveCoupon",
"morePiece",
"combination",
"groupPurchase",
"bargain",
"secKill",
"fullSwap",
"buySwap",
"preSale",
"setMeal",
"attempt"
] as const;
const ACTIVITYS_MAP = {
"specialOffer": {
title: '特价促销',
image: specialOfferImg
},
"plummet": {
title: '直降促销',
image: plummetImg
},
"discount": {
title: '折扣促销',
image: discountImg
},
"fullQuantitySub": {
title: '满量促销--满量减"',
image: fullQuantitySubImg
},
"fullQuantityDiscount": {
title: '满量促销--满量折',
image: fullQuantityDiscountImg
},
"fullMoneySub": {
title: '满额促销--满额减',
image: fullMoneySubImg
},
"fullMoneyDiscount": {
title: '满额促销--满额折"',
image: fullMoneyDiscountImg
},
"giveProduct": {
title: '赠送促销--赠送商品',
image: giveProductImg
},
"giveCoupon": {
title: '赠送促销--赠送优惠劵',
image: giveCouponImg
},
"morePiece": {
title: '多件促销',
image: morePieceImg
},
"combination": {
title: '组合促销',
image: combinationImg
},
"groupPurchase": {
title: '拼团',
image: groupPurchaseImg
},
"bargain": {
title: '砍价',
image: bargainImg
},
"secKill": {
title: '秒杀',
image: secKillImg
},
"fullSwap": {
title: '换购-满额换购',
image: fullSwapImg
},
"buySwap": {
title: '换购-买商品换购',
image: buySwapImg
},
"preSale": {
title: '预售',
image: preSaleImg
},
"setMeal": {
title: '套装',
image: setMealImg
},
"attempt": {
title: '试用',
image: attemptImg
}
} as const;
type ModuleType = {
// title: string,
// visible: boolean,
// dataIndex: string,
treeKey: string,
}
type Turple<T extends readonly string[], P> = {
[key in T[number]]: P
}
const WebComponentModule = () => {
const { pageConfig } = useSelector(['pageConfig']);
console.log("pageConfig", pageConfig)
const modules = useMemo(() => {
const config = pageConfig;
const res: Turple<typeof ACTIVITYS, ModuleType> = {} as Turple<typeof ACTIVITYS, ModuleType>;
Object.keys(config).forEach((_item) => {
const { props = {} } = config[_item];
const dataIndex = config[_item]?.otherProps?.type;
if (ACTIVITYS.includes(dataIndex)) {
// const visible = props.visible ?? true
res[dataIndex] = {
treeKey: _item
}
}
});
return res;
}, [pageConfig]);
// const onModuleVisibleChange = (checked: boolean, option) => {
// const props = pageConfig[option.treeKey];
// changeProps({
// treeKey: option.treeKey,
// props: {
// ...props,
// visible: checked
// }
// });
// };
const handleChange = (isChecked: boolean, _item: keyof typeof ACTIVITYS_MAP) => {
console.log(modules, (isChecked), "_item", _item)
if (!isChecked) {
deleteComponentByKey({
key: modules[_item].treeKey,
parentKey: '0',
parentPropName: ''
})
return;
}
const newKey = Object.keys(pageConfig).length + 1
addChildComponent({
newKey: `${newKey}`,
componentName: 'WebCommodityContainer',
parentPropName: '',
parentKey: '0',
childProps: {
addBtnText: "添加子节点",
canDelete: false,
childComponentName: "WebCommodity",
childNodes: [],
childProps: {
otherProps: {
type: `${_item}Item`
}
},
otherProps: {type: _item},
props: {visible: true, theme: 0, title: ACTIVITYS_MAP[_item].title},
title: ACTIVITYS_MAP[_item].title,
}
})
}
return (
<div className={styles.module}>
{
Object.keys(ACTIVITYS_MAP).map((_item: keyof typeof ACTIVITYS_MAP) => {
// const { visible, title, dataIndex } = _item;
const { title, image } = ACTIVITYS_MAP[_item]
const isChecked = modules[_item] ? true : false
return (
<div className={styles.moduleItem} key={_item}>
<div style={{ height: '160px', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center'}}>
<img style={{width: '24px', height: '24px'}} src={image} />
<div style={{margin: '8px 0'}}>{title}</div>
<div>
<Switch size="small" checked={isChecked} onChange={() => handleChange(!isChecked, _item)} />
</div>
</div>
</div>
);
})
}
</div>
);
};
export default WebComponentModule;
import React from 'react';
interface Iprops {
/** 广告图 */
imageUrl: string,
}
/** WEB装修页 广告图 */
const WebAdvertise: React.FC<Iprops> = (props: Iprops) => {
const { imageUrl, ...other } = props;
const { onClick, onMouseOver, className, getOperateState} = other as any;
const divProps = {
onClick,
onMouseOver,
};
return (
<div style={{height: '460px', width: '1920px'}} className={className} {...divProps}>
<img src={imageUrl} style={{width: '100%', height: '100%'}} />
</div>
)
}
export default WebAdvertise
.card {
display: flex;
flex-direction: column;
.card-header {
display: flex;
flex-direction: row;
align-items: center;
}
.card-header-title {
color: #fff;
font-size: 38px;
line-height: 53px;
margin-bottom: 16px;
}
}
import React from 'react';
import styles from './index.less';
import classNames from 'classnames'
interface Iprops {
extra?: React.ReactNode,
title: string | React.ReactNode,
children: React.ReactNode,
/** 以下属性是装修自带的属性 */
onMouseOver?: () => void,
onClick?: () => void,
className?: string,
}
const WebCard: React.FC<Iprops> = (props: Iprops) => {
const { extra, title, children, className, ...other } = props;
return (
<div className={classNames(styles.card, className)} {...other}>
<div className={styles['card-header']}>
{
typeof title === 'string' && (
<span className={styles['card-header-title']}>{title}</span>
) || title
}
{extra}
</div>
<div className={styles['card-content']}>
{children}
</div>
</div>
)
}
export default WebCard
.container {
width: 1200px;
margin: 40px auto;
}
.commodityList {
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin-right: -16px;
// justify-content: center;
.commodityItem {
// margin-right: 16px;
width: 20%;
padding-right: 16px;
margin-bottom: 16px;
&:last-of-type {
margin-right: 0px;
}
}
}
import React from 'react';
import WebCard from '../WebCard';
import styles from './index.less'
import classNames from 'classnames'
/** web 装修页活动商品容器 */
interface Iprops {
title: string,
children: React.ReactNode,
/** 控制显示隐藏 */
status: boolean,
/** 以下是装修容器提供的属性 */
className: string,
onMouseOver: () => void,
onClick: () => void
}
const WebCommodityContainer: React.FC<Iprops> = (props: Iprops) => {
const { title, children, className, onMouseOver, onClick, status } = props;
const visible = status ?? true
const designProps = {
onMouseOver,
onClick
}
if (!visible) {
return null;
}
const renderChildren = () => {
return React.Children.map(children, (_child: any, _index) => {
return (
<div className={styles.commodityItem} key={_index}>
{
React.cloneElement(_child, {..._child.props})
}
</div>
)
})
}
return (
<div className={classNames(styles.container, className)} {...designProps}>
<WebCard title={title}>
<div className={styles.commodityList}>
{renderChildren()}
</div>
</WebCard>
</div>
)
}
export default WebCommodityContainer
.empty {
min-height: 359px;
height: 100%;
}
.commodity {
display: flex;
flex-direction: column;
overflow: hidden;
border-radius: 8px;
background-color: #fff;
width: 227px;
&-image {
width: 100%;
height: 227px;
}
.commodity-info {
padding: 16px;
.commodity-info-name {
font-size: 14px;
line-height: 20px;
color: #252D37;
margin-bottom: 8px;
}
.commodity-info-tags {
margin-bottom: 8px;
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
.commodity-info-tags-item {
margin-right: 4px;
margin-top: 4px;
}
}
.commodity-info-progress {
.buyBtn {
background-color: #EF3346;
color: #fff;
padding: 6px 12px;
margin-left: 8px;
border-radius: 8px;
}
}
.commodity-info-hasBuy {
font-size: 12px;
color: #91959B;
}
}
}
import React from 'react';
import styles from './item.less'
import { Progress, CustomizeTag } from '@linkseeks/design-ui';
import { TagOutlined } from '@ant-design/icons';
import classNames from 'classnames'
import Price from '../../Price';
import { GetMarketingAdornMerchantActivityListAdornResponseDetail } from '@/services/MarketingV2Api';
// import Label from '../Label';
type ActivityListType = {
/** 活动类型 */
belongType: number,
/** 活动id */
id: number,
/** 活动label */
label: string,
/** 活动名称 */
name: string
/** 类型 */
type: string
}
type ProductType = GetMarketingAdornMerchantActivityListAdornResponseDetail['goodsList'][0] & {
hasSold: number,
activityList: ActivityListType[],
/** 自定义标签 */
label: string[]
}
interface Iprops extends ProductType {
/** 以下是装修容器提供的属性 */
className: string,
onMouseOver: () => void,
onClick: () => void
}
const WebCommodity: React.FC<Iprops> = (props: Iprops) => {
const { className, onMouseOver, onClick, ...productData } = props;
const designProps = {
onMouseOver,
onClick
}
if (!productData.productId) {
return (
<div className={classNames(styles.commodity, className, styles.empty)} {...designProps}/>
)
}
const renderLabels = () => {
const labels = productData.activityList.map((_item: ActivityListType) => _item.label).concat(productData.label).filter(Boolean);
console.log(labels);
return (
<div className={styles['commodity-info-tags']}>
{
labels.map((_item: string, key: number) => {
return (
<div className={styles['commodity-info-tags-item']} key={`${_item}_${key}`}>
<CustomizeTag
type={'danger'}
name={_item}
/>
</div>
)
})
}
</div>
)
}
return (
<div className={classNames(styles.commodity, className)} {...designProps}>
<img className={styles['commodity-image']} src={productData.productImgUrl} />
<div className={styles['commodity-info']}>
<div className={styles['commodity-info-name']}>
{productData.productName}
</div>
{renderLabels()}
<div className={styles['commodity-info-price']}>
<Price originalPrice={productData.price} discountPrice={productData.activityPrice} />
</div>
{/* <div className={styles['commodity-info-progress']}>
<Progress
progressTips='剩余33%'
extra={
<div className={styles.buyBtn}>去抢购</div>
}
/>
</div> */}
<div className={styles['commodity-info-hasBuy']}>
{`已抢 ${productData?.hasSold || 0} ${productData.unit}`}
</div>
</div>
</div>
)
}
export default WebCommodity;
.container {
width: 1200px;
margin: 40px auto;
.couponList {
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin-right: -16px;
margin-bottom: -16px;
// justify-content: center;
.couponItem {
flex-basis: 25%;
padding-right: 16px;
margin-bottom: 16px;
}
}
}
.empty {
height: 113px;
}
.coupon {
display: flex;
flex-direction: row;
background-color: #fff;
align-items: center;
padding: 16px;
border-radius: 8px;
&-conditions-wrap {
display: flex;
flex-direction: column;
padding-right: 16px;
position: relative;
.coupon-money {
color: #EF3346;
font-size: 48px;
line-height: 56px;
.coupon-currency {
font-size: 18px;
line-height: 25px;
}
}
.coupon-condition {
color: #606266;
font-size: 12px;
}
&::after {
content: "";
position: absolute;
top: 0;
bottom: 0;
right: 0;
height: 100%;
width: 1px;
background-color: #F4F5F7;
}
}
.coupon-info {
padding-left: 16px;
display: flex;
flex: 1;
flex-direction: column;
.coupon-info-typeName {
color: #303133;
font-size: 14px;
line-height: 20px;
margin-bottom: 4px;
font-weight: 600;
}
.coupon-info-date {
color: #606266;
font-size: 12px;
line-height: 16px;
margin-bottom: 8px;
}
.coupon-info-btn {
background-color: #EF3346;
color: #fff;
font-size: 14px;
padding: 8px;
border-radius: 8px;
text-align: center;
cursor: pointer;
}
.coupon-info-btn-disabled {
background-color: #BABCC0;
color: #fff;
cursor: not-allowed;
padding: 8px;
border-radius: 8px;
font-size: 14px;
text-align: center;
}
}
}
import React from 'react';
import styles from './index.less';
import classNames from 'classnames';
import { GetMarketingCouponActivityPageSelectPageResponseDetail } from '@/services/MarketingV2Api';
interface Iprops extends GetMarketingCouponActivityPageSelectPageResponseDetail {
/** 以下是装修容器提供的属性 */
className: string,
onMouseOver: () => void,
onClick: () => void
}
/** 优惠券类型 有效类型1-固定有效时间2-自领取开始时间*/
const IS_STABLE = 1
const WebCoupon: React.FC<Iprops> = (props: Iprops) => {
const { className, onMouseOver, onClick, ...couponData } = props;
const designProps = {
onMouseOver,
onClick
}
if (!couponData?.id) {
return (
<div className={classNames(styles.coupon, className, styles.empty)} {...designProps} />
)
}
return (
<div className={classNames(styles.coupon, className)} {...designProps}>
<div className={styles['coupon-conditions-wrap']}>
<span className={styles['coupon-money']}>
<span className={styles['coupon-currency']}></span>
{couponData.denomination}
</span>
<span className={styles['coupon-condition']}>满${couponData.useConditionMoney}立减</span>
</div>
<div className={styles['coupon-info']}>
<span className={styles['coupon-info-typeName']}>{couponData.typeName}</span>
<span className={styles['coupon-info-date']}>
{
couponData.effectiveType === IS_STABLE
? `${couponData.effectiveTimeStart}至${couponData.effectiveTimeEnd}`
: `领取后${couponData.invalidDay}天失效`
}
</span>
<div className={styles['coupon-info-btn']}>立即领取</div>
</div>
</div>
)
}
export default WebCoupon;
import React from 'react';
import Coupon from '.';
import WebCard from '../WebCard';
import styles from './index.less'
import classNames from 'classnames';
interface Iprops {
title: string,
children: React.ReactNode,
status: boolean,
/** 以下是装修容器提供的属性 */
className: string,
onMouseOver: () => void,
onClick: () => void
}
const WebCouponContainer: React.FC<Iprops> = (props: Iprops) => {
const { title, children, className, onMouseOver, onClick, status } = props;
const visible = status
const designProps = {
onMouseOver,
onClick
}
if (!visible) {
return null
}
const renderChildren = () => {
return React.Children.map(children, (_child: any, _index) => {
return (
<div className={styles.couponItem} key={_index}>
{
React.cloneElement(_child, {..._child.props})
}
</div>
)
})
}
return (
<div className={classNames(styles.container, className)} {...designProps}>
<WebCard title={title}>
<div className={styles.couponList}>
{renderChildren()}
</div>
</WebCard>
</div>
)
}
export default WebCouponContainer
import React, { useState } from 'react';
import cx from 'classnames';
import { Tooltip } from 'antd';
interface Iprops {
children: React.ReactNode
/** 以下是装修容器提供的属性 */
className: string,
onMouseOver: () => void,
onClick: () => void
}
const WebCustomCommodity: React.FC<Iprops> = (props: Iprops) => {
const { children, className, ...other } = props;
const classNameStr = cx(className);
const { onClick, onDrag, onDragEnd, onDragEnter, onDragStart, onMouseOver, getOperateState } = other as any;
const divProps = {
onClick, onDrag, onDragEnd, onDragEnter, onDragStart, onMouseOver,
};
const renderComponent = () => {
return (
<div>
{
React.Children.map(children, (_child: any) => {
if (_child) {
return React.cloneElement(_child, {title: '', ..._child?.props || {}});
}
return null;
})
}
</div>
);
};
return (
<Tooltip placement="topLeft" title={"自定义区域"} arrowPointAtCenter>
<div className={classNameStr} style={{width: '1200px', margin: '0 auto', minHeight: '50px'}} {...divProps}>
{
renderComponent()
}
</div>
</Tooltip>
);
};
export default WebCustomCommodity;
import React from 'react';
import { Progress, CustomizeTag } from '@linkseeks/design-ui';
import styles from './index.less';
import classNames from 'classnames'
import Price from '../../Price';
import { GetMarketingAdornMerchantActivityListAdornResponseDetail } from '@/services/MarketingV2Api';
type ActivityListType = {
/** 活动类型 */
belongType: number,
/** 活动id */
id: number,
/** 活动label */
label: string,
/** 活动名称 */
name: string
/** 类型 */
type: string
}
type ProductType = GetMarketingAdornMerchantActivityListAdornResponseDetail['goodsList'][0] & {
hasSold: number,
activityList: ActivityListType[]
}
interface Iprops extends ProductType {
/** 以下是装修容器提供的属性 */
className: string,
onMouseOver: () => void,
onClick: () => void,
}
const HotCommodityItem: React.FC<Iprops> = (props: Iprops) => {
const { className, onMouseOver, onClick, ...productData } = props;
const designProps = {
onMouseOver,
onClick
}
if (!productData?.productId) {
return (
<div className={classNames(styles['hot-commodity'], className, styles.empty)} {...designProps} />
)
}
const renderLabels = () => {
return (
<div className={styles['hot-commodity-info-tags']}>
{
productData.activityList.map((_item: ActivityListType) => {
return (
<div className={styles['hot-commodity-info-tags-item']} key={_item.id}>
<CustomizeTag
type={'danger'}
name={_item.label}
/>
</div>
)
})
}
</div>
)
}
return (
<div className={classNames(styles['hot-commodity'], className)} {...designProps}>
<img src={productData.productImgUrl} />
<div className={styles['hot-commodity-info']}>
<div className={styles['hot-commodity-info-name']}>{productData.productName}</div>
{renderLabels()}
<div className={styles['hot-commodity-info-price']}>
<Price originalPrice={productData.price} discountPrice={productData.activityPrice} unit={productData.unit} />
</div>
<div className={styles['commodity-info-hasSold']}>
{`已抢 ${productData?.hasSold || 0} ${productData.unit}`}
</div>
</div>
</div>
)
}
export default HotCommodityItem
.container {
width: 1300px;
margin: 40px auto 0px auto;
&-title {
margin-left: 50px;
font-size: 38px;
line-height: 53px;
margin-bottom: 16px;
color: #fff;
}
}
.swiper {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
.hidden {
opacity: 0;
}
.swiper-view {
width: 1200px;
overflow: hidden;
.commodityList {
display: flex;
flex-direction: row;
transition: all 1s;
.commodity-item {
margin-right: 16px;
}
}
}
.swiper-prev,
.swiper-next {
width: 40px;
height: 40px;
border-radius: 50%;
background-color: #fff;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
}
.empty {
height: 150px;
}
.hot-commodity {
display: inline-flex;
flex-direction: row;
padding: 8px;
background-color: #fff;
border-radius: 8px;
width: 389px;
img {
width: 134px;
height: 134px;
}
.hot-commodity-info {
margin-left: 16px;
display: flex;
flex-direction: column;
.hot-commodity-info-name {
font-size: 14px;
line-height: 20px;
color: #252D37;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
/*! autoprefixer: off */
-webkit-box-orient: vertical;
}
.hot-commodity-info-tags {
display: flex;
flex-direction: row;
flex-wrap: wrap;
.hot-commodity-info-tags-item {
margin: 4px 4px 0 0;
}
}
.hot-commodity-info-price {
margin-top: auto;
}
.commodity-info-hasSold {
font-size: 12px;
color: #91959B;
margin-top: 8px;
}
}
}
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
import React from 'react';
import styles from './index.less';
import useSwiper from './useSwiper';
import HotCommodity from './hotCommodityItem';
import WebCard from '../WebCard';
import classNames from 'classnames';
interface Iprops {
title: string,
children: React.ReactNode,
/** 控制显示隐藏 */
status: boolean
/** 以下是装修容器提供的属性 */
className: string,
onMouseOver: () => void,
onClick: () => void
}
/** 当前屏幕的swiper 一页的宽度 */
const SCREEN_WIDTH = 1200;
/** 每个 HotCommodityItem 间隔看度 */
const OFFSET_WIDTH = 16
const HotCommoditySwiper: React.FC<Iprops> = (props: Iprops) => {
const { title, children, className, onMouseOver, onClick, status } = props;
const count = React.Children.count(children)
const visible = status;
const { current, onPrev, onNext } = useSwiper({ count: count });
const designProps = {
onMouseOver,
onClick
}
if (!visible) {
return null;
}
const renderChildren = () => {
return React.Children.map(children, (_child: any, _index) => {
return (
<div className={styles['commodity-item']} key={_index}>
{
React.cloneElement(_child, {..._child.props})
}
</div>
)
})
}
return (
<div className={classNames(styles.container, className)} {...designProps}>
<WebCard title={<div className={styles['container-title']}>{title}</div>}>
<div className={styles.swiper}>
<div className={classNames(styles['swiper-prev'], { [styles.hidden]: current === 0 })} onClick={onPrev}>
<LeftOutlined style={{fontSize: '20px', color: 'red'}}/>
</div>
<div className={styles['swiper-view']}>
<div className={styles.commodityList} style={{ transform: `translateX(${((-current * SCREEN_WIDTH) + -(current * OFFSET_WIDTH))}px)` }}>
{renderChildren()}
</div>
</div>
<div className={classNames(styles['swiper-next'], { [styles.hidden]: current * 3 <= count })} onClick={onNext}>
<RightOutlined style={{fontSize: '20px', color: 'red'}} />
</div>
</div>
</WebCard>
</div>
)
}
export default HotCommoditySwiper;
import { useState } from 'react';
type Options = {
/** swiper 子组件数量, 我们已3个一组 */
count: number,
}
function useSwiper(options: Options) {
const { count } = options;
const [current, setCurrent] = useState(0);
const onPrev = () => {
if (current === 0) {
return;
}
setCurrent(current - 1)
}
const onNext = () => {
if ((current + 1) * 3 >= count) {
return;
}
setCurrent(current + 1)
}
return {
current,
onNext,
onPrev
}
}
export default useSwiper;
...@@ -4,10 +4,29 @@ import CommodityList from './CommodityList'; ...@@ -4,10 +4,29 @@ import CommodityList from './CommodityList';
import WrapCommodityList from './WrapCommodityList'; import WrapCommodityList from './WrapCommodityList';
import Combination from './Combination'; import Combination from './Combination';
/** WEB, 需要到 common/configs/webSchma配置 */
import WebAdvertise from './WebAdvertise';
import WebHotCommoditySwiper from './WebHotCommoditySwiper';
import WebHotCommodityItem from './WebHotCommoditySwiper/hotCommodityItem'
import WebCoupon from './WebCoupon';
import WebCouponContainer from './WebCoupon/webCouponContainer';
import WebCommodityContainer from './WebCommodity';
import WebCommodity from './WebCommodity/item'
import WebCustomCommodity from './WebCustomCommodity';
export default { export default {
Advertisement, Advertisement,
Coupon, Coupon,
CommodityList, CommodityList,
WrapCommodityList, WrapCommodityList,
Combination Combination,
WebAdvertise,
WebHotCommoditySwiper,
WebHotCommodityItem,
WebCouponContainer,
WebCoupon,
WebCommodityContainer,
WebCommodity,
WebCustomCommodity
}; };
.price {
display: flex;
flex-direction: row;
align-items: center;
.originalPrice {
font-size: 16px;
line-height: 18px;
color: #EF3346;
.currency {
font-size: 12px;
}
}
.discountPrice {
color: #91959B;
font-size: 12px;
margin-left: 8px;
text-decoration: line-through;
}
}
import React from "react";
import styles from './index.less'
interface Iprops {
originalPrice: number,
discountPrice: number,
unit?: string
}
const Price: React.FC<Iprops> = (props: Iprops) => {
const { unit = '件', originalPrice, discountPrice} = props
return (
<div className={styles.price}>
<div className={styles.originalPrice}>
<span className={styles.currency}></span>
<span>{originalPrice}/{unit}</span>
</div>
<div className={styles.discountPrice}>
{discountPrice}/{unit}
</div>
</div>
)
}
export default Price
.color {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
flex: 1;
position: relative;
.color-item {
width: 16px;
height: 16px;
border-radius: 8px;
margin-right: 8px;
}
.color-item-active {
// padding: 4px;
// border: 1px solid red;
// border-radius: 50%;
}
.color-picker-container {
position: relative;
.select-color {
padding: 2px 4px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
border-radius: 16px;
background-color: #EDEEEF;
.active-color {
width: 16px;
height: 16px;
border-radius: 8px;
}
.active-color-text {
padding: 0 8px;
}
}
.picker {
position: absolute;
right: 0;
top: 0;
display: none;
padding-top: 32px;
}
&:hover {
.picker {
display: block;
}
}
}
}
\ No newline at end of file
import React, { useEffect, useState } from 'react';
import styles from './color.less';
import className from 'classnames';
import { SketchPicker } from 'react-color';
import { useHover } from '@umijs/hooks';
interface Iprops {
/**
* 当前背景颜色
* 16进制, eg. #E80047
*/
color?: string,
onChange?: ((hex: string) => void) | null
}
const Color: React.FC<Iprops> = (props: Iprops) => {
const { onChange, color } = props
const [activeColor, setActiveColor] = useState<string>('#E80047')
const data = ['#E80047', '#9D27B1', '#FF6700', '#0493E2', '#00A98F', '#607E89'] as const;
const handleColorChange = ({ hex }) => {
console.log(hex)
setActiveColor(hex)
onChange?.(hex)
}
useEffect(() => {
if (!color) {
return;
}
setActiveColor(color);
}, [color])
return (
<div className={styles.color}>
{
data.map((_item) => {
return (
<div
key={_item}
className={
className({
[styles['color-item-active']]: activeColor === _item
})
}
>
<div onClick={() => handleColorChange({hex: _item})} className={className(styles['color-item'], )} style={{background: _item}}></div>
</div>
)
})
}
<div className={styles['color-picker-container']}>
<div className={styles['select-color']} >
<div className={styles['active-color']} style={{background: activeColor}} ></div>
<span className={styles['active-color-text']}>{activeColor}</span>
</div>
<div className={styles.picker}>
<SketchPicker
color={activeColor}
onChangeComplete={ handleColorChange }
/>
</div>
</div>
</div>
)
}
export default Color
\ No newline at end of file
...@@ -3,13 +3,22 @@ import { ArrowLeftOutlined } from '@ant-design/icons'; ...@@ -3,13 +3,22 @@ import { ArrowLeftOutlined } from '@ant-design/icons';
import { history } from 'umi'; import { history } from 'umi';
import { Modal } from 'antd'; import { Modal } from 'antd';
import styles from './index.less'; import styles from './index.less';
import { changeProps, STATE_PROPS, useSelector } from '@linkseeks/design-react';
import Color from './color';
interface Iprops { interface Iprops {
extra?: React.ReactNode, extra?: React.ReactNode,
title: string | React.ReactNode, title: string | React.ReactNode,
} }
type SettingPanelType = {
pageConfig: any,
}
const Toolbar: React.FC<Iprops> = (props: Iprops) => { const Toolbar: React.FC<Iprops> = (props: Iprops) => {
const { pageConfig } = useSelector<SettingPanelType, STATE_PROPS>(['pageConfig']);
const color = pageConfig?.[0]?.props?.backgroundColor;
const { title, extra } = props; const { title, extra } = props;
const goback =() => { const goback =() => {
Modal.confirm({ Modal.confirm({
...@@ -20,6 +29,15 @@ const Toolbar: React.FC<Iprops> = (props: Iprops) => { ...@@ -20,6 +29,15 @@ const Toolbar: React.FC<Iprops> = (props: Iprops) => {
}); });
}; };
const handleChangeColor = (hex: string) => {
changeProps({
treeKey: '0',
props: {
backgroundColor: hex,
},
});
}
return ( return (
<div className={styles.toolbar}> <div className={styles.toolbar}>
<div className={styles.back} onClick={goback}> <div className={styles.back} onClick={goback}>
...@@ -28,6 +46,11 @@ const Toolbar: React.FC<Iprops> = (props: Iprops) => { ...@@ -28,6 +46,11 @@ const Toolbar: React.FC<Iprops> = (props: Iprops) => {
<div className={styles.title}> <div className={styles.title}>
{title} {title}
</div> </div>
{
(
<Color onChange={handleChangeColor} color={color} />
)
}
<div className={styles.extra}>{extra}</div> <div className={styles.extra}>{extra}</div>
</div> </div>
); );
......
.layout {
display: flex;
flex-direction: column;
.header {
margin-bottom: 8px;
}
.content {
padding: 0px 0px 24px 0;
}
}
import React from 'react';
import TopBar from './topbar';
import Search from './search'
import styles from './index.less'
import Tabs from './tabs';
/**
* WEB 装修页布局组件
*/
interface Iprops {
children: React.ReactNode,
backgroundColor: string
}
const WebLayout: React.FC<Iprops> = (props: Iprops) => {
const { children, backgroundColor } = props
return (
<div className={styles.layout}>
<div className={styles.header}>
<TopBar />
<Search />
<Tabs />
</div>
<div className={styles.content} style={{background: backgroundColor}}>
{children}
</div>
<div className={styles.footer}></div>
</div>
)
}
export default WebLayout;
.header {
display: flex;
flex-direction: row;
background-color: #fff;
// justify-content: center;
width: 1200px;
margin: 0 auto;
padding: 30px 0;
.site-logo {
width: 200px;
display: flex;
flex-direction: row;
align-items: flex-end;
justify-content: center;
margin-right: 30px;
img {
width: 44px;
height: 44px;
}
}
.search {
// width: 628px;
flex: 1;
.search-type {
font-size: 12px;
margin-bottom: 8px;
span {
padding: 0 12px;
border-left: 1px solid #f5f6f7;
color: #909399;
&:first-child {
padding-left: 0;
border-left: none;
color: #00a98f;
}
}
}
.search-input {
display: flex;
flex-direction: row;
border: 2px solid #00a98f;
border-radius: 8px;
overflow: hidden;
.search-input-inner {
flex: 1;
:global {
.ant-input {
border: none;
}
}
}
.search-btn {
background-color: #00a98f;
font-size: 14px;
color: #fff;
padding: 4px 12px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
}
}
.header-right {
width: 200px;
margin-left: 24px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: flex-end;
}
.btn {
padding: 8px 16px;
background-color: #f5f6f7;
color: #252d37;
font-size: 14px;
border-radius: 8px;
cursor: pointer;
&-text {
margin-left: 8px;
}
}
}
import React from 'react';
import styles from './search.less';
import { Input } from 'antd'
import { CarOutlined } from '@ant-design/icons';
const Search = () => {
return (
<div className={styles.header}>
<div className={styles['site-logo']}>
<img src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fd.paper.i4.cn%2Fmax%2F2017%2F04%2F10%2F13%2F1491802535979_693807.jpg&refer=http%3A%2F%2Fd.paper.i4.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1643428290&t=78ef7ff08b23774f0d5c0074f4bc7f64" />
</div>
<div className={styles.search}>
<div className={styles['search-type']}>
<span>商品</span>
<span>店铺</span>
</div>
<div className={styles['search-input']}>
<div className={styles['search-input-inner']}>
<Input placeholder='请输入关键字' />
</div>
<div className={styles['search-btn']}>
搜索
</div>
</div>
</div>
<div className={styles['header-right']}>
<div className={styles.btn}>
<CarOutlined style={{fontSize: '14px'}}/>
<span className={styles['btn-text']}>进货单</span>
</div>
</div>
</div>
)
}
export default Search;
.tabList {
display: flex;
flex-direction: row;
color: #303133;
font-size: 14px;
font-weight: 500;
width: 1200px;
margin: 0 auto;
.tabItem {
padding: 0 24px;
}
}
import React from 'react';
import styles from './tabs.less';
const Tabs = () => {
const tabList = [
{ title: '商城首页' },
{ title: '现货商品' },
{ title: '询价商品' },
{ title: '优选店铺' },
{ title: '积分商品' },
{ title: '行情资讯' },
]
return (
<div className={styles.tabList}>
{
tabList.map((_item, _key) => {
return (
<div className={styles.tabItem} key={_key}>{_item.title}</div>
)
})
}
<span></span>
</div>
)
}
export default Tabs
.topBar {
width: 100%;
background-color: #FAFBFC;
border-bottom: 1px solid #F4F5F7;
&-content {
height: 30px;
display: flex;
flex-direction: row;
align-items: center;
width: 1200px;
margin: 0 auto;
}
.right {
display: flex;
flex-direction: row;
align-items: center;
margin-left: auto;
.toLogin {
color: #00a98f;
}
.list {
display: flex;
flex-direction: row;
align-items: center;
&-item {
padding: 0 12px;
border-right: 1px solid #e5e5e5;
&:last-child {
border-right: none;
}
}
}
}
}
import React from 'react';
import styles from './topbar.less';
const TopBar = () => {
const rightLayoutList = [
{ title: '免费注册' },
{ title: '会员中心' },
{ title: '我的消息' },
{ title: '客户服务' }
]
return (
<div className={styles.topBar}>
<div className={styles['topBar-content']}>
<div>
<div>瓴犀企业商城</div>
</div>
<div className={styles.right}>
<div className={styles.toLogin}>你好,请登录</div>
<div className={styles.list}>
{
rightLayoutList.map((_item, _key) => {
return (
<div className={styles['list-item']} key={_key}>{_item.title}</div>
)
})
}
</div>
</div>
</div>
</div>
)
}
export default TopBar
import React from 'react';
import { PageConfigType } from '@linkseeks/design-core';
// import MobileUIDemo from './mobileUIDemo'
import { BrickPreview, useSelector, BrickDesign } from '@linkseeks/design-react';
import styles from './index.less';
interface WebDesignPanelPropsType {
theme: string,
isPreview?: boolean,
}
const WebDesignPanel: React.FC<WebDesignPanelPropsType> = (props) => {
const { theme, isPreview } = props;
const { pageConfig } = useSelector(['pageConfig']);
const Component = isPreview ? BrickPreview : BrickDesign;
return (
<div className={styles.container}>
<Component
pageName={"index"}
initState={{ pageConfig }}
themeName={theme}
/>
</div>
);
};
WebDesignPanel.defaultProps = {
isPreview: false
};
export default WebDesignPanel;
.panel {
position: fixed;
right: 0;
top: 49px;
height: calc(100% - 48px);
width: 350px;
z-index: 99;
background: #fff;
font-size: 12px;
border-left: 1px solid #eef0f6;
transition: all .15s ease;
.panel-content {
height: 100%;
display: flex;
flex-direction: column;
position: relative;
.expand-icon {
position: absolute;
top: 35px;
left: -16px;
width: 16px;
height: 32px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08), 0 0 4px rgba(0,0,0, 0.08);
}
}
.header {
padding: 12px 16px;
display: flex;
flex-direction: row;
justify-content: space-between;
font-size: 16px;
font-weight: 600;
border-bottom: 1px solid #F5F6F7;
}
.content {
flex: 1;
padding: 12px;
}
.footer {
padding: 12px 16px 12px 16px;
display: flex;
flex-direction: row-reverse;
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;
}
.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);
}
}
}
.imageTips {
margin-top: 8px;
font-size: 12px;
color: #91959B;
}
}
.hide {
right: -350px;
}
.show {
right: 0px;
}
import React, { useState, useEffect, useMemo } from 'react';
import { changeProps, clearSelectedStatus, SelectedInfoType, STATE_PROPS } from '@linkseeks/design-core';
import { useSelector } from '@linkseeks/design-react';
// import { PageConfigType } from '@lingxi-design/utils';
import cs from 'classnames';
import { CloseOutlined, LeftOutlined, PlusCircleOutlined, RightOutlined } 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: any,
}
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,
} as const;
/**
* selectInfo 的 otherProps属性的 type 类型,
* 此属性表达的意思是某个活动或者组件属性类型
* 比如 coupon 表示的是优惠券, "top" 表示的是广告图
* */
type OtherPropsComponentType =
'top'|
'coupon'|
'hot'|
'plummet'|
'specialOffer'|
'discount'|
'fullQuantitySub'|
'fullQuantityDiscount'|
'fullMoneySub'|
'fullMoneyDiscount'|
'giveProduct'|
'giveCoupon'|
'morePiece'|
'combination'|
'groupPurchase'|
'bargain' |
'secKill' |
'fullSwap' |
'buySwap' |
'preSale' |
'setMeal' |
'attempt' |
'suggestProduct' | 'suggestProductItem' | 'hotItem' | keyof typeof ACTIVITY_MAP
/** 满量减/满额减、赠商品、满额换购, minType = 1 请求接口时带上 */
const minTypeToOne = ["fullQuantitySubItem", "fullMoneySubItem", "giveProductItem", "fullSwapItem"]
const minTypeToTwo = ["fullQuantityDiscountItem", "fullMoneyDiscountItem", "giveCouponItem", "buySwapItem"];
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: OtherPropsComponentType = (selectedInfo as any)?.otherProps?.type;
console.log(componentType)
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' || componentType === 'coupon' ? cardSchema : schemaMap?.[componentType];
const tempFormValue = activityList.includes(componentType) || componentType === 'hot' || componentType === 'suggestProduct' || componentType === 'coupon'
? {
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 };
const isWithMinType = minTypeToOne.includes(componentType) ? 1 : minTypeToTwo.includes(componentType) ? 2 : null
// 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,
minType: isWithMinType,
// fetchOptions: fetchMemberOptions,
});
});
setVisible(true);
}, [selectedInfo]);
const handleSubmit = (values) => {
console.log(values);
const componentType: OtherPropsComponentType = (selectedInfo as any)?.otherProps?.type;
const valueMapToProps = {
top: {
imageUrl: values?.imageUrl?.[0].url,
},
couponItem: {
...values.coupon
},
hot: {
title: values.title,
},
coupon: {
title: values.title,
},
suggestProduct: {
title: values.title,
}
};
let currentProps = {};
if (activityListItem.includes(componentType) || componentType === 'suggestProductItem' || componentType === 'hotItem') {
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: (currentProps as any)?.title || (currentProps as any)?.productName || (currentProps as any)?.name
});
handleOnClose();
// formActions.reset();
};
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>
<div className={styles.imageTips}>建议尺寸 1920 * 460</div>
</>
);
};
const renderForm = () => {
const formProps = {
onSubmit: handleSubmit,
expressionScope: {
renderUploadChild,
},
actions: formActions,
components: {FormilyUpload, FormilyCoupon, FormilyProduct},
};
return (
<NiceForm
value={formValue}
{...formProps}
schema={schema}
/>
);
};
return (
<div className={cs(styles.panel, className)}>
<div className={styles['panel-content']}>
{
selectedInfo && (
<div className={styles['expand-icon']} onClick={() => setVisible(!visible)}>
{!visible ? <LeftOutlined /> : <RightOutlined />}
</div>
) || null
}
<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;
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,
},
'x-rules': [
{
required: true,
message: `请上传图片`
}
]
},
}
}
}
};
/** 优惠券 */
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: `活动名称`,
'x-rules': [
{
required: true,
message: `请填写活动名称`
},
{
limitByte: true, // 自定义校验规则
maxByte: 32,
}
]
}
}
}
}
};
/**
* 活动商品
*/
export const activityProducts: ISchema = {
type: 'object',
properties: {
product: {
type: 'object',
'x-component': 'FormilyProduct',
}
}
};
.container {
padding: 4px 4px;
border-radius: 8px;
position: fixed;
bottom: 16px;
right: 360px;
background-color: hsla(0,0%,100%,.8);
backdrop-filter: blur(3px);
user-select: none;
transition: right .2s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08), 0 0 4px rgba(0,0,0, 0.08);
display: flex;
flex-direction: row;
align-items: center;
z-index: 88;
.select {
width: 60px;
:global {
.ant-select {
border: none;
.ant-select-selector {
border: none;
padding: 0;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
.ant-select-selection-item {
text-align: center;
}
}
}
}
}
.icon {
width: 32px;
height: 32px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
margin: 0 4px;
cursor: pointer;
&:hover {
background-color: #f4f5f9;
}
}
}
import { MinusOutlined, PlusOutlined } from '@ant-design/icons';
import { Select } from 'antd';
import React from 'react';
import styles from './index.less'
const Option = Select.Option
const list = [
{ label: '10%', value: 0.1 },
{ label: '25%', value: 0.25 },
{ label: '50%', value: 0.5 },
{ label: '60%', value: 0.6 },
{ label: '70%', value: 0.7 },
{ label: '75%', value: 0.75 },
{ label: '100%', value: 1 },
{ label: '150%', value: 1.5 },
{ label: '200%', value: 2 },
] as const
export type ScaleValueType = typeof list[number]['value']
type ScaleOptions = {
label: string,
value: ScaleValueType
}
interface Iprops {
scaleValue: ScaleValueType
onChange: (value: number) => void
}
const WebScale: React.FC<Iprops> = (props: Iprops) => {
const { scaleValue, onChange } = props;
const handleScale = (type: 'large' | 'small') => {
const index = list.findIndex((_item) => _item.value === scaleValue);
if (index === 0 && type === 'small') {
return;
}
if (index === list.length - 1 && type === 'large') {
return;
}
let prevOrLast = type === 'small' ? index - 1 : index + 1;
onChange?.(list[prevOrLast].value)
}
const handleSelectChange = (value: ScaleValueType) => {
onChange?.(value)
}
return (
<div className={styles.container}>
<div className={styles.icon} onClick={() => handleScale('small')}>
<MinusOutlined />
</div>
<div className={styles.select}>
<Select
onChange={handleSelectChange}
style={{width: '100%'}}
showArrow={false}
value={scaleValue}
>
{
list.map((_item) => {
return (
<Option key={_item.value} value={_item.value}>{_item.label}</Option>
)
})
}
</Select>
</div>
<div className={styles.icon} onClick={() => handleScale('large')}>
<PlusOutlined />
</div>
</div>
)
}
export default WebScale
@content-height: calc(100vh - 120px);
.page {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
.header {
position: relative;
height: 48px;
width: 100%;
/* background-color: #fff; */
/* border-bottom: 1px solid #eef0f6; */
background: hsla(0,0%,100%,.8);
backdrop-filter: blur(20px);
z-index: 88;
}
.leftBar {
position: absolute;
z-index: 3;
top: 49px;
left: 0;
width: 300px;
margin-left: -300px;
height: calc(100% - 49px);
background: hsla(0,0%,100%,.8);
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
z-index: 5;
border-right: 1px solid #eee;
transition: all 0.2s;
.module-tree {
position: relative;
height: 100%;
overflow: auto;
:global {
.ant-tabs-nav {
margin-bottom: 0px;
}
.ant-tabs-nav-wrap {
flex: 1;
width: 100%;
padding: 0 16px;
}
.ant-tabs-nav-list {
width: 100%;
}
.ant-tabs-tab {
flex: 1;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
}
.expand-icon {
position: absolute;
top: 35px;
right: -16px;
width: 16px;
height: 32px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08), 0 0 4px rgba(0,0,0, 0.08);
}
}
}
.left-bar-show {
transform: translateX(300px);
}
.screen-view {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: auto;
// background-color: yellow;
z-index: 4;
.screen-view-inner {
height: 100%;
width: 100%;
// width: 2800px;
// height: 1600px;s
position: relative;
}
.screen-view-content {
width: 1920px;
height: 1600px;
// height: 100%;
background-color: #ccc;
position: absolute;
top: 50px;
left: 310px;
right: 400px;
transform: scale(0.65);
// position: absolute;
// left: 50%;
// top: 50%;
transform-origin: 0 0;
}
}
}
import React, { useRef, useState } from 'react';
import { message, Spin, Tabs } from 'antd'
import { BrickProvider, ModuleTree } from '@linkseeks/design-react';
import className from 'classnames'
import { LeftOutlined, RightOutlined } from '@ant-design/icons';
import Toolbar from './components/Toolbar';
import styles from './web.less';
import { history } from 'umi';
import ToolbarSubmit from './components/Toolbar/toolbarSubmit';
import useGetLayout from './common/hooks/useGetLayout';
import configs from './common/configs/pageConfigs';
import WebDesignPanel from './components/WebDesignPanel';
import WebEditPanel from './components/WebEditPanel';
import { RenovationProvider } from './common/context/shopContext';
import useGetWebLayout from './common/hooks/useGetWebLayout';
import useDraggable from './common/hooks/useDrag';
import WebScale from './components/WebScale';
import { omit, pick } from 'lodash';
import { usePageStatus } from '@/hooks/usePageStatus';
import { postTemplateWebActivityPageAdorn } from '@/services/TemplateV2Api';
import Module from './components/ComponentTree/web';
const TabPane = Tabs.TabPane
const Web = () => {
const el = useRef<HTMLDivElement>(null);
// useDraggable(el);
const { id } = usePageStatus();
// const [scale, setScale] = useState(0.75);
const [leftBarVisible, setLeftBarVisible] = useState<boolean>(true);
const { detail, loading } = useGetWebLayout();
const [submitLoading, setSubmitLoading] = useState<boolean>(false);
const primaryKey = [
"hot", "specialOffer", "plummet", "discount", "fullQuantitySub", "fullQuantityDiscount", "fullMoneySub", "fullMoneyDiscount", "giveProduct", "giveCoupon", "morePiece", "combination", "groupPurchase", "bargain", "secKill", "fullSwap", "buySwap", "preSale", "setMeal", "attempt"
] as const;
const generaterData = (source: { [key: string]: any }, dataIndex: string, assignData: {[key: string]: any} ) => {
const result = Object.assign(source, {
[dataIndex]: assignData
});
return result;
};
const onSave = async (pageConfig: any) => {
const childNodes = pageConfig[0].childNodes;
setSubmitLoading(true);
let result: any = {};
childNodes.map((_item, _index) => {
const target = pageConfig[_item];
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 childrenData = childNodes.map((_record) => {
const childTargetProps = pageConfig[_record].props;
if(!childTargetProps?.id) {
return null
}
return {
id: childTargetProps.id,
type: childTargetProps.belongType
};
}).filter(Boolean);
result = generaterData(result, dataIndex, {
sort: sort,
props: {
...pick(props, ['theme']),
visible: props.status ?? true,
childrenData: childrenData
}
});
} else if (primaryKey.includes( dataIndex )) {
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: {
...pick(otherProps, ['theme', 'title']),
visible: props.status ?? true,
childrenData: childrenData
}
});
} else if (dataIndex === 'suggestProduct') {
const { ...otherProps } = props || {};
const { childNodes } = target;
const temp = {
sort: sort,
props: {
visible: otherProps.status ?? true,
childrenData: childNodes?.filter((_record) => /\d+-\d+/.test(_record)).map((_row) => {
const childrenNodeTarget = pageConfig[_row];
const { ...childRestProps } = childrenNodeTarget?.props;
return {
title: childRestProps.title,
theme: childRestProps.theme || 0,
childrenData: childrenNodeTarget.childNodes?.map((_listItem) => {
const sonNodeTarget = pageConfig[_listItem];
return {
id: sonNodeTarget?.props.id,
label: sonNodeTarget?.props?.label || []
};
})
};
})
}
};
result = generaterData(result, dataIndex, temp);
}
});
const withThemeStyle = {
...result,
themeStyle: {
sort: 0,
props: {
color: pageConfig[0].props.backgroundColor
}
}
}
const { data, code } = await postTemplateWebActivityPageAdorn({
id: +id,
adornContent: withThemeStyle
} as any);
setSubmitLoading(false);
if (code === 1000) {
history.goBack();
}
}
// const onChangeScale = (value: number) => {
// setScale(value);
// }
return (
<Spin spinning={loading}>
<BrickProvider
config={configs}
warn={(msg: string) => {
message.warning(msg);
}}
>
<div className={styles.page}>
<div className={styles.header}>
<Toolbar
title={"平台装修页装修"}
extra={
<ToolbarSubmit
loading={submitLoading}
onSubmit={onSave}
>
保存
</ToolbarSubmit>
}
/>
</div>
<div
className={className(styles.leftBar, {
[styles['left-bar-show']]: leftBarVisible
})}
>
<div className={styles['module-tree']}>
<Tabs >
<TabPane tab={"组件树"} key="1">
<ModuleTree />
</TabPane>
<TabPane tab={"模块"} key="2">
<div className={styles.module}>
<Module />
</div>
</TabPane>
</Tabs>
{/* <ModuleTree /> */}
{/* <div className={styles['expand-icon']} onClick={() => setLeftBarVisible(!leftBarVisible)}>
{leftBarVisible ? <LeftOutlined /> : <RightOutlined />}
</div> */}
</div>
</div>
<div className={styles['screen-view']} ref={el}>
<div className={styles['screen-view-inner']} >
<div
className={styles['screen-view-content']}
// style={
// {
// transform: `scale(${scale})`,
// marginLeft: `-${(SCREEN_VIEW_WIDTH * scale) / 2}px`,
// marginTop: `-${(1600 * scale) / 2}px`
// }
// }
>
<WebDesignPanel theme={'theme-mall-science'} />
</div>
</div>
</div>
<div className={styles.rightSide}>
<RenovationProvider value={{shopId: detail?.shopId!}}>
<WebEditPanel />
</RenovationProvider>
</div>
{/* <WebScale scaleValue={scale as 0.75} onChange={onChangeScale} /> */}
</div>
</BrickProvider>
</Spin>
)
}
export default Web;
...@@ -125,6 +125,7 @@ const ActivePage = () => { ...@@ -125,6 +125,7 @@ const ActivePage = () => {
status={_item.status} status={_item.status}
onRemove={handleRemove} onRemove={handleRemove}
onChangeStatus={onChangeStatus} onChangeStatus={onChangeStatus}
url={_item.url}
/> />
</div> </div>
); );
......
import { useIntl } from 'umi';
import React, { Fragment, useCallback, useMemo, useState } from 'react'; import React, { Fragment, useCallback, useMemo, useState } from 'react';
import { Badge, Button, Tag, Typography, Image } from 'antd'; import { Badge, Button, Tag, Typography, Image } from 'antd';
import { history } from 'umi'; import { history } from 'umi';
...@@ -30,7 +29,6 @@ import { GlobalConfig } from '@/global/config'; ...@@ -30,7 +29,6 @@ import { GlobalConfig } from '@/global/config';
const { onFormMount$ } = FormEffectHooks; const { onFormMount$ } = FormEffectHooks;
const DetialLayout = () => { const DetialLayout = () => {
const intl = useIntl();
const { query: { id } } = history.location; const { query: { id } } = history.location;
const format = (text, fmt?: string) => { const format = (text, fmt?: string) => {
return <>{moment(text).format(fmt || "YYYY-MM-DD HH:mm:ss")}</> return <>{moment(text).format(fmt || "YYYY-MM-DD HH:mm:ss")}</>
......
This source diff could not be displayed because it is too large. You can view the blob instead.
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