Commit c00b230d authored by GuanHua's avatar GuanHua

feat:店铺信息页面和店铺装修模板页面

parent 1b23a0d7
import { default as CommodityRoute } from './commodityRoute'
import { default as MemberRoute } from './memberRoute'
import CommodityRoute from './commodityRoute'
import MemberRoute from './memberRoute'
import ShopRoute from './shopRoute'
const routes = [CommodityRoute, MemberRoute]
const routes = [CommodityRoute, MemberRoute, ShopRoute]
export default routes
\ No newline at end of file
/*
* 会员能力路由
* @Author: ghua
* @Date: 2020-07-10 16:15:28
* @Last Modified by: ghua
* @Last Modified time: 2020-07-10 16:15:28
*/
const MemberRoute = {
// 会员能力
path: '/memberAbility',
name: 'memberAbility',
key: 'memberAbility',
......
/*
* 店铺能力路由
* @Author: ghua
* @Date: 2020-07-10 16:16:37
* @Last Modified by: ghua
* @Last Modified time: 2020-07-10 16:48:50
*/
const ShopRoute = {
path: '/shopAbility',
name: 'shopAbility',
key: 'shopAbility',
icon: 'smile',
routes: [
{
path: '/shopAbility/infoManage',
name: 'shopInfoManage',
key: 'shopInfoManage',
component: '@/pages/shop/shopInfo',
},
{
path: '/shopAbility/template',
name: 'shopTemplate',
key: 'shopTemplate',
component: '@/pages/shop/shopTemplate',
},
{
path: '/shopAbility/template/detail',
name: 'shopTemplate',
key: 'shopTemplate',
hideInMenu: true,
component: '@/pages/shop/templateDetail',
}
]
}
export default ShopRoute
\ No newline at end of file
.require_item {
display: inline-block;
width: 180px;
color: #6B778C;
&.require {
&::after {
display: inline-block;
margin-left: 4px;
color: #ff4d4f;
font-size: 14px;
font-family: SimSun, sans-serif;
line-height: 1;
content: '*';
}
}
}
\ No newline at end of file
import React from 'react'
import cx from 'classnames'
import styles from './index.less'
interface RequireItemPropsType {
label: string;
isRequire?: boolean;
}
const RequireItem: React.FC<RequireItemPropsType> = (props) => {
const { label, isRequire = false } = props
return <label className={cx(styles.require_item, isRequire ? styles.require : '')}>{label}</label>
}
export default RequireItem
\ No newline at end of file
......@@ -51,6 +51,9 @@ const BasicLayout: React.FC<BasicLayoutProps> = (props) => {
const handleMenuCollapse = (payload: boolean): void => {
setCollapsed(payload)
if (payload) {
setOpenKeys([])
}
console.log('点击了缩起', payload)
};
......@@ -71,11 +74,19 @@ const BasicLayout: React.FC<BasicLayoutProps> = (props) => {
const menuRouter = getMenuRouter(menuData, location.pathname)
if (menuRouter && menuRouter.children) {
useEffect(() => {
setOpenKeys(initOpendKeys(menuRouter.children))
}, [location.pathname])
}
useEffect(() => {
if (menuRouter && menuRouter.children) {
if (collapsed) {
setOpenKeys([])
} else {
setOpenKeys(initOpendKeys(menuRouter.children))
}
} else {
setOpenKeys([])
}
}, [location.pathname, collapsed])
return (
<ProLayout
......
......@@ -60,7 +60,7 @@ const MenuSlider: React.FC<MenuSliderProps> = (props) => {
}
const menuRouter = getMenuRouter(menuData, pathname)
console.log(menuRouter, "menuRouter")
if (menuRouter && menuRouter.children) {
menuItemsCache = getMenus(menuRouter.children)
}
......@@ -74,17 +74,14 @@ const MenuSlider: React.FC<MenuSliderProps> = (props) => {
<Sider theme="light" collapsed={props.collapseState}>
<div className="logo">LOGOLOGOGOG</div>
<div className="menuTitle">
{menuRouter.name}
{menuRouter?.name}
</div>
<Menu
className="menuSlider"
// defaultSelectedKeys={['1']}
// defaultOpenKeys={['sub1']}
onOpenChange={handleOpenchange}
selectedKeys={currentSelectKey}
openKeys={openKeys}
mode="inline"
inlineCollapsed={innerCollapsed}
>
{
menuItemsCache
......
......@@ -42,4 +42,9 @@ export default {
'menu.memberAbility.memberManage': '会员管理',
'menu.memberAbility.memberManage.memberImport': '会员导入',
// 店铺能力
'menu.shopAbility': '店铺',
'menu.shopAbility.shopInfoManage': '店铺信息',
'menu.shopAbility.shopTemplate': '店铺装修模板',
};
\ No newline at end of file
.city_select_line {
display: flex;
&:not(:last-child) {
margin-bottom: 16px;
}
.opration_btn {
width: 32px;
height: 32px;
background: rgba(250, 251, 252, 1);
border: 1px solid rgba(235, 236, 240, 1);
color: #6B778C;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
margin-right: 16px;
cursor: pointer;
&:hover {
opacity: .8;
}
&.add {
background: rgba(0, 179, 122, 1);
border: 1px solid rgba(0, 179, 122, 1);
color: #ffffff;
}
}
}
\ No newline at end of file
import React, { memo, useState, Fragment, forwardRef } from 'react'
import { Select } from 'antd'
import { PlusOutlined, MinusOutlined } from '@ant-design/icons'
import cx from 'classnames'
import styles from './index.less'
interface CitySelectPropsType {
selectData: Array<{
provinceId: number;
cityId: number;
}>;
onAdd: Function;
onReduce: Function;
onChange: Function;
}
interface selectItemType {
index: number;
lable: string;
value: number;
}
const { Option } = Select;
const provinceData = [
{
lable: '广东省',
value: 1,
},
{
lable: '浙江省',
value: 2
},
]
const cityData: any = {
1: [
{
lable: '广州市',
value: 11
},
{
lable: '深圳市',
value: 12
}
],
2: [
{
lable: '杭州市',
value: 21
},
{
lable: '温州市',
value: 22
}
],
};
const getProviceById = (id: number) => {
let result: number = 0
provinceData && provinceData.map(item => {
if (item.value === id) {
result = item.value
}
})
return result
}
const CityCascader: React.FC<CitySelectPropsType> = (props) => {
const { selectData, onAdd, onReduce, onChange } = props
const [cities, setCities] = useState([])
const handleProvinceChange = (value: number, index: number) => {
let newData = JSON.parse(JSON.stringify(selectData))
let proviceById = getProviceById(value)
setCities(cityData[proviceById])
newData.map((item: any) => {
if (item.index === index) {
item.provinceId = value
}
return item
})
onChange(newData)
}
const onSecondCityChange = (value: number, index: number) => {
let newData = JSON.parse(JSON.stringify(selectData))
newData.map((item: any) => {
if (item.index === index) {
item.cityId = value
}
return item
})
onChange(newData)
}
const handleAddNewSelect = (index: number) => {
onAdd({ index, provinceId: 0, cityId: 0 })
}
const handleReduceSelect = (index: number) => {
if (selectData.length > 1) {
onReduce(index)
}
}
return (
<Fragment>
{
selectData && selectData.map((item: any, index) => (
<div className={styles.city_select_line} key={`selectDataItem-${index}`}>
<Select
style={{ width: 278 }}
value={item.provinceId ? item.provinceId : undefined}
onChange={(value) => handleProvinceChange(value, item.index)}
placeholder="请选择"
>
{provinceData.map(item => (
<Option value={item.value} key={item.value}>{item.lable}</Option>
))}
</Select>
<Select
style={{ width: 278, marginLeft: 16, marginRight: 24 }}
value={item.cityId ? item.cityId : undefined}
onChange={(value) => onSecondCityChange(value, item.index)}
placeholder="请选择"
>
{cities.map((item: any) => (
<Option value={item.value} key={item.value}>{item.lable}</Option>
))}
</Select>
{
index === selectData.length - 1 && (
<div className={cx(styles.opration_btn, styles.add)} onClick={() => handleAddNewSelect(index + 1)}>
<PlusOutlined />
</div>
)
}
<div className={styles.opration_btn} onClick={() => handleReduceSelect(item.index)}>
<MinusOutlined />
</div>
</div>
))
}
</Fragment>
)
}
const CitySelect: React.FC<CitySelectPropsType> = forwardRef((props, ref) => {
const { selectData, onAdd, onReduce, onChange } = props
return (
<div className={styles.city_select}>
<CityCascader selectData={selectData} onAdd={onAdd} onReduce={onReduce} onChange={onChange} />
</div>
)
})
export default CitySelect
.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 default_img from '@/assets/imgs/template_default_img.png'
import styles from './index.less'
interface TemplateItemPropsType {
templateInfo: any;
type: string;
}
const TemplateItem: React.FC<TemplateItemPropsType> = (props) => {
const { templateInfo, type } = props
return (
<div className={styles.template_item}>
<div className={styles.img_box} style={{ backgroundImage: `url(${default_img})` }}>
<div className={styles.img_box_mask}>
<Link to={`/shopAbility/template/detail?type=${type}`} className={styles.detail_btn}>查看详情</Link>
</div>
<div className={cx(styles.type_tag, templateInfo.platform === 'H5' ? styles.h5 : '')}>{templateInfo.platform}</div>
</div>
<div className={styles.template_info}>
<div className={styles.template_info_name}>
<span>{templateInfo.name}</span>
{
templateInfo.isDefault && <div className={styles.tag}>默认模板</div>
}
</div>
<div className={cx(styles.template_info_content, type === 'goods' ? styles.goods : '')}>
<div className={styles.template_info_content_text_wrap}>
<div className={styles.template_info_content_text_line}>
<label>使用站点:</label>
<span>{templateInfo.station}</span>
</div>
<div className={styles.template_info_content_text_line}>
<label>使用商城:</label>
<span>{templateInfo.useShopCenter}</span>
</div>
</div>
<div className={cx(styles.template_item_btn, templateInfo.isUse ? styles.active : '')}>
<PlayCircleOutlined />
<label>{templateInfo.isUse ? '启用中' : '启用'}</label>
</div>
</div>
</div>
</div>
)
}
export default TemplateItem
.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;
}
const UseModal: React.FC<UseModalPropsType> = (props) => {
const { visible, onOk, onCancel, title } = props
return (
<Modal
width={576}
title={title}
visible={visible}
onOk={() => onOk()}
centered
onCancel={() => onCancel()}
>
<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>
</Modal>
)
}
export default UseModal
.shop_info {
background-color: #ffffff;
padding: 32px 24px;
border-radius: 8px;
.add_template_form {
:global {
.ant-form-item-label>label::after {
display: none;
}
}
}
.form_item {
width: 572px;
}
.form_item_wrap {
display: flex;
align-items: center;
.size_require {
margin-left: 24px;
color: #97A0AF;
}
}
.upload_btn {
width: 104px;
height: 104px;
display: flex;
align-items: center;
justify-content: center;
color: #6B778C;
flex-direction: column;
background: rgba(250, 251, 252, 1);
border-radius: 2px;
border: 1px dashed rgba(223, 225, 230, 1);
cursor: pointer;
&.large {
width: 175px;
height: 120px;
}
&>p {
margin-top: 12px;
}
}
}
\ No newline at end of file
import React, { useState } from 'react'
import { PageHeaderWrapper } from '@ant-design/pro-layout'
import { Form, Input, Select, Button, Upload } from 'antd'
import { PlusOutlined } from '@ant-design/icons'
import CitySelect from '../components/citySelect'
import RequireItem from '@/components/RequireItem'
import cx from 'classnames'
import styles from './index.less'
const ShopInfo: React.FC = () => {
const [selectCityData, setSelectCityData] = useState([{ index: 0, provinceId: 0, cityId: 0 }])
const uploadProps = {
name: 'file',
action: '',
headers: {
authorization: 'authorization-text',
},
onChange(info: any) {
if (info.file.status !== 'uploading') {
console.log(info.file, info.fileList);
}
if (info.file.status === 'done') {
} else if (info.file.status === 'error') {
}
},
};
const handleAddNewCitySelect = (item: any) => {
let temp = [...selectCityData]
temp.push(item)
setSelectCityData(temp)
}
const handleReduceCitySelect = (index: number) => {
let temp = JSON.parse(JSON.stringify(selectCityData))
temp = temp.filter((item: any) => item.index !== index)
setSelectCityData(temp)
}
const handleCityChange = (data: any) => {
setSelectCityData(data)
}
return (
<PageHeaderWrapper>
<div className={styles.shop_info}>
<Form
className={styles.add_template_form}
hideRequiredMark={true}
>
<Form.Item
labelAlign="left"
name="templateName"
label={<RequireItem label="归属地市" isRequire={true} />}
>
<CitySelect
selectData={selectCityData}
onAdd={handleAddNewCitySelect}
onReduce={handleReduceCitySelect}
onChange={handleCityChange}
/>
</Form.Item>
<Form.Item
labelAlign="left"
name="applicable"
label={<RequireItem label="公司LOGO" isRequire={true} />}
>
<div className={styles.form_item_wrap}>
<Upload {...uploadProps}>
<div className={styles.upload_btn}>
<PlusOutlined />
<p>点击上传</p>
</div>
</Upload>
<div className={styles.size_require}>
<p>支持JPG/PNG/JPEG, <br />最大不超过 50K, <br />尺寸:xxxx</p>
</div>
</div>
</Form.Item>
<Form.Item
labelAlign="left"
name="description"
label={<RequireItem label="公司简介" isRequire={true} />}
rules={[{ required: true, message: "请输入公司简介" }]}
>
<Input.TextArea allowClear rows={4} className={styles.form_item} placeholder="最长400个字条,200个汉字" maxLength={100} />
</Form.Item>
<Form.Item
labelAlign="left"
name="photo"
label={<RequireItem label="厂房照片" />}
>
<div className={styles.form_item_wrap}>
<Upload {...uploadProps}>
<div className={cx(styles.upload_btn, styles.large)}>
<PlusOutlined />
<p>点击上传</p>
</div>
</Upload>
<div className={styles.size_require}>
<p>支持JPG/PNG/JPEG, <br />最大不超过 100K, <br />尺寸:xxxx</p>
</div>
</div>
</Form.Item>
<Form.Item
labelAlign="left"
name="photo"
label={<RequireItem label="资质荣誉" />}
>
<div className={styles.form_item_wrap}>
<Upload {...uploadProps}>
<div className={cx(styles.upload_btn, styles.large)}>
<PlusOutlined />
<p>点击上传</p>
</div>
</Upload>
<div className={styles.size_require}>
<p>支持JPG/PNG/JPEG, <br />最大不超过 100K, <br />尺寸:xxxx</p>
</div>
</div>
</Form.Item>
<Form.Item
name="buttonGroup"
label={<RequireItem label="" />}
>
<Button type="primary" style={{ marginRight: 16 }}>保存</Button>
<Button>取消</Button>
</Form.Item>
</Form>
</div>
</PageHeaderWrapper>
)
}
export default ShopInfo
.shop_center_template {
.add_card {
display: flex;
height: 100%;
border-radius: 2px;
border: 1px dashed rgba(223, 225, 230, 1);
background: rgba(250, 251, 252, 1);
align-items: center;
justify-content: center;
cursor: pointer;
flex-direction: column;
&:hover {
border: 1px dashed rgba(200, 200, 230, 1);
}
.add_card_btn {
display: flex;
justify-content: center;
align-items: center;
width: 48px;
height: 48px;
background: rgba(235, 236, 240, 1);
border-radius: 50%;
margin-bottom: 24px;
font-size: 22px;
color: #97A0AF;
}
&>p {
color: #6B778C;
}
}
}
\ No newline at end of file
import React from 'react'
import { Row, Col } from 'antd'
import { PageHeaderWrapper } from '@ant-design/pro-layout'
import TemplateItem from '../components/templateItem'
import styles from './index.less'
const ShopTemplate: React.FC = () => {
const template_data_mock = [
{
id: 1,
name: '模板001-清新类',
platform: 'PC',
station: '中国站',
useShopCenter: 'WEB用户商城',
isUse: true,
isDefault: true,
},
{
id: 2,
name: '模板002-科技类',
platform: 'H5',
station: '美国站',
useShopCenter: 'H5用户商城',
isUse: true,
isDefault: true,
},
{
id: 3,
name: '模板003-科技类',
platform: 'PC',
station: '美国站',
useShopCenter: 'WEB用户商城',
isUse: false,
isDefault: false,
}
]
return (
<PageHeaderWrapper>
<div className={styles.shop_center_template}>
<Row gutter={24} className={styles.template_list}>
{
template_data_mock.map(item => (
<Col span={6} key={item.id}>
<TemplateItem templateInfo={item} type="shop" />
</Col>
))
}
</Row>
</div>
</PageHeaderWrapper>
)
}
export default ShopTemplate
.template_detail {
height: 224px;
background-color: #ffffff;
display: flex;
margin: -24px;
align-items: center;
.back_btn {
color: #6B778C;
margin-left: 27px;
font-size: 16px;
margin-top: 40px;
cursor: pointer;
align-self: flex-start;
}
.template_info_wrap {
display: flex;
margin-left: 36px;
flex: 1;
align-items: center;
.template_img_box {
position: relative;
width: 250px;
height: 165px;
border-radius: 8px 8px 0px 0px;
overflow: hidden;
.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: auto;
display: block;
margin: 0 auto;
}
}
.template_info {
margin-left: 24px;
.template_info_line {
line-height: 22px;
&:not(:last-child) {
margin-bottom: 8px;
}
&>label {
color: #6B778C;
}
&>span {
color: #172B4D;
}
}
}
}
.btn {
margin-left: 24px;
padding: 0 20px;
min-width: 88px;
height: 40px;
background: rgba(250, 251, 252, 1);
border: 1px solid rgba(235, 236, 240, 1);
color: #42526E;
cursor: pointer;
line-height: 38px;
text-align: center;
&>label {
margin-left: 5px;
}
&.use {
background: #00B37A;
color: #ffffff;
}
&:hover {
opacity: .8;
}
&:last-child {
margin-right: 48px;
}
}
}
\ No newline at end of file
import React, { useState } from 'react'
import { history } from 'umi';
import { ArrowLeftOutlined, EyeOutlined, PushpinOutlined } from '@ant-design/icons'
import cx from 'classnames'
import UseModal from '../components/useModal'
import default_img from '@/assets/imgs/template_default_img.png'
import styles from './index.less'
interface TemplateDetailPropsType {
location: {
query: {
type: string; // 商城模板:shopCenter ;店铺模板:shop; 商品描述模板: goods
}
}
}
const TemplateDetail: React.FC<TemplateDetailPropsType> = (props) => {
const { location: { query } } = props
const [useModalVisible, setUseModalVisible] = useState<boolean>(false)
return (
<div className={styles.template_detail}>
<div className={styles.back_btn} onClick={() => history.goBack()}>
<ArrowLeftOutlined />
</div>
<div className={styles.template_info_wrap}>
<div className={styles.template_img_box}>
<div className={cx(styles.type_tag)}>PC</div>
<img src={default_img} />
</div>
<div className={styles.template_info}>
<div className={styles.template_info_line}>
<label>模板名称:</label>
<span>模板001-清新类</span>
</div>
<div className={styles.template_info_line}>
<label>适用环境:</label>
<span>PC</span>
</div>
<div className={styles.template_info_line}>
<label>模板描述:</label>
<span>此模板只适用于PC端</span>
</div>
<div className={styles.template_info_line}>
<label>使用站点:</label>
<span>中国站</span>
</div>
<div className={styles.template_info_line}>
<label>使用商城:</label>
<span>WEB用户商城</span>
</div>
</div>
</div>
<div className={styles.btn}>
<EyeOutlined />
<label>预览</label>
</div>
<div className={cx(styles.btn, styles.use)}>
<PushpinOutlined />
<label>店铺装修</label>
</div>
<div className={cx(styles.btn, styles.use)} onClick={() => setUseModalVisible(true)}>
<PushpinOutlined />
<label>使用</label>
</div>
<UseModal
title="使用商场模板"
visible={useModalVisible}
onOk={() => { }}
onCancel={() => setUseModalVisible(false)}
/>
</div>
)
}
export default TemplateDetail
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