Commit 270b994f authored by GuanHua's avatar GuanHua

feat:渠道商城和店铺装修页面和接口对接

parent 3f6288f2
registry = "http://10.0.0.21:8081/repository/node-group/"
\ No newline at end of file
registry = "http://10.0.0.21:8081/repository/node-group/"
# registry = "https://registry.npmjs.org/"
\ No newline at end of file
......@@ -35,6 +35,14 @@ const router = [
},
]
},
{
path: '/channel/template/edit',
component: '@/pages/editor/channelEdit',
},
{
path: '/shop/template/edit',
component: '@/pages/editor/shopEdit',
},
memberCenterRoute,
shopRoute,
channelRoute,
......
......@@ -37,7 +37,10 @@
"bizcharts": "^4.0.7",
"copy-to-clipboard": "^3.3.1",
"god": "0.1.20",
"lingxi-design-ui": "^1.0.6",
"lingxi-design": "^1.0.7",
"lingxi-design-ui": "^1.0.8",
"lingxi-editor-core": "^1.0.6",
"lingxi-web": "^1.0.6",
"lint-staged": "^10.0.7",
"mobx": "^5.15.4",
"mobx-react": "^6.2.2",
......
......@@ -20,6 +20,9 @@ setup()
// 路由白名单
const whiteLists = [
'/',
'/channelmall',
'/purchaseOnline',
'/pointsMall',
'/user/login',
'/user/register',
'/memberCenter',
......@@ -99,9 +102,9 @@ export function render(oldRender: Function) {
export function onRouteChange({ routes, matchedRoutes, location, action }) {
console.log('onRouteChange')
// if (isDev) {
// return;
// }
if (isDev) {
return;
}
if (whiteLists.includes(location.pathname)) return;
const routeAuthUrls = getRouters()
......
......@@ -17,19 +17,8 @@ export interface UserRegister {
useType: UseType;
}
export interface ShopInfo {
id: number;
name: string;
type: number;
environment: number;
logoUrl: string;
describe: string;
state: number;
url: string;
}
export interface Web {
shopInfo: ShopInfo[];
shopInfo: any[];
}
export interface PayConfig {
......
......@@ -27,13 +27,14 @@ const TemplateDetail: React.FC<TemplateDetailPropsType> = (props) => {
useEffect(() => {
fetchDetail()
}, [])
const fetchDetail = () => {
//@ts-ignore
PublicApi.getTemplateChannelFindChannelTemplateDetails({ id }).then(res => {
setDetailInfo(res.data)
if (res.code === 1000) {
setDetailInfo(res.data)
}
})
}
......@@ -54,6 +55,10 @@ const TemplateDetail: React.FC<TemplateDetailPropsType> = (props) => {
})
}
const handleLinkEdit = () => {
history.push(`/channel/template/edit?id=${detailInfo.id}&template=${detailInfo.fileName}`)
}
return (
<DetailPage
title="查看模板"
......@@ -91,7 +96,7 @@ const TemplateDetail: React.FC<TemplateDetailPropsType> = (props) => {
<EyeOutlined />
<label>预览</label>
</div>
<div className={cx(styles.btn, styles.fit)}>
<div className={cx(styles.btn, styles.fit)} onClick={() => handleLinkEdit()}>
<LayoutOutlined />
<label>渠道商城装修</label>
</div>
......
export const mallLayoutConfig = {
key: "0",
"0": {
"componentName": "MallLayout",
"props": {
"style": {
"width": "100%",
"minHeight": "100%",
"background": "#FFF"
}
},
"childNodes": ["1", "3", "4", "5", "6"]
},
}
export const topBarConfig = {
key: "1",
"1": {
"componentName": "TopBar",
"props": {
linkdisable: true
},
}
}
export const topAdvertConfig = {
key: "2",
"2": {
"componentName": "Advert",
"props": {
"type": "top",
"linkdisable": true,
"advertList": []
},
},
}
export const headerConfig = {
key: "3",
"3": {
"componentName": "ChannelHeader",
"props": {},
},
}
export const mainNavConfig = {
key: "4",
"4": {
"componentName": "MainNav",
"props": {},
},
}
export const bannerAdvertConfig = {
key: "5",
"5": {
"componentName": "Advert",
"props": {
"type": "banner",
"linkdisable": true,
"advertList": []
}
},
}
export const CommonTitle1Config = {
key: "6",
"6": {
"componentName": "CommonTitle",
"props": {
"title": "热销商品",
"type": "primary"
},
},
}
export const serviceAdvertConfig = {
key: "16",
"16": {
"componentName": "Advert",
"props": {
"type": "service",
"linkdisable": true,
"advertList": []
}
},
}
export const CommonTitle2Config = {
key: "17",
"17": {
"componentName": "CommonTitle",
"props": {
"title": "关于我们",
"type": "primary"
},
},
}
export const AboutUsConfig = {
key: "18",
"18": {
"componentName": "AboutUs",
"props": {},
},
}
export const InformationConfig = {
key: '19',
"19": {
"componentName": "Information",
"props": {},
},
}
export const FooterConfig = {
key: '20',
"20": {
"componentName": "Footer",
"props": {},
},
}
export default {
"0": {
"componentName": "MallLayout",
"props": {
"style": {
"width": "100%",
"minHeight": "100%"
}
},
"childNodes": ["1", "2", "3", "4", "5", "7", "8", "18", "19", "20"]
},
"1": {
"componentName": "TopBar",
"props": {
linkdisable: true
},
},
"2": {
"componentName": "Advert",
"props": {
"type": "top",
"linkdisable": true,
"advertList": []
},
},
"3": {
"componentName": "Header",
"props": {},
},
"4": {
"componentName": "MainNav",
"props": {},
},
"5": {
"componentName": "Advert",
"props": {
"type": "banner",
"hasQuickNav": true,
"linkdisable": true,
"advertList": []
// "advertList": [
// {
// id: 1,
// templateId: 707,
// type: 2,
// name: '意大利进口荔枝纹牛皮会员价促销最低 9 折',
// picUrl: 'https://img.alicdn.com/imgextra/i4/81/O1CN01VT5ViO1CT8h4sY7qc_!!81-0-luban.jpg_q100.jpg_.webp',
// link: 'https://www.baidu.com',
// sort: 1,
// },
// {
// id: 2,
// templateId: 707,
// type: 2,
// name: '意大利进口荔枝纹牛皮会员价促销最低 9 折',
// picUrl: 'https://img.alicdn.com/tps/i4/TB1ICgYL.H1gK0jSZSySuttlpXa.jpg',
// link: 'https://www.baidu.com',
// sort: 2,
// }
// ]
}
},
"7": {
"componentName": "Advert",
"props": {
"type": "interact",
"linkdisable": true,
"advertList": []
},
},
"8": {
"componentName": "FloorLine",
"props": {},
"childNodes": ["9", "10"]
},
"9": {
"componentName": "FloorLine.Horizontal",
"props": {},
"childNodes": ["11", "12"]
},
"10": {
"componentName": "FloorLine.Brand",
"props": {},
},
"11": {
"componentName": "FloorLine.Category",
"props": {},
},
"12": {
"componentName": "FloorLine.Vertical",
"props": {},
"childNodes": ["13", "15"]
},
"13": {
"componentName": "FloorLine.FloorHeader",
"props": {},
"childNodes": ["14"]
},
"14": {
"componentName": "FloorLine.Banner",
"props": {
"type": "category",
},
},
"15": {
"componentName": "FloorLine.Horizontal",
"props": {},
"childNodes": ["16", "17"]
},
"16": {
"componentName": "FloorLine.Goods",
"props": {},
},
"17": {
"componentName": "FloorLine.Shops",
"props": {},
},
"18": {
"componentName": "FindMore",
"props": {},
},
"19": {
"componentName": "Information",
"props": {},
},
"20": {
"componentName": "Footer",
"props": {},
},
}
\ No newline at end of file
@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;
}
.canvas-container {
display: flex;
flex: 1;
justify-content: center;
background-color: #F5F5F5;
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: 16px;
font-weight: bold;
}
}
\ No newline at end of file
import React, { useEffect, useState } from 'react'
import { LegoProvider } from 'lingxi-editor-core'
import ToolBar from '../components/toolBar'
import DesignPanel from '../components/DesignPanel'
import SettingPanel from '../settingsPanel'
import config from '../configs'
import { topBarConfig, topAdvertConfig, headerConfig, mainNavConfig, bannerAdvertConfig, CommonTitle1Config, mallLayoutConfig, serviceAdvertConfig, CommonTitle2Config, AboutUsConfig, InformationConfig, FooterConfig } from './defaultData'
import Loading from './loading'
import { PublicApi } from '@/services/api'
import styles from './index.less'
interface ChannelEditPropsType {
location: {
query: {
/**
* 模板id
*/
id: number;
/**
* 模板名称
*/
template: string;
}
}
}
let TemplateList = ['science']
const ChannelEdit: React.FC<ChannelEditPropsType> = (props) => {
const { query: { id, template } } = props.location
const [loading, setLoading] = useState<boolean>(true)
const [theme, setTheme] = useState<string>('theme-channel-science')
const [componentConfigs, setComponentConfigs] = useState({})
useEffect(() => {
if (!TemplateList.includes(template)) {
setTheme(`theme-channel-${TemplateList[0]}`)
} else {
setTheme(`theme-channel-${template}`)
}
getComponentsConfig()
}, [])
const findFirstAdvertsByType = () => {
return new Promise((resolve) => {
let params = {
templateId: id,
type: 1
}
//@ts-ignore
PublicApi.getTemplateChannelFindAdvertsByType(params).then(res => {
if (res.code === 1000) {
resolve(res.data)
} else {
resolve([])
}
})
})
}
const findSecondAdvertsByType = () => {
return new Promise((resolve) => {
let params = {
templateId: id,
type: 2
}
//@ts-ignore
PublicApi.getTemplateChannelFindAdvertsByType(params).then(res => {
if (res.code === 1000) {
resolve(res.data)
} else {
resolve([])
}
})
})
}
/**
* 获取所有一级品类信息
*/
const fetchFirstCategory = () => {
return new Promise((resolve) => {
PublicApi.getTemplateChannelFindAllFirstCategory().then(res => {
if (res.code === 1000) {
resolve(res.data)
}
})
})
}
/**
* 获取一级品类详细信息
*/
const fetchCategoryById = (categoryId) => {
return new Promise((resolve) => {
let param = {
templateId: id,
categoryId
}
// @ts-ignore
PublicApi.getTemplateChannelFindFirstCategoryDetail(param).then(res => {
resolve(res.data)
})
})
}
const getComponentsConfig = async () => {
// 一号位广告
bannerAdvertConfig[bannerAdvertConfig.key].props.advertList = await findFirstAdvertsByType()
// 二号位广告
serviceAdvertConfig[serviceAdvertConfig.key].props.advertList = await findSecondAdvertsByType()
let initIndex = 100
let floorLineConfig: any = {}
let floorLineKeys: any = []
let firstCategory: any = await fetchFirstCategory()
for (let item of firstCategory) {
let categoryDetail: any = await fetchCategoryById(item.id)
let floorLineConfigItem = {}
floorLineKeys.push(String(initIndex + 1))
let FloorLine = {
[String(initIndex + 1)]: {
"componentName": "ShopFloorLine",
"props": {
title: item.name
},
"childNodes": [String(initIndex + 2), String(initIndex + 3)]
}
}
let Category = {
[String(initIndex + 2)]: {
"componentName": "ShopFloorLine.Category",
"props": {
categoryAdvertPicUrl: categoryDetail.categoryAdvertPicUrl,
categoryid: item.id,
categoryList: categoryDetail.categoryBOList
},
},
}
let Goods = {
[String(initIndex + 3)]: {
"componentName": "ShopFloorLine.Goods",
"props": {
linkdisable: true,
categoryid: item.id,
goodsList: categoryDetail.goodsBOList
},
},
}
floorLineConfigItem = { ...FloorLine, ...Category, ...Goods }
floorLineConfig = { ...floorLineConfig, ...floorLineConfigItem }
initIndex += 100
}
mallLayoutConfig["0"].childNodes = [...mallLayoutConfig["0"].childNodes, ...floorLineKeys, serviceAdvertConfig.key, CommonTitle2Config.key, AboutUsConfig.key, InformationConfig.key, FooterConfig.key]
let config = {
...mallLayoutConfig,
...topBarConfig,
...topAdvertConfig,
...headerConfig,
...mainNavConfig,
...bannerAdvertConfig,
...CommonTitle1Config,
...floorLineConfig,
...serviceAdvertConfig,
...CommonTitle2Config,
...AboutUsConfig,
...InformationConfig,
...FooterConfig
}
console.log(config)
setComponentConfigs(config)
setLoading(false)
}
return !loading ? (
<LegoProvider initState={{ componentConfigs: componentConfigs }} config={config}>
<div className={styles['wrapper']}>
<ToolBar />
<div className={styles['content']}>
<div className={styles['canvas-container']}>
<DesignPanel theme={theme} />
</div>
</div>
</div>
<SettingPanel templateId={id} type="channel" />
</LegoProvider>
) : <Loading />
}
export default ChannelEdit
\ No newline at end of file
import React from 'react'
import { Spin } from 'antd'
import styles from './index.less'
const Loading: React.FC = () => {
return (
<div className={styles.loading_wrap}>
<Spin size="large" />
<p className={styles.loading_text}>正在加载页面数据...</p>
</div>
)
}
export default Loading
import React from 'react'
import { BrickDesign } from 'lingxi-design';
// import { useSelector } from 'lingxi-editor-core';
const DesignPanel = (props) => {
const { theme } = props
// const { platformInfo } = useSelector(['platformInfo'])
// const { size } = platformInfo;
// const style = { width: size[0], maxHeight: size[1], transition: 'all 700ms' };
return <BrickDesign theme={theme} />
}
export default DesignPanel
\ No newline at end of file
.setting_list {
display: flex;
flex-wrap: wrap;
margin: 0 -12px;
&_item {
position: relative;
width: 380px;
height: 136px;
border: 1px solid #ffffff;
margin: 8px 4px;
padding: 8px;
cursor: pointer;
&.hover_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;
}
&:hover {
.setting_mask {
display: block;
opacity: 1;
}
}
}
&.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;
}
}
.setting_mask {
position: absolute;
display: none;
opacity: 0;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.12);
z-index: 8;
transition: all .3s;
.setting_deletebtn {
float: right;
width: 36px;
height: 36px;
line-height: 36px;
background: rgba(0, 0, 0, 0.65);
color: #ffffff;
font-size: 16px;
text-align: center;
cursor: pointer;
}
}
}
}
\ No newline at end of file
import React from 'react'
import cx from 'classnames'
import { DeleteOutlined } from '@ant-design/icons'
import styles from './index.less'
interface SettingItemPropsTYpe {
selected?: boolean;
type?: 'show' | 'select';
onClick: any;
onDelete?: any;
}
interface SettingListPropsType {
type?: 'show' | 'select'
}
enum SettingItemType {
show = 'show',
select = 'select'
}
const SettingItem: React.FC<SettingItemPropsTYpe> = (props) => {
const { children, selected = false, type, onClick, onDelete } = props
return (
<div className={cx(styles.setting_list_item, type === SettingItemType.show ? styles.hover_active : selected ? styles.active : '')} onClick={onClick}>
<div className={styles.setting_mask}>
<div className={styles.setting_deletebtn} onClick={onDelete}>
<DeleteOutlined />
</div>
</div>
{children}
</div>
)
}
const SettingList: any & React.FC<SettingListPropsType> = (props) => {
const { children, type = "show" } = props
return (
<div className={styles.setting_list}>
{
children && React.Children.map(children, (child: any) => {
return React.cloneElement(child,
{
type
},
);
})
}
{/* {children} */}
</div>
)
}
SettingList.SettingItem = SettingItem
export default SettingList
.setting_panel {
position: relative;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
width: 100%;
height: 100%;
&_body {
flex-grow: 1;
padding: 24px;
overflow: auto;
font-size: 14px;
line-height: 1.5715;
}
&_footer {
flex-shrink: 0;
padding: 10px 10px;
border-top: 1px solid #f0f0f0;
}
}
\ No newline at end of file
import React from 'react'
import { Button } from 'antd'
import { SelectedInfoType, clearSelectedStatus, PROPS_TYPES, changeProps } from 'lingxi-editor-core';
import styles from './index.less'
interface SettingPanelPropsType {
footer?: React.ReactNode;
onOK?: Function;
onCancel?: Function;
confirmLoading?: boolean
}
const SettingPanel: React.FC<SettingPanelPropsType> = (props) => {
const { children, footer, onOK = () => { }, onCancel, confirmLoading = false } = props
return (
<div className={styles.setting_panel}>
<div className={styles.setting_panel_body}>
{children}
</div>
{
footer ? (
<div className={styles.setting_panel_footer}>
{footer}
</div>
) : (
<div className={styles.setting_panel_footer}>
<div style={{ textAlign: 'right' }}>
<Button style={{ marginRight: 8 }} onClick={() => !!onCancel ? onCancel() : clearSelectedStatus()}>取消</Button>
<Button type="primary" onClick={(e) => onOK(e)} loading={confirmLoading}>确定</Button>
</div>
</div>
)
}
</div>
)
}
export default SettingPanel
.template_item {
// width: 386px;
background-color: #ffffff;
border-radius: 8px;
overflow: hidden;
height: 100%;
padding-bottom: 24px;
.img_box {
position: relative;
// height: 218px;
height: 0;
padding-bottom: 67%;
overflow: hidden;
background-size: auto 100%;
background-position: center center;
background-repeat: no-repeat;
&:hover {
.img_box_mask {
opacity: 1;
}
}
.img_box_mask {
position: absolute;
width: 100%;
opacity: 0;
transition: all .5s;
height: 100%;
background: rgba(255, 255, 255, 0.45);
z-index: 8;
.detail_btn {
position: relative;
display: block;
left: 0;
right: 0;
top: 50%;
margin: 0 auto;
margin-top: -20px;
width: 240px;
height: 40px;
background: rgba(0, 0, 0, 0.85);
color: #ffffff;
text-align: center;
line-height: 40px;
font-weight: 500;
cursor: pointer;
&:hover {
opacity: .9;
}
}
}
.type_tag {
position: absolute;
top: 0;
width: 72px;
height: 24px;
color: #ffffff;
text-align: center;
line-height: 24px;
font-size: 14px;
background: #4279DF;
border-radius: 8px 0px 8px 0px;
z-index: 3;
&.h5 {
background: #6554C0;
}
}
&>img {
// height: 100%;
width: 100%;
}
}
.template_info {
padding: 0 24px;
.template_info_name {
display: flex;
align-items: center;
color: #172B4D;
font-weight: bold;
line-height: 24px;
padding: 16px 0;
.tag {
padding: 0 8px;
height: 24px;
background: rgba(244, 245, 247, 1);
border-radius: 4px;
margin-left: 8px;
color: #42526E;
font-weight: 400;
}
}
.template_info_content {
display: flex;
align-items: flex-end;
height: 52px;
&.goods {
height: 32px;
}
.template_info_content_text_wrap {
flex: 1;
font-size: 14px;
line-height: 22px;
.template_info_content_text_line {
&:not(:last-child) {
margin-bottom: 8px;
}
&>label {
color: #6B778C;
margin-right: 8px;
}
&>span {
color: #172B4D;
}
}
}
.template_item_btn {
width: 88px;
height: 32px;
display: flex;
justify-content: center;
align-items: center;
background: rgba(250, 251, 252, 1);
border: 1px solid rgba(235, 236, 240, 1);
cursor: pointer;
color: #42526E;
font-size: 14px;
// margin-top: auto;
margin-left: auto;
&.active {
background: #00B37A;
border: 1px solid #00B37A;
color: #ffffff;
}
&>label {
margin-left: 8px;
cursor: pointer;
}
&:hover {
opacity: .8;
}
}
}
}
}
\ No newline at end of file
import React from 'react'
import { PlayCircleOutlined } from '@ant-design/icons'
import cx from 'classnames'
import { Link } from 'umi'
import { TEMPLATE_TYPE_TEXT } from '@/constants'
import styles from './index.less'
interface TemplateItemPropsType {
templateInfo: any;
type: string;
}
const TemplateItem: React.FC<TemplateItemPropsType> = (props) => {
const { templateInfo, type } = props
const Environment_Status = {
0: {
name: "所有"
},
1: {
name: "PC "
},
2: {
name: "H5 "
},
3: {
name: "APP "
}
}
return (
<div className={styles.template_item}>
<div className={styles.img_box} style={{ backgroundImage: `url(${templateInfo?.templatePicUrl})` }}>
<div className={styles.img_box_mask}>
<Link to={`/pageCustomized/template/detail?type=${type}&id=${templateInfo.id}`} className={styles.detail_btn}>查看详情</Link>
</div>
<div className={cx(styles.type_tag, templateInfo.environment === 2 ? styles.h5 : '')}>{Environment_Status[templateInfo.environment].name}</div>
</div>
<div className={styles.template_info}>
<div className={styles.template_info_name}>
<span>{templateInfo.templateName}</span>
{
templateInfo.isDefault && <div className={styles.tag}>默认模板</div>
}
</div>
<div className={cx(styles.template_info_content, type === TEMPLATE_TYPE_TEXT.goods ? styles.goods : '')}>
<div className={styles.template_info_content_text_wrap}>
<div className={styles.template_info_content_text_line}>
<label>使用站点:</label>
<span>{templateInfo.siteName}</span>
</div>
{
type !== TEMPLATE_TYPE_TEXT.goods && (
<div className={styles.template_info_content_text_line}>
<label>使用商城:</label>
<span>{templateInfo.shopName}</span>
</div>
)
}
</div>
{
type === TEMPLATE_TYPE_TEXT.mall ? (
<div className={cx(styles.template_item_btn, templateInfo.use === 1 ? styles.active : '')}>
<PlayCircleOutlined />
<label>{templateInfo.use === 1 ? '启用中' : '启用'}</label>
</div>
) : (
<div className={cx(styles.template_item_btn, templateInfo.shelf === 1 ? styles.active : '')}>
<PlayCircleOutlined />
<label>{templateInfo.shelf === 1 ? '启用中' : '启用'}</label>
</div>
)
}
</div>
</div>
</div>
)
}
export default TemplateItem
.toolbar {
width: 100%;
height: 64px;
background-color: #FFF;
display: flex;
align-items: center;
border-bottom: 1px solid #42526E;
&_back_btn {
width: 64px;
height: 64px;
text-align: center;
line-height: 64px;
background-color: #42526E;
color: #FFF;
cursor: pointer;
}
&_title {
color: #6B778C;
padding-left: 12px;
font-size: 14px;
&>label {
color: #172B4D;
font-size: 20px;
margin-left: 4px;
}
}
}
\ No newline at end of file
import React from 'react'
import { ArrowLeftOutlined } from '@ant-design/icons'
import { history } from 'umi'
import styles from './index.less'
const ToolBar: React.FC = () => {
return (
<div className={styles.toolbar}>
<div className={styles.toolbar_back_btn} onClick={() => history.goBack()}>
<ArrowLeftOutlined />
</div>
<div className={styles.toolbar_title}>
<span>正在编辑:</span>
<label>首页</label>
</div>
</div>
)
}
export default ToolBar
.selectBox {
width: 100%;
}
.text_line {
margin-bottom: 8px;
&>span {
color: #6B778C;
}
&>label {
color: #172B4D;
margin: 0 3px;
}
}
\ No newline at end of file
import React from 'react'
import { Modal, Form, Select, Checkbox } from 'antd'
import styles from './index.less'
interface UseModalPropsType {
visible: boolean;
onOk: Function;
onCancel: Function;
title: string;
type: string;
}
const UseModal: React.FC<UseModalPropsType> = (props) => {
const { visible, onOk, onCancel, title, type } = props
const [form] = Form.useForm()
const layout = {
labelCol: { span: 24 },
wrapperCol: { span: 24 },
};
return (
<Modal
width={576}
title={title}
visible={visible}
onOk={() => onOk()}
centered
onCancel={() => onCancel()}
>
<Form {...layout} form={form}>
<Form.Item
name="station"
label="选择站点"
rules={[{ required: true }]}
>
<Select className={styles.selectBox}>
<Select.Option value="1">中国站</Select.Option>
<Select.Option value="2">美国站</Select.Option>
</Select>
</Form.Item>
<Form.Item
name="shopCenter"
label="选择商城"
rules={[{ required: true }]}
>
<Select className={styles.selectBox}>
<Select.Option value="1">WEB商城</Select.Option>
<Select.Option value="2">H5商城</Select.Option>
</Select>
</Form.Item>
{
type === "shop" ? (
<div>
<Checkbox >允许使用</Checkbox>
</div>
) : (
<>
<div className={styles.text_line}>
<span>您选择的站点</span>
<label>美国站-WEB商城</label>
<span>现在使用的模板是</span>
<label>“模板003-清新类”</label>
<span>模板,</span>
</div>
<div className={styles.text_line}>
<span>您是否使用</span>
<label>“模板001-清新类”</label>
<span>模板,来替换您正在使用的模板</span>
</div>
</>
)
}
</Form>
</Modal>
)
}
export default UseModal
import { ComponentConfigTypes, PROPS_TYPES } from 'lingxi-editor-core';
const a: ComponentConfigTypes = {
propsConfig: {
target: {
label: '规定在何处打开链接文档',
type: PROPS_TYPES.enum,
enumData: ['_blank', '_father', '_self', '_top', 'framename'],
},
href: {
label: '规定链接指向的页面的 URL',
type: PROPS_TYPES.string,
},
className: {
label: '类名',
type: PROPS_TYPES.string,
},
onClick: {
label: '点击事件',
type: PROPS_TYPES.function,
placeholder: '()=>{}',
},
children: {
label: '文本内容',
type: PROPS_TYPES.string,
},
},
};
export default a;
import { ComponentConfigTypes, PROPS_TYPES } from 'lingxi-editor-core';
const div: ComponentConfigTypes = {
propsConfig: {
children: {
label: '文本内容',
type: PROPS_TYPES.string,
},
},
};
export default div;
import { ComponentConfigTypes, PROPS_TYPES } from 'lingxi-editor-core';
const img: ComponentConfigTypes = {
propsConfig: {
alt: {
label: '图像的替代文本',
type: PROPS_TYPES.string,
},
src: {
label: '上传图像',
type: PROPS_TYPES.string,
},
height: {
label: '规定图像的高度',
type: PROPS_TYPES.string,
},
width: {
label: '规定图像的宽度',
type: PROPS_TYPES.string,
},
},
};
export default img;
export { default as div } from './div';
export { default as a } from './a';
export { default as span } from './span';
export { default as img } from './img';
import { ComponentConfigTypes, PROPS_TYPES } from 'lingxi-editor-core';
const span: ComponentConfigTypes = {
propsConfig: {
children: {
label: '文本内容',
type: PROPS_TYPES.string,
},
},
};
export default span;
import { ComponentConfigTypes, PROPS_TYPES } from 'lingxi-editor-core';
const AboutUs: ComponentConfigTypes = {
propsConfig: {
children: {
label: '文本内容',
type: PROPS_TYPES.string,
},
},
};
export default AboutUs;
import { ComponentConfigTypes, PROPS_TYPES } from 'lingxi-editor-core';
const Advert: ComponentConfigTypes = {
propsConfig: {
componentType: {
label: '广告编辑',
type: PROPS_TYPES.advert
},
sliderList: {
label: '广告编辑',
type: PROPS_TYPES.carousel,
},
},
};
export default Advert;
import { ComponentConfigTypes, PROPS_TYPES } from 'lingxi-editor-core';
const Carousel: ComponentConfigTypes = {
propsConfig: {
sliderList: {
label: '轮播广告编辑',
type: PROPS_TYPES.carousel,
},
},
};
export default Carousel;
import { ComponentConfigTypes, PROPS_TYPES } from 'lingxi-editor-core';
const ChannelHeader: ComponentConfigTypes = {
propsConfig: {
children: {
label: '文本内容',
type: PROPS_TYPES.string,
},
},
};
export default ChannelHeader;
import { ComponentConfigTypes, PROPS_TYPES } from 'lingxi-editor-core';
const CommonTitle: ComponentConfigTypes = {
propsConfig: {
children: {
label: '文本内容',
type: PROPS_TYPES.string,
},
},
};
export default CommonTitle;
import { ComponentConfigTypes, PROPS_TYPES } from 'lingxi-editor-core';
const FindMore: ComponentConfigTypes = {
propsConfig: {
children: {
label: '文本内容',
type: PROPS_TYPES.string,
},
},
};
export default FindMore;
import { ComponentConfigTypes, NODE_PROPS_TYPES, PROPS_TYPES } from 'lingxi-editor-core';
const FloorLine: ComponentConfigTypes = {
nodePropsConfig: {
overflowedIndicator: {
type: NODE_PROPS_TYPES.reactNode,
isOnlyNode: true,
},
children: {
type: NODE_PROPS_TYPES.reactNode,
childNodesRule: [
'FloorLine.Brand',
'FloorLine.Shops',
'FloorLine.Goods',
'FloorLine.Category',
'FloorLine.FloorHeader',
'FloorLine.Banner',
'FloorLine.Vertical',
'FloorLine.Horizontal'],
},
},
propsConfig: {
},
};
const Brand: ComponentConfigTypes = {
propsConfig: {
componentType: {
label: '品牌推荐设置',
type: PROPS_TYPES.brand
},
dataList: {
label: '品牌展示编辑',
type: PROPS_TYPES.brand,
},
},
};
const Shops: ComponentConfigTypes = {
propsConfig: {
componentType: {
label: '店铺推荐设置',
type: PROPS_TYPES.shop
},
dataList: {
label: '店铺展示编辑',
type: PROPS_TYPES.shop,
},
},
};
const Goods: ComponentConfigTypes = {
propsConfig: {
componentType: {
label: '商品推荐设置',
type: PROPS_TYPES.goods
},
dataList: {
label: '商品展示编辑',
type: PROPS_TYPES.goods,
},
},
};
const Category: ComponentConfigTypes = {
propsConfig: {
componentType: {
label: '分类推荐设置',
type: PROPS_TYPES.category
},
dataList: {
label: '分类推荐设置',
type: PROPS_TYPES.category,
},
},
};
const FloorHeader: ComponentConfigTypes = {
propsConfig: {
dataList: {
label: '商品展示编辑',
type: PROPS_TYPES.goods,
},
},
};
const Banner: ComponentConfigTypes = {
propsConfig: {
componentType: {
label: '分类广告推荐设置',
type: PROPS_TYPES.categoryBanner
},
dataList: {
label: '分类广告推荐设置',
type: PROPS_TYPES.categoryBanner,
},
},
};
const Vertical: ComponentConfigTypes = {
propsConfig: {
dataList: {
label: '商品展示编辑',
type: PROPS_TYPES.goods,
},
},
};
const Horizontal: ComponentConfigTypes = {
propsConfig: {
dataList: {
label: '商品展示编辑',
type: PROPS_TYPES.goods,
},
},
};
export default {
FloorLine,
'FloorLine.Brand': Brand,
'FloorLine.Shops': Shops,
'FloorLine.Goods': Goods,
'FloorLine.Category': Category,
'FloorLine.FloorHeader': FloorHeader,
'FloorLine.Banner': Banner,
'FloorLine.Vertical': Vertical,
'FloorLine.Horizontal': Horizontal,
};
import { ComponentConfigTypes, PROPS_TYPES } from 'lingxi-editor-core';
const Footer: ComponentConfigTypes = {
propsConfig: {
children: {
label: '文本内容',
type: PROPS_TYPES.string,
},
},
};
export default Footer;
import { ComponentConfigTypes, PROPS_TYPES } from 'lingxi-editor-core';
const Header: ComponentConfigTypes = {
propsConfig: {
children: {
label: '文本内容',
type: PROPS_TYPES.string,
},
},
};
export default Header;
import { ComponentConfigTypes, PROPS_TYPES } from 'lingxi-editor-core';
const Infomation: ComponentConfigTypes = {
propsConfig: {
children: {
label: '文本内容',
type: PROPS_TYPES.string,
},
},
};
export default Infomation;
import { ComponentConfigTypes, PROPS_TYPES } from 'lingxi-editor-core';
const MainNav: ComponentConfigTypes = {
propsConfig: {
children: {
label: '文本内容',
type: PROPS_TYPES.string,
},
},
};
export default MainNav;
import { ComponentConfigTypes, PROPS_TYPES } from 'lingxi-editor-core';
const MallLayout: ComponentConfigTypes = {
propsConfig: {
children: {
label: '文本内容',
type: PROPS_TYPES.string,
},
},
};
export default MallLayout;
import { ComponentConfigTypes, PROPS_TYPES } from 'lingxi-editor-core';
const Navigation: ComponentConfigTypes = {
propsConfig: {
navList: {
label: '导航栏编辑',
type: PROPS_TYPES.navigation,
},
},
};
export default Navigation;
import { ComponentConfigTypes, PROPS_TYPES } from 'lingxi-editor-core';
const QuickNav: ComponentConfigTypes = {
propsConfig: {
children: {
label: '文本内容',
type: PROPS_TYPES.string,
},
},
};
export default QuickNav;
import { ComponentConfigTypes, NODE_PROPS_TYPES, PROPS_TYPES } from 'lingxi-editor-core';
const ShopFloorLine: ComponentConfigTypes = {
nodePropsConfig: {
overflowedIndicator: {
type: NODE_PROPS_TYPES.reactNode,
isOnlyNode: true,
},
children: {
type: NODE_PROPS_TYPES.reactNode,
childNodesRule: [
'ShopFloorLine.Category',
'ShopFloorLine.Goods',
]
},
},
propsConfig: {
},
};
const Goods: ComponentConfigTypes = {
propsConfig: {
componentType: {
label: '商品推荐设置',
type: PROPS_TYPES.goods
},
dataList: {
label: '商品展示编辑',
type: PROPS_TYPES.goods,
},
},
};
const Category: ComponentConfigTypes = {
propsConfig: {
componentType: {
label: '分类推荐设置',
type: PROPS_TYPES.category
},
dataList: {
label: '分类推荐设置',
type: PROPS_TYPES.category,
},
},
};
export default {
ShopFloorLine,
'ShopFloorLine.Category': Category,
'ShopFloorLine.Goods': Goods
};
import { ComponentConfigTypes, PROPS_TYPES } from 'lingxi-editor-core';
const ShopHeader: ComponentConfigTypes = {
propsConfig: {
children: {
label: '文本内容',
type: PROPS_TYPES.string,
},
},
};
export default ShopHeader;
import { ComponentConfigTypes, NODE_PROPS_TYPES, PROPS_TYPES } from 'lingxi-editor-core';
const ShowCase: ComponentConfigTypes = {
nodePropsConfig: {
overflowedIndicator: {
type: NODE_PROPS_TYPES.reactNode,
isOnlyNode: true,
},
children: {
type: NODE_PROPS_TYPES.reactNode,
childNodesRule: ['ShowCase.Brand', 'ShowCase.Shop', 'ShowCase.Goods'],
},
},
propsConfig: {
},
};
const Brand: ComponentConfigTypes = {
propsConfig: {
dataList: {
label: '品牌展示编辑',
type: PROPS_TYPES.brand,
},
},
};
const Shop: ComponentConfigTypes = {
propsConfig: {
dataList: {
label: '店铺展示编辑',
type: PROPS_TYPES.shop,
},
},
};
const Goods: ComponentConfigTypes = {
propsConfig: {
dataList: {
label: '商品展示编辑',
type: PROPS_TYPES.goods,
},
},
};
export default {
ShowCase,
'ShowCase.Brand': Brand,
'ShowCase.Shop': Shop,
'ShowCase.Goods': Goods,
};
import { ComponentConfigTypes, PROPS_TYPES } from 'lingxi-editor-core';
const TopBar: ComponentConfigTypes = {
propsConfig: {
children: {
label: '文本内容',
type: PROPS_TYPES.string,
},
},
};
export default TopBar;
import TopBar from './TopBar'
import Header from './Header';
import Navigation from './Navigation'
import Carousel from './Carousel'
import ShowCase from './ShowCase'
import MallLayout from './MallLayout'
import MainNav from './MainNav'
import Advert from './Advert'
import QuickNav from './QuickNav'
import FloorLine from './FloorLine'
import ShopFloorLine from './ShopFloorLine'
import AboutUs from './AboutUs'
import CommonTitle from './CommonTitle'
import ChannelHeader from './ChannelHeader'
import ShopHeader from './ShopHeader'
import FindMore from './FindMore'
import Information from './Information'
import Footer from './Footer'
export default {
TopBar,
Header,
Navigation,
Carousel,
MallLayout,
MainNav,
Advert,
QuickNav,
FindMore,
Information,
Footer,
AboutUs,
CommonTitle,
ChannelHeader,
ShopHeader,
...FloorLine,
...ShopFloorLine,
...ShowCase
}
import * as HTML from './HTML';
import LingXiUI from './LingXiUI';
export default {
...HTML,
...LingXiUI
};
import { CategoryType } from 'lingxi-editor-core';
export const htmlContainers: CategoryType = {
'HTMLTag': {
span: 24,
components: {
'div': null,
'a': null,
'span': null,
},
},
};
export const htmlNonContainers: CategoryType = {
'HTMLTag': {
components: {
'img': {
props: [{
style: { height: '100%' },
src: 'https://dss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=1191630624,1109312732&fm=26&gp=0.jpg',
}],
},
},
},
};
import { htmlContainers, htmlNonContainers } from './htmlCategory';
import { reactContainers, reactNonContainers } from './reactCategory';
// import * as Ants from 'antd/es';
import * as LingXiUI from 'lingxi-design-ui'
import AllComponentConfigs from './componentConfigs';
import { CategoryType, ConfigType } from 'lingxi-editor-core';
import { flattenDeepArray } from '../utils';
import { message } from 'antd';
const originalComponents = { ...LingXiUI }
/**
* 容器组件分类
*/
export const CONTAINER_CATEGORY = { ...reactContainers, ...htmlContainers };
/**
* 非容器组件分类
* @type {{Input, InputNumber, Slider, Checkbox, Rate, Radio, Icon, Typography}}
*/
export const NON_CONTAINER_CATEGORY = { ...reactNonContainers, ...htmlNonContainers };
/**
* 设计面板iframe 模板,如果集成到项目中,需要将拖拽组件所依赖的样式在模板中设置,
* 否则设计面板渲染的页面将是无样式的效果
*/
const config: ConfigType = {
OriginalComponents: originalComponents,
AllComponentConfigs,
containers: flattenDeepArray(CONTAINER_CATEGORY),
warn: (msg: string) => { message.warning(msg) }
};
export default config;
import { CategoryType } from 'lingxi-editor-core';
export const reactContainers: CategoryType = {
Customer: {
components: {
MallLayout: null,
'Header': {
props: [{
style: {
}
}]
},
'Navigation': {
props: [{
style: {
}
}]
},
}
},
'FloorLine': {
components: {
'FloorLine': {
props: [{
style: {
height: 100,
}
}]
},
'FloorLine.Brand': {
props: [{
style: {
height: 100,
}
}]
},
'FloorLine.Goods': null,
'FloorLine.Shops': null,
'FloorLine.Category': null,
'FloorLine.FloorHeader': null,
'FloorLine.Banner': null,
'FloorLine.Vertical': null,
'FloorLine.Horizontal': null,
}
},
'ShopFloorLine': {
components: {
'ShopFloorLine': {
props: [{
style: {
height: 100,
}
}]
},
'ShopFloorLine.Category': null,
'ShopFloorLine.Goods': null,
}
},
'ShowCase': {
components: {
'ShowCase': {
props: [{
style: {
height: 100,
}
}]
},
'ShowCase.Brand': {
props: [{
style: {
height: 100,
}
}]
},
'ShowCase.Goods': {
props: [{
style: {
height: 100,
}
}]
},
'ShowCase.Shop': {
props: [{
style: {
height: 100,
}
}]
}
},
},
};
export const reactNonContainers: CategoryType = {
Customer: {
components: {
TopBar: null,
MainNav: null,
Advert: null,
QuickNav: null,
FindMore: null,
Information: null,
Footer: null
}
}
}
import React, { useState } from 'react';
import { Tabs } from 'antd/lib/index';
import PropsSettings from './propsSettings';
import { ComponentConfigsType, SelectedInfoType, STATE_PROPS, useSelector } from 'lingxi-editor-core';
import get from 'lodash/get';
const { TabPane } = Tabs;
type SettingPanelType = {
selectedInfo: SelectedInfoType,
componentConfigs: ComponentConfigsType,
templateId: number,
type: 'channel' | 'shop'
}
const SettingPanel = (props) => {
const { selectedInfo, componentConfigs } = useSelector<SettingPanelType, STATE_PROPS>(['selectedInfo', 'componentConfigs'])
const [activeKey, setActiveKey] = useState('2');
const { props: selectProps, selectedKey } = selectedInfo || {}
const style = get(componentConfigs, [selectedKey, 'props', 'style'])
return <PropsSettings type={props.type} selectedInfo={selectedInfo} templateId={props.templateId} />
}
export default SettingPanel;
const options = [
{
title: 'Attention Seekers',
data: [
'bounce',
'flash',
'pulse',
'rubberBand',
'shake',
'swing',
'tada',
'wobble',
'jello',
'heartBeat',
],
},
{
title: 'Bouncing Entrances',
data: ['bounceIn', 'bounceInDown', 'bounceInLeft', 'bounceInRight', 'bounceInUp'],
},
{
title: 'Bouncing Exits',
data: [
'bounceOut',
'bounceOutDown',
'bounceOutLeft',
'bounceOutRight',
'bounceOutUp',
],
},
{
title: 'Fading Entrances',
data: [
'fadeIn',
'fadeInDown',
'fadeInDownBig',
'fadeInLeft',
'fadeInLeftBig',
'fadeInRight',
'fadeInRightBig',
'fadeInUp',
'fadeInUpBig',
],
},
{
title: 'Fading Exits',
data: [
'fadeOut',
'fadeOutDown',
'fadeOutDownBig',
'fadeOutLeft',
'fadeOutLeftBig',
'fadeOutRight',
'fadeOutRightBig',
'fadeOutUp',
'fadeOutUpBig',
],
},
{
title: 'Flippers',
data: [
'flip',
'flipInX',
'flipInY',
'flipOutX',
'flipOutY',
],
},
{
title: 'Lightspeed',
data: [
'lightSpeedIn',
'lightSpeedOut',
],
},
{
title: 'Rotating Entrances',
data: [
'rotateIn',
'rotateInDownLeft',
'rotateInDownRight',
'rotateInUpLeft',
'rotateInUpRight',
],
},
{
title: 'Rotating Exits',
data: [
'rotateOut',
'rotateOutDownLeft',
'rotateOutDownRight',
'rotateOutUpLeft',
'rotateOutUpRight',
],
},
{
title: 'Sliding Entrances',
data: [
'slideInUp',
'slideInDown',
'slideInLeft',
'slideInRight',
],
},
{
title: 'Sliding Exits',
data: [
'slideOutUp',
'slideOutDown',
'slideOutLeft',
'slideOutRight',
],
},
{
title: 'Zoom Entrances',
data: [
'zoomIn',
'zoomInDown',
'zoomInLeft',
'zoomInRight',
'zoomInUp',
],
},
{
title: 'Zoom Exits',
data: [
'zoomOut',
'zoomOutDown',
'zoomOutLeft',
'zoomOutRight',
'zoomOutUp',
],
},
{
title: 'Specials',
data: [
'hinge',
'jackInTheBox',
'rollIn',
'rollOut',
],
},
];
export default options;
.animate-wrap {
width: 100%;
height: 60px;
background: #fff;
outline: none;
position: relative;
list-style-type: none;
padding: 4px 0;
margin: 0;
text-align: left;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
background-clip: padding-box;
}
.select-box, .animate-btn {
height: 24px;
font-family: PingFang-SC-Regular;
font-size: 14px;
letter-spacing: 0.2px;
color: #555;
border-radius: 2px;
float: left
}
.select-box {
:global {
.ant-select-selection--single {
height: 24px;
}
.ant-select-selection__rendered {
line-height: 22px;
}
}
}
.animate-btn {
width: 100%;
color: #1979E9;
border: 1px solid #1979E9;
}
.animate-box {
font-size: 30px;
text-align: center;
color: #f35626;
background-image: linear-gradient(92deg, #f35626 0%, #feab3a 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transfather;
}
:global {
.ant-select-dropdown-menu-item-group-title {
color: rgba(0, 0, 0, 0.45);
padding: 0 12px;
height: 32px;
line-height: 32px;
font-size: 12px;
position: relative;
display: block;
white-space: nowrap;
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
transition: background 0.3s ease;
}
}
import React, { forwardRef, memo, useState } from 'react';
import map from 'lodash/map';
import { Button, Col, Dropdown, Row, TreeSelect } from 'antd';
import classNames from 'classnames';
import styles from './index.less';
import options from './config';
import { propsAreEqual } from '../../../utils';
const { TreeNode } = TreeSelect;
interface AnimatePropsType {
value: string,
onChange: (value: any) => void
}
function Animate(props: AnimatePropsType, ref: any) {
const { value, onChange } = props;
const [dropdownVisible, setDropdownVisible] = useState(false);
function handleChange(val: any) {
let animatedClass = val ? `${val} animated` : undefined;
onChange && onChange(animatedClass);
};
function renderAnimateBox(animateName: string) {
const animateClass = animateName ? `${animateName} animated` : '';
return (
<div className={styles['animate-wrap']}>
{/* //这里一定要加上key
//className是animate.css中的类名,显示不同动画 */}
<div key="amache" className={classNames(styles['animate-box'], animateClass)}>
Animate
</div>
</div>
);
};
return (
<Row gutter={10} className={styles['animate-component-warp']}>
<Col style={{ lineHeight: 0 }} span={14}>
<TreeSelect
showSearch
value={value ? value.split(' ')[0] : ''}
style={{ width: '100%' }}
dropdownStyle={{ maxHeight: 300, overflow: 'auto' }}
placeholder="请选择"
allowClear
treeDefaultExpandAll
dropdownMatchSelectWidth
className={styles['select-box']}
onChange={handleChange}
>
{
map(options, (optGroup) => (
<TreeNode value={optGroup.title} title={optGroup.title} key={optGroup.title} disabled>
{map(optGroup.data, option => (
<TreeNode value={option} title={option} key={option}/>
))}
</TreeNode>
),
)
}
</TreeSelect>
</Col>
<Col style={{ lineHeight: 0, position: 'relative' }} span={10} id='drop-down'>
<Dropdown visible={dropdownVisible}
getPopupContainer={(triggerNode: any) => triggerNode}
overlay={renderAnimateBox(value ? value.split(' ')[0] : '')}
placement="bottomRight">
<Button size={'small'} className={styles['animate-btn']} onClick={() => setDropdownVisible(!dropdownVisible)}>Animate
It</Button>
</Dropdown>
</Col>
</Row>
);
}
export default memo(forwardRef(Animate), propsAreEqual);
.advert_setting {
.advert_setting_title {
position: relative;
height: 32px;
line-height: 32px;
}
.advert_setting_line {
margin-bottom: 12px;
display: flex;
&_main {
position: relative;
flex: 1;
}
&_addItem {
flex: 1;
margin-top: 16px;
&_input {
width: 100%;
}
&_line {
display: flex;
margin-bottom: 16px;
&_label {
width: 80px;
}
&_brief {
flex: 1;
}
}
}
&_sort {
width: 32px;
background-color: #FAFBFC;
height: 32px;
line-height: 32px;
text-align: center;
margin-right: 8px;
}
&_name {
display: flex;
flex: 1;
height: 32px;
align-items: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
background-color: #FAFBFC;
padding-left: 12px;
&>.icon {
margin-left: auto;
cursor: pointer;
width: 32px;
height: 32px;
line-height: 32px;
}
}
&_operation {
&_btn {
margin-left: 16px;
}
}
}
.add_btn {
width: 100%;
border: 1px dashed #DFE1E6;
}
}
\ No newline at end of file
import React, { forwardRef, useState, useCallback } from 'react'
import { Button, Input } from 'antd'
import UploadImage from '@/components/UploadImage'
import { filterPropsFunction, getAdvertType } from '../../../../utils'
import SettingPanel from '../../../../components/SettingPanel'
import { message } from 'antd'
import { isEmpty } from '@formily/antd/esm/shared'
import { PublicApi } from '@/services/api'
import { clearSelectedStatus, changeProps } from 'lingxi-editor-core';
import { ArrowUpOutlined, DeleteOutlined, PlusOutlined, ArrowDownOutlined, CaretDownOutlined, CaretUpOutlined } from '@ant-design/icons'
import styles from './index.less'
interface advertItemType {
/**
* ID
*/
id: number;
/**
* 模板ID
*/
templateId: number;
/**
* 分类ID 当广告类型为四号广告时才有ID值
*/
categoryId: number;
/**
* 广告类型: 1.一号广告 2.二号广告 3.三号广告 4.四号广告
*/
type: number;
/**
* 广告名称
*/
name: string;
/**
* 广告图片
*/
picUrl: string;
/**
* 链接
*/
link: string;
/**
* 排序
*/
sort: number;
/**
* 创建时间
*/
createTime: number;
bgColor?: string;
expand?: boolean;
}
interface AdvertSettingPropsType {
advertList: advertItemType[];
onChange: Function;
type: 'top' | 'banner' | 'interact' | 'category';
templateid: number;
categoryid?: number;
templateType: 'channel' | 'shop'
}
const AdvertSetting: React.FC<AdvertSettingPropsType> = forwardRef((props, ref) => {
const { advertList = [], templateid, type, categoryid, templateType } = props
const [list, setList] = useState<advertItemType[]>(advertList)
const [confirmLoading, setConfirmLoading] = useState<boolean>(false)
const [newProps, setNewProps] = useState(props)
const getImgSize = () => {
switch (type) {
case 'top':
return "1920*70"
case 'banner':
return "1920*460"
case 'interact':
return "285*120"
case 'category':
return "500*90"
}
}
const changeNewProps = (key: string, data: any) => {
const newProps = filterPropsFunction(props)
newProps[key] = data
setNewProps(newProps)
}
const sortUp = (index: number, item: advertItemType) => {
let newList = JSON.parse(JSON.stringify(list))
let tempItem = JSON.parse(JSON.stringify(item))
let temp = newList[index - 1]
newList[index - 1] = item
newList[index - 1].sort = temp.sort
newList[index] = temp
newList[index].sort = tempItem.sort
setList(newList)
changeNewProps('advertList', newList)
}
const sortDown = (index: number, item: advertItemType) => {
let newList = JSON.parse(JSON.stringify(list))
let temp = newList[index + 1]
let tempItem = JSON.parse(JSON.stringify(item))
newList[index + 1] = item
newList[index + 1].sort = temp.sort
newList[index] = temp
newList[index].sort = tempItem.sort
setList(newList)
changeNewProps('advertList', newList)
}
const addSliderItem = () => {
let newList = JSON.parse(JSON.stringify(list))
let sort = 0
if (newList.length <= 0) {
sort = 1
} else {
sort = newList[newList.length - 1].sort + 1
}
let tempItem: any = {
templateId: Number(templateid),
type: getAdvertType(type),
name: '',
picUrl: '',
link: '',
sort,
expand: true
}
if (type === 'category') {
tempItem.categoryId = categoryid
}
newList.push(tempItem)
setList(newList)
changeNewProps('advertList', newList)
}
const handleDeleteItem = (index: number) => {
let newList = JSON.parse(JSON.stringify(list))
newList.splice(index, 1)
let sort = 1
newList.map(item => {
item.sort = sort
sort++
})
setList(newList)
changeNewProps('advertList', newList)
}
const handleExpand = (sort: number, state: boolean) => {
let newList = JSON.parse(JSON.stringify(list))
newList.map(item => {
if (item.sort === sort) {
item.expand = state
}
})
setList(newList)
}
const handleKeyChange = (value: string, sort: number, key: string) => {
let newList = JSON.parse(JSON.stringify(list))
newList.map(item => {
if (item.sort === sort) {
item[key] = value
}
})
setList(newList)
changeNewProps('advertList', newList)
}
const handleConfirmSave = useCallback((e: any) => {
e.preventDefault();
if (JSON.stringify(props) === JSON.stringify(newProps)) {
return
}
setConfirmLoading(true)
saveAdvert(newProps).then(() => {
changeProps({
props: newProps
})
clearSelectedStatus()
setConfirmLoading(false)
}).catch(() => {
setConfirmLoading(false)
})
}, [newProps])
/**
* 检查广告数据完整性
*/
const checkAdvertList = (dataList: any) => {
if (isEmpty(dataList)) {
message.destroy()
message.error('请至少添加一个广告')
return false
}
return dataList.every(item => {
message.destroy()
if (!item.name) {
message.error('请输入广告名称')
return false
} else if (!item.picUrl) {
message.error('请上传广告图片')
return false
} else {
return true
}
})
}
/**
* 保存广告
*/
const saveAdvert = (data: any) => {
return new Promise((resolve, reject) => {
const { advertList, type } = data
if (!checkAdvertList(advertList)) {
reject()
return
}
let param: any = {
templateId: templateid,
type: getAdvertType(type),
adverts: advertList
}
let postFn;
console.log(param)
if (templateType === 'channel') {
postFn = PublicApi.postTemplateChannelSaveAdvert
} else {
postFn = PublicApi.postTemplateShopSaveAdvert
}
postFn(param).then(res => {
if (res.code === 1000) {
resolve()
} else {
message.error(res.message)
reject()
}
})
})
}
return (
<SettingPanel
confirmLoading={confirmLoading}
onOK={handleConfirmSave}
>
<div className={styles.advert_setting}>
{
list && list.map((item, index) => (
<div className={styles.advert_setting_line} key={`advert_setting_${index}`}>
<div className={styles.advert_setting_line_sort}>{item.sort}</div>
<div className={styles.advert_setting_line_main}>
<div className={styles.advert_setting_line_name} onClick={() => handleExpand(item.sort, !item.expand)}>
<span>{item.name}</span>
{
!!item.expand ? <CaretUpOutlined className={styles.icon} /> : <CaretDownOutlined className={styles.icon} />
}
</div>
{
!!item.expand && (
<div className={styles.advert_setting_line_addItem}>
<div className={styles.advert_setting_line_addItem_line}>
<div className={styles.advert_setting_line_addItem_line_label}>名称</div>
<div className={styles.advert_setting_line_addItem_line_brief}>
<Input className={styles.advert_setting_line_addItem_input} value={item.name} onChange={(e) => handleKeyChange(e.target.value, item.sort, 'name')} maxLength={15} />
</div>
</div>
<div className={styles.advert_setting_line_addItem_line}>
<div className={styles.advert_setting_line_addItem_line_label}>图片</div>
<div className={styles.advert_setting_line_addItem_line_brief}>
<UploadImage
imgUrl={item.picUrl}
size={getImgSize()}
onChange={(val) => handleKeyChange(val, item.sort, 'picUrl')}
/>
</div>
</div>
<div className={styles.advert_setting_line_addItem_line}>
<div className={styles.advert_setting_line_addItem_line_label}>链接</div>
<div className={styles.advert_setting_line_addItem_line_brief}>
<Input className={styles.advert_setting_line_addItem_input} value={item.link} onChange={(e) => handleKeyChange(e.target.value, item.sort, 'link')} />
</div>
</div>
</div>
)
}
</div>
<div className={styles.advert_setting_line_operation}>
<Button disabled={index === 0} onClick={() => sortUp(index, item)} className={styles.advert_setting_line_operation_btn} icon={<ArrowUpOutlined />}></Button>
<Button disabled={index === list.length - 1} onClick={() => sortDown(index, item)} className={styles.advert_setting_line_operation_btn} icon={<ArrowDownOutlined />}></Button>
<Button className={styles.advert_setting_line_operation_btn} onClick={() => handleDeleteItem(index)} icon={<DeleteOutlined />}></Button>
</div>
</div>
))
}
<Button onClick={addSliderItem} className={styles.add_btn} icon={<PlusOutlined />}>添加广告</Button>
</div>
</SettingPanel>
)
})
export default AdvertSetting
\ No newline at end of file
import React from 'react'
import styles from './index.less'
interface BrandItemPropsType {
dataInfo: any
}
const BrandItem: React.FC<BrandItemPropsType> = (props) => {
const { dataInfo = {} } = props
return (
<div className={styles.brand_item}>
<div className={styles.brand_item_imgbox}>
<div className={styles.brand_item_logo}>
<img src={dataInfo.brandLogo} />
</div>
</div>
<div className={styles.brand_item_info}>
<div className={styles.brand_item_info_name}>{dataInfo.brandName}</div>
</div>
</div>
)
}
export default BrandItem
.brand_item {
display: flex;
flex-wrap: nowrap;
&_imgbox {
width: 120px;
height: 120px;
display: flex;
align-items: center;
.brand_item_logo {
width: 100%;
height: 60px;
overflow: hidden;
&>img {
display: block;
width: auto;
// height: 100%;
width: 100%;
}
}
}
&_info {
flex: 1;
width: 0;
display: flex;
align-items: center;
margin-left: 12px;
&_name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 14px;
color: #172B4D;
}
&_price {
color: #D32F2F;
font-size: 14px;
margin-top: 8px;
margin-bottom: 4px;
&>i {
font-size: 12px;
font-style: normal;
margin-right: 4px;
}
}
&_brief {
color: #6B778C;
margin-top: 2px;
}
}
}
.pagintion_wrap {
text-align: right;
margin-top: 24px;
}
\ No newline at end of file
import React, { useState, useEffect } from 'react'
import { clearSelectedStatus, changeProps } from 'lingxi-editor-core'
import { Row, Col, Button, Input, Pagination, Form, message } from 'antd'
import SettingList from '../../../../components/SettingList'
import BrandItem from './BrandItem'
import SettingPanel from '../../../../components/SettingPanel'
import { GetTemplatePlatformFindBrandListResponseDetail } from '@/services'
import { PublicApi } from '@/services/api'
import { filterProps } from '../../../../utils'
import styles from './index.less'
interface BrandItemType {
brandId: number;
brandLogo: string;
brandName: string;
}
interface ShopSettingPropsType {
templateid: number;
categoryid?: number;
brandList: BrandItemType[];
}
enum OPERATION_TYPE {
select = "select",
list = "list"
}
const BrandSetting: React.FC<ShopSettingPropsType> = (props) => {
const [form] = Form.useForm()
const [confirmLoading, setConfirmLoading] = useState<boolean>(false)
const [operationType, setOprationType] = useState<string>(OPERATION_TYPE.list) // 操作类型:list:显示已选列表;select:选择列表
const { templateid, categoryid, brandList = [] } = props
const [current, setCurrent] = useState<number>(1)
const [pageSize, setPageSize] = useState<number>(10)
const [noSelectBrands, setNoSelectBrands] = useState<GetTemplatePlatformFindBrandListResponseDetail[]>([])
const [selectList, setSelectList] = useState<any>([])
const [selectBrandList, setSelectBrandList] = useState<any>(brandList)
const [brandIds, setBrandIds] = useState<any>([])
const [totalCount, setTotalCount] = useState<number>(0)
const [filterParam, setFilterParam] = useState()
useEffect(() => {
fetchBrandsList()
}, [filterParam])
const fetchBrandsList = () => {
let param = {
type: 0,
templateId: templateid,
categoryId: categoryid,
current,
pageSize
}
if (filterParam) {
param = Object.assign(param, filterParam)
}
//@ts-ignore
PublicApi.getTemplatePlatformFindBrandList(param).then((res: any) => {
if (res.code === 1000) {
const dataInfo = res.data
setTotalCount(dataInfo.totalCount)
setNoSelectBrands(dataInfo.data || [])
}
})
}
const handleSelectItem = (goodItem: any) => {
const status = selectList.some((item: any) => item.brandId === goodItem.brandId)
if (status) {
setSelectList(selectList.filter(item => item.brandId !== goodItem.brandId))
setBrandIds(brandIds.filter(id => id !== goodItem.brandId))
} else {
setSelectList([...selectList, goodItem])
setBrandIds([...brandIds, goodItem.brandId])
}
}
const handleDeleteSelect = (goodItem: any) => {
setSelectBrandList(selectBrandList.filter(item => item.brandId !== goodItem.brandId))
}
const handlePageChange = (page: number, pageSize?: number | undefined) => {
setCurrent(page)
setPageSize(pageSize || 10)
fetchBrandsList()
}
const handleSearch = () => {
setCurrent(1)
let param = filterProps(form.getFieldsValue())
setFilterParam(param)
}
/**
* 确认
*/
const handleConfirm = () => {
if (operationType === OPERATION_TYPE.select) {
setSelectBrandList(selectList)
setOprationType(OPERATION_TYPE.list)
} else {
handleConfirmSave()
}
}
const handleConfirmSave = () => {
if (JSON.stringify(brandList) === JSON.stringify(selectBrandList)) {
return
}
if (selectBrandList.length <= 0) {
message.error("请选择要推荐的品牌")
return
}
setConfirmLoading(true)
saveBrand().then(() => {
changeProps({
props: {
templateid,
categoryid,
brandList: selectBrandList
}
})
clearSelectedStatus()
setConfirmLoading(false)
}).catch(() => {
setConfirmLoading(false)
})
}
const saveBrand = () => {
return new Promise((resolve, reject) => {
let param: any = {
templateId: templateid,
categoryId: categoryid,
}
let ids: number[] = []
for (let item of selectBrandList) {
ids.push(item.brandId)
}
param.brandIds = ids
PublicApi.postTemplatePlatformSaveBrand(param).then(res => {
if (res.code === 1000) {
resolve()
} else {
reject()
}
})
})
}
const handleChangeOprationType = (type: string) => {
setOprationType(type)
let keys: number[] = []
for (let item of selectBrandList) {
keys.push(item.brandId)
}
setBrandIds(keys)
setSelectList(selectBrandList)
}
return (
<SettingPanel
confirmLoading={confirmLoading}
onOK={() => handleConfirm()}
>
<div className={styles.goods_setting}>
<div className={styles.goods_setting_searchbar}>
<Form form={form}>
<Row>
<Col span={24}>
<Row gutter={[16, 16]} justify="space-between" style={{ marginBottom: 0 }}>
<Col span={6}>
{
operationType === 'list' ? (
<Button
type='primary'
onClick={() => handleChangeOprationType('select')}
>
选择品牌
</Button>
) : (
<Button
onClick={() => setOprationType('list')}
>
查看已选择品牌({selectBrandList.length})
</Button>
)
}
</Col>
{
operationType === 'select' && (
<Col span={18} style={{ display: 'flex', justifyContent: 'flex-end', padding: '0 8px' }}>
<Col>
<Form.Item
className={styles.mar_bot_0}
name="shopName"
>
<Input.Search
style={{ width: 240 }}
placeholder="品牌名称"
allowClear
onSearch={handleSearch}
/>
</Form.Item>
</Col>
</Col>
)
}
</Row>
</Col>
</Row>
</Form>
</div>
{
operationType === 'list' ? (
<>
<SettingList >
{
selectBrandList && selectBrandList.map(item => (
<SettingList.SettingItem onDelete={() => handleDeleteSelect(item)} selected={true} key={`setting_item_${item.brandId}`}>
<BrandItem dataInfo={item} />
</SettingList.SettingItem>
))
}
</SettingList>
</>
) : (
<>
<SettingList type="select">
{
(noSelectBrands && noSelectBrands.length > 0) && noSelectBrands.map(item => (
<SettingList.SettingItem onClick={() => handleSelectItem(item)} key={item.brandId} selected={brandIds.includes(item.brandId)}>
<BrandItem dataInfo={item} />
</SettingList.SettingItem>
))
}
</SettingList>
<div className={styles.pagintion_wrap}>
<Pagination
current={current}
total={totalCount}
showSizeChanger
showQuickJumper
onChange={handlePageChange}
showTotal={total => `共 ${totalCount} 条`}
/>
</div>
</>
)
}
</div>
</SettingPanel>
)
}
export default BrandSetting
.category_recommend {
&_title {
position: relative;
padding: 8px;
border-bottom: 1px solid #F4F5F7;
color: #42526E;
font-size: 14px;
&::before {
position: absolute;
content: '';
width: 2px;
height: 16px;
background-color: #00B37A;
top: 0;
bottom: 0;
left: 0;
margin: auto 0;
}
}
&_body {
padding: 32px 0;
.category_item {
margin-bottom: 24px;
}
}
}
\ No newline at end of file
import React, { useState, useEffect, useCallback } from 'react'
import { Checkbox, Row, Col, message } from 'antd'
import UploadImage from '@/components/UploadImage'
import SettingPanel from '../../../../components/SettingPanel'
import { clearSelectedStatus, changeProps } from 'lingxi-editor-core'
import { PublicApi } from '@/services/api'
import styles from './index.less'
interface categoryItemType {
categoryId: number,
categoryName: string
}
interface CategoryRecommendSettingPropsType {
categoryList: categoryItemType[];
categoryAdvertPicUrl: string;
onChange: Function;
templateid: number;
categoryid?: number;
templateType: 'channel' | 'shop'
}
const CategoryRecommendSetting: React.FC<CategoryRecommendSettingPropsType> = (props) => {
const { categoryList = [], categoryAdvertPicUrl = '', onChange, templateid, categoryid, templateType } = props
const [imgUrl, setImgUrl] = useState<string>(categoryAdvertPicUrl)
const [selectKeys, setSelectKeys] = useState<number[]>([])
const [categoryResponseList, setCategoryResponseList] = useState<any>([])
const [confirmLoading, setConfirmLoading] = useState<boolean>(false)
const [newProps, setNewProps] = useState(props)
const changeNewProps = (key: string, data: any) => {
const result = { ...newProps }
result[key] = data
setNewProps(result)
}
useEffect(() => {
fetchCategoryList()
initSelectKeys()
}, [])
const initSelectKeys = () => {
let result: number[] = []
for (let item of categoryList) {
result.push(item.categoryId)
}
setSelectKeys(result)
}
const fetchCategoryList = () => {
let param = {
categoryId: categoryid,
templateId: templateid
}
let getFn;
if (templateType === 'channel') {
getFn = PublicApi.getTemplateChannelFindCategoryList
} else {
getFn = PublicApi.getTemplateShopFindCategoryList
}
//@ts-ignore
getFn(param).then(res => {
const data = res.data
setCategoryResponseList(data.categoryBOList || [])
})
}
const handleSecondCategoryChange = (keys: any) => {
setSelectKeys(keys)
let result = categoryResponseList.filter(item => keys.includes(item.categoryId))
changeNewProps('categoryList', result)
}
const handleImgChange = (url: string) => {
setImgUrl(url)
changeNewProps('categoryAdvertPicUrl', url)
}
const handleConfirmSave = useCallback((e: any) => {
e.preventDefault();
if (JSON.stringify(props) === JSON.stringify(newProps)) {
return
}
setConfirmLoading(true)
saveCategory(newProps).then(() => {
changeProps({
props: newProps
})
clearSelectedStatus()
setConfirmLoading(false)
}).catch(() => {
setConfirmLoading(false)
})
}, [newProps])
const saveCategory = (data: any) => {
return new Promise((resolve, reject) => {
const { categoryAdvertPicUrl } = data
if (!categoryAdvertPicUrl) {
message.error('请上传品类广告图片')
reject()
return
}
let param = {
templateId: templateid,
categoryId: categoryid,
categoryAdvertPicUrl,
twoCategoryIds: selectKeys
}
let postFn;
if (templateType === 'channel') {
postFn = PublicApi.postTemplateChannelSaveCategory
} else {
postFn = PublicApi.postTemplateShopSaveCategory
}
//@ts-ignore
postFn(param).then(res => {
if (res.code === 1000) {
resolve()
} else {
reject()
}
})
})
}
return (
<SettingPanel
confirmLoading={confirmLoading}
onOK={handleConfirmSave}
>
<div className={styles.category_recommend}>
<div className={styles.category_recommend_title}>
<span>一级品类广告图片设置</span>
</div>
<div className={styles.category_recommend_body}>
<UploadImage
imgUrl={imgUrl}
size="157*517"
onChange={(val) => handleImgChange(val)}
/>
</div>
<div className={styles.category_recommend_title}>
<span>二级推荐分类设置</span>
</div>
<div className={styles.category_recommend_body}>
<Checkbox.Group value={selectKeys} style={{ width: '100%' }} onChange={handleSecondCategoryChange}>
<Row>
{
categoryResponseList.map((item, index) => (
<Col span={4} className={styles.category_item} key={`category_item_${index}`}>
<Checkbox value={item.categoryId}>{item.categoryName}</Checkbox>
</Col>
))
}
</Row>
</Checkbox.Group>
</div>
</div>
</SettingPanel>
)
}
export default CategoryRecommendSetting
import React from 'react'
import styles from './index.less'
interface GoodsItemPropsType {
dataInfo: any
}
const GoodsItem: React.FC<GoodsItemPropsType> = (props) => {
const { dataInfo = {} } = props
return (
<div className={styles.goods_item}>
<div className={styles.goods_item_imgbox}>
<img src={dataInfo.goodsPicUrl} />
</div>
<div className={styles.goods_item_info}>
<div className={styles.goods_item_info_name}>{dataInfo.goodsName}</div>
<div className={styles.goods_item_info_price}>
<i></i>
<span>{dataInfo.priceRange}</span>
</div>
<div className={styles.goods_item_info_brief}>
<span>品类:</span>
<span>{dataInfo.categoryName}</span>
</div>
<div className={styles.goods_item_info_brief}>
<span>品牌:</span>
<span>{dataInfo.brandName}</span>
</div>
</div>
</div>
)
}
export default GoodsItem
.goods_item {
display: flex;
flex-wrap: nowrap;
&_imgbox {
width: 120px;
height: 120px;
overflow: hidden;
&>img {
display: block;
width: 100%;
height: auto;
}
}
&_info {
flex: 1;
width: 0;
margin-left: 12px;
&_name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 14px;
color: #172B4D;
}
&_price {
color: #D32F2F;
font-size: 14px;
margin-top: 8px;
margin-bottom: 4px;
&>i {
font-size: 12px;
font-style: normal;
margin-right: 4px;
}
}
&_brief {
color: #6B778C;
margin-top: 2px;
}
}
}
.mar_bot_0 {
margin-bottom: 0;
}
.pagintion_wrap {
text-align: right;
margin-top: 24px;
}
\ No newline at end of file
import React from 'react'
import styles from './index.less'
interface ShopItemPropsType {
dataInfo: any
}
const ShopItem: React.FC<ShopItemPropsType> = (props) => {
const { dataInfo = {} } = props
return (
<div className={styles.shop_item}>
<div className={styles.shop_item_imgbox}>
<img src={dataInfo.shopLogo} />
</div>
<div className={styles.shop_item_info}>
<div className={styles.shop_item_info_name}>{dataInfo.shopName}</div>
</div>
</div>
)
}
export default ShopItem
.shop_item {
display: flex;
flex-wrap: nowrap;
&_imgbox {
width: 120px;
height: 120px;
overflow: hidden;
&>img {
display: block;
width: 100%;
height: auto;
}
}
&_info {
display: flex;
flex: 1;
width: 0;
margin-left: 12px;
align-items: center;
&_name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 14px;
color: #172B4D;
}
&_price {
color: #D32F2F;
font-size: 14px;
margin-top: 8px;
margin-bottom: 4px;
&>i {
font-size: 12px;
font-style: normal;
margin-right: 4px;
}
}
&_brief {
color: #6B778C;
margin-top: 2px;
}
}
}
.pagintion_wrap {
text-align: right;
margin-top: 24px;
}
\ No newline at end of file
import React, { useState, useEffect } from 'react'
import { clearSelectedStatus, changeProps } from 'lingxi-editor-core'
import { Row, Col, Button, Input, Pagination, Form, message } from 'antd'
import SettingList from '../../../../components/SettingList'
import ShopItem from './ShopItem'
import SettingPanel from '../../../../components/SettingPanel'
import { GetTemplatePlatformFindShopListResponseDetail } from '@/services'
import { PublicApi } from '@/services/api'
import { filterProps } from '../../../../utils'
import styles from './index.less'
interface ShopItemType {
shopId: number;
shopLogo: string;
shopName: string;
}
interface ShopSettingPropsType {
templateid: number;
categoryid?: number;
shopsList: ShopItemType[];
}
enum OPERATION_TYPE {
select = "select",
list = "list"
}
const ShopSetting: React.FC<ShopSettingPropsType> = (props) => {
const [form] = Form.useForm()
const [confirmLoading, setConfirmLoading] = useState<boolean>(false)
const [operationType, setOprationType] = useState<string>(OPERATION_TYPE.list) // 操作类型:list:显示已选列表;select:选择列表
const { templateid, categoryid, shopsList = [] } = props
const [current, setCurrent] = useState<number>(1)
const [pageSize, setPageSize] = useState<number>(10)
const [noSelectShops, setNoSelectShops] = useState<GetTemplatePlatformFindShopListResponseDetail[]>([])
const [selectList, setSelectList] = useState<any>([])
const [selectShopsList, setSelectShopsList] = useState<any>(shopsList)
const [shopsIds, setShopsIds] = useState<any>([])
const [totalCount, setTotalCount] = useState<number>(0)
const [filterParam, setFilterParam] = useState()
useEffect(() => {
fetchShopsList()
}, [filterParam])
const fetchShopsList = () => {
let param = {
type: 0,
templateId: templateid,
categoryId: categoryid,
current,
pageSize
}
if (filterParam) {
param = Object.assign(param, filterParam)
}
//@ts-ignore
PublicApi.getTemplatePlatformFindShopList(param).then((res: any) => {
if (res.code === 1000) {
const dataInfo = res.data
setTotalCount(dataInfo.totalCount)
setNoSelectShops(dataInfo.data || [])
}
})
}
const handleSelectItem = (goodItem: any) => {
const status = selectList.some((item: any) => item.shopId === goodItem.shopId)
if (status) {
setSelectList(selectList.filter(item => item.shopId !== goodItem.shopId))
setShopsIds(shopsIds.filter(id => id !== goodItem.shopId))
} else {
setSelectList([...selectList, goodItem])
setShopsIds([...shopsIds, goodItem.shopId])
}
}
const handleDeleteSelect = (goodItem: any) => {
setSelectShopsList(selectShopsList.filter(item => item.shopId !== goodItem.shopId))
}
const handlePageChange = (page: number, pageSize?: number | undefined) => {
setCurrent(page)
setPageSize(pageSize || 10)
fetchShopsList()
}
const handleSearch = () => {
setCurrent(1)
let param = filterProps(form.getFieldsValue())
setFilterParam(param)
}
/**
* 确认
*/
const handleConfirm = () => {
if (operationType === OPERATION_TYPE.select) {
setSelectShopsList(selectList)
setOprationType(OPERATION_TYPE.list)
} else {
handleConfirmSave()
}
}
const handleConfirmSave = () => {
if (JSON.stringify(shopsList) === JSON.stringify(selectShopsList)) {
return
}
if (selectShopsList.length <= 0) {
message.error("请选择要推荐的店铺")
return
}
setConfirmLoading(true)
saveShop().then(() => {
changeProps({
props: {
templateid,
categoryid,
shopsList: selectShopsList
}
})
clearSelectedStatus()
setConfirmLoading(false)
}).catch(() => {
setConfirmLoading(false)
})
}
const saveShop = () => {
return new Promise((resolve, reject) => {
let param: any = {
templateId: templateid,
categoryId: categoryid,
}
let ids: number[] = []
for (let item of selectShopsList) {
ids.push(item.shopId)
}
param.shopIds = ids
PublicApi.postTemplatePlatformSaveShop(param).then(res => {
if (res.code === 1000) {
resolve()
} else {
reject()
}
})
})
}
const handleChangeOprationType = (type: string) => {
setOprationType(type)
let keys: number[] = []
for (let item of selectShopsList) {
keys.push(item.shopId)
}
setShopsIds(keys)
setSelectList(selectShopsList)
}
return (
<SettingPanel
confirmLoading={confirmLoading}
onOK={() => handleConfirm()}
>
<div className={styles.goods_setting}>
<div className={styles.goods_setting_searchbar}>
<Form form={form}>
<Row>
<Col span={24}>
<Row gutter={[16, 16]} justify="space-between" style={{ marginBottom: 0 }}>
<Col span={6}>
{
operationType === 'list' ? (
<Button
type='primary'
onClick={() => handleChangeOprationType('select')}
>
选择店铺
</Button>
) : (
<Button
onClick={() => setOprationType('list')}
>
查看已选择店铺({selectShopsList.length})
</Button>
)
}
</Col>
{
operationType === 'select' && (
<Col span={18} style={{ display: 'flex', justifyContent: 'flex-end', padding: '0 8px' }}>
<Col>
<Form.Item
className={styles.mar_bot_0}
name="shopName"
>
<Input.Search
style={{ width: 240 }}
placeholder="店铺名称"
allowClear
onSearch={handleSearch}
/>
</Form.Item>
</Col>
</Col>
)
}
</Row>
</Col>
</Row>
</Form>
</div>
{
operationType === 'list' ? (
<>
<SettingList >
{
selectShopsList && selectShopsList.map(item => (
<SettingList.SettingItem onDelete={() => handleDeleteSelect(item)} selected={true} key={`setting_item_${item.shopId}`}>
<ShopItem dataInfo={item} />
</SettingList.SettingItem>
))
}
</SettingList>
</>
) : (
<>
<SettingList type="select">
{
(noSelectShops && noSelectShops.length > 0) && noSelectShops.map(item => (
<SettingList.SettingItem onClick={() => handleSelectItem(item)} key={item.shopId} selected={shopsIds.includes(item.shopId)}>
<ShopItem dataInfo={item} />
</SettingList.SettingItem>
))
}
</SettingList>
<div className={styles.pagintion_wrap}>
<Pagination
current={current}
total={totalCount}
showSizeChanger
showQuickJumper
onChange={handlePageChange}
showTotal={total => `共 ${totalCount} 条`}
/>
</div>
</>
)
}
</div>
</SettingPanel>
)
}
export default ShopSetting
import Animate from '../Animate';
import { TYPES_TO_COMPONENT as COMPONENT } from 'lingxi-web'
import { PROPS_TYPES } from 'lingxi-editor-core';
/**
* 默认属性
* @type {{className: {label: string, type: string}, animateClass: {label: string, type: string}}}
*/
export const DEFAULT_PROPS = {
// className: { label: '类名', type: PROPS_TYPES.stringArray },
// animateClass: { label: '动画', type: 'animate' },
};
/**
* 类型映射组件
*/
export const TYPES_TO_COMPONENT: any = {
...COMPONENT,
// animate: Animate,
};
@content-height: calc(100vh - 120px);
.divider {
border: 1px #bcbcbc dashed;
margin-bottom: 10px;
padding: 10px !important;
}
.children-container {
background: #ffffff;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 4px;
padding: 12px 12px 0;
min-height: 50px;
}
.main-container {
position: relative;
box-sizing: border-box;
height: calc(@content-height - 70px);
overflow: auto;
}
.form-container {
padding: 0 10px;
}
.btn-wrap {
background: #fff;
padding: 10px;
box-shadow: 1px 1px 1px rgba(206, 202, 202, 0.15)
}
.ant-drawer-body {
padding: 0 !important;
}
:global {
.card-container {
.ant-form-item {
margin-bottom: 5px !important;
}
.ant-form-item-label {
padding-bottom: 5px !important;
}
}
}
.sort-item {
box-shadow: 0 0 3px 0 rgba(0, 0, 0, 0.2);
border-radius: 4px;
background-color: #ffffff;
min-width: 92px;
height: 29px;
display: flex;
justify-content: center;
align-items: center;
font-size: 12px;
padding-left: 10px;
padding-right: 10px;
margin-left: 2px;
margin-right: 2px;
color: rgba(0, 0, 0, 0.65);
}
.multi-type {
display: flex;
align-items: center;
}
\ No newline at end of file
import React, { useState, useEffect } from 'react'
import { Drawer } from 'antd'
import GoodsSetting from './components/GoodsSetting'
import BrandSetting from './components/BrandSetting'
import ShopSetting from './components/ShopSetting'
import AdvertSetting from './components/AdvertSetting'
import CategoryRecommendSetting from './components/CategoryreCommendSetting'
import { SelectedInfoType, clearSelectedStatus, PROPS_TYPES } from 'lingxi-editor-core';
import './index.less'
import { PropTypes } from 'mobx-react'
interface PropsSettingsPropsType {
selectedInfo?: SelectedInfoType,
templateId: number,
type: 'channel' | 'shop'
}
const PropsSettings: React.FC<PropsSettingsPropsType> = (props) => {
const [drawerVisible, setDrawerVisible] = useState<boolean>(false)
const [drawerTitle, setDrawerTitle] = useState<string>('')
const [drawerWidth, setDrawerWidth] = useState<number>(800)
const { selectedInfo, templateId, type } = props
const onClose = () => {
setDrawerVisible(false)
clearSelectedStatus()
}
const renderDrawerComponent = () => {
const { props: initProps, propsConfig } = selectedInfo || {};
let componentType = propsConfig?.componentType
if (componentType) {
switch (componentType.type) {
case PROPS_TYPES.goods:
return <GoodsSetting templateid={templateId} {...initProps} templateType={type} />
case PROPS_TYPES.category:
return <CategoryRecommendSetting templateid={templateId} {...initProps} templateType={type} />
case PROPS_TYPES.categoryBanner:
case PROPS_TYPES.advert:
return <AdvertSetting templateid={templateId} {...initProps} templateType={type} />
}
}
return null
}
useEffect(() => {
const { propsConfig } = selectedInfo || {};
const componentType = propsConfig?.componentType
if (componentType) {
setDrawerTitle(componentType.label || '')
const typeList: any = [PROPS_TYPES.advert, PROPS_TYPES.categoryBanner, PROPS_TYPES.category]
if (typeList.includes(componentType.type)) {
setDrawerWidth(600)
} else {
setDrawerWidth(800)
}
setDrawerVisible(true)
} else {
setDrawerVisible(false)
}
}, [selectedInfo])
return (
<Drawer
// maskClosable={false}
title={drawerTitle}
placement="right"
onClose={onClose}
width={drawerWidth}
visible={drawerVisible}
>
{renderDrawerComponent()}
</Drawer>
)
}
export default PropsSettings
export const mallLayoutConfig = {
key: "0",
"0": {
"componentName": "MallLayout",
"props": {
"style": {
"width": "100%",
"minHeight": "100%",
"background": "#FFF"
}
},
"childNodes": ["1", "3", "4", "5", "6"]
},
}
export const topBarConfig = {
key: "1",
"1": {
"componentName": "TopBar",
"props": {
linkdisable: true
},
}
}
export const topAdvertConfig = {
key: "2",
"2": {
"componentName": "Advert",
"props": {
"type": "top",
"linkdisable": true,
"advertList": []
},
},
}
export const headerConfig = {
key: "3",
"3": {
"componentName": "ShopHeader",
"props": {},
},
}
export const mainNavConfig = {
key: "4",
"4": {
"componentName": "MainNav",
"props": {},
},
}
export const bannerAdvertConfig = {
key: "5",
"5": {
"componentName": "Advert",
"props": {
"type": "banner",
"linkdisable": true,
"advertList": []
}
},
}
export const CommonTitle1Config = {
key: "6",
"6": {
"componentName": "CommonTitle",
"props": {
"title": "热销商品",
"type": "primary"
},
},
}
export const serviceAdvertConfig = {
key: "16",
"16": {
"componentName": "Advert",
"props": {
"type": "service",
"linkdisable": true,
"advertList": []
}
},
}
export const CommonTitle2Config = {
key: "17",
"17": {
"componentName": "CommonTitle",
"props": {
"title": "关于我们",
"type": "primary"
},
},
}
export const AboutUsConfig = {
key: "18",
"18": {
"componentName": "AboutUs",
"props": {},
},
}
export const InformationConfig = {
key: '19',
"19": {
"componentName": "Information",
"props": {},
},
}
export const FooterConfig = {
key: '20',
"20": {
"componentName": "Footer",
"props": {},
},
}
export default {
"0": {
"componentName": "MallLayout",
"props": {
"style": {
"width": "100%",
"minHeight": "100%"
}
},
"childNodes": ["1", "2", "3", "4", "5", "7", "8", "18", "19", "20"]
},
"1": {
"componentName": "TopBar",
"props": {
linkdisable: true
},
},
"2": {
"componentName": "Advert",
"props": {
"type": "top",
"linkdisable": true,
"advertList": []
},
},
"3": {
"componentName": "Header",
"props": {},
},
"4": {
"componentName": "MainNav",
"props": {},
},
"5": {
"componentName": "Advert",
"props": {
"type": "banner",
"hasQuickNav": true,
"linkdisable": true,
"advertList": []
// "advertList": [
// {
// id: 1,
// templateId: 707,
// type: 2,
// name: '意大利进口荔枝纹牛皮会员价促销最低 9 折',
// picUrl: 'https://img.alicdn.com/imgextra/i4/81/O1CN01VT5ViO1CT8h4sY7qc_!!81-0-luban.jpg_q100.jpg_.webp',
// link: 'https://www.baidu.com',
// sort: 1,
// },
// {
// id: 2,
// templateId: 707,
// type: 2,
// name: '意大利进口荔枝纹牛皮会员价促销最低 9 折',
// picUrl: 'https://img.alicdn.com/tps/i4/TB1ICgYL.H1gK0jSZSySuttlpXa.jpg',
// link: 'https://www.baidu.com',
// sort: 2,
// }
// ]
}
},
"7": {
"componentName": "Advert",
"props": {
"type": "interact",
"linkdisable": true,
"advertList": []
},
},
"8": {
"componentName": "FloorLine",
"props": {},
"childNodes": ["9", "10"]
},
"9": {
"componentName": "FloorLine.Horizontal",
"props": {},
"childNodes": ["11", "12"]
},
"10": {
"componentName": "FloorLine.Brand",
"props": {},
},
"11": {
"componentName": "FloorLine.Category",
"props": {},
},
"12": {
"componentName": "FloorLine.Vertical",
"props": {},
"childNodes": ["13", "15"]
},
"13": {
"componentName": "FloorLine.FloorHeader",
"props": {},
"childNodes": ["14"]
},
"14": {
"componentName": "FloorLine.Banner",
"props": {
"type": "category",
},
},
"15": {
"componentName": "FloorLine.Horizontal",
"props": {},
"childNodes": ["16", "17"]
},
"16": {
"componentName": "FloorLine.Goods",
"props": {},
},
"17": {
"componentName": "FloorLine.Shops",
"props": {},
},
"18": {
"componentName": "FindMore",
"props": {},
},
"19": {
"componentName": "Information",
"props": {},
},
"20": {
"componentName": "Footer",
"props": {},
},
}
\ No newline at end of file
@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;
}
.canvas-container {
display: flex;
flex: 1;
justify-content: center;
background-color: #F5F5F5;
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: 16px;
font-weight: bold;
}
}
\ No newline at end of file
import React, { useEffect, useState } from 'react'
import { LegoProvider } from 'lingxi-editor-core'
import ToolBar from '../components/toolBar'
import DesignPanel from '../components/DesignPanel'
import SettingPanel from '../settingsPanel'
import config from '../configs'
import { topBarConfig, topAdvertConfig, headerConfig, mainNavConfig, bannerAdvertConfig, CommonTitle1Config, mallLayoutConfig, serviceAdvertConfig, CommonTitle2Config, AboutUsConfig, InformationConfig, FooterConfig } from './defaultData'
import Loading from './loading'
import { PublicApi } from '@/services/api'
import styles from './index.less'
interface ShopEditPropsType {
location: {
query: {
/**
* 模板id
*/
id: number;
/**
* 模板名称
*/
template: string;
}
}
}
let TemplateList = ['science']
const ShopEdit: React.FC<ShopEditPropsType> = (props) => {
const { query: { id, template } } = props.location
const [loading, setLoading] = useState<boolean>(true)
const [theme, setTheme] = useState<string>('theme-shop-science')
const [componentConfigs, setComponentConfigs] = useState({})
useEffect(() => {
if (!TemplateList.includes(template)) {
setTheme(`theme-shop-${TemplateList[0]}`)
} else {
setTheme(`theme-shop-${template}`)
}
getComponentsConfig()
}, [])
const findFirstAdvertsByType = () => {
return new Promise((resolve) => {
let params = {
templateId: id,
type: 1
}
//@ts-ignore
PublicApi.getTemplateShopFindAdvertsByType(params).then(res => {
if (res.code === 1000) {
resolve(res.data)
} else {
resolve([])
}
})
})
}
const findSecondAdvertsByType = () => {
return new Promise((resolve) => {
let params = {
templateId: id,
type: 2
}
//@ts-ignore
PublicApi.getTemplateShopFindAdvertsByType(params).then(res => {
if (res.code === 1000) {
resolve(res.data)
} else {
resolve([])
}
})
})
}
/**
* 获取所有一级品类信息
*/
const fetchFirstCategory = () => {
return new Promise((resolve) => {
PublicApi.getTemplateShopFindAllFirstCategory().then(res => {
resolve(res.data)
})
})
}
/**
* 获取一级品类详细信息
*/
const fetchCategoryById = (categoryId) => {
return new Promise((resolve) => {
let param = {
templateId: id,
categoryId
}
// @ts-ignore
PublicApi.getTemplateShopFindFirstCategoryDetail(param).then(res => {
resolve(res.data)
})
})
}
const getComponentsConfig = async () => {
// 一号位广告
bannerAdvertConfig[bannerAdvertConfig.key].props.advertList = await findFirstAdvertsByType()
// 二号位广告
serviceAdvertConfig[serviceAdvertConfig.key].props.advertList = await findSecondAdvertsByType()
let initIndex = 100
let floorLineConfig: any = {}
let floorLineKeys: any = []
let firstCategory: any = await fetchFirstCategory()
for (let item of firstCategory) {
let categoryDetail: any = await fetchCategoryById(item.id)
let floorLineConfigItem = {}
floorLineKeys.push(String(initIndex + 1))
let FloorLine = {
[String(initIndex + 1)]: {
"componentName": "ShopFloorLine",
"props": {
title: item.name
},
"childNodes": [String(initIndex + 2), String(initIndex + 3)]
}
}
let Category = {
[String(initIndex + 2)]: {
"componentName": "ShopFloorLine.Category",
"props": {
categoryAdvertPicUrl: categoryDetail.categoryAdvertPicUrl,
categoryid: item.id,
categoryList: categoryDetail.categoryBOList
},
},
}
let Goods = {
[String(initIndex + 3)]: {
"componentName": "ShopFloorLine.Goods",
"props": {
linkdisable: true,
categoryid: item.id,
goodsList: categoryDetail.goodsBOList
},
},
}
floorLineConfigItem = { ...FloorLine, ...Category, ...Goods }
floorLineConfig = { ...floorLineConfig, ...floorLineConfigItem }
initIndex += 100
}
mallLayoutConfig["0"].childNodes = [...mallLayoutConfig["0"].childNodes, ...floorLineKeys, serviceAdvertConfig.key, CommonTitle2Config.key, AboutUsConfig.key, InformationConfig.key, FooterConfig.key]
let config = {
...mallLayoutConfig,
...topBarConfig,
...topAdvertConfig,
...headerConfig,
...mainNavConfig,
...bannerAdvertConfig,
...CommonTitle1Config,
...floorLineConfig,
...serviceAdvertConfig,
...CommonTitle2Config,
...AboutUsConfig,
...InformationConfig,
...FooterConfig
}
console.log(config)
setComponentConfigs(config)
setLoading(false)
}
return !loading ? (
<LegoProvider initState={{ componentConfigs: componentConfigs }} config={config}>
<div className={styles['wrapper']}>
<ToolBar />
<div className={styles['content']}>
<div className={styles['canvas-container']}>
<DesignPanel theme={theme} />
</div>
</div>
</div>
<SettingPanel templateId={id} type="shop" />
</LegoProvider>
) : <Loading />
}
export default ShopEdit
\ No newline at end of file
import React from 'react'
import { Spin } from 'antd'
import styles from './index.less'
const Loading: React.FC = () => {
return (
<div className={styles.loading_wrap}>
<Spin size="large" />
<p className={styles.loading_text}>正在加载页面数据...</p>
</div>
)
}
export default Loading
import { useEffect, useRef } from 'react';
import each from 'lodash/each';
import get from 'lodash/get';
import isEmpty from 'lodash/isEmpty';
import isObject from 'lodash/isObject';
import isUndefined from 'lodash/isUndefined';
import flattenDeep from 'lodash/flattenDeep';
import map from 'lodash/map';
import isEqual from 'lodash/isEqual';
import keys from 'lodash/keys';
import { CategoryType, PROPS_TYPES } from 'lingxi-editor-core';
export const SPECIAL_STRING_CONSTANTS: any = {
'null': null,
};
export const formatSpecialProps = (props: any, propsConfig: any) => {
const nextProps = props;
each(props, (v, k) => {
if (get(propsConfig, k)) {
if (!isObject(v)) {
if (SPECIAL_STRING_CONSTANTS[v] !== undefined) {
nextProps[k] = SPECIAL_STRING_CONSTANTS[v];
} else if (propsConfig[k].type === PROPS_TYPES.function) {
const funcTemplate = get(propsConfig, `${k}.placeholder`);
if (funcTemplate) {
nextProps[k] = () => eval(funcTemplate);
} else {
nextProps[k] = () => {
};
}
}
} else if (isObject(v) && !isEmpty(propsConfig[k].childPropsConfig) && isEqual(keys(v), keys(propsConfig[k].childPropsConfig))) {
formatSpecialProps(v, propsConfig[k].childPropsConfig);
}
} else if (isUndefined(v)) {
delete nextProps[k];
}
});
return nextProps;
};
/**
* 用于获取组件名字数组
* @param data
* @returns {Array}
*/
export function flattenDeepArray(data: CategoryType) {
return flattenDeep(map(data, (v, k) => {
if (v && v.components) return map(v.components, (_, subK) => subK);
return k;
}));
}
/**
* 过滤掉值为undefined的字段
* @param value
* @returns {undefined}
*/
export const filterProps = (value: any) => {
const props: any = {};
each(value, (v, k) => {
if (v !== undefined) {
props[k] = v;
}
});
return isEmpty(props) ? undefined : props;
};
/**
* 过滤掉值为函数的字段
* @param value
* @returns {undefined}
*/
export const filterPropsFunction = (value: any) => {
const props: any = {};
each(value, (v, k) => {
if (typeof v !== 'function') {
props[k] = v;
}
});
return isEmpty(props) ? undefined : props;
};
/**
* 获取字段在props中的位置
* @param fieldConfigLocation
* @returns {string}
*/
export const getFieldInPropsLocation = (fieldConfigLocation: string) => {
return fieldConfigLocation.split('.').filter(location => location !== 'childPropsConfig').join('.');
};
/**
* 格式化字段在属性配置中的路径
* @param type
* @param field
* @param fatherFieldLocation
* @param tabIndex
* @returns {string|string}
*/
export const formatPropsFieldConfigLocation = (type: PROPS_TYPES, field: string, fatherFieldLocation: string, tabIndex?: number) => {
let fieldConfigLocation = fatherFieldLocation ? `${fatherFieldLocation}.` : '';
if (type === PROPS_TYPES.object) {
fieldConfigLocation = `${fieldConfigLocation}${field}.childPropsConfig`;
} else if (type === PROPS_TYPES.objectArray) {
fieldConfigLocation = `${fieldConfigLocation}${field}.childPropsConfig${
tabIndex !== undefined ? `.[${tabIndex}]` : ''
}`;
}
return fieldConfigLocation;
};
/**
* form 方法受控组件减少不必要渲染
* @param prevProps
* @param nextProps
*/
export const propsAreEqual = (prevProps: any, nextProps: any) => isEqual(prevProps.value, nextProps.value);
/**
* 处理父级属性与组件属性的异同
*/
export const diffProps = (restProps: any, props: any) => {
each(restProps, (v, k) => {
if (!isEqual(props[k], v)) {
props[k] = v;
}
});
return props;
};
export function usePrevious<T>(value: any) {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
/**
* 获取广告类型对应编号
*/
export const getAdvertType = (type) => {
switch (type) {
case 'banner':
return 1
case 'service':
case 'interact':
return 2
}
}
\ No newline at end of file
......@@ -3,6 +3,7 @@
.channel_index {
padding-bottom: 60px;
background-color: #FFF;
}
......@@ -22,137 +23,6 @@
}
}
.channel_floor_title {
&_split {
width: 527px;
height: 4px;
background: rgba(245, 245, 245, 1);
}
&_text {
font-size: 24px;
margin: 0 20px;
&>.primary {
color: #D32F2F;
margin-left: 3px;
}
}
.channel_floor_container {
width: 1200px;
margin: 0 auto;
display: flex;
align-items: center;
padding-top: 40px;
}
}
.channel_info_about {
padding-top: 36px;
&_container {
width: 1200px;
margin: 0 auto;
position: relative;
display: flex;
.channel_info_about_img {
width: 600px;
&_list {
position: relative;
height: 460px;
// background-color: cyan;
}
.channel_info_about_img_item {
&_body {
.channel_info_about_img_item_img {
width: 600px;
height: 400px;
background-repeat: no-repeat;
background-position: center center;
background-size: 100% auto;
}
}
}
:global {
.ant-carousel .slick-dots li button {
background: rgba(0, 0, 0, 0.2);
}
.ant-carousel .slick-dots li {
&.slick-active {
button {
background: rgba(0, 0, 0, 1);
}
}
}
.ant-carousel .slick-dots li {
width: 30px;
}
}
}
.channel_info_about_info {
position: relative;
margin-left: 55px;
flex: 1;
width: 0;
padding-right: 68px;
&_tabs {
display: flex;
&_item {
width: 100px;
height: 36px;
line-height: 34px;
text-align: center;
border: 1px solid #FFF;
margin-right: 20px;
cursor: pointer;
&.active {
border: 1px solid rgba(238, 238, 238, 1);
}
}
}
&_title {
font-size: 24px;
font-weight: 500;
margin-top: 48px;
margin-bottom: 20px;
}
&_brief {
color: #666666;
}
&_more {
margin-top: 25px;
&>span {
cursor: pointer;
margin-right: 8px;
}
}
}
}
}
.interact_list {
display: flex;
width: 1200px;
......
import React, { Fragment, useEffect, useMemo, useState } from 'react'
import { Carousel } from 'antd'
import { ArrowRightOutlined } from '@ant-design/icons'
import ShopFloorLine from '../components/ShopFloorLine'
import React, { useEffect, useMemo, useState } from 'react'
import Information from '../components/Information'
import FloorAnchor from '../components/FloorAnchor'
import { Advert } from 'lingxi-design-ui'
import cx from 'classnames'
import companyImg from '@/assets/imgs/company_img.png'
import CommonTitle from '../components/CommonTitle'
import { Advert, ShopFloorLine } from 'lingxi-design-ui'
import AboutUs from '../components/AboutUs'
import { inject, observer } from 'mobx-react'
import { PublicApi } from '@/services/api'
import Loading from '../components/Loading'
import { GetTemplatePlatformFindAllFirstCategoryResponse } from '@/services'
import styles from './index.less'
const ChannelIndex: React.FC = () => {
const [aboutTab, setAboutTab] = useState<number>(1) //1:公司简介 2:资质荣誉
interface ChannelIndexPropsType {
SiteStore?: any;
}
const ChannelIndex: React.FC<ChannelIndexPropsType> = (props) => {
const { channelTemplateId } = props.SiteStore
const [categoryList, setCategoryList] = useState<GetTemplatePlatformFindAllFirstCategoryResponse>([])
const [firstAdvertList, setFirstAdvertList] = useState([])
const [secondAdvertList, setSecondAdvertList] = useState([])
const [categoryComponents, setCategoryComponents] = useState<React.ReactNode>()
const category_list = [
{
id: 1,
title: '灯饰照明'
},
{
id: 2,
title: '家装建材'
},
{
id: 3,
title: '电工电气'
},
]
useEffect(() => {
getCategoryComponents()
findFirstAdvertsByType()
findSecondAdvertsByType()
}, [])
let data: any = [
{
id: 1,
picUrl: 'https://img.alicdn.com/tps/i4/TB1ICgYL.H1gK0jSZSySuttlpXa.jpg'
},
{
id: 2,
picUrl: 'https://img.alicdn.com/imgextra/i4/81/O1CN01VT5ViO1CT8h4sY7qc_!!81-0-luban.jpg'
const findFirstAdvertsByType = () => {
let params = {
templateId: channelTemplateId,
type: 1
}
]
//@ts-ignore
PublicApi.getTemplateChannelFindAdvertsByType(params).then(res => {
if (res.code === 1000) {
setFirstAdvertList(res.data)
}
})
}
const handleChangeAboutTab = (tab: number) => {
if (aboutTab !== tab) {
setAboutTab(tab)
const findSecondAdvertsByType = () => {
let params = {
templateId: channelTemplateId,
type: 2
}
//@ts-ignore
PublicApi.getTemplateChannelFindAdvertsByType(params).then(res => {
if (res.code === 1000) {
setSecondAdvertList(res.data)
}
})
}
/**
* 获取所有一级品类信息
*/
const fetchFirstCategory = () => {
return new Promise((resolve) => {
PublicApi.getTemplateChannelFindAllFirstCategory().then(res => {
if (res.code === 1000) {
setCategoryList(res.data)
resolve(res.data)
}
})
})
}
/**
* 获取一级品类详细信息
*/
const fetchCategoryById = (categoryId) => {
return new Promise((resolve) => {
let param = {
templateId: channelTemplateId,
categoryId
}
// @ts-ignore
PublicApi.getTemplateChannelFindFirstCategoryDetail(param).then(res => {
resolve(res.data)
})
})
}
const getCategoryComponents = async () => {
let result = []
let firstCategory: any = await fetchFirstCategory()
for (let item of firstCategory) {
let categoryDetail: any = await fetchCategoryById(item.id)
result.push(
<ShopFloorLine
anchor={`floorline_${item.id}`}
key={item.id}
title={item.name}
>
<ShopFloorLine.Category categoryAdvertPicUrl={categoryDetail.categoryAdvertPicUrl} categoryList={categoryDetail.categoryBOList} />
<ShopFloorLine.Goods goodsList={categoryDetail.goodsBOList} />
</ShopFloorLine>
)
}
setCategoryComponents(result)
}
return (
<div className={styles.channel_index}>
<div className={styles.banner}>
{
useMemo(() => <Advert type="banner" advertList={data} />, [])
useMemo(() => <Advert type="banner" advertList={firstAdvertList} hasQuickNav={false} />, [firstAdvertList])
}
{/*
<Carousel className="banner_list" autoplay pauseOnDotsHover>
<div className="banner_list_item" >
<div className="banner_list_item_body" style={{ backgroundColor: 'rgb(193, 193, 195)' }}>
<a href="">
<div className="banner_list_item_img" style={{ background: `url(https://img.alicdn.com/tps/i4/TB1ICgYL.H1gK0jSZSySuttlpXa.jpg) center center no-repeat` }}></div>
</a>
</div>
</div>
<div className="banner_list_item" >
<div className="banner_list_item_body" style={{ backgroundColor: 'rgb(170, 0, 239)' }}>
<a href="">
<div className="banner_list_item_img" style={{ background: `url(https://img.alicdn.com/imgextra/i4/81/O1CN01VT5ViO1CT8h4sY7qc_!!81-0-luban.jpg_q100.jpg_.webp) center center no-repeat` }}></div>
</a>
</div>
</div>
</Carousel>
*/}
</div>
<FloorAnchor anchorList={category_list} type="shop" />
<div className={styles.channel_floor_title}>
<div className={styles.channel_floor_container}>
<div className={styles.channel_floor_title_split}></div>
<div className={styles.channel_floor_title_text}>
<span>热销</span>
<span className={styles.primary}>商品</span>
</div>
<div className={styles.channel_floor_title_split}></div>
</div>
</div>
<FloorAnchor anchorList={categoryList} type="shop" />
<CommonTitle title="热销商品" type="primary" />
{
category_list.map(item => (
<ShopFloorLine
anchor={`floorline_${item.id}`}
key={item.id}
title={item.title}
>
<ShopFloorLine.Category />
<ShopFloorLine.Goods />
</ShopFloorLine>
))
categoryComponents ? categoryComponents : <Loading />
}
<div className={styles.channel_floor_title}>
<div className={styles.channel_floor_container}>
<div className={styles.channel_floor_title_split}></div>
<div className={styles.channel_floor_title_text}>
<span>关于</span>
<span className={styles.primary}>我们</span>
</div>
<div className={styles.channel_floor_title_split}></div>
</div>
</div>
<div className={styles.channel_info_about}>
<div className={styles.channel_info_about_container}>
<div className={styles.channel_info_about_img}>
<Carousel className={styles.channel_info_about_img_list} pauseOnDotsHover>
<div className={styles.channel_info_about_img_item} >
<div className={styles.channel_info_about_img_item_body}>
<a href="">
<div className={styles.channel_info_about_img_item_img} style={{ backgroundImage: `url(${companyImg})` }}></div>
</a>
</div>
</div>
<div className={styles.channel_info_about_img_item} >
<div className={styles.channel_info_about_img_item_body}>
<a href="">
<div className={styles.channel_info_about_img_item_img} style={{ backgroundImage: `url(${companyImg})` }}></div>
</a>
</div>
</div>
</Carousel>
</div>
<div className={styles.channel_info_about_info}>
<div className={styles.channel_info_about_info_tabs}>
<div className={cx(styles.channel_info_about_info_tabs_item, aboutTab === 1 ? styles.active : '')} onClick={() => handleChangeAboutTab(1)}>公司简介</div>
<div className={cx(styles.channel_info_about_info_tabs_item, aboutTab === 2 ? styles.active : '')} onClick={() => handleChangeAboutTab(2)}>资质荣誉</div>
</div>
<div className={styles.channel_info_about_info_title}>温州市龙昌皮具有限公司</div>
<p className={styles.channel_info_about_info_brief}>
公司位于温州,多年行业经验,专业经营各种成品真皮,包括全粒面牛皮,修面皮,漆色皮,打腊皮,水腊皮……二层皮胚,硅胶二层等,产品主要用于时尚女鞋、男鞋、箱包、皮带、服装、汽车内饰等高端皮具制品行业。
本着质量保证,品种多样,现货充足,款式新颖的经营特色和薄利多销的原则,为客户提供长期优质的服务!
</p>
<p>
同时公司长期寻找各皮厂合作,要求皮厂有自已的生产线及高性价比特色产品,可作为普通供应商合作,也可作为其核心经销商战略合作。欢迎来人来样,当面洽谈。
</p>
<div className={styles.channel_info_about_info_more}>
<span>查看更多</span>
<ArrowRightOutlined />
</div>
</div>
</div>
</div>
<Advert type="service" advertList={secondAdvertList} />
<CommonTitle title="关于我们" type="primary" />
<AboutUs />
<Information />
</div >
)
}
export default ChannelIndex
export default inject('SiteStore')(observer(ChannelIndex))
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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