Commit 20ad6596 authored by Bill's avatar Bill

feat: 营销装修

parent 5478572e
import React from 'react';
interface Iprops {
files: {
name: string,
url: string
}[]
}
const FileListRender = (props: Iprops) => {
const { files } = props;
const length = files?.length || 0
return (
<div style={{display: 'flex', flexDirection: 'column', width: '100%'}}>
{
files?.map((_row, index) => {
return (
<a style={ index + 1 === length ? {} : {marginBottom: '4px'}} key={_row.url} href={_row.url}>{_row.name}</a>
)
})
}
</div>
)
}
export default FileListRender;
import React, { useEffect } from 'react';
import UploadFiles from './UploadFiles';
import { UploadProps, UploadChangeParam, UploadFile } from 'antd/lib/upload/interface'
import { Button } from 'antd';
interface Iprops {
value: UploadFile[],
editable: boolean,
props: {
['x-component-props']: {
/**
* ☹
* 这里表现相对奇怪,当这个组件放在了table 下切这个组件是必填的话,会出现两个错误情况,我通过外部传值干掉其中一个
*/
showError?: boolean,
} & {
[key: string]: any
},
},
mutators: {
change: (params: UploadFile[]) => void
},
errors: string[],
}
const toArray = (value: string | UploadFile[]): UploadFile[] => {
if (typeof value === 'undefined' || !value) {
return [];
}
if (Array.isArray(value)) {
return value;
}
}
const FormilyUploadFiles: React.FC<Iprops> = (props: Iprops) => {
const { value, editable, errors, } = props;
const componentProps = props.props?.['x-component-props'] || {};
const { showError, ...rest } = componentProps;
const isShowError = typeof showError === 'undefined' ? true : showError;
const fileList = toArray(value);
const onChange = (info: UploadChangeParam) => {
const fileList = info.fileList;
const newList = fileList.map((file) => {
return {
name: file.name,
url: file.url || file.response?.data,
uid: file.uid,
status: file.status,
percent: file.percent,
size: file.size,
type: file.type,
}
})
props.mutators.change(newList)
}
const onRemove = (fileItem: UploadFile) => {
const list = [...fileList];
const newList = list.filter((_item) => _item.url !== fileItem.url);
props.mutators.change(newList);
}
return (
<div style={{width: '100%'}}>
<UploadFiles fileList={fileList} onChange={onChange} onRemove={onRemove} disable={!editable} {...rest} />
{/* {
isShowError && errors.length > 0 && (
<div>
<p style={{color: '#ff4d4f'}}>{errors.join("")}</p>
</div>
)
} */}
</div>
)
}
const WrapFormilyUploadFiles: typeof FormilyUploadFiles & {
isFieldComponent?: boolean,
} = FormilyUploadFiles;
WrapFormilyUploadFiles.isFieldComponent = true
export default WrapFormilyUploadFiles
@import '../../global/styles/utils.less';
.renderFileContainer {
display: flex;
flex-direction: column;
margin-bottom: @margin-md;
.renderFileItemContainer {
position: relative;
padding: 4px 0;
display: flex;
flex-direction: column;
justify-content: center;
cursor: pointer;
.uploadProgress {
position: absolute;
top: -5px;
left: 0;
right: 0;
display: none;
}
.renderFileItem {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
.fileItem-left {
display: flex;
align-items: center;
flex: 1;
overflow: hidden;
.img {
width: 16px;
height: 16px;
margin-right: @margin-sm;
}
.error {
color: #ff4d4f;
}
> a {
flex: 1;
.textOverflow();
}
}
}
// &:hover {
// background-color: @secondary-color;
// color: @main-color;
// }
}
}
.fileEmpty {
margin-bottom: 0;
}
import React, { useEffect, useState } from 'react';
import cx from 'classnames';
import { UploadProps, UploadChangeParam, UploadFile } from 'antd/lib/upload/interface';
import { UPLOAD_TYPE } from '@/constants';
import { Upload, Progress, Button, message } from 'antd';
import { CloudUploadOutlined, DeleteOutlined } from '@ant-design/icons';
import pdfIcon from '@/asserts/pdf_icon.png';
import { getAuth } from '@/utils/auth';
import styles from './UploadFiles.less';
type PickProps = "headers" | "action" | "accept" | "beforeUpload" | "onChange" | "fileList"
interface PickUploadProps extends Pick<UploadProps, PickProps> {
containerStyle?: React.CSSProperties,
/**
* 表示当前上传组件的order值, 越大越后, 即flex-order
*/
uploadOrder?: number,
children?: React.ReactNode,
disable?: boolean,
mode?: "text" | "link" | "ghost" | "default" | "primary" | "dashed",
buttonText?: string,
fileContainerClassName?: string
customizeItemRender?: ((files: UploadFile[], handleRemove: (fileItem: UploadFile) => void) => React.ReactNode) | null,
onRemove?: ((fileItem: UploadFile) => void) | null,
/** 是否显示文件 */
showFiles?: boolean
}
const UploadFiles: React.FC<PickUploadProps> = (props: PickUploadProps) => {
const {
uploadOrder,
containerStyle,
headers,
action,
accept,
beforeUpload,
onChange,
children,
customizeItemRender,
onRemove,
disable,
mode,
buttonText,
fileContainerClassName,
showFiles
} = props;
const hasFileListProps = "fileList" in props;
const auth = getAuth();
const [files, setFiles] = useState<UploadFile[]>(() => props.fileList || []);
// const renderFiles = hasFileListProps ? props.fileList : files;
useEffect(() => {
if (!hasFileListProps) {
return;
}
setFiles(props.fileList);
}, [props.fileList]);
const filesContainerCs = cx({
[styles.fileEmpty]: files.length === 0,
}, styles.renderFileContainer, fileContainerClassName);
const uploadProps = {
name: 'file',
fileList: files,
accept: accept,
action: action,
headers: { ...headers, token: auth.token },
data: {
fileType: UPLOAD_TYPE
},
// disabled: loading || disabled,
showUploadList: false,
onChange(info: UploadChangeParam) {
console.log(info.file);
if (info.file.status === 'error' || (info.file.status === 'done' && info.file.response?.code !== 1000)) {
message.error(info.file.response?.message || "上传失败, 请检查上传接口");
return;
}
console.log(123123);
// 如果不存在fileList, 只存在onChange 那么也要改变组件的file
if (!("fileList" in props)) {
const fileList = info.fileList;
const newList = fileList.map((file) => {
return {
name: file.name,
url: file.url || file.response?.data,
uid: file.uid,
status: file.status,
percent: file.percent,
size: file.size,
type: file.type,
};
});
setFiles(newList);
}
if (onChange) {
onChange(info);
}
},
beforeUpload
};
const handleRemove = (fileItem: UploadFile) => {
if (disable) {
return;
}
if (onRemove) {
onRemove(fileItem);
}
if (!hasFileListProps) {
const newFileList = files.filter((_item) => _item.url !== fileItem.url);
setFiles(newFileList);
}
};
const renderFileItem = () => {
return (
<div className={filesContainerCs} style={{ order: 1 }}>
{
files.map((_item: UploadFile, key) => {
return (
<div className={styles.renderFileItemContainer} key={key}>
{
_item.status === 'uploading' && (
<div className={styles.uploadProgress}>
<Progress percent={_item.percent} status="active" size="small" showInfo={false} />
</div>
)
}
<div className={styles.renderFileItem} >
<div className={styles['fileItem-left']}>
<img className={styles.img} src={pdfIcon} />
<a
className={cx({ [styles.error]: _item.status === 'error' || !_item.url })}
href={_item.url}
target="_blank"
title={_item.name} rel="noreferrer"
>
{_item.name}
</a>
</div>
{
!disable && (
<DeleteOutlined onClick={() => handleRemove(_item)} />
)
}
</div>
</div>
);
})
}
</div>
);
};
const renderFileContainer = () => {
if (!showFiles) {
return null;
}
return customizeItemRender ? customizeItemRender(files, handleRemove) : renderFileItem();
};
return (
<div style={containerStyle} >
{
renderFileContainer()
}
{
!disable && (
<div style={{ order: uploadOrder }}>
<Upload {...uploadProps} >
{
children || (
<Button type={mode} icon={<CloudUploadOutlined />}>
{buttonText}
</Button>
)
}
</Upload>
</div>
)
}
</div>
);
};
UploadFiles.defaultProps = {
containerStyle: {
display: "flex",
flexDirection: 'column'
},
uploadOrder: 2,
action: '/api/file/file/upload',
headers: {},
beforeUpload: (file: UploadFile) => {
if (file.size > 20 * 1024 * 1024) {
message.error("上传文件不能大于20m");
return Upload.LIST_IGNORE;
}
return true;
},
onChange: (file: UploadChangeParam) => {},
customizeItemRender: null,
onRemove: null,
disable: false,
mode: 'default',
buttonText: '上传文件',
fileContainerClassName: '',
showFiles: true
// fileList: []
};
export default UploadFiles;
import { PROPS_SETTING_TYPES } from '@lingxi-disign/core';
import { PageConfigType, PROPS_SETTING_TYPES } from '@lingxi-disign/core';
const mallLayoutConfig = {
key: "0",
const mallLayoutConfig: PageConfigType = {
"0": {
"componentName": "MallLayout",
title: '组件树',
"props": {
"style": {
"width": "100%",
"minHeight": "100%",
"background": "#F7F8FA",
"background": "#DD3041",
"overflowX": "hidden",
"paddingBottom": "50px",
}
},
"childNodes": ["1", "5"]
"childNodes": ["1", "2", "4"]
},
};
const divWrap = {
key: "1",
const divWrap: PageConfigType = {
"1": {
"componentName": "div",
"props": {
"style": {
"width": "100%",
"background": "#FFF",
"componentName": 'Advertisement',
title: "广告图",
canEdit: true,
canHide: true,
props: {
style: {
height: '176px',
width: '100%',
background: 'blue',
}
},
"childNodes": ["2", "3"]
}
}
};
const mobileHeaderNav = {
key: "2",
const couponContainer: PageConfigType = {
"2": {
"componentName": "CustomizeTag",
"componentType": PROPS_SETTING_TYPES.mobileHeaderNav,
"title": "顶部导航",
"canEdit": true,
"canHide": false,
"props": {
type: 'primary'
},
}
};
const bannerWrap = {
key: "3",
"componentName": 'Coupon',
title: '优惠券容器',
canEdit: true,
canHide: true,
props: {},
childNodes: ['3'],
childComponentName: "Coupon.Item",
addBtnText: '添加优惠券'
},
"3": {
"componentName": "div",
"props": {
"style": {
"marginTop": "-50px",
}
},
"childNodes": ["4"]
loop: [1, 2, 3, 4],
"componentName": 'Coupon.Item',
title: "优惠券",
canEdit: true,
canHide: false,
props: {},
}
};
const mobileBanner = {
key: "4",
/** 活动推荐 */
const activityContainer: PageConfigType = {
"4": {
"componentName": "MobileBanner",
"componentType": PROPS_SETTING_TYPES.mobileBanner,
"title": "轮播广告",
"canEdit": true,
"canHide": true,
"props": {
visible: true,
dataList: []
}
}
};
const mobileQuickNav = {
key: "5",
componentName: 'CommodityList',
title: '活动推荐',
props: {
title: '活动推荐',
},
childNodes: ['5'],
},
"5": {
childNodes: ["6"],
"componentName": "MobileQuickNav",
"componentType": PROPS_SETTING_TYPES.mobileQuickNav,
"title": "功能入口",
"canEdit": true,
"canHide": true,
"props": {
dataList: [
{
"icon": "https://shushangyun-lingxi.oss-cn-shenzhen.aliyuncs.com/85de453a73a44983ba9c441c54bdc2261619070264426.png",
"name": "找现货",
"type": 1,
"url": "",
},
{
"icon": "https://shushangyun-lingxi.oss-cn-shenzhen.aliyuncs.com/646cba64f3984e8887b8cda29e561f471619070308310.png",
"name": "找供应",
"type": 2,
"url": "",
},
{
"icon": "https://shushangyun-lingxi.oss-cn-shenzhen.aliyuncs.com/c2d16fe4c2104859a5ffc4ddd2b3bebe1619070341564.png",
"name": "找店铺",
"type": 6,
"url": "",
},
{
"icon": "https://shushangyun-lingxi.oss-cn-shenzhen.aliyuncs.com/d6818a5f4c044e0b9fcc28e243b7ddcc1619070365026.png",
"name": "看资讯",
"type": 7,
"url": "",
},
{
"icon": "https://shushangyun-lingxi.oss-cn-shenzhen.aliyuncs.com/bbb358d8b1c64eb5ab0bfb9f5396297c1619070388474.png",
"name": "人气店铺",
"type": 9,
"url": "",
},
]
}
}
};
const mobileBanner1 = {
key: "6",
"6": {
"componentName": "div",
"props": {
"style": {
"width": "100%",
"background": "red",
marginTop: '600px',
}
loop: [1,2,3],
componentName: 'CommodityList.Item',
title: '活动推荐商品',
props: {
empty: true,
},
"title": "嘻嘻",
"canEdit": true,
"canHide": true,
childNodes: ["7"]
}
addBtnText: '添加'
},
};
const mobileBanner2 = {
key: "7",
"7": {
"componentName": "div",
"props": {
"style": {
"width": "100%",
"background": "red",
marginTop: '600px',
}
},
"title": "哈哈哈",
"canEdit": true,
"canHide": true,
}
};
const configs = {
...mallLayoutConfig,
...divWrap,
...mobileHeaderNav,
...bannerWrap,
...mobileBanner,
...mobileQuickNav,
...mobileBanner1,
...mobileBanner2
...couponContainer,
...activityContainer
};
export default configs;
import * as LingXiUI from 'lingxi-design-ui';
import * as LxUI from '@lingxi-disign/ui';
import { ConfigType } from '@lingxi-disign/react';
import MallLayout from '@/pages/pageCustomized/configs/componentConfigs/LingXiUI/MallLayout';
// import MobileQuickNav from '@/pages/pageCustomized/configs/componentConfigs/LingXiUI/MobileQuickNav';
import * as HTML from '@/pages/pageCustomized/configs/componentConfigs/HTML';
import schema from './schema';
import CustomLayouts from '../../components/Layouts';
const componentSchemasMap = { MallLayout, ...schema, ...HTML };
const originalComponents = { ...LxUI, ...LingXiUI, ...CustomLayouts };
// /**
// * 容器组件分类
// */
// 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 = {
componentsMap: originalComponents,
componentSchemasMap: componentSchemasMap,
};
export default config;
import { ComponentSchemaType, PROPS_SETTING_TYPES, PROPS_TYPES } from '@lingxi-disign/core';
const View: ComponentSchemaType = {
propsConfig: {
children: {
label: '内容',
type: PROPS_TYPES.string,
}
},
};
const Advertisement: ComponentSchemaType = {
propsConfig: {
componentType: {
type: "Advertisement",
},
imageUrl: {
label: '图片链接',
type: PROPS_TYPES.string,
},
style: {
label: '样式',
type: PROPS_TYPES.object,
}
},
};
const Coupon = {
Coupon: {
propsConfig: {
children: {
label: '内容',
type: PROPS_TYPES.string,
},
componentType: {
type: "CouponMultiple",
},
},
},
"Coupon.Item": {
propsConfig: {
children: {
label: '内容',
type: PROPS_TYPES.string,
}
},
}
};
const MarketingCard = {
"MarketingCard": {
propsConfig: {},
},
"MarketingCard.CouponsItem": {
propsConfig: {
componentType: {
label: '优惠券',
type: PROPS_SETTING_TYPES.category
},
},
}
};
const Commodity = {
propsConfig: {
children: {
label: '文本内容',
type: PROPS_TYPES.string,
},
},
};
const CommodityList = {
CommodityList: {
propsConfig: {}
},
"CommodityList.Item": {
propsConfig: {}
}
};
export default {
Commodity,
Advertisement,
...Coupon,
View,
...MarketingCard,
...CommodityList,
};
import React, { useCallback } from 'react';
import { useToggle } from '@umijs/hooks';
import { Button } from 'antd';
import { FORM_FILTER_PATH } from '@/formSchema/const';
import { useStateFilterSearchLinkageEffect } from '@/formSchema/effects/useFilterSearch';
import { ISchema } from '@formily/antd';
import TableModal from './tableModal';
interface Iprops {
visible: boolean,
onCancel: () => void,
}
const CouponSelect: React.FC<Iprops> = (props: Iprops) => {
// const { state: visible, toggle } = useToggle();
const { visible, onCancel } = props;
const columns = [
{
title: '优惠券信息',
dataIndex: 'couponInfo'
},
{
title: '类型',
dataIndex: 'type',
},
{
title: '领券方式',
dataIndex: 'method'
},
{
title: '面额',
dataIndex: 'price'
},
{
title: '使用条件',
dataIndex: 'condition'
},
{
title: '有效期',
dataIndex: 'time'
},
{
title: '所属',
dataIndex: 'belong'
}
];
const fetchData = useCallback(async (params: any) => {
return {
totalCount: 0,
data: []
};
}, []);
const triggerOk = () => {
console.log(123);
};
const schema: ISchema = {
type: 'object',
properties: {
megaLayout: {
type: 'object',
'x-component': 'mega-layout',
properties: {
id: {
type: 'string',
'x-component': 'Search',
'x-component-props': {
placeholder: '搜索优惠券ID',
align: 'flex-left',
tip: '输入优惠券ID进行搜索',
},
},
[FORM_FILTER_PATH]: {
type: 'object',
'x-component': 'mega-layout',
'x-component-props': {
inline:true,
full: true,
},
properties: {
couponName: {
type: 'string',
"x-component-props": {
placeholder: '优惠券名称'
}
},
submit: {
'x-component': 'Submit',
'x-mega-props': {
span: 1,
},
'x-component-props': {
children: '查询',
},
},
},
},
},
},
},
};
return (
<>
<TableModal
modalType={"Drawer"}
width={920}
visible={visible}
onClose={onCancel}
title={"选择优惠券"}
columns={columns}
schema={schema}
onOk={triggerOk}
fetchData={fetchData}
tableProps={{
rowKey: (record) => `id`,
}}
effects={($, actions) => {
useStateFilterSearchLinkageEffect($, actions, 'id', FORM_FILTER_PATH);
}}
mode={"checkbox"}
/>
{/* <Button onClick={() => toggle(true)}>选择</Button> */}
</>
);
};
export default CouponSelect;
import React, { useEffect, useRef, useState } from 'react';
import { ISchema , createFormActions } from '@formily/antd';
import { Modal, Row, Col, Drawer,Button } from 'antd';
import { StandardTable } from 'god';
import { ColumnsType } from 'antd/es/table';
import NiceForm from '@/components/NiceForm';
const formActions = createFormActions();
interface Iprops {
modalType?: "Modal" | "Drawer"
/**
* 是否显示
*/
visible: boolean,
/**
* Modal 标题
*/
title: string,
/**
* 搜索schema
*/
schema: ISchema,
/**
* table Ccolumn
*/
columns: ColumnsType,
footer?: React.ReactNode,
tableProps?: {
rowKey: string | ((record) => any),
expandable?: any,
},
width?: number,
mode: 'checkbox' | 'radio',
customizeRadio?: boolean,
/** 回显值 */
value?: {[key: string]: any}[],
/**
* onChange
*/
expressionScope?: {[key: string]: any}
/**
* format话参数
*/
format?: ((value) => any) | null,
components?: { [key: string]: any },
effects?: ($, actions) => void,
fetchData: (params: any) => any,
onClose: () => void,
onOk: (selectRow: number[] | string[], selectedRows: {[key: string]: any}[]) => void,
/**
* 勾选前操作,
*/
beforeChecked?: ((record: any, selected: boolean, selectedRows: any[]) => boolean) | ((record: any, selected: boolean, selectedRows: any[]) => Promise<any>),
/**
* rowSelection
*/
rowSelection?: {
getCheckboxProps?: (record) => any,
}
}
const TableModal: React.FC<Iprops> = (props: Iprops) => {
const {
title,
visible,
schema,
columns,
effects,
tableProps,
mode,
expressionScope,
fetchData,
onClose,
onOk,
value,
format,
customizeRadio,
modalType,
footer,
width,
components,
beforeChecked,
rowSelection
} = props;
const ref = useRef<any>({});
const isFirstLoad = useRef<boolean>(true);
const [selectRow, setSelectRow] = useState<number[] | string[]>(() => {
return value.map( (_row) => typeof tableProps.rowKey === 'string' ? _row[tableProps.rowKey as string] : tableProps.rowKey(_row));
});
const [selectRowRecord, setSelectRowRecord] = useState<{[key: string]: any}[]>([]);
useEffect(() => {
if (!visible) {
return;
}
let list = value;
const currentMode = customizeRadio || mode === 'radio';
if (currentMode) {
list = list.slice(-1);
}
const keys = list.map(
(_row) => {
// console.log(typeof tableProps.rowKey === 'string' && tableProps.rowKey(_row))
return typeof tableProps.rowKey === 'string' ? _row[tableProps.rowKey as string] : tableProps.rowKey(_row);
});
setSelectRow(keys);
setSelectRowRecord(value);
}, [visible, value, mode, customizeRadio]);
const handleEffects = ($: any, actions: any) => {
effects?.($, actions);
};
const handleOnClose = () => {
onClose?.();
};
const handleOk = () => {
onOk?.(selectRow, selectRowRecord);
};
useEffect(() => {
if (!visible) {
return ;
}
if (!isFirstLoad.current) {
ref.current?.reload?.();
}
isFirstLoad.current = false;
}, [visible]);
const onSelectChange = async (record, selected: boolean, selectedRows) => {
const recordRows = customizeRadio || mode === 'radio' ? selectedRows.slice(-1) : selectedRows;
const keys = recordRows.map((_item) => typeof tableProps.rowKey === 'string' ? _item[tableProps.rowKey as string] : tableProps.rowKey(_item));
// if (selected) {
const returnValue =await beforeChecked(record, selected, recordRows);
if (returnValue === false) {
return;
}
// }
setSelectRowRecord(recordRows);
setSelectRow(keys);
};
const onSelectAll = async (selected: boolean, selectedRows: any[], changeRows: any[]) => {
const keys = selectedRows.map((_item) => typeof tableProps.rowKey === 'string' ? _item[tableProps.rowKey as string] : tableProps.rowKey(_item));
setSelectRowRecord(selectedRows);
setSelectRow(keys);
};
const handleSearch = (params: any) => {
const res = (format && format(params)) || params;
ref.current?.reload(res);
};
const Component = modalType === 'Modal' ? Modal : Drawer;
const renderFooter = () => {
return (
<div style={{ textAlign: 'right'}}>
<Button onClick={handleOnClose} style={{ marginRight: 8 }}>
取消
</Button>
<Button onClick={handleOk} type="primary">
提交
</Button>
</div>
);
};
const otherProps = modalType === 'Drawer' ? { footer: renderFooter(), maskClosable: true, onClose: handleOnClose } : { onOk: handleOk};
return (
<Component
title={title}
visible={visible}
onCancel={handleOnClose}
// onOk={handleOk}
width={width}
{...otherProps}
>
<StandardTable
columns={columns}
tableProps={{
...tableProps,
pagination: false
}}
keepAlive={false}
fetchTableData={fetchData}
currentRef={ref}
rowSelection={{
type: customizeRadio && mode === 'radio' ? 'checkbox' : mode,
onSelect: onSelectChange,
onSelectAll: onSelectAll,
selectedRowKeys: selectRow,
hideSelectAll: customizeRadio,
...rowSelection
}}
formRender={(child, ps) => (
// <div style={{display: "flex", flexDirection: 'row', justifyContent: 'space-between'}}>
// <div>{child}</div>
// <div>{ps}</div>
// </div>
<div style={{position: "relative", }}>
<div >{child}</div>
<div style={{position: 'absolute', right: 0, top: 4}}>{ps}</div>
</div>
)}
controlRender={
<NiceForm
schema={schema}
components={components}
actions={formActions}
onSubmit={handleSearch}
expressionScope={expressionScope}
effects={($, actions) => handleEffects($, actions)}
/>
}
>
</StandardTable>
</Component>
);
};
TableModal.defaultProps = {
rowSelection: {},
mode: 'radio',
tableProps: {
rowKey: 'memberId'
},
value: [],
expressionScope: {},
format: null,
customizeRadio: false,
modalType: "Modal",
footer: null,
width: 840,
components: {},
beforeChecked: () => true
};
export default TableModal;
.container {
display: flex;
flex-direction: column;
.header {
padding: 16px;
}
}
import React from 'react';
import { Button } from 'antd';
import styles from './index.less';
const CouponSetting = () => {
return (
<div className={styles.container}>
<div className={styles.header}>
<span>优惠券内容</span>
</div>
<div className={styles.content}>
<div className={styles.module}>
<Button>选择优惠券</Button>
</div>
</div>
</div>
);
};
export default CouponSetting;
.drawer {
background-color: #fff;
height: 100%;
overflow-y: auto;
transition: width .25s;
display: flex;
flex-direction: column;
.header {
border-bottom: 1px solid #F4F5F7;
padding: 16px;
.title {
font-size: 16px;
color: #252D37;
}
}
.body {
padding: 16px;
flex: 1;
}
.footer {
height: 56px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
border-top: 1px solid #F4F5F7;
padding: 0 16px;
}
}
.visible {
width: 440px;
}
import React from 'react';
import cx from 'classnames';
import { Button, Space } from 'antd';
import styles from './index.less';
interface Iprops {
children: React.ReactNode,
title: string,
visible: boolean,
header?: React.ReactNode,
onCancel: null | (() => void),
onSubmit: null | (() => void),
footer?: null | React.ReactNode
}
const Drawer: React.FC<Iprops> = (props) => {
const { children, title, visible, header = null, onCancel = null, onSubmit = null, footer } = props;
const handleCancel = () => {
onCancel?.();
};
const handleSubmit = () => {
onSubmit?.();
};
return (
<div className={cx(styles.drawer, { [styles.visible]: visible })}>
{
header || (
<div className={styles.header}>
<span>{title}</span>
</div>
)
}
<div className={styles.body}>
{children}
</div>
<div className={styles.footer}>
{
footer || (
<Space>
<Button onClick={handleCancel}>取消</Button>
<Button onClick={handleSubmit}>确定</Button>
</Space>
)
}
</div>
</div>
);
};
export default Drawer;
.editPanel {
background-color: #fff;
height: 100%;
overflow-y: auto;
transition: width .25s;
&.hide {
width: 0;
}
&.show {
width: 440px;
}
.settingTabs {
:global {
.ant-tabs-tab {
margin: 0 32px;
.ant-tabs-tab-btn {
color: #909399;
font-weight: bold;
font-size: 14px;
}
&.ant-tabs-tab-active {
.ant-tabs-tab-btn {
color: #303133;
}
}
}
}
.panel {
padding: 0 16px;
}
}
}
import React, { useEffect } from 'react';
import { Tabs } from 'antd';
import cx from 'classnames';
import { PageConfigType, SelectedInfoType, STATE_PROPS, } from '@lingxi-disign/core';
import { useSelector } from '@lingxi-disign/react';
import { useToggle } from '@umijs/hooks';
import styles from './index.less';
import Image from '../Image';
import CouponSelect from '../CouponSelect';
const { TabPane } = Tabs;
type SettingPanelType = {
selectedInfo: SelectedInfoType,
pageConfig: PageConfigType,
}
interface Iprops {
title: string,
}
const ComponentsMap = {
"Advertisement": Image,
"CouponMultiple": CouponSelect,
};
const EditPanel = () => {
const { selectedInfo, pageConfig } = useSelector<SettingPanelType, STATE_PROPS>(['selectedInfo', 'pageConfig']);
const {state: visible, toggle: setVisible } = useToggle(false);
console.log(selectedInfo, pageConfig );
useEffect(() => {
if (selectedInfo) {
setVisible(true);
}
}, [selectedInfo]);
const handleOncancel = () => {
setVisible(false);
};
const dataProps = {
visible: visible,
onCancel: handleOncancel
};
const componentType = selectedInfo?.propsConfig?.componentType?.type || '';
const Component = componentType && ComponentsMap[componentType as unknown as "Advertisement"];
return (
Component && <Component {...dataProps} /> || null
);
};
export default EditPanel;
.uploadImage {
width: 100%;
:global {
.ant-upload {
width: 100%;
}
}
.footer {
text-align: center;
margin-top: 8px;
color: #91959B;
}
}
.container {
width: 100%;
height: 176px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #FAFBFC;
position: relative;
overflow: hidden;
cursor: pointer;
.tips {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
padding: 8px;
background-color: rgba(0, 0, 0, 0.35);
font-size: 12px;
color: #fff;
position: absolute;
left: 0;
right: 0;
bottom: -34px;
transition: all 0.6s;
}
&:hover {
.tips {
transform: translateY(-34px);
}
}
}
import React from 'react';
import UploadFiles from '@/components/UploadFiles/UploadFiles';
import { PlusCircleOutlined } from '@ant-design/icons';
import styles from './index.less';
import Drawer from '../Drawer';
interface Iprops {
value: string,
onChange: (file) => void,
visible: boolean
}
const Image: React.FC<Iprops> = (props: Iprops) => {
const { visible } = props;
return (
<Drawer title="修改广告图" visible={visible}>
<div className={styles.uploadImage}>
<UploadFiles>
<div className={styles.container}>
<PlusCircleOutlined style={{fontSize: '16px'}} />
<div className={styles.tips}>
选择图像
</div>
</div>
</UploadFiles>
<div className={styles.footer}>建议尺寸: 1920 * 750</div>
</div>
</Drawer>
);
};
export default Image;
import React, { CSSProperties, useMemo } from 'react';
import cx from 'classnames';
interface Iprops {
imageUrl: string,
width: number | string,
height: number | string,
style: CSSProperties,
className: any,
}
const Advertisement: React.FC<Iprops> = (props: Iprops) => {
const { imageUrl, width = "100%", height = "100%", style = {}, className, ...other } = props;
const cacheWidth = useMemo(() => typeof width === 'number' ? `${width}px` : width, [width]);
const cacheHeight = useMemo(() => typeof height === 'number' ? `${height}px` : height, [height]);
const classNames = cx(className);
return (
<img
src={imageUrl}
style={{width: cacheWidth, height: cacheHeight, background: 'yellow', ...style}}
className={classNames}
{...other}
/>
);
};
export default Advertisement;
.recommand {
padding: 0 8px;
margin-top: 24px;
.title {
color: #fff;
font-size: 20px;
margin-bottom: 8px;
}
}
.item {
margin-bottom: 8px;
}
import React from 'react';
import cx from 'classnames';
import { Commodity, Progress } from '@lingxi-disign/ui';
import styles from './index.less';
interface Iprops {
className: string,
children: React.ReactNode,
title: string,
}
function omit(props, whiteList: string[]) {
const result: { [key: string]: any } = {};
Object.keys(props).map((_item) => {
if (whiteList.includes(_item)) {
return;
}
result[_item] = props[_item];
});
return result;
}
const CommodityList: React.FC<Iprops> & { Item: typeof CommodityItem } = (props: Iprops) => {
const { children, className, title, ...other } = props;
const classNameStr = cx(styles.recommand, className);
return (
<div className={classNameStr} {...other}>
<span className={styles.title}>{title}</span>
<div className={styles.container}>
{children}
</div>
</div>
);
};
interface Iprops {
className: string,
}
const CommodityItem: React.FC<Iprops> = (props: Iprops) => {
const { className, ...other } = props;
const classNameStr = cx(className, styles.item);
const rest = omit(other, ["draggable", "getOperateState", "onClick", "onDrag", "onDragEnd", "onDragEnter", "onDragStart", "onMouseOver"]);
return (
<div className={classNameStr} {...other}>
<Commodity {...rest} />
</div>
);
};
CommodityList.Item = CommodityItem;
export default CommodityList;
.container {
padding: 12px 0px 0 8px;
display: flex;
flex-direction: row;
flex-wrap: wrap;
.item {
flex: 50%;
padding-right: 8px;
margin-bottom: 12px;
}
}
import React from 'react';
import { MarketingCard } from '@lingxi-disign/ui';
import cx from 'classnames';
import styles from './index.less';
const { CouponsItem } = MarketingCard;
interface Iprops {
children: React.ReactNode,
className: string,
}
const Coupon: React.FC<Iprops> & { Item: typeof CouponItem } = (props: Iprops) => {
const { children, className, ...other } = props;
const classNameStr = cx(styles.container, className);
return (
<div className={classNameStr} {...other}>
{children}
</div>
);
};
interface ItemIprops {
children: React.ReactNode,
className: string,
}
const CouponItem: React.FC<ItemIprops> = (props: ItemIprops) => {
const { children, className, ...other} = props;
return (
<div className={cx(className, styles.item)} {...other} >
<CouponsItem />
</div>
);
};
Coupon.Item = CouponItem;
export default Coupon;
import Advertisement from './Advertisement';
import Coupon from './Coupon';
import CommodityList from './CommodityList';
export default {
Advertisement,
Coupon,
CommodityList
};
......@@ -15,6 +15,10 @@
justify-content: center;
background-color: #F2F3F5;
height: calc(100vh - 64px);
.tree {
width: 335px;
}
}
.app-wrapper {
......
import React from 'react';
import { Spin, message } from 'antd';
import { BrickProvider } from '@lingxi-disign/react';
import config from '@/pages/pageCustomized/configs';
import { BrickProvider, ModuleTree } from '@lingxi-disign/react';
import MobileDesignPanel from '@/pages/pageCustomized/components/MobileDesignPanel';
import ComponentTree from './components/ComponentTree';
import componentConfigs from './common/configs';
import Toolbar from './components/Toolbar';
import styles from './index.less';
import EditPanel from './components/EditPanel';
import configs from './common/configs/pageConfigs';
// import config from '@/pages/pageCustomized/configs/index'
interface Iprops {
location: {
......@@ -24,7 +26,7 @@ const Fixtures: React.FC<Iprops> = (props: Iprops) => {
return (
<Spin spinning={false}>
<BrickProvider
config={config}
config={configs}
warn={(msg: string) => {
message.warning(msg);
}}
......@@ -33,13 +35,15 @@ const Fixtures: React.FC<Iprops> = (props: Iprops) => {
<Toolbar title="正在编辑:平台营销活动页" />
<div className={styles['content']}>
{/* <AllComponents /> */}
<ComponentTree />
<div className={styles.tree}>
<ModuleTree />
</div>
<div className={styles['app-wrapper']}>
<div className={styles['app-canvas-container']}>
<MobileDesignPanel theme={'theme-mall-science'} onlyEidt pageConfig={componentConfigs} />
</div>
</div>
<EditPanel />
</div>
</div>
......
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