Commit b370fe1b authored by GuanHua's avatar GuanHua

feat: APP店铺装修页面开发

parent a4f92b89
......@@ -38,21 +38,30 @@ const router = [
]
},
{
// web渠道商城模板装修
path: '/channel/template/edit',
component: '@/pages/editor/channelEdit',
},
{
// web店铺模板装修
path: '/shop/template/edit',
component: '@/pages/editor/shopEdit',
},
{
// web渠道商城模板预览
path: '/channel/template/preview',
component: '@/pages/preview/channelPreview',
},
{
// web店铺模板预览
path: '/shop/template/preview',
component: '@/pages/preview/shopPreview',
},
{
// app店铺模板装修
path: '/mobile/shop/template/edit',
component: '@/pages/mobileTemplate/shopTemplateEdit',
},
memberCenterRoute,
...mallRoute,
{
......@@ -62,4 +71,4 @@ const router = [
}
]
export default router
\ No newline at end of file
export default router
......@@ -73,6 +73,8 @@
"react-dnd": "^11.1.3",
"react-dnd-html5-backend": "^11.1.3",
"react-dom": "^16.12.0",
"react-sortablejs": "^6.0.0",
"sortablejs": "^1.12.0",
"react-fontawesome": "^1.7.1",
"react-image-crop": "^8.6.4",
"react-reconciler": "^0.25.1",
......@@ -83,6 +85,7 @@
"yorkie": "^2.0.0"
},
"devDependencies": {
"@types/sortablejs": "^1.10.6",
"@types/qrcode": "^1.3.4",
"async": "^3.2.0",
"axios": "^0.19.2",
......
......@@ -4,23 +4,24 @@ import styles from './index.less'
import cx from 'classnames'
interface ImageBoxPropsType {
width?: number;
width?: number | string;
height?: number;
imgUrl: string;
className?: any;
direction?: "column" | "row";
circle?: boolean
circle?: boolean,
style?: React.CSSProperties
}
const ImageBox: React.FC<ImageBoxPropsType> = (props) => {
const { width = 120, height = 80, imgUrl = "", className, direction = "row", circle = false } = props
const { width = 120, height = 80, imgUrl = "", className, direction = "row", circle = false, style } = props
return (
<div className={cx(styles.image_box, direction === 'column' ? styles.column : '', circle ? styles.circle : '')}>
<div className={cx(styles.image_box_img, className)} style={{ backgroundImage: `url(${imgUrl})`, width: `${width}px`, height: `${height}px` }}></div>
<div className={cx(styles.image_box, direction === 'column' ? styles.column : '', circle ? styles.circle : '')} style={style}>
<div className={cx(styles.image_box_img, className)} style={{ backgroundImage: `url(${imgUrl})`, width: typeof width === 'string' ? width : `${width}px`, height: `${height}px` }}></div>
</div>
)
}
export default ImageBox
\ No newline at end of file
export default ImageBox
import React, { useState, Fragment, forwardRef } from 'react'
import React, { useState, Fragment, forwardRef, Children } from 'react'
import { Upload, message, Button } from 'antd'
import { LoadingOutlined, PlusOutlined, DeleteOutlined } from '@ant-design/icons'
import { UploadFile, UploadChangeParam } from 'antd/lib/upload/interface'
import { UploadFile, UploadChangeParam } from 'antd/lib/upload/interface';
import { UPLOAD_TYPE } from '@/constants/index';
import cx from 'classnames'
import styles from './index.less'
import { UPLOAD_TYPE } from '@/constants'
interface UploadImagePorpsType {
imgUrl: string;
imgUrl?: string;
size?: string;
onChange: Function;
disabled?: boolean;
large?: boolean;
fileMaxSize?: number;
showDesc?: boolean
showDesc?: boolean;
listType?: "picture-card" | "text"
}
const UploadImage: React.FC<UploadImagePorpsType> = forwardRef((props, ref) => {
const { imgUrl, onChange, showDesc = true, size = "386x256", disabled = false, large = false, fileMaxSize = 200 } = props
const { children, imgUrl, onChange, showDesc = true, size = "386x256", disabled = false, large = false, fileMaxSize = 200, listType = "picture-card" } = props
const [loading, setLoading] = useState<boolean>(false)
const beforeUpload = (file: UploadFile) => {
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/jpg';
const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/jpg' || file.type === 'image/gif';
if (!isJpgOrPng) {
message.error('仅支持上传JPEG/JPG/PNG文件!');
}
......@@ -69,30 +70,145 @@ const UploadImage: React.FC<UploadImagePorpsType> = forwardRef((props, ref) => {
</Fragment>
);
return (
<div className={cx(styles.upload_image_wrap, large ? styles.large : '')}>
<div className={cx(styles.upload_wrap, large ? styles.large : '')}>
<Upload {...uploadProps}>
{<div className={cx(styles.upload_btn, !imgUrl ? styles.isAdd : "", large ? styles.large : '')}>
const renderUploadComponentByListType = () => {
switch(listType) {
case "picture-card":
return (
<div className={styles.upload_image_wrap}>
<div className={cx(styles.upload_wrap, large ? styles.large : '')}>
<Upload {...uploadProps}>
{<div className={cx(styles.upload_btn, !imgUrl ? styles.isAdd : "", large ? styles.large : '')}>
{
imgUrl ? <img src={imgUrl} /> : uploadButton
}
</div>}
</Upload>
{
imgUrl && <div className={styles.delete_wrap}><Button onClick={clearImage} className={styles.delete_btn} type="text" icon={<DeleteOutlined />} /></div>
}
</div>
{
imgUrl ? <img src={imgUrl} /> : uploadButton
showDesc &&
<div className={styles.size_require}>
<p>支持JPG/PNG/JPEG, <br />最大不超过 {fileMaxSize}K, <br />尺寸:{size}</p>
</div>
}
</div>}
</Upload>
{
imgUrl && <div className={styles.delete_wrap}><Button onClick={clearImage} className={styles.delete_btn} type="text" icon={<DeleteOutlined />} /></div>
}
</div>
{
showDesc &&
<div className={styles.size_require}>
<p>支持JPG/PNG/JPEG, <br />最大不超过 {fileMaxSize}K, <br />尺寸:{size}</p>
</div>
}
</div>
)
</div>
)
case "text":
return (
<Upload {...uploadProps}>
{children}
</Upload>
)
default:
return null
}
}
return renderUploadComponentByListType()
})
UploadImage.displayName = "UploadImage"
export default UploadImage
// import React, { useState, Fragment, forwardRef } from 'react'
// import { Upload, message, Button } from 'antd'
// import { LoadingOutlined, PlusOutlined, DeleteOutlined } from '@ant-design/icons'
// import { UploadFile, UploadChangeParam } from 'antd/lib/upload/interface'
// import cx from 'classnames'
// import styles from './index.less'
// import { UPLOAD_TYPE } from '@/constants'
// interface UploadImagePorpsType {
// imgUrl: string;
// size?: string;
// onChange: Function;
// disabled?: boolean;
// large?: boolean;
// fileMaxSize?: number;
// showDesc?: boolean
// }
// const UploadImage: React.FC<UploadImagePorpsType> = forwardRef((props, ref) => {
// const { imgUrl, onChange, showDesc = true, size = "386x256", disabled = false, large = false, fileMaxSize = 200 } = props
// const [loading, setLoading] = useState<boolean>(false)
// const beforeUpload = (file: UploadFile) => {
// const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/jpg';
// if (!isJpgOrPng) {
// message.error('仅支持上传JPEG/JPG/PNG文件!');
// }
// const isSizeLimit = file.size / 1024 < fileMaxSize;
// if (!isSizeLimit) {
// message.error(`上传图片不超过${fileMaxSize}K!`);
// }
// return isJpgOrPng && isSizeLimit;
// }
// const uploadProps = {
// name: 'file',
// action: '/api/file/file/upload',
// headers: {},
// data: {
// fileType: UPLOAD_TYPE
// },
// disabled: loading || disabled,
// showUploadList: false,
// onChange(info: UploadChangeParam) {
// if (info.file.status === 'uploading') {
// setLoading(true)
// return;
// }
// if (info.file.status === 'done') {
// // 图片回显
// const { code, data } = info.file.response
// if (code === 1000) {
// console.log('upload success')
// onChange(data)
// }
// setLoading(false)
// }
// },
// beforeUpload
// };
// const clearImage = () => {
// onChange('')
// }
// const uploadButton = (
// <Fragment>
// {loading ? <LoadingOutlined /> : <PlusOutlined />}
// <p>上传图片</p>
// </Fragment>
// );
// return (
// <div className={cx(styles.upload_image_wrap, large ? styles.large : '')}>
// <div className={cx(styles.upload_wrap, large ? styles.large : '')}>
// <Upload {...uploadProps}>
// {<div className={cx(styles.upload_btn, !imgUrl ? styles.isAdd : "", large ? styles.large : '')}>
// {
// imgUrl ? <img src={imgUrl} /> : uploadButton
// }
// </div>}
// </Upload>
// {
// imgUrl && <div className={styles.delete_wrap}><Button onClick={clearImage} className={styles.delete_btn} type="text" icon={<DeleteOutlined />} /></div>
// }
// </div>
// {
// showDesc &&
// <div className={styles.size_require}>
// <p>支持JPG/PNG/JPEG, <br />最大不超过 {fileMaxSize}K, <br />尺寸:{size}</p>
// </div>
// }
// </div>
// )
// })
// UploadImage.displayName = "UploadImage"
// export default UploadImage
......@@ -72,6 +72,7 @@ export const isDev = process.env.NODE_ENV === "development"
// export const isDev = false
export const Environment_Status = {
0: "所有",
1: "web",
2: "H5",
3: "小程序",
......
......@@ -10,6 +10,9 @@ interface TemplateItemPropsType {
}
const Environment_Status = {
0: {
name: "所有"
},
1: {
name: "web"
},
......
import React, { useEffect, useState } from 'react';
import map from 'lodash/map'
import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons'
import cx from 'classnames'
import { selectComponent, useSelector, ComponentConfigsType, SelectedInfoBaseType, SelectedInfoType, clearSelectedStatus, STATE_PROPS, VirtualDOMType } from 'lingxi-editor-core';
import styles from './index.less'
type SettingPanelType = {
selectedInfo: SelectedInfoType,
componentConfigs: ComponentConfigsType
}
const AllComponents = () => {
const [allComponents, setAllComponents] = useState<VirtualDOMType[]>([])
const { selectedInfo, componentConfigs } = useSelector<SettingPanelType, STATE_PROPS>(['selectedInfo', 'componentConfigs'])
useEffect(() => {
const newList: VirtualDOMType[] = []
Object.keys(componentConfigs).forEach(key => {
if(componentConfigs[key].canEdit) {
componentConfigs[key].key = key
newList.push(componentConfigs[key])
}
})
setAllComponents(newList)
}, [componentConfigs, selectedInfo])
const handleSelectComponent = (key: string) => {
if(!selectedInfo || selectedInfo.selectedKey !== key) {
const specialProps: SelectedInfoBaseType = {
parentKey: "0",
key,
domTreeKeys: ["0", key],
draggable: false,
}
selectComponent(specialProps)
} else {
clearSelectedStatus()
}
}
const renderCompontentItem = (item: VirtualDOMType) => {
return (
<div className={cx(styles.components_item, (selectedInfo && selectedInfo.selectedKey === item.key) ? styles.active : {})} key={item.title} onClick={() => handleSelectComponent(item.key)}>
<span className={styles.components_item_text}>{item.title}</span>
{
item.canHide && (item.props.visible !== false ? <EyeOutlined className={cx(styles.components_item_icon)} /> : <EyeInvisibleOutlined className={cx(styles.components_item_icon, styles.disable)} />)
}
</div>
)
}
return (
<div className={styles.allcomponents_container}>
<div className={styles.header}>
<span>我的模块</span>
</div>
<div className={styles.components_list}>
{
map(allComponents, (item) => renderCompontentItem(item))
}
</div>
</div>
);
}
export default AllComponents;
import React from 'react'
import { BrickDesign } from 'lingxi-design'
import MobileUIDemo from './mobileUIDemo'
import styles from './index.less'
const MobileDesignPanel = (props) => {
const { theme } = props
return (
<div className={styles.mobileDesignContainer}>
<div className={styles.mobileDesignWrap}>
<BrickDesign theme={theme} mobile />
{/* <MobileUIDemo /> */}
</div>
<div className={styles.appBottom}>
<div className={styles.appBottomStrip}></div>
</div>
</div>
)
}
export default MobileDesignPanel
.allcomponents_container {
width: 340px;
background-color: #FFF;
.header {
height: 48px;
padding: 0 20px;
line-height: 48px;
color: #303133;
font-size: 16px;
font-weight: bold;
border-bottom: 1px solid #EEF0F3;
}
.components_list {
padding: 20px 16px;
display: flex;
flex-direction: column;
.components_item {
display: flex;
height: 40px;
border-radius: 4px;
border: 1px solid #DCDFE6;
margin-bottom: 16px;
align-items: center;
padding: 0 16px;
&.active {
border: 1px solid #00B37A;
background: #EBF7F2;
}
&:hover {
border: 1px solid #00B37A;
cursor: pointer;
}
&_text {
font-size: 14px;
}
&_icon {
font-size: 16px;
color: #666666;
margin-left: auto;
&.disable {
color: #999999;
}
}
}
}
}
.mobileDesignContainer {
position: relative;
width: 100%;
height: calc(100vh - 156px);
.mobileDesignWrap {
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
background-color: #F7F8FA;
padding-bottom: 25px;
}
.appBottom {
position: absolute;
background-color: #FFF;
bottom: 0;
width: 100%;
height: 30px;
margin-top: 0;
z-index: 99;
.appBottomStrip {
position: relative;
width: 134px;
height: 5px;
background: #000000;
border-radius: 100px;
margin: 0 auto;
top: 12px;
}
}
}
@prefixCls: lingxi;
@headerNavDefaultColor: #FFF;
@headerNavScienceColor: #3877FF;
.nomar {
margin-top: 0;
}
.mall_latyout {
width: 100%;
min-height: 100%;
overflow: hidden;
background-color: #F7F8FA;
}
.@{prefixCls}-shop-header-nav {
position: relative;
height: 176px;
z-index: 1;
&-wrap {
position: absolute;
background: rgba(0, 0, 0, 0.4);
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 3;
}
.@{prefixCls}-status-bar {
// position: absolute;
// top: 0;
// left: 0;
display: flex;
align-items: center;
height: 44px;
padding: 0 20px;
color: #FFF;
&-time {
font-size: 15px;
width: 54px;
text-align: center;
}
&-right {
margin-left: auto;
font-size: 20px;
&-icon {
margin-left: 8px;
width: 20px;
height: 20px;
}
}
}
.@{prefixCls}-header-search {
display: flex;
align-items: "cetner";
padding: 4px 12px;
&-left {
&>img {
width: 24px;
height: 24px;
}
}
&-right {
&>img {
width: 24px;
height: 24px;
}
}
&-body {
flex: 1;
margin: 0 8px;
background-color: #FFF;
border-radius: 18px;
height: 28px;
padding: 0 10px;
display: flex;
align-items: center;
}
&-icon {
font-size: 20px;
color: #909399;
margin-right: 10px;
}
&-keyword {
font-size: 14px;
color: #909399;
margin-right: 8px;
}
}
.@{prefixCls}-header-shopheader {
display: flex;
padding: 12px;
align-items: center;
&-shoplogo {
width: 40px;
height: 40px;
overflow: hidden;
border-radius: 50%;
background-color: #FFF;
margin-right: 8px;
}
&-shopinfo {
display: flex;
flex-direction: column;
flex: 1;
}
&-shopname {
font-size: 12px;
font-weight: bold;
margin-bottom: 4px;
color: #FFF;
}
&-shopdetail {
display: flex;
border-radius: 2px;
height: 16px;
align-items: center;
&-info {
background-color: rgba(255, 255, 255, 0.08);
margin-right: 16px;
padding-right: 4px;
overflow: hidden;
display: flex;
align-items: center;
color: #FFF;
&.year {
background: rgba(211, 47, 47, 0.08);
color: #D32F2F;
}
&-icon {
width: 16px;
height: 16px;
margin-right: 4px;
}
}
&-rate {
font-size: 16px;
margin-left: auto;
}
}
&-enterbtn {
background-color: @headerNavScienceColor;
border-radius: 34px;
height: 24px;
width: 40px;
display: flex;
align-items: center;
justify-content: center;
color: #FFF;
}
}
}
.@{prefixCls}-bottom-navigation {
position: fixed;
bottom: 0;
width: 375px;
left: 0;
height: 50px;
background-color: #FFF;
&-list {
display: flex;
&-item {
flex: 1;
display: flex;
height: 50px;
flex-direction: column;
align-items: center;
justify-content: center;
&.hide {
display: none;
}
&-icon {
width: 24px;
margin-bottom: 2px;
height: 24px;
border: none;
outline: none;
}
&-name {
font-size: 12px;
line-height: 16px;
color: #909399;
}
}
}
}
.@{prefixCls}-shop-commodity-list {
margin-top: 8px;
&.hide {
display: none;
}
&-pagination {
display: flex;
padding: 6px 0;
margin-bottom: 6px;
align-items: center;
&-item {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0 10px;
color: #333333;
&.active {
color: @headerNavScienceColor;
.@{prefixCls}-shop-commodity-list-pagination-item-title {
color: #FFF;
background-color: @headerNavScienceColor;
}
.@{prefixCls}-shop-commodity-list-pagination-item-imgbox {
border: 1px solid @headerNavScienceColor;
}
}
&-imgbox {
width: 40px;
height: 40px;
border-radius: 50%;
border: 1px solid #FFF;
overflow: hidden;
}
&-title {
font-size: 12px;
color: #909399;
padding: 0 4px;
height: 15px;
line-height: 15px;
border-radius: 8px;
margin-top: 6px;
}
&-subtitle {
font-size: 10px;
}
&-split {
width: 1px;
height: 16px;
background-color: #EBECF0,
}
&-wrap {
display: flex;
align-items: center;
}
}
}
&-container {
position: relative;
}
&-commodity {
&-list {
display: flex;
flex-direction: column;
padding: 0 4px;
flex-wrap: wrap;
&-item {
display: flex;
flex-direction: row;
overflow: hidden;
border-radius: 8px;
background-color: #FFF;
padding: 8px;
margin: 0 4px 8px 4px;
&-info {
flex: 1;
display: flex;
flex-direction: column;
padding: 0 8px;
}
&-name {
font-size: 14px;
color: #303133;
display: -webkit-box;
/* autoprefixer: off */
-webkit-box-orient: vertical;
/* autoprefixer: on */
-webkit-line-clamp: 2;
overflow: hidden;
}
&-sellpoints {
display: flex;
align-items: center;
&-item {
color: #909399;
font-size: 12px;
&-split {
width: 1px;
height: 10px;
margin: 0 10px;
background-color: #909399;
}
}
}
&-price {
display: flex;
margin-top: auto;
align-items: flex-end;
&>.price {
font-size: 16px;
color: #D32F2F;
font-weight: bold;
line-height: 16px;
}
&>.unit {
color: #909399;
line-height: 12px;
font-size: 12px;
}
}
&-sale {
color: #909399;
font-size: 12px;
margin-left: auto;
}
}
}
}
}
This diff is collapsed.
......@@ -20,7 +20,6 @@
&_title {
color: #909399;
padding-left: 12px;
font-size: 12px;
&>label {
......@@ -29,8 +28,13 @@
margin-left: 4px;
}
}
.toolbar_actions {
margin-left: auto;
padding-right: 24px;
}
}
.modal_confirm {
width: 420px !important;
}
\ No newline at end of file
}
import React from 'react'
import React, { useCallback, useState } from 'react'
import { Modal, Button, message } from 'antd'
import { ArrowLeftOutlined } from '@ant-design/icons'
import { PublicApi } from '@/services/api'
import { LAYOUT_TYPE } from '@/constants'
import { useSelector, STATE_PROPS, ComponentConfigsType, PROPS_TYPES } from 'lingxi-editor-core'
import { history } from 'umi'
import { Modal } from 'antd'
import styles from './index.less'
interface ToolBarPropsType {
type?: number;
title?: string
title?: string,
showActions?: boolean,
templateId?: number,
layoutType?: LAYOUT_TYPE
}
const ToolBar: React.FC<ToolBarPropsType> = (props) => {
const { type = 1, title = "首页" } = props
const { type = 1, title = "首页", showActions, templateId } = props
const [saveLoading, setSaveLoading] = useState<boolean>(false)
const { componentConfigs } = useSelector<ComponentConfigsType, STATE_PROPS>(['componentConfigs'])
const handleGoBack = () => {
if (type === 1) {
......@@ -28,6 +36,77 @@ const ToolBar: React.FC<ToolBarPropsType> = (props) => {
}
}
const handleSave = useCallback(() => {
const param: any = {
templateId: Number(templateId),
appEnterpriseBO: {},
}
Object.keys(componentConfigs).forEach(key => {
const componentConfigsItem = componentConfigs[key]
if(componentConfigsItem.componentType) {
switch(componentConfigsItem.componentType) {
case PROPS_TYPES.mobileHeaderNav:
param.appEnterpriseBO.topBO = {
style: componentConfigsItem.props.styleTheme || 0,
status: true,
topDetailsBOList: componentConfigsItem.props.dataList || []
}
break
case PROPS_TYPES.mobileBanner:
param.appEnterpriseBO.advertBO = {
status: componentConfigsItem.props.visible || true,
advertDetailsBOList: componentConfigsItem.props.dataList || []
}
break
case PROPS_TYPES.mobileQuickNav:
param.appEnterpriseBO.functionBO = {
status: componentConfigsItem.props.visible || true,
functionDetailsBO: componentConfigsItem.props.dataList || []
}
break
case PROPS_TYPES.mobileShowCase:
param.appEnterpriseBO.showcaseBO = {
style: componentConfigsItem.props.styleTheme || 0,
status: componentConfigsItem.props.visible || true,
showcaseDetailsBO: componentConfigsItem.props.dataList || []
}
break
case PROPS_TYPES.mobileRecommentShops:
param.appEnterpriseBO.storeBO = {
status: componentConfigsItem.props.visible || true,
storeIdList: componentConfigsItem.props.dataList ? componentConfigsItem.props.dataList.map(item => item.selectId) : []
}
break
case PROPS_TYPES.mobileQuality:
param.appEnterpriseBO.excellentBO = {
status: componentConfigsItem.props.visible || true,
excellentDetailsBO: componentConfigsItem.props.dataList || []
}
break
case PROPS_TYPES.mobileBottomNavigation:
param.appEnterpriseBO.bottomBO = {
status: true,
bottomDetailsBOList: componentConfigsItem.props.dataList || []
}
break
default:
break
}
}
})
console.log(JSON.stringify(param), "param")
saveAppEnterprise(param)
}, [componentConfigs])
const saveAppEnterprise = (param) => {
PublicApi.postTemplateAdornAppEnterpriseSave(param).then(res => {
if(res.code === 1000) {
message.destroy()
message.success("保存成功")
}
})
}
return (
<div className={styles.toolbar}>
<div className={styles.toolbar_back_btn} onClick={() => handleGoBack()}>
......@@ -37,8 +116,20 @@ const ToolBar: React.FC<ToolBarPropsType> = (props) => {
<span>{type === 1 ? '正在编辑:' : '正在预览:'}</span>
<label>{title}</label>
</div>
{
showActions && <div className={styles.toolbar_actions}>
<Button type="link" onClick={() => handleGoBack()}>取消</Button>
<Button loading={saveLoading} type="primary" onClick={() => handleSave()}>保存</Button>
</div>
}
</div>
)
}
ToolBar.defaultProps = {
type: 1,
title: "首页",
showActions: false,
}
export default ToolBar
import { ComponentConfigTypes, PROPS_TYPES } from 'lingxi-editor-core';
const MobileBanner: ComponentConfigTypes = {
propsConfig: {
componentType: {
label: '编辑',
type: PROPS_TYPES.mobileBanner
},
},
};
export default MobileBanner;
import { ComponentConfigTypes, PROPS_TYPES } from 'lingxi-editor-core';
const MobileQuickNav: ComponentConfigTypes = {
propsConfig: {
componentType: {
label: '编辑',
type: PROPS_TYPES.mobileQuickNav
},
},
};
export default MobileQuickNav;
import { ComponentConfigTypes, PROPS_TYPES } from 'lingxi-editor-core';
const MobileShopCommodityList: ComponentConfigTypes = {
propsConfig: {
componentType: {
label: '编辑',
type: PROPS_TYPES.mobileShopCommodity
},
},
};
export default MobileShopCommodityList;
import { ComponentConfigTypes, PROPS_TYPES } from 'lingxi-editor-core';
const MobileShopHeaderNav: ComponentConfigTypes = {
propsConfig: {
componentType: {
label: '编辑',
type: PROPS_TYPES.mobileShopHeaderNav
},
},
};
export default MobileShopHeaderNav;
......@@ -16,6 +16,10 @@ import ShopHeader from './ShopHeader'
import FindMore from './FindMore'
import Information from './Information'
import Footer from './Footer'
import MobileBanner from './MobileBanner'
import MobileQuickNav from './MobileQuickNav'
import MobileShopCommodityList from './MobileShopCommodityList'
import MobileShopHeaderNav from './MobileShopHeaderNav'
export default {
TopBar,
......@@ -33,6 +37,10 @@ export default {
CommonTitle,
ChannelHeader,
ShopHeader,
MobileBanner,
MobileQuickNav,
MobileShopCommodityList,
MobileShopHeaderNav,
...FloorLine,
...ShopFloorLine,
...ShowCase
......
.mobileSettingPanel {
height: 100%;
overflow-y: auto;
background-color: #FFF;
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;
}
}
}
}
}
}
import React, { useEffect, useState } from 'react'
import { ComponentConfigsType, SelectedInfoType, STATE_PROPS, useSelector } from 'lingxi-editor-core';
import get from 'lodash/get';
import cx from 'classnames'
import { Tabs } from 'antd'
import StyleSettings from './styleSettings'
import PropsSettings from './propsSettings'
import styles from './index.less'
type SettingPanelType = {
selectedInfo: SelectedInfoType,
componentConfigs: ComponentConfigsType,
}
const { TabPane } = Tabs
const MobileSettingPanel: React.FC = ()=> {
const { selectedInfo, componentConfigs } = useSelector<SettingPanelType, STATE_PROPS>(['selectedInfo', 'componentConfigs'])
const { propsConfig } = selectedInfo || {}
const [ newSelectInfo, setNewSelectInfo ] = useState<SelectedInfoType>()
useEffect(() => {
const updateSelectInfo = () => {
if(selectedInfo) {
const { props: oldProps ,selectedKey } = selectedInfo
const newProps = get(componentConfigs, [selectedKey, 'props'], oldProps)
const updateSelectInfo = { ...selectedInfo }
updateSelectInfo.props = newProps
console.log(JSON.stringify(newProps))
setNewSelectInfo(updateSelectInfo)
}
}
updateSelectInfo()
}, [selectedInfo, componentConfigs])
return (
<div className={cx(styles.mobileSettingPanel, selectedInfo ? styles.show : styles.hide)}>
<Tabs
defaultActiveKey="1"
className={styles.settingTabs}
>
<TabPane tab="编辑" key="props">
<PropsSettings selectedInfo={newSelectInfo} />
</TabPane>
{
(propsConfig && propsConfig.styleType) && (
<TabPane tab="样式" key="style">
<StyleSettings selectedInfo={newSelectInfo} />
</TabPane>
)
}
</Tabs>
</div>
)
}
export default MobileSettingPanel
@import "../../../../global//styles/utils.less";
.setting {
.setting_title {
position: relative;
height: 32px;
line-height: 32px;
}
.setting_line {
margin-bottom: 24px;
display: flex;
&_main {
position: relative;
flex: 1;
}
&_addItem {
flex: 1;
padding: 24px 12px 0 12px;
.deleteItem {
text-align: right;
padding-bottom: 8px;
&_icon {
margin-right: 4px;
}
&>label {
cursor: pointer;
}
}
&_input {
width: 100%;
}
&_line {
display: flex;
margin-bottom: 16px;
font-size: 14px;
&_label {
width: 80px;
line-height: 32px;
color: #909399;
}
&_brief {
flex: 1;
line-height: 32px;
}
}
}
&_sort {
width: 32px;
background-color: #FAFBFC;
height: 32px;
line-height: 32px;
text-align: center;
margin-right: 8px;
}
&_name {
display: flex;
flex: 1;
font-size: 14px;
height: 32px;
color: #303133;
align-items: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
background-color: #FAFBFC;
padding: 0 8px;
.icon {
cursor: pointer;
width: 16px;
height: 16px;
font-size: 12px;
margin-right: 8px;
}
}
&_operation {
margin-left: auto;
&_btn {
margin-left: 16px;
width: 16px;
&_icon {
width: 16px;
height: 16px;
}
}
}
}
.hideModule {
text-align: right;
padding-bottom: 18px;
}
.uploadIconWrap {
display: flex;
align-items: center;
.uploadIconBtn {
height: 90px;
width: 90px;
border: 1px dashed #D8DDE6;
background-color: #FAFBFC;
margin-left: 16px;
display: flex;
font-size: 12px;
align-items: center;
flex-direction: column;
justify-content: center;
color: #909399;
cursor: pointer;
&.small {
width: 100px;
height: 32px;
margin-left: 0;
margin-right: 16px;
flex-direction: row;
color: #606266;
.uploadIconBtnIcon {
font-size: 12px;
margin-bottom: 0;
}
}
.uploadIconBtnIcon {
font-size: 20px;
margin-bottom: 10px;
}
&:hover {
opacity: .8;
}
}
}
.uploadIconTip {
font-size: 12px;
color: #909399;
margin-right: 16px;
}
.previewIconWrap {
width: 100px;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #EBECF0;
margin-top: 16px;
.previewIcon {
width: 24px;
height: 24px;
&.large {
width: 48px;
height: 48px;
}
}
}
.add_btn {
width: 100%;
border: 1px dashed #DFE1E6;
}
}
.selectInfoBox {
position: relative;
display: flex;
z-index: 3;
&_delete {
position: absolute;
display: none;
right: 0;
top: 0;
width: 24px;
height: 24px;
background: rgba(0, 0, 0, 0.65);
line-height: 24px;
text-align: center;
color: #ffffff;
font-size: 12px;
cursor: pointer;
z-index: 7;
}
&::after {
content: "";
display: none;
position: absolute;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.12);
z-index: 5;
}
&:hover {
&::after {
display: block;
}
.selectInfoBox_delete {
display: block;
}
}
.selectInfo {
display: flex;
flex: 1;
width: 0;
font-size: 14px;
margin-left: 8px;
flex-direction: column;
&.integral {
.selectInfo_price {
color: #D69B5D;
}
}
&.information {
justify-content: center;
.selectInfo_name {
line-height: 22px;
.textOverflowMulti2(2);
}
}
&.shop {
justify-content: center;
.selectInfo_name {
line-height: 22px;
margin-bottom: 0;
.textOverflowMulti2(1);
}
}
&_name {
color: #303133;
line-height: 14px;
margin-bottom: 12px;
.textOverflowMulti2(1);
}
&_price {
color: #D32F2F;
font-size: 14px;
line-height: 14px;
}
}
}
import React from 'react'
import { numFormat, priceFormat } from '@/utils/numberFomat'
import ImageBox from '@/components/ImageBox'
const showMainPic = (mainPic: string) => <ImageBox width={32} height={32} imgUrl={mainPic} />
export const promptCommodityColumn = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
width: 80,
},
{
title: "商品图片",
dataIndex: "mainPic",
render: (mainPic: string) => showMainPic(mainPic)
},
{
title: "商品名称",
dataIndex: "name",
width: 300,
ellipsis: true,
},
{
title: "品类",
// render: (_, record) => record.customerCategory.name
},
{
title: "品牌",
// render: (_, record) => record.brand.name
},
{
title: "价格",
dataIndex: "min",
render: (_, record) => ${priceFormat(record.min)}`
},
]
export const integralCommodityColumn = [
{
title: "商品图片",
dataIndex: "mainPic",
render: (mainPic: string) => showMainPic(mainPic)
},
{
title: "商品名称",
dataIndex: "name",
width: 300,
ellipsis: true,
},
{
title: "需要积分",
dataIndex: "min",
render: (_, record) => `${numFormat(record.min)}`
},
]
export const shopColumn = [
{
title: "店铺图片",
dataIndex: "logo",
render: (logo: string) => showMainPic(logo)
},
{
title: "店铺名称",
dataIndex: "memberName",
width: 300,
ellipsis: true,
},
]
export const informationColumn = [
{
title: "资讯图片",
dataIndex: "imageUrl",
render: (imageUrl: string) => showMainPic(imageUrl)
},
{
title: "资讯标题",
dataIndex: "title",
width: 360,
ellipsis: true,
},
]
const tableColumn = {
1: promptCommodityColumn,
2: integralCommodityColumn,
3: shopColumn,
4: informationColumn
}
export default tableColumn
import { ISchema } from '@formily/antd'
import { FORM_FILTER_PATH } from '@/formSchema/const'
import { PublicApi } from '@/services/api'
export const formProduct: ISchema = {
type: 'object',
properties: {
name: {
type: 'string',
'x-component': 'ModalSearch',
'x-component-props': {
placeholder: '搜索',
align: 'flex-left',
},
},
[FORM_FILTER_PATH]: {
type: 'object',
'x-component': 'flex-layout',
'x-component-props': {
rowStyle: {
flexWrap: 'nowrap',
style: {
marginRight: 0
}
},
colStyle: {
marginTop: 20,
},
},
properties: {
customerCategoryId: {
type: 'string',
"x-component": 'SearchSelect',
"x-component-props": {
placeholder: '请选择品类',
className: 'fixed-ant-selected-down', // 该类强制将显示的下拉框出现在select下, 只有这里出现问题, ??
fetchSearch: PublicApi.getProductSelectGetSelectCategory,
style: {
width: 160
}
}
},
brandId: {
type: 'string',
"x-component": 'SearchSelect',
"x-component-props": {
placeholder: '请选择品牌',
fetchSearch: PublicApi.getProductSelectGetSelectPlatformBrand,
style: {
width: 160
}
}
},
submit: {
"x-component": 'Submit',
"x-mega-props": {
span: 1
},
"x-component-props": {
children: '查询'
}
}
}
}
}
}
export const basicSchema: ISchema = {
type: 'object',
properties: {
name: {
type: 'string',
'x-component': 'Search',
'x-component-props': {
placeholder: '搜索',
align: 'flex-left',
},
},
}
}
@import "../../../../../../global//styles/utils.less";
@import "../../common.less";
.selectBtn {
display: block;
width: 100%;
background-color: #FAFBFC;
border: 1px dashed #D8DDE6;
}
.uploadPreview {
border: 1px solid #EBECF0;
}
import React from 'react'
import { numFormat, priceFormat } from '@/utils/numberFomat'
import ImageBox from '@/components/ImageBox'
const showMainPic = (mainPic: string) => <ImageBox width={32} height={32} imgUrl={mainPic} />
export const promptCommodityColumn = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
width: 80,
},
{
title: "商品图片",
dataIndex: "mainPic",
render: (mainPic: string) => showMainPic(mainPic)
},
{
title: "商品名称",
dataIndex: "name",
width: 300,
ellipsis: true,
},
{
title: "品类",
// render: (_, record) => record.customerCategory.name
},
{
title: "品牌",
// render: (_, record) => record.brand.name
},
{
title: "价格",
dataIndex: "min",
render: (_, record) => ${priceFormat(record.min)}`
},
]
export const integralCommodityColumn = [
{
title: "商品图片",
dataIndex: "mainPic",
render: (mainPic: string) => showMainPic(mainPic)
},
{
title: "商品名称",
dataIndex: "name",
width: 300,
ellipsis: true,
},
{
title: "需要积分",
dataIndex: "min",
render: (_, record) => `${numFormat(record.min)}`
},
]
export const shopColumn = [
{
title: "店铺图片",
dataIndex: "logo",
render: (logo: string) => showMainPic(logo)
},
{
title: "店铺名称",
dataIndex: "memberName",
width: 300,
ellipsis: true,
},
]
export const informationColumn = [
{
title: "资讯图片",
dataIndex: "imageUrl",
render: (imageUrl: string) => showMainPic(imageUrl)
},
{
title: "资讯标题",
dataIndex: "title",
width: 360,
ellipsis: true,
},
]
const tableColumn = {
1: promptCommodityColumn,
2: integralCommodityColumn,
3: shopColumn,
4: informationColumn
}
export default tableColumn
import { ISchema } from '@formily/antd'
import { FORM_FILTER_PATH } from '@/formSchema/const'
import { PublicApi } from '@/services/api'
export const formProduct: ISchema = {
type: 'object',
properties: {
name: {
type: 'string',
'x-component': 'ModalSearch',
'x-component-props': {
placeholder: '搜索',
align: 'flex-left',
},
},
[FORM_FILTER_PATH]: {
type: 'object',
'x-component': 'flex-layout',
'x-component-props': {
rowStyle: {
flexWrap: 'nowrap',
style: {
marginRight: 0
}
},
colStyle: {
marginTop: 20,
},
},
properties: {
customerCategoryId: {
type: 'string',
"x-component": 'SearchSelect',
"x-component-props": {
placeholder: '请选择品类',
className: 'fixed-ant-selected-down', // 该类强制将显示的下拉框出现在select下, 只有这里出现问题, ??
fetchSearch: PublicApi.getProductSelectGetSelectCategory,
style: {
width: 160
}
}
},
brandId: {
type: 'string',
"x-component": 'SearchSelect',
"x-component-props": {
placeholder: '请选择品牌',
fetchSearch: PublicApi.getProductSelectGetSelectPlatformBrand,
style: {
width: 160
}
}
},
submit: {
"x-component": 'Submit',
"x-mega-props": {
span: 1
},
"x-component-props": {
children: '查询'
}
}
}
}
}
}
export const basicSchema: ISchema = {
type: 'object',
properties: {
name: {
type: 'string',
'x-component': 'Search',
'x-component-props': {
placeholder: '搜索',
align: 'flex-left',
},
},
}
}
@import "../../../../../../global//styles/utils.less";
@import "../../common.less";
.selectBtn {
display: block;
width: 100%;
background-color: #FAFBFC;
border: 1px dashed #D8DDE6;
}
.uploadPreview {
border: 1px solid #EBECF0;
}
import React, { useState, useEffect } from 'react'
import { Button, Input, Radio } from 'antd'
import { PlusOutlined } from '@ant-design/icons'
import { changeProps } from 'lingxi-editor-core'
import { ReactSortable } from "react-sortablejs"
import cx from 'classnames'
import isEmpty from 'lodash/isEmpty'
import UploadImage from '@/components/UploadImage'
import arrowRightIcon from '@/assets/icons/arrow_right.png'
import arrowLeftIcon from '@/assets/icons/arrow_left.png'
import arrowUpIcon from '@/assets/icons/arrow_up.png'
import arrowDownIcon from '@/assets/icons/arrow_down.png'
import sortIcon from '@/assets/icons/sort_icon.png'
import styles from './index.less'
interface DataItemType {
id: number,
name: string,
content: string,
status: boolean,
expand: boolean
}
interface HeaderNavPropsType {
dataList: DataItemType[]
}
const HeaderNav: React.FC<HeaderNavPropsType> = (props) => {
const { dataList } = props
const [list, setList] = useState<DataItemType[]>([])
useEffect(() => {
initDataList()
}, [dataList])
const initDataList = () => {
if(dataList) {
const newDataList = dataList.map((item: DataItemType, index: number) => {
item.id = index + 1
item.expand = item.expand || false
return item
})
setList(newDataList)
}
}
const handleExpand = (id: number, expand: boolean) => {
const newList = [...list]
newList.map(item => {
if (item.id === id) {
item.expand = expand
} else {
item.expand = false
}
})
setList(newList)
}
const sortUp = (index: number, item: DataItemType) => {
const newList = JSON.parse(JSON.stringify(list))
const tempItem = JSON.parse(JSON.stringify(item))
const temp = newList[index - 1]
newList[index - 1] = item
newList[index - 1].id = temp.id
newList[index] = temp
newList[index].id = tempItem.id
setList(newList)
changeProps({
props:Object.assign({...props}, {dataList: newList })
})
}
const sortDown = (index: number, item: DataItemType) => {
const newList = JSON.parse(JSON.stringify(list))
const temp = newList[index + 1]
const tempItem = JSON.parse(JSON.stringify(item))
newList[index + 1] = item
newList[index + 1].id = temp.id
newList[index] = temp
newList[index].id = tempItem.id
setList(newList)
changeProps({
props:Object.assign({...props}, {dataList: newList })
})
}
const handleDeleteItem = (index: number) => {
console.log('handleDeleteItem')
}
const handleSearchContentChange = (e) => {
const value = e.target.value
const newList = [...list]
newList.map(item => {
if (item.name === "搜索框") {
item.content = value
}
})
setList(newList)
changeProps({
props:Object.assign({ ...props }, { dataList: newList })
})
}
const handleStatusChange = (e, name: string) => {
const check = e.target.value
const newList = [...list]
newList.map(item => {
if (item.name === name) {
item.status = check
}
})
setList(newList)
changeProps({
props:Object.assign({ ...props }, { dataList: newList })
})
}
const handleIconChange = (url: string, name: string) => {
const newList = [...list]
newList.map(item => {
if (item.name === name) {
item.content = url
}
})
setList(newList)
changeProps({
props:Object.assign({ ...props }, { dataList: newList })
})
}
const handleSortableChange = (evt, sortable, store) => {
console.log(evt, sortable, store)
}
return (
<div className={styles.setting}>
<ReactSortable
list={list}
setList={(newList) => {
setList(newList)
if(!isEmpty(newList)) {
changeProps({
props:Object.assign({ ...props }, { dataList: newList })
})
}
}}
onChange={handleSortableChange}
handle=".draghandle"
>
{list.map((item, index) => (
<div className={styles.setting_line} key={`setting_${index}`}>
<div className={styles.setting_line_main}>
<div className={styles.setting_line_name} >
<div style={{ flex: 1 }} onClick={() => handleExpand(item.id, !item.expand)}>
{
item.expand ? <img className={styles.icon} src={arrowLeftIcon} /> : <img className={styles.icon} src={arrowRightIcon} />
}
<span>{item.name}</span>
</div>
<div className={styles.setting_line_operation}>
<Button type="link" disabled={index === 0} onClick={() => sortUp(index, item)} className={styles.setting_line_operation_btn} icon={<img className={styles.setting_line_operation_btn_icon} src={arrowUpIcon} />}></Button>
<Button type="link" disabled={index === list.length - 1} onClick={() => sortDown(index, item)} className={styles.setting_line_operation_btn} icon={<img className={styles.setting_line_operation_btn_icon} src={arrowDownIcon} />}></Button>
<Button type="link" className={cx(styles.setting_line_operation_btn, "draghandle")} onClick={() => handleDeleteItem(index)} icon={<img className={styles.setting_line_operation_btn_icon} src={sortIcon} />}></Button>
</div>
</div>
{
!!item.expand && (
<div className={styles.setting_line_addItem}>
<div className={styles.setting_line_addItem_line}>
<div className={styles.setting_line_addItem_line_label}>名称:</div>
<div className={styles.setting_line_addItem_line_brief}>
{item.name}
</div>
</div>
{
item.name === "搜索框" ? (
<div className={styles.setting_line_addItem_line}>
<div className={styles.setting_line_addItem_line_label}>提示语:</div>
<div className={styles.setting_line_addItem_line_brief}>
<Input placeholder="请输入搜索关键词" maxLength={8} value={item.content} onChange={handleSearchContentChange} />
</div>
</div>
) : (
<div className={styles.setting_line_addItem_line}>
<div className={styles.setting_line_addItem_line_label}>图标:</div>
<div className={styles.setting_line_addItem_line_brief}>
<div className={styles.uploadIconWrap}>
<UploadImage
onChange={(url) => handleIconChange(url, item.name)}
listType="text"
>
<div className={cx(styles.uploadIconBtn, styles.small)}>
<PlusOutlined className={styles.uploadIconBtnIcon} />
<span>上传图标</span>
</div>
</UploadImage>
<label className={styles.uploadIconTip}>最佳尺寸:160*160</label>
</div>
<div className={styles.previewIconWrap}>
{item.content && <img src={item.content} className={styles.previewIcon} alt={item.name} />}
</div>
</div>
</div>
)
}
{
(item.name === "我的" || item.name === "客服") && (
<div className={styles.setting_line_addItem_line}>
<div className={styles.setting_line_addItem_line_label}>是否显示:</div>
<div className={styles.setting_line_addItem_line_brief}>
<Radio.Group onChange={(e) => handleStatusChange(e, item.name)} value={item.status}>
<Radio value={true}>显示</Radio>
<Radio value={false}>隐藏</Radio>
</Radio.Group>
</div>
</div>
)
}
</div>
)
}
</div>
</div>
))}
</ReactSortable>
</div>
)
}
export default HeaderNav
@import "../../../../../../global//styles/utils.less";
@import "../../common.less";
.selectBtn {
display: block;
width: 100%;
background-color: #FAFBFC;
border: 1px dashed #D8DDE6;
}
.uploadPreview {
border: 1px solid #EBECF0;
}
.propsSettingsWrapper {
padding: 8px 24px 24px 24px;
}
import React from 'react'
import { SelectedInfoType, clearSelectedStatus, PROPS_TYPES } from 'lingxi-editor-core';
import HeaderNav from './components/headerNav'
import Banner from './components/banner'
import QuickNav from './components/quickNav'
import BottomNavigation from './components/bottomNavigation'
import styles from './index.less'
interface PropsSettingsPropsType {
selectedInfo: SelectedInfoType | undefined,
}
const PropsSettings: React.FC<PropsSettingsPropsType> = (props) => {
const { selectedInfo } = props
const renderSettingItem = () => {
const { props: initProps, propsConfig } = selectedInfo || {};
const componentType = propsConfig?.componentType
if (componentType) {
switch (componentType.type) {
case PROPS_TYPES.mobileHeaderNav:
return <HeaderNav {...initProps} />
case PROPS_TYPES.mobileBanner:
return <Banner {...initProps} />
case PROPS_TYPES.mobileQuickNav:
return <QuickNav {...initProps} />
case PROPS_TYPES.mobileBottomNavigation:
return <BottomNavigation {...initProps} />
default:
return null
}
}
}
return (
<div className={styles.propsSettingsWrapper}>
<div className={styles.propsSettingsBody}>
{renderSettingItem()}
</div>
</div>
)
}
export default PropsSettings
import { ISchema } from '@formily/antd'
import { FORM_FILTER_PATH } from '@/formSchema/const'
import { PublicApi } from '@/services/api'
export const formProduct: ISchema = {
type: 'object',
properties: {
name: {
type: 'string',
'x-component': 'ModalSearch',
'x-component-props': {
placeholder: '搜索',
align: 'flex-left',
},
},
[FORM_FILTER_PATH]: {
type: 'object',
'x-component': 'flex-layout',
'x-component-props': {
rowStyle: {
flexWrap: 'nowrap',
style: {
marginRight: 0
}
},
colStyle: {
marginTop: 20,
},
},
properties: {
customerCategoryId: {
type: 'string',
"x-component": 'SearchSelect',
"x-component-props": {
placeholder: '请选择品类',
className: 'fixed-ant-selected-down', // 该类强制将显示的下拉框出现在select下, 只有这里出现问题, ??
fetchSearch: PublicApi.getProductSelectGetSelectCategory,
style: {
width: 160
}
}
},
brandId: {
type: 'string',
"x-component": 'SearchSelect',
"x-component-props": {
placeholder: '请选择品牌',
fetchSearch: PublicApi.getProductSelectGetSelectPlatformBrand,
style: {
width: 160
}
}
},
submit: {
"x-component": 'Submit',
"x-mega-props": {
span: 1
},
"x-component-props": {
children: '查询'
}
}
}
}
}
}
export const basicSchema: ISchema = {
type: 'object',
properties: {
name: {
type: 'string',
'x-component': 'Search',
'x-component-props': {
placeholder: '搜索',
align: 'flex-left',
},
},
}
}
.styleSettings {
padding-top: 8px;
.styleList {
display: flex;
padding: 0 12px;
flex-wrap: wrap;
.styleItem {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 184px;
height: 138px;
border: 1px solid #E4E6EB;
margin: 0 12px;
margin-bottom: 24px;
cursor: pointer;
&.active {
border: 1px solid #00B382;
&::after {
content: '';
position: absolute;
width: 0;
height: 0;
border-bottom: 12px solid #00B37A;
border-left: 12px solid transparent;
bottom: 0;
right: 0;
z-index: 5;
}
}
}
.themeImg {
width: 152px;
height: 105px;
}
}
}
import React, { useState } from 'react'
import cx from 'classnames'
import { SelectedInfoType, changeProps } from 'lingxi-editor-core'
import styles from './index.less'
interface StyleSettingsPropsType {
selectedInfo: SelectedInfoType | undefined,
}
const StyleSettings: React.FC<StyleSettingsPropsType> = ({ selectedInfo }) => {
const { props: selectProps } = selectedInfo || {}
const [ selectKey, setSelectKey ] = useState<number>(selectProps.styleTheme)
/**
* 更换样式模板
* @param key
*/
const handleChangeStyleTheme = (key: number) => {
if(selectKey !== key) {
setSelectKey(key)
changeProps({
props: Object.assign({ ...selectProps }, { styleTheme: key })
})
}
}
return (
<div className={styles.styleSettings}>
<div className={styles.styleList}>
{
(selectProps && selectProps.stylesThemeList) && selectProps.stylesThemeList.map(item => (
<div className={cx(styles.styleItem, selectKey === item.key ? styles.active : {})} key={item.key} onClick={() => handleChangeStyleTheme(item.key)}>
<img className={styles.themeImg} src={item.img} title={item.key} />
</div>
))
}
</div>
</div>
)
}
export default StyleSettings
import React, { useEffect } from 'react'
import { MenuOutlined, RightOutlined } from '@ant-design/icons'
import { inject, observer } from 'mobx-react'
import { Link } from 'umi'
import cx from 'classnames'
import ImageBox from '@/components/ImageBox'
import { LAYOUT_TYPE } from '@/constants'
import { PublicApi } from '@/services/api'
import { GlobalConfig } from '@/global/config'
......@@ -110,7 +110,7 @@ const Category: React.FC<CategoryPropsType> = (props) => {
<div className={styles.sub_category}>
{
item.children.map((childCategory, childIndex) => childIndex < 3 && (
<Link to={getNavLink(childCategory)} key={childCategory.id}>{childCategory.title}</Link>
<a href={getNavLink(childCategory)} key={childCategory.id}>{childCategory.title}</a>
))
}
</div>
......@@ -122,14 +122,14 @@ const Category: React.FC<CategoryPropsType> = (props) => {
item.children.map(childCategory => (
<div className={styles.second_category_type} key={childCategory.id}>
<div className={styles.title}>
<Link to={getNavLink(childCategory)}>
<a href={getNavLink(childCategory)}>
{childCategory.title} <RightOutlined />
</Link>
</a>
</div>
<ul className={styles.third_category_type_list}>
{
childCategory.children.map(thirdChildItem => (
<li key={thirdChildItem.id}><Link to={getNavLink(thirdChildItem)}>{thirdChildItem.title}</Link></li>
<li key={thirdChildItem.id}><a href={getNavLink(thirdChildItem)}>{thirdChildItem.title}</a></li>
))
}
</ul>
......@@ -140,9 +140,11 @@ const Category: React.FC<CategoryPropsType> = (props) => {
<div className={styles.category_type_right_wrap}>
<div className={styles.category_advert}>
{
(item.brandBOList && item.brandBOList.length > 0) && item.brandBOList.map((brandItem, brandIndex) => brandIndex < 4 && (
(item.brandList && item.brandList.length > 0) && item.brandList.map((brandItem, brandIndex) => brandIndex < 4 && (
<div key={`category_advert_item_${brandIndex}`} className={styles.category_advert_item}>
<Link to={getBrandLink(brandItem)}> <img src={brandItem.brandLogo} /></Link>
<a href={getBrandLink(brandItem)}>
<ImageBox width={160} height={80} imgUrl={brandItem.brandLogo} />
</a>
</div>
))
}
......
import { PROPS_TYPES } from 'lingxi-editor-core'
export const mallLayoutConfig = {
key: "0",
"0": {
"componentName": "MallLayout",
"props": {
"style": {
"width": "100%",
"minHeight": "100%",
"background": "#F7F8FA",
"overflowX": "hidden",
"paddingBottom": "50px",
}
},
"childNodes": ["1", "2", "5"]
},
}
export const mobileShopHeaderNav = {
key: "1",
"1": {
"componentName": "MobileShopHeaderNav",
"title": "背景图编辑",
"canEdit": true,
"canHide": false,
"props": {
"shopInfo": {
memberName: "温州市龙昌皮业有限公司",
logo: "https://shushangyun-lingxi.oss-cn-shenzhen.aliyuncs.com/f60693caed3f47868e5897bd1ccf40ea1610331248766.png",
creditPoint: 998,
registerYears: 2,
avgTradeCommentStar: 3,
}
},
}
}
export const divWrap = {
key: "2",
"2": {
"componentName": "div",
"props": {
"style": {
position: "relative",
marginTop: -24,
backgroundColor: "#FFF",
borderTopLeftRadius: '16px',
borderTopRightRadius: '16px',
zIndex: 6,
padding: '2px 4px',
}
},
"childNodes": ["3", "4"]
}
}
export const mobileBanner = {
key: "3",
"3": {
"componentName": "MobileBanner",
"componentType": PROPS_TYPES.mobileBanner,
"title": "轮播广告",
"canEdit": true,
"canHide": false,
"props": {
dataList: []
}
}
}
export const mobileQuickNav = {
key: "4",
"4": {
"componentName": "MobileQuickNav",
"componentType": PROPS_TYPES.mobileQuickNav,
"title": "导航模块",
"canEdit": true,
"canHide": false,
"props": {
dataList: [
{
"id":1,
"expand":false,
"icon":"https://shushangyun-lingxi.oss-cn-shenzhen.aliyuncs.com/c5d66f1488cc47d0a73279ce1ef11c991610677462848.png",
"type":1,
"name":"商城",
"url":""
},
{
"id":2,
"expand":false,
"icon":"https://shushangyun-lingxi.oss-cn-shenzhen.aliyuncs.com/9f105bdebcfb4010b5827f7b64fb53281610696444606.png",
"type":2,
"name":"分类",
"url":""
},
{
"id":3,
"expand":false,
"icon":"https://shushangyun-lingxi.oss-cn-shenzhen.aliyuncs.com/d4383c684c6e4707b405f46f281796d71610696469970.png",
"type":3,
"name":"积分兑换",
"url":""
},
{
"id":4,
"expand":false,
"icon":"https://shushangyun-lingxi.oss-cn-shenzhen.aliyuncs.com/d4383c684c6e4707b405f46f281796d71610696469970.png",
"type":4,
"name":"公司介绍",
"url":""
},
{
"id":5,
"expand":false,
"icon":"https://shushangyun-lingxi.oss-cn-shenzhen.aliyuncs.com/441a66ebeb3b45e6a64ecfa9977f411c1610696489991.png",
"type":5,
"name":"成为会员",
"url":""
},
]
}
}
}
export const mobileShopCommodityList = {
key: "5",
"5": {
"componentName": "MobileShopCommodityList",
"componentType": PROPS_TYPES.mobileShopCommodity,
"title": "商品推荐",
"canEdit": true,
"canHide": false,
"props": {
"dataList": [
{
categoryId: 1,
categoryName: "灯光照明",
categoryImage: "https://shushangyun-lingxi.oss-cn-shenzhen.aliyuncs.com/19466a6f8a5448c5b1a2011f642126611610677625949.png",
productList: [
{
id: 11,
name: '三级抗震螺纹钢 HRB400E 25*12三钢',
sellPoints: ["硬度适中偏软", "手感舒适"],
min: 79,
unitName: "吨",
sold: 3133,
mainPic: "https://shushangyun-lingxi.oss-cn-shenzhen.aliyuncs.com/19466a6f8a5448c5b1a2011f642126611610677625949.png"
},
{
id: 12,
name: '三级抗震螺纹钢 HRB400E 25*12三钢',
sellPoints: ["硬度适中偏软", "手感舒适"],
min: 79,
unitName: "吨",
sold: 3133,
mainPic: "https://shushangyun-lingxi.oss-cn-shenzhen.aliyuncs.com/19466a6f8a5448c5b1a2011f642126611610677625949.png"
},
{
id: 13,
name: '三级抗震螺纹钢 HRB400E 25*12三钢',
sellPoints: ["硬度适中偏软", "手感舒适"],
min: 79,
unitName: "吨",
sold: 3133,
mainPic: "https://shushangyun-lingxi.oss-cn-shenzhen.aliyuncs.com/19466a6f8a5448c5b1a2011f642126611610677625949.png"
},
{
id: 14,
name: '三级抗震螺纹钢 HRB400E 25*12三钢',
sellPoints: ["硬度适中偏软", "手感舒适"],
min: 79,
unitName: "吨",
sold: 3133,
mainPic: "https://shushangyun-lingxi.oss-cn-shenzhen.aliyuncs.com/19466a6f8a5448c5b1a2011f642126611610677625949.png"
}
]
},
{
categoryId: 2,
categoryName: "灯光照明",
categoryImage: "https://shushangyun-lingxi.oss-cn-shenzhen.aliyuncs.com/19466a6f8a5448c5b1a2011f642126611610677625949.png",
productList: [
{
id: 21,
name: '四级抗震螺纹钢 HRB400E 25*12三钢',
sellPoints: ["硬度适中偏软"],
min: 79,
unitName: "吨",
sold: 3133,
mainPic: "https://shushangyun-lingxi.oss-cn-shenzhen.aliyuncs.com/19466a6f8a5448c5b1a2011f642126611610677625949.png"
},
{
id: 22,
name: '三级抗震螺纹钢 HRB400E 25*12三钢',
sellPoints: ["硬度适中偏软", "手感舒适"],
min: 79,
unitName: "吨",
sold: 3133,
mainPic: "https://shushangyun-lingxi.oss-cn-shenzhen.aliyuncs.com/19466a6f8a5448c5b1a2011f642126611610677625949.png"
}
]
}
]
}
}
}
@content-height: calc(100vh - 120px);
.wrapper {
background: white;
display: flex;
flex-direction: column;
box-shadow: 2px 0 4px 0 rgba(174, 174, 174, 0.50);
transition: all .3s;
}
.content {
display: flex;
flex: 1;
flex-direction: row;
justify-content: center;
background-color: #F2F3F5;
height: calc(100vh - 64px);
}
.app-wrapper {
display: flex;
flex: 1;
justify-content: center;
}
.app-canvas-container {
display: flex;
width: 381px;
margin-top: 40px;
margin-bottom: 52px;
justify-content: center;
background-color: #F4F5F7;
// height: calc(@content-height + 50px);
overflow: hidden;
}
.loading_wrap {
width: 100%;
height: 100vh;
justify-content: center;
flex-direction: column;
display: flex;
align-items: center;
.loading_text {
margin-top: 16px;
font-size: 14px;
font-weight: bold;
}
}
/*
* @Author: ghua
* @Date: 2021-01-14 17:03:08
* @Last Modified by: ghua
* @Last Modified time: 2021-01-15 17:47:38
* @Description 店铺主页装修
*/
import React, { useEffect, useState } from 'react'
import { LegoProvider } from 'lingxi-editor-core'
import ToolBar from '../../editor/components/toolBar'
import MobileDesignPanel from '../../editor/components/MobileDesignPanel'
import AllComponents from '../../editor/components/ComponentsPreview'
import { message } from 'antd'
import config from '../../editor/configs'
import {
mallLayoutConfig,
divWrap,
mobileShopHeaderNav,
mobileBanner,
mobileQuickNav,
mobileShopCommodityList,
} from './config'
import Loading from '../../editor/components/Loading'
import { PublicApi } from '@/services/api'
import { LAYOUT_TYPE } from '@/constants'
import { GetTemplateAdornAppStoreFindResponse } from '@/services/TemplateApi'
// import { GlobalConfig } from '@/global/config'
import MobileSettingPanel from '../../editor/mobileSettingPanel'
import { getAuth } from '@/utils/auth'
import styles from './index.less'
interface ShopPreviewPropsType {
location: {
query: {
/**
* 模板id
*/
id: number;
/**
* 模板名称
*/
template: string;
}
}
}
const TemplateList = ['science']
const mobileShopTempleteEdit: React.FC<ShopPreviewPropsType> = (props) => {
const { query: { id, template } } = props.location
const [loading, setLoading] = useState<boolean>(true)
const [theme, setTheme] = useState<string>('theme-mall-science')
const [componentConfigs, setComponentConfigs] = useState({})
const { memberId, memberRoleId } = getAuth() || {}
useEffect(() => {
if (!TemplateList.includes(template)) {
setTheme(`theme-mall-${TemplateList[0]}`)
} else {
setTheme(`theme-mall-${template}`)
}
getComponentsConfig()
}, [])
/**
* 获取店铺信息
*/
const fetchShopInfo = () => {
return new Promise((resolve) => {
const param: any = {
memberId,
roleId: memberRoleId
}
PublicApi.getTemplateWebMemberShopWebFindByMemberIdAndRoleId(param).then(res => {
if (res.code === 1000) {
if (res.code === 1000) {
resolve(res.data)
}
}
})
})
}
/**
* 获取app店铺装修信息
*/
const getAppShopConfig = (): Promise<GetTemplateAdornAppStoreFindResponse | null> => {
return new Promise((resolve, reject) => {
const param: any = {
templateId: id
}
PublicApi.getTemplateAdornAppStoreFind(param).then(res => {
if(res.code === 1000) {
resolve(res.data)
} else {
reject(res)
}
}).catch((eror) => {
reject(eror)
})
})
}
const getRecommendShopList = (param) => {
return new Promise((resolve, reject) => {
PublicApi.getTemplateWebMemberShopWebRecommendList(param).then(res => {
if(res.code === 1000) {
resolve(res.data.data)
} else {
reject(false)
}
}).catch((eror) => {
reject(false)
})
})
}
const getStoreBOList = (dataList) => {
if(dataList) {
const res = dataList.map(item => {
item.selectId = item.id
item.name = item.memberName
item.selectInfo = Object.assign({}, item)
return item
})
return res
} else {
return []
}
}
/**
* 根据选中的类型和id获取信息
* @param data
*/
const getSelectInfo = (data): Promise<any[] | undefined> => {
return new Promise((resolve) => {
let getFn: any = null
const param: any = {
current: 1,
pageSize: 100,
}
switch(data.type) {
case 1:
param.idInList = data.recommend
getFn = PublicApi.postSearchMobileShopEnterpriseGetCommodityList
break
case 2:
param.idList = data.recommend
getFn = PublicApi.getTemplateWebMemberShopWebRecommendList
break
case 3:
param.idList = data.recommend
getFn = PublicApi.postSearchMobileShopEnterpriseGetCategoryBrand
break
case 4:
param.idList = data.recommend
getFn = PublicApi.getManageContentInformationPageByIdIn
break
default:
break
}
getFn ? getFn(param).then(res => {
message.destroy()
resolve(data.type === 3 ? res.data : res.data.data)
}).catch(() => {
resolve(undefined)
}) : resolve(undefined)
})
}
const getExcellentDetailsBO = async (dataList) => {
if(dataList) {
const newRes: any = []
for(const item of dataList) {
const temp = { ...item }
temp.recommendList = await getSelectInfo(item)
newRes.push(temp)
}
console.log(newRes, 'newRes')
return newRes
} else {
return []
}
}
const getComponentsConfig = async () => {
try {
// const appConfig = await getAppShopConfig()
// console.log(appConfig, "appConfig")
//店铺信息
const shopInfo = await fetchShopInfo()
mobileShopHeaderNav[mobileShopHeaderNav.key].props.shopInfo = shopInfo
// if(appConfig?.topBO) {
// // 顶部导航
// mobileHeaderNav[mobileHeaderNav.key].props.styleTheme = appConfig?.topBO.style
// mobileHeaderNav[mobileHeaderNav.key].props.dataList = appConfig?.topBO.topDetailsBOList
// }
// if(appConfig?.advertBO) {
// // 轮播广告
// mobileBanner[mobileBanner.key].props = {
// visible: appConfig.advertBO.status,
// dataList: appConfig.advertBO.advertDetailsBOList
// }
// }
// if(appConfig?.functionBO) {
// // 功能入口
// mobileQuickNav[mobileQuickNav.key].props = {
// visible: appConfig.functionBO.status,
// dataList: appConfig.functionBO.functionDetailsBO,
// }
// }
// if(appConfig?.showcaseBO) {
// // 橱窗推荐
// mobileShowCase[mobileShowCase.key].props = {
// visible: appConfig?.showcaseBO.status,
// dataList: appConfig?.showcaseBO.showcaseDetailsBO,
// }
// }
// if(appConfig?.storeBO) {
// const storeBOParam = {
// current: 1,
// pageSize: 100,
// idList: appConfig?.storeBO.storeIdList || [],
// }
// console.log(storeBOParam, "storeBOParam")
// const storeBORes = await getRecommendShopList(storeBOParam)
// // 推荐店铺
// mobileRecommendShops[mobileRecommendShops.key].props = {
// visible: appConfig?.storeBO.status,
// dataList: getStoreBOList(storeBORes),
// }
// }
// if(appConfig?.excellentBO) {
// // 优质推荐
// mobileQuality[mobileQuality.key].props = {
// visible: appConfig?.excellentBO.status,
// dataList: await getExcellentDetailsBO(appConfig?.excellentBO.excellentDetailsBO),
// }
// }
// if(appConfig?.bottomBO) {
// // 底部导航
// mobileBottomNavigation[mobileBottomNavigation.key].props = {
// visible: appConfig?.bottomBO.status,
// dataList: appConfig?.bottomBO.bottomDetailsBOList,
// }
// }
const config = {
...mallLayoutConfig,
...mobileShopHeaderNav,
...divWrap,
...mobileBanner,
...mobileQuickNav,
...mobileShopCommodityList,
}
setComponentConfigs(config)
setLoading(false)
} catch (error) {
console.log(error)
}
}
return !loading ? (
<LegoProvider initState={{ componentConfigs: componentConfigs }} config={config}>
<div className={styles['wrapper']}>
<ToolBar type={1} title="店铺主页" showActions={true} layoutType={LAYOUT_TYPE.shop} templateId={id} />
<div className={styles['content']}>
<AllComponents />
<div className={styles['app-wrapper']}>
<div className={styles['app-canvas-container']}>
<MobileDesignPanel theme={theme} />
</div>
</div>
<MobileSettingPanel />
</div>
</div>
</LegoProvider>
) : <Loading />
}
export default mobileShopTempleteEdit
......@@ -11,6 +11,9 @@ interface TemplateItemPropsType {
}
const Environment_Status = {
0: {
name: "所有"
},
1: {
name: "web"
},
......
import React, { useState, useEffect } from 'react'
import { LayoutOutlined, EyeOutlined, PushpinOutlined } from '@ant-design/icons'
import cx from 'classnames'
import { message } from 'antd'
import DetailPage from '@/components/DetailPage'
import UseModal from '../components/useModal'
import { PublicApi } from '@/services/api'
......@@ -57,8 +58,15 @@ const TemplateDetail: React.FC<TemplateDetailPropsType> = (props) => {
}
const handleLinkEdit = () => {
window.location.href = `/shop/template/edit?id=${detailInfo.id}&template=${detailInfo.fileName}`
// history.push(`/shop/template/edit?id=${detailInfo.id}&template=${detailInfo.fileName}`)
if(detailInfo?.environment === 1) {
// web店铺装修
window.location.href = `/shop/template/edit?id=${detailInfo.id}&template=${detailInfo.fileName}`
} else if(detailInfo?.environment === 4) {
// app店铺装修
window.location.href = `/mobile/shop/template/edit?id=${detailInfo.id}&template=${detailInfo.fileName}`
} else {
message.info("暂不支持该类型模板装修")
}
}
const handleLinkPreview = () => {
......
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