3
.gitignore
vendored
@ -102,3 +102,6 @@ dist
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
.umi/
|
||||
.umi-production/
|
||||
@ -6,26 +6,34 @@ export default defineConfig({
|
||||
loading: '@/components/LoadingCp',
|
||||
},
|
||||
dva: {
|
||||
immer: true
|
||||
immer: true,
|
||||
},
|
||||
devtool: 'source-map',
|
||||
antd: {},
|
||||
sass: {
|
||||
implementation: require('node-sass'),
|
||||
},
|
||||
title: '趣谈前端-h5-visible-tool',
|
||||
title: '趣谈前端-h5-dooring',
|
||||
exportStatic: {},
|
||||
base: 'h5_plus',
|
||||
publicPath: '/h5_plus/',
|
||||
outputPath: '../server/static/h5_plus',
|
||||
|
||||
routes: [
|
||||
{
|
||||
exact: false,
|
||||
path: '/',
|
||||
// component: '@/layouts',
|
||||
component: '@/layouts/index',
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
component: '../pages/home',
|
||||
},
|
||||
{
|
||||
path: '/editor',
|
||||
component: '../pages/editor',
|
||||
},
|
||||
{
|
||||
path: '/ide',
|
||||
component: '../pages/ide',
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
component: '../pages/login',
|
||||
@ -37,22 +45,19 @@ export default defineConfig({
|
||||
{
|
||||
path: '/preview',
|
||||
component: '../pages/editor/preview',
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
"primary-color": "#2F54EB",
|
||||
'primary-color': '#2F54EB',
|
||||
// "btn-primary-bg": "#2F54EB"
|
||||
},
|
||||
extraBabelPlugins: [
|
||||
['import', { libraryName: "zarm", style: true }],
|
||||
],
|
||||
extraBabelPlugins: [['import', { libraryName: 'zarm', style: true }]],
|
||||
// sass: {},
|
||||
alias: {
|
||||
components: path.resolve(__dirname, 'src/components/'),
|
||||
utils: path.resolve(__dirname, 'src/utils/'),
|
||||
assets: path.resolve(__dirname, 'src/assets/')
|
||||
}
|
||||
assets: path.resolve(__dirname, 'src/assets/'),
|
||||
},
|
||||
});
|
||||
|
||||
29315
npm-shrinkwrap.json
generated
85
package.json
@ -1,8 +1,30 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "h5-dooring",
|
||||
"version": "1.3.0",
|
||||
"description": "H5-Dooring是一款功能强大,开源免费的H5可视化页面配置解决方案,致力于提供一套简单方便、专业可靠、无限可能的H5落地页最佳实践。技术栈以react为主, 后台采用nodejs开发。",
|
||||
"private": false,
|
||||
"author": {
|
||||
"name": "徐小夕",
|
||||
"email": "xujiang156@qq.com",
|
||||
"url": "http://io.nainor.com/h5_visible"
|
||||
},
|
||||
"keywords": [
|
||||
"h5 editor",
|
||||
"h5",
|
||||
"react",
|
||||
"antd",
|
||||
"react-dnd",
|
||||
"web visible"
|
||||
],
|
||||
"contributors": [
|
||||
"徐小夕 <xujiang156@qq.com> (https://github.com/MrXujiang))",
|
||||
"yehuozhili <yehuozhili@outlook.com> (https://github.com/yehuozhili))"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "umi dev",
|
||||
"build": "umi build",
|
||||
"server": "node server.js",
|
||||
"postinstall": "umi generate tmp",
|
||||
"prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
|
||||
"test": "umi-test",
|
||||
"test:coverage": "umi-test --coverage"
|
||||
@ -18,32 +40,77 @@
|
||||
"prettier --parser=typescript --write"
|
||||
]
|
||||
},
|
||||
"homepage": "http://io.nainor.com/h5_visible",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/MrXujiang/h5-Dooring.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/MrXujiang/h5-Dooring/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/charts": "^0.9.9",
|
||||
"@ant-design/icons": "^4.2.1",
|
||||
"@antv/f2": "^3.7.7",
|
||||
"@umijs/plugin-sass": "^1.1.1",
|
||||
"@umijs/preset-react": "1.x",
|
||||
"@umijs/test": "^3.0.12",
|
||||
"@umijs/test": "^3.2.19",
|
||||
"antd": "^4.2.3",
|
||||
"antd-img-crop": "^3.10.0",
|
||||
"axios": "^0.19.2",
|
||||
"babel-plugin-import": "^1.13.0",
|
||||
"chatbot-antd": "^0.6.0",
|
||||
"codemirror": "^5.57.0",
|
||||
"file-saver": "^2.0.2",
|
||||
"lint-staged": "^10.0.7",
|
||||
"node-sass": "^4.14.1",
|
||||
"prettier": "^1.19.1",
|
||||
"qrcode.react": "^1.0.0",
|
||||
"react": "^16.12.0",
|
||||
"react-codemirror2": "^7.2.1",
|
||||
"react-color": "^2.18.1",
|
||||
"react-dnd": "^11.1.3",
|
||||
"react-dnd-html5-backend": "^11.1.3",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-draggable": "^4.4.3",
|
||||
"react-grid-layout": "^1.0.0",
|
||||
"sass-loader": "^9.0.3",
|
||||
"umi": "^3.0.12",
|
||||
"react-hotkeys-hook": "^2.3.1",
|
||||
"react-text-loop": "^2.3.0",
|
||||
"redux-undo": "^1.0.1",
|
||||
"socket.io-client": "^2.3.0",
|
||||
"umi": "^3.2.19",
|
||||
"video-react": "^0.14.1",
|
||||
"xlsx": "^0.16.7",
|
||||
"yorkie": "^2.0.0",
|
||||
"zarm": "^2.5.1"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/qrcode.react": "^1.0.1",
|
||||
"@types/classnames": "^2.2.10",
|
||||
"@types/codemirror": "^0.0.98",
|
||||
"@types/events": "^3.0.0",
|
||||
"@types/file-saver": "^2.0.1",
|
||||
"@types/node": "^14.6.2",
|
||||
"@types/react-color": "^3.0.4",
|
||||
"@types/react-grid-layout": "^1.1.0",
|
||||
"@types/redux-logger": "^3.0.8",
|
||||
"@types/xlsx": "^0.0.36",
|
||||
"@typescript-eslint/eslint-plugin": "4.1.1",
|
||||
"@typescript-eslint/parser": "4.1.1",
|
||||
"babel-eslint": "10.x",
|
||||
"babel-plugin-import": "^1.13.0",
|
||||
"eslint": "6.x",
|
||||
"eslint-config-react-app": "^5.2.1",
|
||||
"eslint-plugin-flowtype": "4.x",
|
||||
"eslint-plugin-import": "2.x",
|
||||
"eslint-plugin-jsx-a11y": "6.x",
|
||||
"eslint-plugin-react": "7.x",
|
||||
"eslint-plugin-react-hooks": "2.x",
|
||||
"koa": "^2.13.0",
|
||||
"koa-body": "^4.2.0",
|
||||
"koa-logger": "^3.2.1",
|
||||
"koa-static": "^5.0.0",
|
||||
"koa2-cors": "^2.0.6",
|
||||
"lint-staged": "^10.0.7",
|
||||
"prettier": "^1.19.1",
|
||||
"redux-logger": "^3.0.6",
|
||||
"sass-loader": "^9.0.3",
|
||||
"typescript": "^4.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
226
readme.md
@ -15,7 +15,7 @@
|
||||
|
||||
### ✨ [Demo](http://io.nainor.com/h5_plus/editor?tid=123456)
|
||||
|
||||
<img src="http://io.nainor.com/uploads/56_1741c466be0.png" alt="H5可视化编辑器" />
|
||||
<img src="http://io.nainor.com/uploads/demo_1748b27f0e4.png" alt="H5可视化编辑器" />
|
||||
|
||||
## Author
|
||||
|
||||
@ -46,171 +46,13 @@ Give a ⭐️ if this project helped you!
|
||||
* **@koa/router** 基于koa2的服务端路由中间件
|
||||
* **ramda** 优秀的函数式js工具库
|
||||
|
||||
## 需求分析
|
||||
在思考需求分析之前我们先来看看**Dooring**的使用演示:
|
||||

|
||||
由上面的gif图我们可以分析出,可视化编辑器主要有以下几部分组成:
|
||||
* 可拖拽的组件库 draggable components
|
||||
* 盛放组件的画布 canvas
|
||||
* 组件编辑器 FormEditor
|
||||
* 头部工具栏 toolBar
|
||||
|
||||
可拖拽组件我们可以用社区比较火的**react-dnd**,**react-draggable**来实现,由于我们的画布是可拖拽可放大缩小的,所以这里需要对画布赋能,具体实现可参考下文。
|
||||
|
||||
其次就是H5编辑器部分,这部分是核心功能,后面我们会详细分析。还有就是预览,生成预览链接,保存**json**文件, 保存模版这些功能本质上是对我们**json**文件的操作,可是目前可视化搭建技术的常用手段之一。先来看看这些功能的演示:
|
||||

|
||||
|
||||
## 基础准备
|
||||
我们的**h5页面可视化编辑器**采用**umi**来作为脚手架工具.
|
||||
> **umi**是可扩展的企业级前端应用框架,以路由为基础的,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展。然后配以生命周期完善的插件体系,覆盖从源码到构建产物的每个生命周期,支持各种功能扩展和业务需求.
|
||||
|
||||
这样我们不会关注繁琐的工程配置细节, 可以直接在项目中使用 **antd** 和 **less** 这些方案, 并且集成了目前比较流行的**css module**, 可以方便我们在项目里对**css**进行模块化开发. umi创建项目的具体使用流程如下:
|
||||
``` js
|
||||
// 创建并进入工程目录
|
||||
mkdir dooring && cd dooring
|
||||
// 创建umi应用
|
||||
yarn create @umijs/umi-app
|
||||
// 安装依赖
|
||||
yarn // 或者使用npm install
|
||||
```
|
||||
简单的三步走策略就能轻松搭建我们的项目工程, 是不是省去了很多麻烦? (在使用这些方式之前我们首先确保自己本地的**node** 版本是 10.13 或以上)
|
||||
|
||||
在项目创建完之后我们还需要安装可视化方面必备的第三方组件, 笔者调研社区精选组件之后采用了一下方案:
|
||||
* **react-dnd** react拖拽组件
|
||||
* **react-color** react颜色选择组件,用于H5编辑器的编辑颜色部分
|
||||
* **react-draggable** 用于组件或者画布的拖拽移动
|
||||
* **react.qrcode** 基于react的二维码生成组件, 能以react组件的方式生成二维码
|
||||
|
||||
以上组件在运行项目前大家可以自行安装.
|
||||
|
||||
## 介绍
|
||||
在最好项目开发准备之后,我们就来开始设计我们的h5页面可视化编辑器-**Dooring**.
|
||||
|
||||
### H5编辑器实现
|
||||
H5可视化编辑器主要需要4个部分,在文章开头也分析过, 这里用图来巩固一下:
|
||||

|
||||
以上是最基本也是最核心的功能展示模型,接下来我们会一一将其拆解并逐个实现.
|
||||
#### 实现原理
|
||||
我们都知道, 目前比较流行的页面可视化搭建方案可以有如下几种:
|
||||
* 在线编辑代码实现
|
||||
* 在线编辑json实现
|
||||
* 无代码化拖拽实现(底层基于json配置文件)
|
||||
|
||||
笔者做了一下优缺点对比图,如下:
|
||||
|
||||
| 方案 | 定制化程度 | 缺点 |
|
||||
| :--------- | :--: | -----------: |
|
||||
| 在线编辑代码 | 最高 | 使用成本高,对非技术人员不友好,效率低 |
|
||||
| 在线编辑json | 较高 | 需要熟悉json,有一定使用成本, 对非技术人员不友好,效率一般 |
|
||||
| 无代码化拖拽实现 | 高 | 使用成本低, 操作基本无门槛,效率较高 |
|
||||
|
||||
由以上分析来看, 为了开发一个低门槛, 对任何人适用的可视化编辑器, 笔者将采用第三种方案来实现, 目前市面上已有的产品也有很多, 比如说易企秀, 兔展, 百度H5等等. **实现原理其实还是基于json, 我们通过可视化的手段将自己配置的 页面转化为json数据,最后在基于json渲染器来动态生成H5站点**.
|
||||

|
||||
|
||||
#### 数据结构设计
|
||||
为了提供组件的自定义能力,我们需要定义一套高可用的数据结构, 这样才能实现因组件需求变更而带来的维护性优势.
|
||||
|
||||
在开始设计数据结构之前我们先来拆解一下模块:
|
||||

|
||||
不同的组件都对应不同的"编辑区域".我们需要设计一套统一的标准的配置来约定它, 这样对于表单编辑器的设计也非常有利, 具体拆解如下:
|
||||

|
||||
|
||||
经过以上分析之后, 笔者设计了类似下面的数据结构:
|
||||
``` js
|
||||
"Text": {
|
||||
"editData": [
|
||||
{
|
||||
"key": "text",
|
||||
"name": "文字",
|
||||
"type": "Text"
|
||||
},
|
||||
{
|
||||
"key": "color",
|
||||
"name": "标题颜色",
|
||||
"type": "Color"
|
||||
},
|
||||
{
|
||||
"key": "fontSize",
|
||||
"name": "字体大小",
|
||||
"type": "Number"
|
||||
},
|
||||
{
|
||||
"key": "align",
|
||||
"name": "对齐方式",
|
||||
"type": "Select",
|
||||
"range": [
|
||||
{
|
||||
"key": "left",
|
||||
"text": "左对齐"
|
||||
},
|
||||
{
|
||||
"key": "center",
|
||||
"text": "居中对齐"
|
||||
},
|
||||
{
|
||||
"key": "right",
|
||||
"text": "右对齐"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "lineHeight",
|
||||
"name": "行高",
|
||||
"type": "Number"
|
||||
}
|
||||
],
|
||||
"config": {
|
||||
"text": "我是文本",
|
||||
"color": "rgba(60,60,60,1)",
|
||||
"fontSize": 18,
|
||||
"align": "center",
|
||||
"lineHeight": 2
|
||||
}
|
||||
}
|
||||
```
|
||||
通过这种标准化结构设计之后,我们可以很方便的实现我们所需要的编辑页面的功能, 并且后期扩展非常方便, 只需要往editData添加配置即可. 至于动态表单编辑器的实现,方案有很多, 笔者之前也写过相关的文章, 这里就不详细介绍了.
|
||||
|
||||
[基于react搭建一个通用的表单管理配置平台(vue同)](https://juejin.im/post/6844904137390292999)
|
||||
|
||||
#### 组件库设计
|
||||
组件库设计考虑的一个重要的问题就是体积和渲染问题, 一旦组件库变的越来越多, 那意味着页面加载会非常慢,所以我们需要实现异步加载组件和代码分割的能力, umi提供了这样的功能,我们可以基于它提供的api去实现自己的额按需组件.
|
||||
``` js
|
||||
import { dynamic } from 'umi';
|
||||
|
||||
export default dynamic({
|
||||
loader: async function() {
|
||||
// 这里的注释 webpackChunkName 可以指导 webpack 将该组件 HugeA 以这个名字单独拆出去
|
||||
const { default: HugeA } = await import(/* webpackChunkName: "external_A" */ './HugeA');
|
||||
return HugeA;
|
||||
},
|
||||
});
|
||||
```
|
||||
通过以上的方式来定义包裹我们的每一个组件, 这样就能实现按需加载了, 但是最好的建议是不需要每个组件都按需加载和拆包,对于**标题**, **通知栏**,**页头**,**页脚**这些组件, 我们完全可以把它放在一个组里,这样不但对不会影响加载速度, 还能减少一定的http请求.
|
||||
|
||||
笔者这里简单举一个组件实现的例子,方便大家理解:
|
||||
``` js
|
||||
const Header = memo((props) => {
|
||||
const {
|
||||
bgColor,
|
||||
logo,
|
||||
logoText,
|
||||
fontSize,
|
||||
color
|
||||
} = props
|
||||
return <header className={styles.header} style={{backgroundColor: bgColor}}>
|
||||
<div className={styles.logo}>
|
||||
<img src={logo && logo[0].url} alt={logoText} />
|
||||
</div>
|
||||
<div className={styles.title} style={{fontSize, color}}>{ logoText }</div>
|
||||
</header>
|
||||
})
|
||||
```
|
||||
上面的Header组件的props属性完全是由我们之前设计的json结构来定义的,在用户编辑的过程中将收据收集并传给Header组件。最后一步是将这些组件动态传给**dynamic**组件, 这块在上文也介绍过了,大家可以根据自己的实现来做动态化渲染。
|
||||
|
||||
### 实现预览功能
|
||||
### 预览功能
|
||||
预览功能这块比较简单, 我们只需要将用户生成的json数据丢进H5渲染器中即可, 这里我们需要做一个渲染页面单独用来预览组件. 先来看看几个预览效果:
|
||||
|
||||
<img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/582e67cb0a874bee947efb05af622a55~tplv-k3u1fbpfcp-zoom-1.image" alt="h5-editor" width="375px" />
|
||||
<br />
|
||||
<img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7c50d89f770f4ae5b4c5c4ec9f0052c1~tplv-k3u1fbpfcp-zoom-1.image" alt="h5-editor" width="375px" />
|
||||
|
||||
前面的渲染器原理已经介绍了, 这里就不一一介绍了,感兴趣的可以交流讨论.
|
||||
|
||||
### 实现在线下载功能
|
||||
@ -222,7 +64,7 @@ FileSaver.saveAs(blob, "hello world.txt");
|
||||
```
|
||||
以上代码可以实现将传入的数据下载为txt文件, 如果是Blob, 是不是还能在线下载图片, html呢? 答案是肯定的, 所以我们的下载任务采用该方案来实现.
|
||||
|
||||
### 后端部分实现
|
||||
### 后端部分
|
||||
后端部分由于涉及的知识点比较多, 不是本文考虑的重点, 所以这里大致提几个点, 大家可以用完全不同的技术来实现后台服务, 比如说**PHP**, **Java**, **Python**或者**Egg**. 笔者这里采用的是**koa**. 主要实现功能如下:
|
||||
* 保存模板
|
||||
* 真机原理的数据源存储
|
||||
@ -235,40 +77,70 @@ FileSaver.saveAs(blob, "hello world.txt");
|
||||
|
||||
模式基本一致.
|
||||
|
||||
## wiki(参考文档)
|
||||
* [H5可视化编辑器(H5 Dooring)介绍](https://github.com/MrXujiang/h5-Dooring/wiki/H5%E5%8F%AF%E8%A7%86%E5%8C%96%E7%BC%96%E8%BE%91%E5%99%A8(H5-Dooring)%E4%BB%8B%E7%BB%8D)
|
||||
* [Form Editor(动态表单设计器)](https://github.com/MrXujiang/h5-Dooring/wiki/Form-Editor(%E5%8A%A8%E6%80%81%E8%A1%A8%E5%8D%95%E8%AE%BE%E8%AE%A1%E5%99%A8))
|
||||
* [基于f2实现移动端可视化编辑器(dooring升级版)](https://github.com/MrXujiang/h5-Dooring/wiki/%E5%9F%BA%E4%BA%8Ef2%E5%AE%9E%E7%8E%B0%E7%A7%BB%E5%8A%A8%E7%AB%AF%E5%8F%AF%E8%A7%86%E5%8C%96%E7%BC%96%E8%BE%91%E5%99%A8(dooring%E5%8D%87%E7%BA%A7%E7%89%88))
|
||||
|
||||
## 已完成功能
|
||||
* 1. 组件库拖拽和显示
|
||||
* 2. 组件库动态编辑
|
||||
* 3. H5页面预览功能
|
||||
* 4. 保存H5页面配置文件
|
||||
* 5. 保存为模版
|
||||
* 6. 移动端跨端适配
|
||||
* 7. 媒体组件
|
||||
1. 组件库拖拽和显示
|
||||
2. 组件库动态编辑
|
||||
3. H5页面预览功能
|
||||
4. 保存H5页面配置文件
|
||||
5. 保存为模版
|
||||
6. 移动端跨端适配
|
||||
7. 媒体组件
|
||||
8. 在线下载网站代码功能
|
||||
9. 添加typescript支持
|
||||
10. 表单设计器/自定义表单组件
|
||||
11. 可视化组件Chart实现
|
||||
12. 在线编程模块(Mini Web IDE)
|
||||
13. 新增图表组件 面积图,折线图, 饼图
|
||||
|
||||
## 正在完成功能
|
||||
* 添加模版库模块
|
||||
* 添加在线下载网站代码功能
|
||||
* 丰富组件库组件,添加可视化组件
|
||||
* 升级模版库
|
||||
* 丰富组件库组件,添加可视化组件如折线图, 饼图, 面积图等
|
||||
* 添加配置交互功能
|
||||
* 组件细分和代码优化
|
||||
* 添加typescript支持和单元测试
|
||||
* 单元测试
|
||||
|
||||
## Install(安装)
|
||||
1. 下载代码
|
||||
```sh
|
||||
git clone https://github.com/MrXujiang/h5-Dooring.git
|
||||
```
|
||||
2. 进入项目目录
|
||||
```sh
|
||||
cd ./h5-Dooring
|
||||
```
|
||||
|
||||
3. 安装依赖包
|
||||
```sh
|
||||
yarn install
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
启动应用
|
||||
```sh
|
||||
yarn run start
|
||||
```
|
||||
|
||||
## 更新日志
|
||||
1. 添加在线编程模块(在执行代码前先启动node服务 npm run server)
|
||||
2. 添加客服机器人模块[chatbot-antd](https://www.npmjs.com/package/chatbot-antd)
|
||||
|
||||
|
||||
## 持续升级
|
||||
正在升级1.1版本,敬请期待...
|
||||
正在升级1.3版本,敬请期待...
|
||||
|
||||
## 赞助
|
||||
开源不易, 有了您的赞助, 我们会做的更好~
|
||||
|
||||
## 技术反馈和交流
|
||||
微信:beautifulFront
|
||||
|
||||
|
||||
<img src="http://io.nainor.com/uploads/code_1741c445027.png" width="180px" />
|
||||
|
||||
## 技术交流群
|
||||
<img src="http://io.nainor.com/uploads/i_1749a2713f0.png" width="180px" />
|
||||
|
||||
50
server.js
Normal file
@ -0,0 +1,50 @@
|
||||
const Koa = require('koa');
|
||||
const { resolve } = require('path');
|
||||
const staticServer = require('koa-static');
|
||||
const koaBody = require('koa-body');
|
||||
const cors = require('koa2-cors');
|
||||
const logger = require('koa-logger');
|
||||
|
||||
const app = new Koa();
|
||||
|
||||
app.use(staticServer(resolve(__dirname, './static')));
|
||||
app.use(koaBody());
|
||||
app.use(logger());
|
||||
|
||||
// 设置跨域
|
||||
app.use(
|
||||
cors({
|
||||
origin: function(ctx) {
|
||||
if (ctx.url.indexOf('/dooring') > -1) {
|
||||
return '*'; // 允许来自所有域名请求
|
||||
}
|
||||
return '';
|
||||
},
|
||||
exposeHeaders: ['WWW-Authenticate', 'Server-Authorization', 'x-test-code'],
|
||||
maxAge: 5, // 该字段可选,用来指定本次预检请求的有效期,单位为秒
|
||||
credentials: true,
|
||||
allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
allowHeaders: [
|
||||
'Content-Type',
|
||||
'Authorization',
|
||||
'Accept',
|
||||
'x-requested-with',
|
||||
'Content-Encoding',
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
let htmlStr = '';
|
||||
|
||||
app.use(async (ctx, next) => {
|
||||
console.log(ctx.url);
|
||||
if (ctx.url === '/dooring/render') {
|
||||
htmlStr = ctx.request.body;
|
||||
ctx.body = 'success';
|
||||
} else if (ctx.url.indexOf('/html') === 0) {
|
||||
ctx.type = 'html';
|
||||
ctx.body = htmlStr;
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
@ -1,51 +0,0 @@
|
||||
// @ts-nocheck
|
||||
|
||||
if (window.g_initWebpackHotDevClient) {
|
||||
function tryApplyUpdates(onHotUpdateSuccess?: Function) {
|
||||
// @ts-ignore
|
||||
if (!module.hot) {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
function isUpdateAvailable() {
|
||||
// @ts-ignore
|
||||
return window.g_getMostRecentCompilationHash() !== __webpack_hash__;
|
||||
}
|
||||
|
||||
// TODO: is update available?
|
||||
// @ts-ignore
|
||||
if (!isUpdateAvailable() || module.hot.status() !== 'idle') {
|
||||
return;
|
||||
}
|
||||
|
||||
function handleApplyUpdates(err: Error | null, updatedModules: any) {
|
||||
if (err || !updatedModules || window.g_getHadRuntimeError()) {
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
onHotUpdateSuccess?.();
|
||||
|
||||
if (isUpdateAvailable()) {
|
||||
// While we were updating, there was a new update! Do it again.
|
||||
tryApplyUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
module.hot.check(true).then(
|
||||
function (updatedModules: any) {
|
||||
handleApplyUpdates(null, updatedModules);
|
||||
},
|
||||
function (err: Error) {
|
||||
handleApplyUpdates(err, null);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
window.g_initWebpackHotDevClient({
|
||||
tryApplyUpdates,
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import { createBrowserHistory } from '/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/node_modules/@umijs/runtime';
|
||||
|
||||
let options = {
|
||||
"basename": "h5_plus"
|
||||
};
|
||||
if ((<any>window).routerBase) {
|
||||
options.basename = (<any>window).routerBase;
|
||||
}
|
||||
|
||||
// remove initial history because of ssr
|
||||
let history: any = process.env.__IS_SERVER ? null : createBrowserHistory(options);
|
||||
export const createHistory = (hotReload = false) => {
|
||||
if (!hotReload) {
|
||||
history = createBrowserHistory(options);
|
||||
}
|
||||
|
||||
return history;
|
||||
};
|
||||
|
||||
// 通常仅微前端场景需要调用这个 API
|
||||
export const setCreateHistoryOptions = (newOpts: any = {}) => {
|
||||
options = { ...options, ...newOpts };
|
||||
};
|
||||
|
||||
export { history };
|
||||
@ -1,8 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import { Plugin } from '/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/node_modules/@umijs/runtime';
|
||||
|
||||
const plugin = new Plugin({
|
||||
validKeys: ['modifyClientRenderOpts','patchRoutes','rootContainer','render','onRouteChange','dva','getInitialState','request',],
|
||||
});
|
||||
|
||||
export { plugin };
|
||||
210
src/.umi/core/pluginConfig.d.ts
vendored
@ -1,210 +0,0 @@
|
||||
/** Created by Umi Plugin **/
|
||||
|
||||
export interface IConfigFromPlugins {
|
||||
routes?: {
|
||||
/**
|
||||
* Any valid URL path
|
||||
*/
|
||||
path?: string;
|
||||
/**
|
||||
* A React component to render only when the location matches.
|
||||
*/
|
||||
component?: string | (() => any);
|
||||
wrappers?: string[];
|
||||
/**
|
||||
* navigate to a new location
|
||||
*/
|
||||
redirect?: string;
|
||||
/**
|
||||
* When true, the active class/style will only be applied if the location is matched exactly.
|
||||
*/
|
||||
exact?: boolean;
|
||||
routes?: any[];
|
||||
[k: string]: any;
|
||||
}[];
|
||||
history?: {
|
||||
type?: "browser" | "hash" | "memory";
|
||||
options?: {};
|
||||
};
|
||||
polyfill?: {
|
||||
imports?: string[];
|
||||
};
|
||||
alias?: {};
|
||||
analyze?: {
|
||||
analyzerMode?: "server" | "static" | "disabled";
|
||||
analyzerHost?: string;
|
||||
analyzerPort?: any;
|
||||
openAnalyzer?: boolean;
|
||||
generateStatsFile?: boolean;
|
||||
statsFilename?: string;
|
||||
logLevel?: "info" | "warn" | "error" | "silent";
|
||||
defaultSizes?: "stat" | "parsed" | "gzip";
|
||||
[k: string]: any;
|
||||
};
|
||||
/**
|
||||
* postcss autoprefixer, default flexbox: no-2009
|
||||
*/
|
||||
autoprefixer?: {};
|
||||
base?: string;
|
||||
chainWebpack?: () => any;
|
||||
chunks?: string[];
|
||||
/**
|
||||
* more css-loader options see https://webpack.js.org/loaders/css-loader/#options
|
||||
*/
|
||||
cssLoader?: {
|
||||
url?: boolean | (() => any);
|
||||
import?: boolean | (() => any);
|
||||
modules?: boolean | string | {};
|
||||
sourceMap?: boolean;
|
||||
importLoaders?: number;
|
||||
onlyLocals?: boolean;
|
||||
esModule?: boolean;
|
||||
localsConvention?: "asIs" | "camelCase" | "camelCaseOnly" | "dashes" | "dashesOnly";
|
||||
};
|
||||
cssModulesTypescriptLoader?: {
|
||||
mode?: "emit" | "verify";
|
||||
};
|
||||
cssnano?: {};
|
||||
copy?: string[];
|
||||
define?: {};
|
||||
devScripts?: {};
|
||||
/**
|
||||
* devServer configs
|
||||
*/
|
||||
devServer?: {
|
||||
/**
|
||||
* devServer port, default 8000
|
||||
*/
|
||||
port?: number;
|
||||
host?: string;
|
||||
https?:
|
||||
| {
|
||||
key?: string;
|
||||
cert?: string;
|
||||
[k: string]: any;
|
||||
}
|
||||
| boolean;
|
||||
headers?: {};
|
||||
writeToDisk?: boolean | (() => any);
|
||||
[k: string]: any;
|
||||
};
|
||||
devtool?: string;
|
||||
/**
|
||||
* Code splitting for performance optimization
|
||||
*/
|
||||
dynamicImport?: {
|
||||
/**
|
||||
* loading the component before loaded
|
||||
*/
|
||||
loading?: string;
|
||||
};
|
||||
exportStatic?: {
|
||||
htmlSuffix?: boolean;
|
||||
dynamicRoot?: boolean;
|
||||
/**
|
||||
* extra render paths only enable in ssr
|
||||
*/
|
||||
extraRoutePaths?: () => any;
|
||||
};
|
||||
externals?: {} | string | (() => any);
|
||||
extraBabelPlugins?: any[];
|
||||
extraBabelPresets?: any[];
|
||||
extraPostCSSPlugins?: any[];
|
||||
/**
|
||||
* fork-ts-checker-webpack-plugin options see https://github.com/TypeStrong/fork-ts-checker-webpack-plugin#options
|
||||
*/
|
||||
forkTSChecker?: {
|
||||
async?: boolean;
|
||||
typescript?: boolean | {};
|
||||
eslint?: {};
|
||||
issue?: {};
|
||||
formatter?: string | {};
|
||||
logger?: {};
|
||||
[k: string]: any;
|
||||
};
|
||||
hash?: boolean;
|
||||
ignoreMomentLocale?: boolean;
|
||||
inlineLimit?: number;
|
||||
lessLoader?: {};
|
||||
manifest?: {
|
||||
fileName?: string;
|
||||
publicPath?: "";
|
||||
basePath?: string;
|
||||
writeToFileEmit?: boolean;
|
||||
};
|
||||
mountElementId?: "";
|
||||
mpa?: {};
|
||||
nodeModulesTransform?: {
|
||||
type?: "all" | "none";
|
||||
exclude?: string[];
|
||||
};
|
||||
outputPath?: "";
|
||||
plugins?: string[];
|
||||
postcssLoader?: {};
|
||||
presets?: string[];
|
||||
proxy?: {};
|
||||
publicPath?: string;
|
||||
runtimePublicPath?: boolean;
|
||||
ssr?: {
|
||||
/**
|
||||
* remove window.g_initialProps in html, to force execing Page getInitialProps functions
|
||||
*/
|
||||
forceInitial?: boolean;
|
||||
/**
|
||||
* disable serve-side render in umi dev mode.
|
||||
*/
|
||||
devServerRender?: boolean;
|
||||
mode?: "stream" | "string";
|
||||
/**
|
||||
* static markup in static site
|
||||
*/
|
||||
staticMarkup?: boolean;
|
||||
};
|
||||
singular?: boolean;
|
||||
styleLoader?: {};
|
||||
targets?: {};
|
||||
terserOptions?: {};
|
||||
theme?: {};
|
||||
runtimeHistory?: {};
|
||||
favicon?: string;
|
||||
headScripts?: any[];
|
||||
links?: any[];
|
||||
metas?: any[];
|
||||
scripts?: any[];
|
||||
styles?: any[];
|
||||
title?: string;
|
||||
mock?: {
|
||||
exclude?: string[];
|
||||
};
|
||||
antd?: {
|
||||
dark?: boolean;
|
||||
compact?: boolean;
|
||||
config?: {};
|
||||
};
|
||||
dva?: {
|
||||
immer?: boolean;
|
||||
hmr?: boolean;
|
||||
skipModelValidate?: boolean;
|
||||
extraModels?: string[];
|
||||
};
|
||||
locale?: {
|
||||
default?: string;
|
||||
useLocalStorage?: boolean;
|
||||
baseNavigator?: boolean;
|
||||
title?: boolean;
|
||||
antd?: boolean;
|
||||
baseSeparator?: string;
|
||||
};
|
||||
layout?: {};
|
||||
request?: {
|
||||
dataField?: "";
|
||||
};
|
||||
sass?: {
|
||||
implementation?: any;
|
||||
sassOptions?: {};
|
||||
prependData?: string | (() => any);
|
||||
sourceMap?: boolean;
|
||||
webpackImporter?: boolean;
|
||||
};
|
||||
[k: string]: any;
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import { plugin } from './plugin';
|
||||
import * as Plugin_0 from '/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/src/.umi/plugin-dva/runtime.tsx';
|
||||
import * as Plugin_1 from '../plugin-initial-state/runtime';
|
||||
import * as Plugin_2 from '../plugin-model/runtime';
|
||||
|
||||
plugin.register({
|
||||
apply: Plugin_0,
|
||||
path: '/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/src/.umi/plugin-dva/runtime.tsx',
|
||||
});
|
||||
plugin.register({
|
||||
apply: Plugin_1,
|
||||
path: '../plugin-initial-state/runtime',
|
||||
});
|
||||
plugin.register({
|
||||
apply: Plugin_2,
|
||||
path: '../plugin-model/runtime',
|
||||
});
|
||||
@ -1,3 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import 'core-js';
|
||||
import 'regenerator-runtime/runtime';
|
||||
@ -1,38 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import { ApplyPluginsType, dynamic } from '/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/node_modules/@umijs/runtime';
|
||||
import { plugin } from './plugin';
|
||||
import LoadingComponent from '@/components/LoadingCp';
|
||||
|
||||
export function getRoutes() {
|
||||
const routes = [
|
||||
{
|
||||
"path": "/",
|
||||
"routes": [
|
||||
{
|
||||
"path": "/editor",
|
||||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__editor' */'/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/src/pages/editor'), loading: LoadingComponent}),
|
||||
"exact": true
|
||||
},
|
||||
{
|
||||
"path": "/login",
|
||||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__login' */'/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/src/pages/login'), loading: LoadingComponent}),
|
||||
"exact": true
|
||||
},
|
||||
{
|
||||
"path": "/preview",
|
||||
"component": dynamic({ loader: () => import(/* webpackChunkName: 'p__editor__preview' */'/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/src/pages/editor/preview'), loading: LoadingComponent}),
|
||||
"exact": true
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// allow user to extend routes
|
||||
plugin.applyPlugins({
|
||||
key: 'patchRoutes',
|
||||
type: ApplyPluginsType.event,
|
||||
args: { routes },
|
||||
});
|
||||
|
||||
return routes;
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
// @ts-nocheck
|
||||
export { history, setCreateHistoryOptions } from './history';
|
||||
export { plugin } from './plugin';
|
||||
export * from '../plugin-dva/exports';
|
||||
export * from '../plugin-dva/connect';
|
||||
export * from '../plugin-initial-state/exports';
|
||||
export * from '../plugin-model/useModel';
|
||||
export * from '../plugin-request/request';
|
||||
export * from '../plugin-helmet/exports';
|
||||
@ -1,83 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import { IRoute } from '@umijs/core';
|
||||
import { AnyAction } from 'redux';
|
||||
import React from 'react';
|
||||
import { EffectsCommandMap, SubscriptionAPI } from 'dva';
|
||||
import { match } from 'react-router-dom';
|
||||
import { Location, LocationState, History } from 'history';
|
||||
|
||||
export * from '/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/src/pages/editor/models/editorModal';
|
||||
|
||||
export interface Action<T = any> {
|
||||
type: T
|
||||
}
|
||||
|
||||
export type Reducer<S = any, A extends Action = AnyAction> = (
|
||||
state: S | undefined,
|
||||
action: A
|
||||
) => S;
|
||||
|
||||
export type ImmerReducer<S = any, A extends Action = AnyAction> = (
|
||||
state: S,
|
||||
action: A
|
||||
) => void;
|
||||
|
||||
export type Effect = (
|
||||
action: AnyAction,
|
||||
effects: EffectsCommandMap,
|
||||
) => void;
|
||||
|
||||
/**
|
||||
* @type P: Type of payload
|
||||
* @type C: Type of callback
|
||||
*/
|
||||
export type Dispatch = <P = any, C = (payload: P) => void>(action: {
|
||||
type: string;
|
||||
payload?: P;
|
||||
callback?: C;
|
||||
[key: string]: any;
|
||||
}) => any;
|
||||
|
||||
export type Subscription = (api: SubscriptionAPI, done: Function) => void | Function;
|
||||
|
||||
export interface Loading {
|
||||
global: boolean;
|
||||
effects: { [key: string]: boolean | undefined };
|
||||
models: {
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @type P: Params matched in dynamic routing
|
||||
*/
|
||||
export interface ConnectProps<
|
||||
P extends { [K in keyof P]?: string } = {},
|
||||
S = LocationState,
|
||||
T = {}
|
||||
> {
|
||||
dispatch?: Dispatch;
|
||||
// https://github.com/umijs/umi/pull/2194
|
||||
match?: match<P>;
|
||||
location: Location<S> & { query: T };
|
||||
history: History;
|
||||
route: IRoute;
|
||||
}
|
||||
|
||||
export type RequiredConnectProps<
|
||||
P extends { [K in keyof P]?: string } = {},
|
||||
S = LocationState,
|
||||
T = {}
|
||||
> = Required<ConnectProps<P, S, T>>
|
||||
|
||||
/**
|
||||
* @type T: React props
|
||||
* @type U: match props types
|
||||
*/
|
||||
export type ConnectRC<
|
||||
T = {},
|
||||
U = {},
|
||||
S = {},
|
||||
Q = {}
|
||||
> = React.ForwardRefRenderFunction<any, T & RequiredConnectProps<U, S, Q>>;
|
||||
|
||||
@ -1,68 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import { Component } from 'react';
|
||||
import { ApplyPluginsType } from 'umi';
|
||||
import dva from 'dva';
|
||||
// @ts-ignore
|
||||
import createLoading from '/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/node_modules/dva-loading/dist/index.esm.js';
|
||||
import { plugin, history } from '../core/umiExports';
|
||||
|
||||
let app:any = null;
|
||||
|
||||
export function _onCreate(options = {}) {
|
||||
const runtimeDva = plugin.applyPlugins({
|
||||
key: 'dva',
|
||||
type: ApplyPluginsType.modify,
|
||||
initialValue: {},
|
||||
});
|
||||
app = dva({
|
||||
history,
|
||||
|
||||
...(runtimeDva.config || {}),
|
||||
// @ts-ignore
|
||||
...(typeof window !== 'undefined' && window.g_useSSR ? { initialState: window.g_initialProps } : {}),
|
||||
...(options || {}),
|
||||
});
|
||||
|
||||
app.use(createLoading());
|
||||
app.use(require('/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/node_modules/dva-immer/dist/index.js')());
|
||||
(runtimeDva.plugins || []).forEach((plugin:any) => {
|
||||
app.use(plugin);
|
||||
});
|
||||
app.model({ namespace: 'editorModal', ...(require('/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/src/pages/editor/models/editorModal.js').default) });
|
||||
return app;
|
||||
}
|
||||
|
||||
export function getApp() {
|
||||
return app;
|
||||
}
|
||||
|
||||
export class _DvaContainer extends Component {
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
// run only in client, avoid override server _onCreate()
|
||||
if (typeof window !== 'undefined') {
|
||||
_onCreate();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
let app = getApp();
|
||||
app._models.forEach((model:any) => {
|
||||
app.unmodel(model.namespace);
|
||||
});
|
||||
app._models = [];
|
||||
try {
|
||||
// 释放 app,for gc
|
||||
// immer 场景 app 是 read-only 的,这里 try catch 一下
|
||||
app = null;
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const app = getApp();
|
||||
app.router(() => this.props.children);
|
||||
return app.start()();
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
// @ts-nocheck
|
||||
|
||||
export { connect, useDispatch, useStore, useSelector } from '/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/node_modules/dva';
|
||||
export { getApp as getDvaApp } from './dva';
|
||||
@ -1,8 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { _DvaContainer, getApp, _onCreate } from './dva';
|
||||
|
||||
export function rootContainer(container) {
|
||||
return React.createElement(_DvaContainer, null, container);
|
||||
}
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
// @ts-nocheck
|
||||
// @ts-ignore
|
||||
export { Helmet } from '/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/node_modules/react-helmet';
|
||||
@ -1,26 +0,0 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import { useModel } from '../plugin-model/useModel';
|
||||
if (typeof useModel !== 'function') {
|
||||
throw new Error('[plugin-initial-state]: useModel is not a function, @umijs/plugin-model is required.')
|
||||
}
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
export default (props: Props) => {
|
||||
const { children } = props;
|
||||
const appLoaded = useRef(false);
|
||||
const { loading = false } = useModel('@@initialState') || {};
|
||||
useEffect(()=>{
|
||||
if(!loading){
|
||||
appLoaded.current = true
|
||||
}
|
||||
}, [loading])
|
||||
// initial state loading 时,阻塞渲染
|
||||
if (loading && !appLoaded.current) {
|
||||
return null;
|
||||
}
|
||||
return children;
|
||||
};
|
||||
@ -1,7 +0,0 @@
|
||||
// @ts-nocheck
|
||||
|
||||
// @ts-ignore
|
||||
import { InitialState as InitialStateType } from '../plugin-initial-state/models/initialState';
|
||||
|
||||
export type InitialState = InitialStateType;
|
||||
export const __PLUGIN_INITIAL_STATE = 1;
|
||||
@ -1,2 +0,0 @@
|
||||
// @ts-nocheck
|
||||
export default () => ({ loading: false, refresh: () => {} })
|
||||
@ -1,13 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import Provider from './Provider';
|
||||
|
||||
export function rootContainer(container: React.ReactNode) {
|
||||
return React.createElement(
|
||||
// 这里的 plugin-initial-state 不能从 constant 里取,里面有 path 依赖
|
||||
// 但 webpack-5 没有 node 补丁(包括 path)
|
||||
Provider,
|
||||
null,
|
||||
container,
|
||||
);
|
||||
}
|
||||
@ -1,39 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import initialState from '/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/src/.umi/plugin-initial-state/models/initialState';
|
||||
|
||||
// @ts-ignore
|
||||
import Dispatcher from '/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/node_modules/@umijs/plugin-model/lib/helpers/dispatcher';
|
||||
// @ts-ignore
|
||||
import Executor from '/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/node_modules/@umijs/plugin-model/lib/helpers/executor';
|
||||
// @ts-ignore
|
||||
import { UmiContext } from '/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/node_modules/@umijs/plugin-model/lib/helpers/constant';
|
||||
|
||||
export const models = { '@@initialState': initialState, };
|
||||
|
||||
export type Model<T extends keyof typeof models> = {
|
||||
[key in keyof typeof models]: ReturnType<typeof models[T]>;
|
||||
};
|
||||
|
||||
export type Models<T extends keyof typeof models> = Model<T>[T]
|
||||
|
||||
const dispatcher = new Dispatcher!();
|
||||
const Exe = Executor!;
|
||||
|
||||
export default ({ children }: { children: React.ReactNode }) => {
|
||||
|
||||
return (
|
||||
<UmiContext.Provider value={dispatcher}>
|
||||
{
|
||||
Object.entries(models).map(pair => (
|
||||
<Exe key={pair[0]} namespace={pair[0]} hook={pair[1] as any} onUpdate={(val: any) => {
|
||||
const [ns] = pair as [keyof typeof models, any];
|
||||
dispatcher.data[ns] = val;
|
||||
dispatcher.update(ns);
|
||||
}} />
|
||||
))
|
||||
}
|
||||
{children}
|
||||
</UmiContext.Provider>
|
||||
)
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
// @ts-nocheck
|
||||
/* eslint-disable import/no-dynamic-require */
|
||||
import React from 'react';
|
||||
import Provider from './Provider';
|
||||
|
||||
export function rootContainer(container: React.ReactNode) {
|
||||
return React.createElement(
|
||||
Provider,
|
||||
null,
|
||||
container,
|
||||
);
|
||||
}
|
||||
@ -1,53 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import { useState, useEffect, useContext, useRef } from 'react';
|
||||
// @ts-ignore
|
||||
import isEqual from '/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/node_modules/fast-deep-equal/index.js';
|
||||
// @ts-ignore
|
||||
import { UmiContext } from '/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/node_modules/@umijs/plugin-model/lib/helpers/constant';
|
||||
import { Model, models } from './Provider';
|
||||
|
||||
export type Models<T extends keyof typeof models> = Model<T>[T]
|
||||
|
||||
export function useModel<T extends keyof Model<T>>(model: T): Model<T>[T]
|
||||
export function useModel<T extends keyof Model<T>, U>(model: T, selector: (model: Model<T>[T]) => U): U
|
||||
|
||||
export function useModel<T extends keyof Model<T>, U>(
|
||||
namespace: T,
|
||||
updater?: (model: Model<T>[T]) => U
|
||||
) : typeof updater extends undefined ? Model<T>[T] : ReturnType<NonNullable<typeof updater>>{
|
||||
|
||||
type RetState = typeof updater extends undefined ? Model<T>[T] : ReturnType<NonNullable<typeof updater>>
|
||||
const dispatcher = useContext<any>(UmiContext);
|
||||
const updaterRef = useRef(updater);
|
||||
updaterRef.current = updater;
|
||||
const [state, setState] = useState<RetState>(
|
||||
() => updaterRef.current ? updaterRef.current(dispatcher.data![namespace]) : dispatcher.data![namespace]
|
||||
);
|
||||
const stateRef = useRef<any>(state);
|
||||
stateRef.current = state;
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (e: any) => {
|
||||
if(updater && updaterRef.current){
|
||||
const currentState = updaterRef.current(e);
|
||||
const previousState = stateRef.current
|
||||
if(!isEqual(currentState, previousState)){
|
||||
setState(currentState);
|
||||
}
|
||||
} else {
|
||||
setState(e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
dispatcher.callbacks![namespace]!.add(handler);
|
||||
} catch (e) {
|
||||
dispatcher.callbacks![namespace] = new Set();
|
||||
dispatcher.callbacks![namespace]!.add(handler);
|
||||
}
|
||||
return () => {
|
||||
dispatcher.callbacks![namespace]!.delete(handler);
|
||||
}
|
||||
}, [namespace]);
|
||||
|
||||
return state;
|
||||
};
|
||||
@ -1,274 +0,0 @@
|
||||
// @ts-nocheck
|
||||
/**
|
||||
* Base on https://github.com/umijs//Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/node_modules/umi-request
|
||||
*/
|
||||
import {
|
||||
extend,
|
||||
Context,
|
||||
RequestOptionsInit,
|
||||
OnionMiddleware,
|
||||
RequestOptionsWithoutResponse,
|
||||
RequestMethod,
|
||||
RequestOptionsWithResponse,
|
||||
RequestResponse,
|
||||
RequestInterceptor,
|
||||
ResponseInterceptor,
|
||||
} from '/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/node_modules/umi-request';
|
||||
// @ts-ignore
|
||||
|
||||
import { ApplyPluginsType } from 'umi';
|
||||
import { history, plugin } from '../core/umiExports';
|
||||
|
||||
import { message, notification } from 'antd';
|
||||
import useUmiRequest, { UseRequestProvider } from '/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/node_modules/@ahooksjs/use-request';
|
||||
import {
|
||||
BaseOptions,
|
||||
BasePaginatedOptions,
|
||||
BaseResult,
|
||||
CombineService,
|
||||
LoadMoreFormatReturn,
|
||||
LoadMoreOptions,
|
||||
LoadMoreOptionsWithFormat,
|
||||
LoadMoreParams,
|
||||
LoadMoreResult,
|
||||
OptionsWithFormat,
|
||||
PaginatedFormatReturn,
|
||||
PaginatedOptionsWithFormat,
|
||||
PaginatedParams,
|
||||
PaginatedResult,
|
||||
} from '/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/node_modules/@ahooksjs/use-request/lib/types';
|
||||
|
||||
type ResultWithData<T = any> = { data?: T; [key: string]: any };
|
||||
|
||||
function useRequest<
|
||||
R = any,
|
||||
P extends any[] = any,
|
||||
U = any,
|
||||
UU extends U = any
|
||||
>(
|
||||
service: CombineService<R, P>,
|
||||
options: OptionsWithFormat<R, P, U, UU>,
|
||||
): BaseResult<U, P>;
|
||||
function useRequest<R extends ResultWithData = any, P extends any[] = any>(
|
||||
service: CombineService<R, P>,
|
||||
options?: BaseOptions<R['data'], P>,
|
||||
): BaseResult<R['data'], P>;
|
||||
function useRequest<R extends LoadMoreFormatReturn = any, RR = any>(
|
||||
service: CombineService<RR, LoadMoreParams<R>>,
|
||||
options: LoadMoreOptionsWithFormat<R, RR>,
|
||||
): LoadMoreResult<R>;
|
||||
function useRequest<
|
||||
R extends ResultWithData<LoadMoreFormatReturn | any> = any,
|
||||
RR extends R = any
|
||||
>(
|
||||
service: CombineService<R, LoadMoreParams<R['data']>>,
|
||||
options: LoadMoreOptions<RR['data']>,
|
||||
): LoadMoreResult<R['data']>;
|
||||
|
||||
function useRequest<R = any, Item = any, U extends Item = any>(
|
||||
service: CombineService<R, PaginatedParams>,
|
||||
options: PaginatedOptionsWithFormat<R, Item, U>,
|
||||
): PaginatedResult<Item>;
|
||||
function useRequest<Item = any, U extends Item = any>(
|
||||
service: CombineService<
|
||||
ResultWithData<PaginatedFormatReturn<Item>>,
|
||||
PaginatedParams
|
||||
>,
|
||||
options: BasePaginatedOptions<U>,
|
||||
): PaginatedResult<Item>;
|
||||
function useRequest(service: any, options: any = {}) {
|
||||
return useUmiRequest(service, {
|
||||
formatResult: result => result?.data,
|
||||
requestMethod: (requestOptions: any) => {
|
||||
if (typeof requestOptions === 'string') {
|
||||
return request(requestOptions);
|
||||
}
|
||||
if (typeof requestOptions === 'object') {
|
||||
const { url, ...rest } = requestOptions;
|
||||
return request(url, rest);
|
||||
}
|
||||
throw new Error('request options error');
|
||||
},
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
export interface RequestConfig extends RequestOptionsInit {
|
||||
errorConfig?: {
|
||||
errorPage?: string;
|
||||
adaptor?: (resData: any, ctx: Context) => ErrorInfoStructure;
|
||||
};
|
||||
middlewares?: OnionMiddleware[];
|
||||
requestInterceptors?: RequestInterceptor[];
|
||||
responseInterceptors?: ResponseInterceptor[];
|
||||
}
|
||||
|
||||
export enum ErrorShowType {
|
||||
SILENT = 0,
|
||||
WARN_MESSAGE = 1,
|
||||
ERROR_MESSAGE = 2,
|
||||
NOTIFICATION = 4,
|
||||
REDIRECT = 9,
|
||||
}
|
||||
|
||||
interface ErrorInfoStructure {
|
||||
success: boolean;
|
||||
data?: any;
|
||||
errorCode?: string;
|
||||
errorMessage?: string;
|
||||
showType?: ErrorShowType;
|
||||
traceId?: string;
|
||||
host?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface RequestError extends Error {
|
||||
data?: any;
|
||||
info?: ErrorInfoStructure;
|
||||
request?: Context['req'];
|
||||
response?: Context['res'];
|
||||
}
|
||||
|
||||
const DEFAULT_ERROR_PAGE = '/exception';
|
||||
|
||||
let requestMethodInstance: RequestMethod;
|
||||
const getRequestMethod = () => {
|
||||
if (requestMethodInstance) {
|
||||
// request method 已经示例化
|
||||
return requestMethodInstance;
|
||||
}
|
||||
|
||||
// runtime 配置可能应为依赖顺序的问题在模块初始化的时候无法获取,所以需要封装一层在异步调用后初始化相关方法
|
||||
// 当用户的 app.ts 中依赖了该文件的情况下就该模块的初始化时间就会被提前,无法获取到运行时配置
|
||||
const requestConfig: RequestConfig = plugin.applyPlugins({
|
||||
key: 'request',
|
||||
type: ApplyPluginsType.modify,
|
||||
initialValue: {},
|
||||
});
|
||||
|
||||
const errorAdaptor =
|
||||
requestConfig.errorConfig?.adaptor || (resData => resData);
|
||||
|
||||
requestMethodInstance = extend({
|
||||
errorHandler: (error: RequestError) => {
|
||||
// @ts-ignore
|
||||
if (error?.request?.options?.skipErrorHandler) {
|
||||
throw error;
|
||||
}
|
||||
let errorInfo: ErrorInfoStructure | undefined;
|
||||
if (error.name === 'ResponseError' && error.data && error.request) {
|
||||
const ctx: Context = {
|
||||
req: error.request,
|
||||
res: error.response,
|
||||
};
|
||||
errorInfo = errorAdaptor(error.data, ctx);
|
||||
error.message = errorInfo?.errorMessage || error.message;
|
||||
error.data = error.data;
|
||||
error.info = errorInfo;
|
||||
}
|
||||
errorInfo = error.info;
|
||||
|
||||
if (errorInfo) {
|
||||
const errorMessage = errorInfo?.errorMessage;
|
||||
const errorCode = errorInfo?.errorCode;
|
||||
const errorPage =
|
||||
requestConfig.errorConfig?.errorPage || DEFAULT_ERROR_PAGE;
|
||||
|
||||
switch (errorInfo?.showType) {
|
||||
case ErrorShowType.SILENT:
|
||||
// do nothing
|
||||
break;
|
||||
case ErrorShowType.WARN_MESSAGE:
|
||||
message.warn(errorMessage);
|
||||
break;
|
||||
case ErrorShowType.ERROR_MESSAGE:
|
||||
message.error(errorMessage);
|
||||
break;
|
||||
case ErrorShowType.NOTIFICATION:
|
||||
notification.open({
|
||||
message: errorMessage,
|
||||
});
|
||||
break;
|
||||
case ErrorShowType.REDIRECT:
|
||||
// @ts-ignore
|
||||
history.push({
|
||||
pathname: errorPage,
|
||||
query: { errorCode, errorMessage },
|
||||
});
|
||||
// redirect to error page
|
||||
break;
|
||||
default:
|
||||
message.error(errorMessage);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
message.error(error.message || 'Request error, please retry.');
|
||||
}
|
||||
throw error;
|
||||
},
|
||||
...requestConfig,
|
||||
});
|
||||
|
||||
// 中间件统一错误处理
|
||||
// 后端返回格式 { success: boolean, data: any }
|
||||
// 按照项目具体情况修改该部分逻辑
|
||||
requestMethodInstance.use(async (ctx, next) => {
|
||||
await next();
|
||||
const { req, res } = ctx;
|
||||
// @ts-ignore
|
||||
if (req.options?.skipErrorHandler) {
|
||||
return;
|
||||
}
|
||||
const { options } = req;
|
||||
const { getResponse } = options;
|
||||
const resData = getResponse ? res.data : res;
|
||||
const errorInfo = errorAdaptor(resData, ctx);
|
||||
if (errorInfo.success === false) {
|
||||
// 抛出错误到 errorHandler 中处理
|
||||
const error: RequestError = new Error(errorInfo.errorMessage);
|
||||
error.name = 'BizError';
|
||||
error.data = resData;
|
||||
error.info = errorInfo;
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
// Add user custom middlewares
|
||||
const customMiddlewares = requestConfig.middlewares || [];
|
||||
customMiddlewares.forEach(mw => {
|
||||
requestMethodInstance.use(mw);
|
||||
});
|
||||
|
||||
// Add user custom interceptors
|
||||
const requestInterceptors = requestConfig.requestInterceptors || [];
|
||||
const responseInterceptors = requestConfig.responseInterceptors || [];
|
||||
requestInterceptors.map(ri => {
|
||||
requestMethodInstance.interceptors.request.use(ri);
|
||||
});
|
||||
responseInterceptors.map(ri => {
|
||||
requestMethodInstance.interceptors.response.use(ri);
|
||||
});
|
||||
|
||||
return requestMethodInstance;
|
||||
};
|
||||
|
||||
interface RequestMethodInUmi<R = false> {
|
||||
<T = any>(
|
||||
url: string,
|
||||
options: RequestOptionsWithResponse & { skipErrorHandler?: boolean },
|
||||
): Promise<RequestResponse<T>>;
|
||||
<T = any>(
|
||||
url: string,
|
||||
options: RequestOptionsWithoutResponse & { skipErrorHandler?: boolean },
|
||||
): Promise<T>;
|
||||
<T = any>(
|
||||
url: string,
|
||||
options?: RequestOptionsInit & { skipErrorHandler?: boolean },
|
||||
): R extends true ? Promise<RequestResponse<T>> : Promise<T>;
|
||||
}
|
||||
const request: RequestMethodInUmi = (url: any, options: any) => {
|
||||
const requestMethod = getRequestMethod();
|
||||
return requestMethod(url, options);
|
||||
};
|
||||
|
||||
export { request, useRequest, UseRequestProvider };
|
||||
@ -1,59 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import './core/polyfill';
|
||||
import '@@/core/devScripts';
|
||||
import { plugin } from './core/plugin';
|
||||
import './core/pluginRegister';
|
||||
import { createHistory } from './core/history';
|
||||
import { ApplyPluginsType } from '/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/node_modules/@umijs/runtime';
|
||||
import { renderClient } from '/Users/apple/Desktop/github/zhiku.tec/h5-visible-tool/node_modules/@umijs/renderer-react/dist/index.js';
|
||||
import { getRoutes } from './core/routes';
|
||||
|
||||
|
||||
require('../global.css');
|
||||
|
||||
const getClientRender = (args: { hot?: boolean; routes?: any[] } = {}) => plugin.applyPlugins({
|
||||
key: 'render',
|
||||
type: ApplyPluginsType.compose,
|
||||
initialValue: () => {
|
||||
const opts = plugin.applyPlugins({
|
||||
key: 'modifyClientRenderOpts',
|
||||
type: ApplyPluginsType.modify,
|
||||
initialValue: {
|
||||
routes: args.routes || getRoutes(),
|
||||
plugin,
|
||||
history: createHistory(args.hot),
|
||||
isServer: process.env.__IS_SERVER,
|
||||
dynamicImport: true,
|
||||
rootElement: 'root',
|
||||
defaultTitle: `趣谈前端-h5-visible-tool`,
|
||||
},
|
||||
});
|
||||
return renderClient(opts);
|
||||
},
|
||||
args,
|
||||
});
|
||||
|
||||
const clientRender = getClientRender();
|
||||
export default clientRender();
|
||||
|
||||
|
||||
window.g_umi = {
|
||||
version: '3.2.16',
|
||||
};
|
||||
|
||||
|
||||
// hot module replacement
|
||||
// @ts-ignore
|
||||
if (module.hot) {
|
||||
// @ts-ignore
|
||||
module.hot.accept('./core/routes', () => {
|
||||
const ret = require('./core/routes');
|
||||
if (ret.then) {
|
||||
ret.then(({ getRoutes }) => {
|
||||
getClientRender({ hot: true, routes: getRoutes() })();
|
||||
});
|
||||
} else {
|
||||
getClientRender({ hot: true, routes: ret.getRoutes() })();
|
||||
}
|
||||
});
|
||||
}
|
||||
22
src/app.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { createLogger } from 'redux-logger';
|
||||
import { message } from 'antd';
|
||||
import undoable, { StateWithHistory } from 'redux-undo';
|
||||
import { Reducer, AnyAction } from 'redux';
|
||||
import { isDev } from './utils/tool';
|
||||
|
||||
export const dva = {
|
||||
config: {
|
||||
onAction: isDev ? createLogger() : undefined,
|
||||
onError(e: Error) {
|
||||
message.error(e.message, 3);
|
||||
},
|
||||
onReducer: (reducer: Reducer<any, AnyAction>) => {
|
||||
let undoReducer = undoable(reducer);
|
||||
return function(state: StateWithHistory<any>, action: AnyAction) {
|
||||
let newState = undoReducer(state, action);
|
||||
let router = newState.present.router ? newState.present.router : newState.present.routing;
|
||||
return { ...newState, router: router };
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
BIN
src/assets/area.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
src/assets/chart copy.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
src/assets/chart.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
src/assets/icon.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
src/assets/line.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
src/assets/logo.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
src/assets/pie.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
@ -1,34 +0,0 @@
|
||||
import { memo } from 'react'
|
||||
import { BackToTop, Icon } from 'zarm'
|
||||
|
||||
const themeObj = {
|
||||
simple: { bgColor: '#fff', color: '#999' },
|
||||
black: { bgColor: '#000', color: '#fff' },
|
||||
danger: { bgColor: '#ff5050', color: '#fff' },
|
||||
primary: { bgColor: '#00bc71', color: '#fff' },
|
||||
blue: { bgColor: '#06c', color: '#fff' }
|
||||
}
|
||||
const BackTop = memo((props) => {
|
||||
const {
|
||||
theme = 'simple'
|
||||
} = props
|
||||
|
||||
return <BackToTop>
|
||||
<div style={{
|
||||
width: 48,
|
||||
height: 48,
|
||||
lineHeight: '48px',
|
||||
textAlign: 'center',
|
||||
backgroundColor: themeObj[theme].bgColor,
|
||||
color: themeObj[theme].color,
|
||||
fontSize: 20,
|
||||
borderRadius: 30,
|
||||
boxShadow: '0 2px 10px 0 rgba(0, 0, 0, 0.2)',
|
||||
cursor: 'pointer',
|
||||
}}>
|
||||
<Icon type="arrow-top" />
|
||||
</div>
|
||||
</BackToTop>
|
||||
})
|
||||
|
||||
export default BackTop
|
||||
36
src/components/BackTop/index.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { memo } from 'react';
|
||||
import { BackToTop, Icon } from 'zarm';
|
||||
import React from 'react';
|
||||
const themeObj = {
|
||||
simple: { bgColor: '#fff', color: '#999' },
|
||||
black: { bgColor: '#000', color: '#fff' },
|
||||
danger: { bgColor: '#ff5050', color: '#fff' },
|
||||
primary: { bgColor: '#00bc71', color: '#fff' },
|
||||
blue: { bgColor: '#06c', color: '#fff' },
|
||||
};
|
||||
const BackTop = memo((props: { theme: keyof typeof themeObj }) => {
|
||||
const { theme = 'simple' } = props;
|
||||
|
||||
return (
|
||||
<BackToTop>
|
||||
<div
|
||||
style={{
|
||||
width: 48,
|
||||
height: 48,
|
||||
lineHeight: '48px',
|
||||
textAlign: 'center',
|
||||
backgroundColor: themeObj[theme].bgColor,
|
||||
color: themeObj[theme].color,
|
||||
fontSize: 20,
|
||||
borderRadius: 30,
|
||||
boxShadow: '0 2px 10px 0 rgba(0, 0, 0, 0.2)',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
<Icon type="arrow-top" />
|
||||
</div>
|
||||
</BackToTop>
|
||||
);
|
||||
});
|
||||
|
||||
export default BackTop;
|
||||
@ -7,4 +7,4 @@
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
53
src/components/BasicShop/BasicComponents/Carousel/index.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import React, { memo } from 'react';
|
||||
import { Carousel } from 'zarm';
|
||||
import styles from './index.less';
|
||||
import { ICarouselConfig } from './schema';
|
||||
|
||||
const XCarousel = memo((props: ICarouselConfig) => {
|
||||
const { direction, swipeable, autoPlay, isTpl, imgList, tplImg } = props;
|
||||
const contentRender = () => {
|
||||
return imgList.map((item, i) => {
|
||||
return (
|
||||
<div className={styles.carousel__item__pic} key={+i}>
|
||||
<img src={item.imgUrl.length > 0 ? item.imgUrl[0].url : ''} alt="" />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{isTpl ? (
|
||||
<div className={styles.carousel__item__pic}>
|
||||
<img src={tplImg} alt="" />
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
position: 'absolute',
|
||||
width: `${props.baseWidth}%`,
|
||||
height: `${props.baseHeight}%`,
|
||||
borderRadius: props.baseRadius,
|
||||
transform: `translate(${props.baseLeft}px,${props.baseTop}px)
|
||||
scale(${props.baseScale / 100})
|
||||
rotate(${props.baseRotate}deg)`,
|
||||
}}
|
||||
>
|
||||
<Carousel
|
||||
onChange={index => {
|
||||
// console.log(`onChange: ${index}`);
|
||||
}}
|
||||
direction={direction}
|
||||
swipeable={swipeable}
|
||||
autoPlay={autoPlay}
|
||||
loop
|
||||
>
|
||||
{contentRender()}
|
||||
</Carousel>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default XCarousel;
|
||||
106
src/components/BasicShop/BasicComponents/Carousel/schema.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import {
|
||||
IDataListConfigType,
|
||||
INumberConfigType,
|
||||
IRadioConfigType,
|
||||
ISwitchConfigType,
|
||||
TDataListDefaultType,
|
||||
TRadioDefaultType,
|
||||
TSwitchDefaultType,
|
||||
} from '@/components/PanelComponents/FormEditor/types';
|
||||
import { baseConfig, baseDefault, ICommonBaseType } from '../../common';
|
||||
|
||||
export type CarouselDirectionKeyType = 'down' | 'left';
|
||||
|
||||
export type TCarouselEditData = Array<
|
||||
| INumberConfigType
|
||||
| IRadioConfigType<CarouselDirectionKeyType>
|
||||
| ISwitchConfigType
|
||||
| IDataListConfigType
|
||||
>;
|
||||
export interface ICarouselConfig extends ICommonBaseType {
|
||||
direction: TRadioDefaultType<CarouselDirectionKeyType>;
|
||||
swipeable: TSwitchDefaultType;
|
||||
autoPlay: TSwitchDefaultType;
|
||||
imgList: TDataListDefaultType;
|
||||
tplImg: string;
|
||||
}
|
||||
|
||||
export interface ICarouselSchema {
|
||||
editData: TCarouselEditData;
|
||||
config: ICarouselConfig;
|
||||
}
|
||||
|
||||
const Carousel: ICarouselSchema = {
|
||||
editData: [
|
||||
...baseConfig,
|
||||
|
||||
{
|
||||
key: 'direction',
|
||||
name: '方向',
|
||||
type: 'Radio',
|
||||
range: [
|
||||
{
|
||||
key: 'down',
|
||||
text: '从上到下',
|
||||
},
|
||||
{
|
||||
key: 'left',
|
||||
text: '从左到右',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'swipeable',
|
||||
name: '是否可拖拽',
|
||||
type: 'Switch',
|
||||
},
|
||||
{
|
||||
key: 'autoPlay',
|
||||
name: '是否自动播放',
|
||||
type: 'Switch',
|
||||
},
|
||||
{
|
||||
key: 'imgList',
|
||||
name: '图片列表',
|
||||
type: 'DataList',
|
||||
},
|
||||
],
|
||||
config: {
|
||||
direction: 'left',
|
||||
swipeable: false,
|
||||
autoPlay: false,
|
||||
imgList: [
|
||||
{
|
||||
id: '1',
|
||||
title: '趣谈小课1',
|
||||
desc: '致力于打造优质小课程',
|
||||
link: 'xxxxx',
|
||||
imgUrl: [
|
||||
{
|
||||
uid: '001',
|
||||
name: 'image.png',
|
||||
status: 'done',
|
||||
url: 'http://io.nainor.com/uploads/1_1740bd7c3dc.png',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: '趣谈小课1',
|
||||
desc: '致力于打造优质小课程',
|
||||
link: 'xxxxx',
|
||||
imgUrl: [
|
||||
{
|
||||
uid: '001',
|
||||
name: 'image.png',
|
||||
status: 'done',
|
||||
url: 'http://io.nainor.com/uploads/2_1740bd8d525.png',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
...baseDefault,
|
||||
tplImg: 'http://io.nainor.com/uploads/carousal_17442e1420f.png',
|
||||
},
|
||||
};
|
||||
export default Carousel;
|
||||
@ -0,0 +1,5 @@
|
||||
const template = {
|
||||
type: 'Carousel',
|
||||
h: 82,
|
||||
};
|
||||
export default template;
|
||||
53
src/components/BasicShop/BasicComponents/Footer/index.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import React, { memo } from 'react';
|
||||
import { IFooterConfig } from './schema';
|
||||
const Footer = memo((props: IFooterConfig) => {
|
||||
const { bgColor, text, color, align, fontSize, height } = props;
|
||||
console.log(props);
|
||||
return (
|
||||
<>
|
||||
{props.isTpl && (
|
||||
<footer
|
||||
style={{
|
||||
backgroundColor: bgColor,
|
||||
color,
|
||||
fontSize,
|
||||
textAlign: align,
|
||||
height,
|
||||
lineHeight: height + 'px',
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</footer>
|
||||
)}
|
||||
{!props.isTpl && (
|
||||
<div
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
position: 'absolute',
|
||||
width: `${props.baseWidth}%`,
|
||||
height: `${props.baseHeight}%`,
|
||||
borderRadius: props.baseRadius,
|
||||
transform: `translate(${props.baseLeft}px,${props.baseTop}px)
|
||||
scale(${props.baseScale / 100})
|
||||
rotate(${props.baseRotate}deg)`,
|
||||
}}
|
||||
>
|
||||
<footer
|
||||
style={{
|
||||
backgroundColor: bgColor,
|
||||
color,
|
||||
fontSize,
|
||||
textAlign: align,
|
||||
height,
|
||||
lineHeight: height + 'px',
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</footer>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default Footer;
|
||||
90
src/components/BasicShop/BasicComponents/Footer/schema.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import {
|
||||
IColorConfigType,
|
||||
INumberConfigType,
|
||||
ISelectConfigType,
|
||||
ITextConfigType,
|
||||
TColorDefaultType,
|
||||
TNumberDefaultType,
|
||||
TSelectDefaultType,
|
||||
TTextDefaultType,
|
||||
} from '@/components/PanelComponents/FormEditor/types';
|
||||
import { baseConfig, baseDefault, ICommonBaseType } from '../../common';
|
||||
|
||||
export type TfooterSelectKeyType = 'left' | 'center' | 'right';
|
||||
|
||||
export type TFooterEditData = Array<
|
||||
IColorConfigType | INumberConfigType | ITextConfigType | ISelectConfigType<TfooterSelectKeyType>
|
||||
>;
|
||||
export interface IFooterConfig extends ICommonBaseType {
|
||||
bgColor: TColorDefaultType;
|
||||
text: TTextDefaultType;
|
||||
color: TColorDefaultType;
|
||||
align: TSelectDefaultType<TfooterSelectKeyType>;
|
||||
fontSize: TNumberDefaultType;
|
||||
height: TNumberDefaultType;
|
||||
}
|
||||
|
||||
export interface IFooterSchema {
|
||||
editData: TFooterEditData;
|
||||
config: IFooterConfig;
|
||||
}
|
||||
|
||||
const Footer: IFooterSchema = {
|
||||
editData: [
|
||||
...baseConfig,
|
||||
{
|
||||
key: 'bgColor',
|
||||
name: '背景色',
|
||||
type: 'Color',
|
||||
},
|
||||
{
|
||||
key: 'height',
|
||||
name: '高度',
|
||||
type: 'Number',
|
||||
},
|
||||
{
|
||||
key: 'text',
|
||||
name: '文字',
|
||||
type: 'Text',
|
||||
},
|
||||
{
|
||||
key: 'fontSize',
|
||||
name: '字体大小',
|
||||
type: 'Number',
|
||||
},
|
||||
{
|
||||
key: 'color',
|
||||
name: '文字颜色',
|
||||
type: 'Color',
|
||||
},
|
||||
{
|
||||
key: 'align',
|
||||
name: '对齐方式',
|
||||
type: 'Select',
|
||||
range: [
|
||||
{
|
||||
key: 'left',
|
||||
text: '左对齐',
|
||||
},
|
||||
{
|
||||
key: 'center',
|
||||
text: '居中对齐',
|
||||
},
|
||||
{
|
||||
key: 'right',
|
||||
text: '右对齐',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
config: {
|
||||
bgColor: 'rgba(0,0,0,1)',
|
||||
text: '页脚Footer',
|
||||
color: 'rgba(255,255,255,1)',
|
||||
align: 'center',
|
||||
fontSize: 16,
|
||||
height: 48,
|
||||
...baseDefault,
|
||||
},
|
||||
};
|
||||
export default Footer;
|
||||
@ -0,0 +1,5 @@
|
||||
const template = {
|
||||
type: 'Footer',
|
||||
h: 24,
|
||||
};
|
||||
export default template;
|
||||
96
src/components/BasicShop/BasicComponents/Form/BaseForm.tsx
Normal file
@ -0,0 +1,96 @@
|
||||
import { Input, Cell, DateSelect, Radio, Select } from 'zarm';
|
||||
import styles from './baseForm.less';
|
||||
import React from 'react';
|
||||
import {
|
||||
baseFormDateTpl,
|
||||
baseFormMyRadioTpl,
|
||||
baseFormMySelectTpl,
|
||||
baseFormNumberTpl,
|
||||
baseFormTextAreaTpl,
|
||||
baseFormTextTpl,
|
||||
baseFormUnionType,
|
||||
} from '@/components/PanelComponents/FormEditor/types';
|
||||
// 维护表单控件, 提高form渲染性能
|
||||
|
||||
type TBaseForm = {
|
||||
[key in baseFormUnionType]: any;
|
||||
};
|
||||
|
||||
const BaseForm: TBaseForm = {
|
||||
Text: (props: baseFormTextTpl & { onChange: (v: string | undefined) => void }) => {
|
||||
const { label, placeholder, onChange } = props;
|
||||
return (
|
||||
<Cell title={label}>
|
||||
<Input clearable type="text" placeholder={placeholder} onChange={onChange} />
|
||||
</Cell>
|
||||
);
|
||||
},
|
||||
Textarea: (props: baseFormTextAreaTpl & { onChange: (v: string | undefined) => void }) => {
|
||||
const { label, placeholder, onChange } = props;
|
||||
return (
|
||||
<Cell title={label}>
|
||||
<Input
|
||||
type="text"
|
||||
rows={3}
|
||||
autoHeight
|
||||
showLength
|
||||
placeholder={placeholder}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</Cell>
|
||||
);
|
||||
},
|
||||
Number: (props: baseFormNumberTpl & { onChange: (v: string | undefined | number) => void }) => {
|
||||
const { label, placeholder, onChange } = props;
|
||||
return (
|
||||
<Cell title={label}>
|
||||
<Input type="number" placeholder={placeholder} onChange={onChange} />
|
||||
</Cell>
|
||||
);
|
||||
},
|
||||
MyRadio: (props: baseFormMyRadioTpl & { onChange: (v: string | undefined | number) => void }) => {
|
||||
const { label, options, onChange } = props;
|
||||
return (
|
||||
<div className={styles.radioWrap}>
|
||||
<div className={styles.radioTitle}>{label}</div>
|
||||
<Cell>
|
||||
<Radio.Group onChange={onChange}>
|
||||
{options.map((item, i) => {
|
||||
return (
|
||||
<Radio value={item.value} key={i} className={styles.radioItem}>
|
||||
{item.label}
|
||||
</Radio>
|
||||
);
|
||||
})}
|
||||
</Radio.Group>
|
||||
</Cell>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
Date: (props: baseFormDateTpl & { onChange: (v: Date) => void }) => {
|
||||
const { label, placeholder, onChange } = props;
|
||||
return (
|
||||
<Cell title={label}>
|
||||
<DateSelect
|
||||
placeholder={placeholder}
|
||||
mode="date"
|
||||
min="1949-05-15"
|
||||
max="2100-05-15"
|
||||
onOk={onChange}
|
||||
/>
|
||||
</Cell>
|
||||
);
|
||||
},
|
||||
MySelect: (
|
||||
props: baseFormMySelectTpl & { onChange: ((v: Record<string, any>) => void) | undefined },
|
||||
) => {
|
||||
const { label, options, onChange } = props;
|
||||
return (
|
||||
<Cell title={label}>
|
||||
<Select dataSource={options} onOk={onChange} />
|
||||
</Cell>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default BaseForm;
|
||||
10
src/components/BasicShop/BasicComponents/Form/baseForm.less
Normal file
@ -0,0 +1,10 @@
|
||||
.radioWrap {
|
||||
margin-bottom: 10px;
|
||||
.radioTitle {
|
||||
padding: 6px 16px;
|
||||
font-size: 15px;
|
||||
}
|
||||
.radioItem {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
14
src/components/BasicShop/BasicComponents/Form/index.less
Normal file
@ -0,0 +1,14 @@
|
||||
.formWrap {
|
||||
margin: 10px;
|
||||
padding: 20px 16px;
|
||||
border-radius: 6px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 6px #f0f0f0;
|
||||
.title {
|
||||
padding-bottom: 20px;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
}
|
||||
.formContent {
|
||||
}
|
||||
}
|
||||
104
src/components/BasicShop/BasicComponents/Form/index.tsx
Normal file
@ -0,0 +1,104 @@
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import { Button } from 'zarm';
|
||||
import BaseForm from './BaseForm';
|
||||
import styles from './index.less';
|
||||
import { IFormConfig } from './schema';
|
||||
|
||||
const FormComponent = (props: IFormConfig) => {
|
||||
const { title, bgColor, fontSize, titColor, btnColor, btnTextColor, api, formControls } = props;
|
||||
const formData: Record<string, any> = {};
|
||||
const handleChange = useCallback(
|
||||
(item, v) => {
|
||||
if (item.options) {
|
||||
formData[item.label] = v[0].label;
|
||||
return;
|
||||
}
|
||||
formData[item.label] = v;
|
||||
},
|
||||
[formData],
|
||||
);
|
||||
const handleSubmit = () => {
|
||||
if (api) {
|
||||
fetch(api, {
|
||||
body: JSON.stringify(formData),
|
||||
cache: 'no-cache',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
});
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{props.isTpl && (
|
||||
<div className={styles.formWrap} style={{ backgroundColor: bgColor }}>
|
||||
{title && (
|
||||
<div className={styles.title} style={{ fontSize, color: titColor }}>
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.formContent}>
|
||||
{formControls.map(item => {
|
||||
const FormItem = BaseForm[item.type];
|
||||
return <FormItem onChange={handleChange.bind(this, item)} {...item} key={item.id} />;
|
||||
})}
|
||||
<div style={{ textAlign: 'center', padding: '16px 0' }}>
|
||||
<Button
|
||||
theme="primary"
|
||||
size="sm"
|
||||
block
|
||||
onClick={handleSubmit}
|
||||
style={{ backgroundColor: btnColor, borderColor: btnColor, color: btnTextColor }}
|
||||
>
|
||||
提交
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!props.isTpl && (
|
||||
<div
|
||||
className={styles.formWrap}
|
||||
style={{
|
||||
backgroundColor: bgColor,
|
||||
overflow: 'hidden',
|
||||
position: 'absolute',
|
||||
width: `${props.baseWidth}%`,
|
||||
height: `${props.baseHeight}%`,
|
||||
borderRadius: props.baseRadius,
|
||||
transform: `translate(${props.baseLeft}px,${props.baseTop}px)
|
||||
scale(${props.baseScale / 100})
|
||||
rotate(${props.baseRotate}deg)`,
|
||||
}}
|
||||
>
|
||||
{title && (
|
||||
<div className={styles.title} style={{ fontSize, color: titColor }}>
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.formContent}>
|
||||
{formControls.map(item => {
|
||||
const FormItem = BaseForm[item.type];
|
||||
return <FormItem onChange={handleChange.bind(this, item)} {...item} key={item.id} />;
|
||||
})}
|
||||
<div style={{ textAlign: 'center', padding: '16px 0' }}>
|
||||
<Button
|
||||
theme="primary"
|
||||
size="sm"
|
||||
block
|
||||
onClick={handleSubmit}
|
||||
style={{ backgroundColor: btnColor, borderColor: btnColor, color: btnTextColor }}
|
||||
>
|
||||
提交
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(FormComponent);
|
||||
111
src/components/BasicShop/BasicComponents/Form/schema.ts
Normal file
@ -0,0 +1,111 @@
|
||||
import {
|
||||
IColorConfigType,
|
||||
IFormItemsConfigType,
|
||||
INumberConfigType,
|
||||
ITextConfigType,
|
||||
TColorDefaultType,
|
||||
TFormItemsDefaultType,
|
||||
TNumberDefaultType,
|
||||
TTextDefaultType,
|
||||
} from '@/components/PanelComponents/FormEditor/types';
|
||||
import { baseConfig, baseDefault, ICommonBaseType } from '../../common';
|
||||
|
||||
export type TFormEditData = Array<
|
||||
ITextConfigType | INumberConfigType | IColorConfigType | ITextConfigType | IFormItemsConfigType
|
||||
>;
|
||||
export interface IFormConfig extends ICommonBaseType {
|
||||
title: TTextDefaultType;
|
||||
fontSize: TNumberDefaultType;
|
||||
titColor: TColorDefaultType;
|
||||
bgColor: TColorDefaultType;
|
||||
btnColor: TColorDefaultType;
|
||||
btnTextColor: TColorDefaultType;
|
||||
api: TTextDefaultType;
|
||||
formControls: TFormItemsDefaultType;
|
||||
}
|
||||
|
||||
export interface IFormSchema {
|
||||
editData: TFormEditData;
|
||||
config: IFormConfig;
|
||||
}
|
||||
|
||||
const Form: IFormSchema = {
|
||||
editData: [
|
||||
...baseConfig,
|
||||
{
|
||||
key: 'title',
|
||||
name: '标题',
|
||||
type: 'Text',
|
||||
},
|
||||
{
|
||||
key: 'fontSize',
|
||||
name: '标题大小',
|
||||
type: 'Number',
|
||||
},
|
||||
{
|
||||
key: 'titColor',
|
||||
name: '标题颜色',
|
||||
type: 'Color',
|
||||
},
|
||||
{
|
||||
key: 'bgColor',
|
||||
name: '背景色',
|
||||
type: 'Color',
|
||||
},
|
||||
{
|
||||
key: 'btnColor',
|
||||
name: '按钮颜色',
|
||||
type: 'Color',
|
||||
},
|
||||
{
|
||||
key: 'btnTextColor',
|
||||
name: '按钮文本颜色',
|
||||
type: 'Color',
|
||||
},
|
||||
{
|
||||
key: 'api',
|
||||
name: '表单Api地址',
|
||||
type: 'Text',
|
||||
},
|
||||
{
|
||||
key: 'formControls',
|
||||
name: '表单控件',
|
||||
type: 'FormItems',
|
||||
},
|
||||
],
|
||||
config: {
|
||||
title: '表单定制组件',
|
||||
fontSize: 18,
|
||||
titColor: 'rgba(60,60,60,1)',
|
||||
bgColor: 'rgba(255,255,255,1)',
|
||||
btnColor: 'rgba(129,173,173,1)',
|
||||
btnTextColor: 'rgba(255,255,255,1)',
|
||||
api: '',
|
||||
formControls: [
|
||||
{
|
||||
id: '1',
|
||||
type: 'Text',
|
||||
label: '姓名',
|
||||
placeholder: '请输入姓名',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'Number',
|
||||
label: '年龄',
|
||||
placeholder: ' 请输入年龄',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
type: 'MySelect',
|
||||
label: '爱好',
|
||||
options: [
|
||||
{ label: '选项一', value: '1' },
|
||||
{ label: '选项二', value: '2' },
|
||||
{ label: '选项三', value: '3' },
|
||||
],
|
||||
},
|
||||
],
|
||||
...baseDefault,
|
||||
},
|
||||
};
|
||||
export default Form;
|
||||
@ -0,0 +1,5 @@
|
||||
const template = {
|
||||
type: 'Form',
|
||||
h: 172,
|
||||
};
|
||||
export default template;
|
||||
@ -21,23 +21,3 @@
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
.list {
|
||||
margin: 20px auto;
|
||||
width: 94%;
|
||||
.sourceList {
|
||||
.sourceItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
.imgWrap {
|
||||
|
||||
}
|
||||
.content {
|
||||
margin-left: 12px;
|
||||
.tit {
|
||||
line-height: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
47
src/components/BasicShop/BasicComponents/Header/index.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import { memo } from 'react';
|
||||
import styles from './index.less';
|
||||
import React from 'react';
|
||||
import { IHeaderConfig } from './schema';
|
||||
|
||||
const Header = memo((props: IHeaderConfig) => {
|
||||
const { bgColor, logo, logoText, fontSize, color } = props;
|
||||
return (
|
||||
<>
|
||||
{props.isTpl && (
|
||||
<header className={styles.header} style={{ backgroundColor: bgColor }}>
|
||||
<div className={styles.logo}>
|
||||
<img src={logo && logo[0].url} alt={logoText} />
|
||||
</div>
|
||||
<div className={styles.title} style={{ fontSize, color }}>
|
||||
{logoText}
|
||||
</div>
|
||||
</header>
|
||||
)}
|
||||
{!props.isTpl && (
|
||||
<header
|
||||
className={styles.header}
|
||||
style={{
|
||||
backgroundColor: bgColor,
|
||||
overflow: 'hidden',
|
||||
position: 'absolute',
|
||||
width: `${props.baseWidth}%`,
|
||||
height: `${props.baseHeight}%`,
|
||||
borderRadius: props.baseRadius,
|
||||
transform: `translate(${props.baseLeft}px,${props.baseTop}px)
|
||||
scale(${props.baseScale / 100})
|
||||
rotate(${props.baseRotate}deg)`,
|
||||
}}
|
||||
>
|
||||
<div className={styles.logo}>
|
||||
<img src={logo && logo[0].url} alt={logoText} />
|
||||
</div>
|
||||
<div className={styles.title} style={{ fontSize, color }}>
|
||||
{logoText}
|
||||
</div>
|
||||
</header>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default Header;
|
||||
84
src/components/BasicShop/BasicComponents/Header/schema.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import {
|
||||
IColorConfigType,
|
||||
INumberConfigType,
|
||||
ITextConfigType,
|
||||
IUploadConfigType,
|
||||
TColorDefaultType,
|
||||
TNumberDefaultType,
|
||||
TTextDefaultType,
|
||||
TUploadDefaultType,
|
||||
} from '@/components/PanelComponents/FormEditor/types';
|
||||
import { baseConfig, baseDefault, ICommonBaseType } from '../../common';
|
||||
|
||||
export type THeaderEditData = Array<
|
||||
IColorConfigType | INumberConfigType | IUploadConfigType | ITextConfigType
|
||||
>;
|
||||
export interface IHeaderConfig extends ICommonBaseType {
|
||||
bgColor: TColorDefaultType;
|
||||
logo: TUploadDefaultType;
|
||||
logoText: TTextDefaultType;
|
||||
fontSize: TNumberDefaultType;
|
||||
color: TColorDefaultType;
|
||||
height: TNumberDefaultType;
|
||||
}
|
||||
|
||||
export interface IHeaderSchema {
|
||||
editData: THeaderEditData;
|
||||
config: IHeaderConfig;
|
||||
}
|
||||
|
||||
const Header: IHeaderSchema = {
|
||||
editData: [
|
||||
...baseConfig,
|
||||
{
|
||||
key: 'bgColor',
|
||||
name: '背景色',
|
||||
type: 'Color',
|
||||
},
|
||||
{
|
||||
key: 'height',
|
||||
name: '高度',
|
||||
type: 'Number',
|
||||
},
|
||||
{
|
||||
key: 'logo',
|
||||
name: 'logo',
|
||||
type: 'Upload',
|
||||
isCrop: true,
|
||||
cropRate: 1000 / 618,
|
||||
},
|
||||
{
|
||||
key: 'logoText',
|
||||
name: 'logo文字',
|
||||
type: 'Text',
|
||||
},
|
||||
{
|
||||
key: 'color',
|
||||
name: '文字颜色',
|
||||
type: 'Color',
|
||||
},
|
||||
{
|
||||
key: 'fontSize',
|
||||
name: '文字大小',
|
||||
type: 'Number',
|
||||
},
|
||||
],
|
||||
config: {
|
||||
bgColor: 'rgba(0,0,0,1)',
|
||||
logo: [
|
||||
{
|
||||
uid: '001',
|
||||
name: 'image.png',
|
||||
status: 'done',
|
||||
url: 'http://io.nainor.com/uploads/3_1740be8a482.png',
|
||||
},
|
||||
],
|
||||
logoText: '页头Header',
|
||||
fontSize: 20,
|
||||
color: 'rgba(255,255,255,1)',
|
||||
height: 50,
|
||||
...baseDefault,
|
||||
},
|
||||
};
|
||||
|
||||
export default Header;
|
||||
@ -0,0 +1,5 @@
|
||||
const template = {
|
||||
type: 'Header',
|
||||
h: 28,
|
||||
};
|
||||
export default template;
|
||||
364
src/components/BasicShop/BasicComponents/Icon/icon.ts
Normal file
@ -0,0 +1,364 @@
|
||||
export type AntdIconType =
|
||||
| 'max'
|
||||
| 'required'
|
||||
| 'default'
|
||||
| 'high'
|
||||
| 'low'
|
||||
| 'disabled'
|
||||
| 'start'
|
||||
| 'open'
|
||||
| 'media'
|
||||
| 'hidden'
|
||||
| 'cite'
|
||||
| 'data'
|
||||
| 'dir'
|
||||
| 'form'
|
||||
| 'label'
|
||||
| 'slot'
|
||||
| 'span'
|
||||
| 'style'
|
||||
| 'summary'
|
||||
| 'title'
|
||||
| 'pattern'
|
||||
| 'async'
|
||||
| 'defer'
|
||||
| 'manifest'
|
||||
| 'color'
|
||||
| 'content'
|
||||
| 'size'
|
||||
| 'wrap'
|
||||
| 'multiple'
|
||||
| 'height'
|
||||
| 'rotate'
|
||||
| 'translate'
|
||||
| 'width'
|
||||
| 'prefix'
|
||||
| 'src'
|
||||
| 'children'
|
||||
| 'key'
|
||||
| 'list'
|
||||
| 'step'
|
||||
| 'aria-label'
|
||||
| 'spin'
|
||||
| 'accept'
|
||||
| 'acceptCharset'
|
||||
| 'action'
|
||||
| 'allowFullScreen'
|
||||
| 'allowTransparency'
|
||||
| 'alt'
|
||||
| 'as'
|
||||
| 'autoComplete'
|
||||
| 'autoFocus'
|
||||
| 'autoPlay'
|
||||
| 'capture'
|
||||
| 'cellPadding'
|
||||
| 'cellSpacing'
|
||||
| 'charSet'
|
||||
| 'challenge'
|
||||
| 'checked'
|
||||
| 'classID'
|
||||
| 'cols'
|
||||
| 'colSpan'
|
||||
| 'controls'
|
||||
| 'coords'
|
||||
| 'crossOrigin'
|
||||
| 'dateTime'
|
||||
| 'download'
|
||||
| 'encType'
|
||||
| 'formAction'
|
||||
| 'formEncType'
|
||||
| 'formMethod'
|
||||
| 'formNoValidate'
|
||||
| 'formTarget'
|
||||
| 'frameBorder'
|
||||
| 'headers'
|
||||
| 'href'
|
||||
| 'hrefLang'
|
||||
| 'htmlFor'
|
||||
| 'httpEquiv'
|
||||
| 'integrity'
|
||||
| 'keyParams'
|
||||
| 'keyType'
|
||||
| 'kind'
|
||||
| 'loop'
|
||||
| 'marginHeight'
|
||||
| 'marginWidth'
|
||||
| 'maxLength'
|
||||
| 'mediaGroup'
|
||||
| 'method'
|
||||
| 'min'
|
||||
| 'minLength'
|
||||
| 'muted'
|
||||
| 'name'
|
||||
| 'nonce'
|
||||
| 'noValidate'
|
||||
| 'optimum'
|
||||
| 'placeholder'
|
||||
| 'playsInline'
|
||||
| 'poster'
|
||||
| 'preload'
|
||||
| 'readOnly'
|
||||
| 'rel'
|
||||
| 'reversed'
|
||||
| 'rows'
|
||||
| 'rowSpan'
|
||||
| 'sandbox'
|
||||
| 'scope'
|
||||
| 'scoped'
|
||||
| 'scrolling'
|
||||
| 'seamless'
|
||||
| 'selected'
|
||||
| 'shape'
|
||||
| 'sizes'
|
||||
| 'srcDoc'
|
||||
| 'srcLang'
|
||||
| 'srcSet'
|
||||
| 'target'
|
||||
| 'type'
|
||||
| 'useMap'
|
||||
| 'value'
|
||||
| 'wmode'
|
||||
| 'defaultChecked'
|
||||
| 'defaultValue'
|
||||
| 'suppressContentEditableWarning'
|
||||
| 'suppressHydrationWarning'
|
||||
| 'accessKey'
|
||||
| 'className'
|
||||
| 'contentEditable'
|
||||
| 'contextMenu'
|
||||
| 'draggable'
|
||||
| 'id'
|
||||
| 'lang'
|
||||
| 'spellCheck'
|
||||
| 'tabIndex'
|
||||
| 'radioGroup'
|
||||
| 'role'
|
||||
| 'about'
|
||||
| 'datatype'
|
||||
| 'inlist'
|
||||
| 'property'
|
||||
| 'resource'
|
||||
| 'typeof'
|
||||
| 'vocab'
|
||||
| 'autoCapitalize'
|
||||
| 'autoCorrect'
|
||||
| 'autoSave'
|
||||
| 'itemProp'
|
||||
| 'itemScope'
|
||||
| 'itemType'
|
||||
| 'itemID'
|
||||
| 'itemRef'
|
||||
| 'results'
|
||||
| 'security'
|
||||
| 'unselectable'
|
||||
| 'inputMode'
|
||||
| 'is'
|
||||
| 'aria-activedescendant'
|
||||
| 'aria-atomic'
|
||||
| 'aria-autocomplete'
|
||||
| 'aria-busy'
|
||||
| 'aria-checked'
|
||||
| 'aria-colcount'
|
||||
| 'aria-colindex'
|
||||
| 'aria-colspan'
|
||||
| 'aria-controls'
|
||||
| 'aria-current'
|
||||
| 'aria-describedby'
|
||||
| 'aria-details'
|
||||
| 'aria-disabled'
|
||||
| 'aria-dropeffect'
|
||||
| 'aria-errormessage'
|
||||
| 'aria-expanded'
|
||||
| 'aria-flowto'
|
||||
| 'aria-grabbed'
|
||||
| 'aria-haspopup'
|
||||
| 'aria-hidden'
|
||||
| 'aria-invalid'
|
||||
| 'aria-keyshortcuts'
|
||||
| 'aria-labelledby'
|
||||
| 'aria-level'
|
||||
| 'aria-live'
|
||||
| 'aria-modal'
|
||||
| 'aria-multiline'
|
||||
| 'aria-multiselectable'
|
||||
| 'aria-orientation'
|
||||
| 'aria-owns'
|
||||
| 'aria-placeholder'
|
||||
| 'aria-posinset'
|
||||
| 'aria-pressed'
|
||||
| 'aria-readonly'
|
||||
| 'aria-relevant'
|
||||
| 'aria-required'
|
||||
| 'aria-roledescription'
|
||||
| 'aria-rowcount'
|
||||
| 'aria-rowindex'
|
||||
| 'aria-rowspan'
|
||||
| 'aria-selected'
|
||||
| 'aria-setsize'
|
||||
| 'aria-sort'
|
||||
| 'aria-valuemax'
|
||||
| 'aria-valuemin'
|
||||
| 'aria-valuenow'
|
||||
| 'aria-valuetext'
|
||||
| 'dangerouslySetInnerHTML'
|
||||
| 'onCopy'
|
||||
| 'onCopyCapture'
|
||||
| 'onCut'
|
||||
| 'onCutCapture'
|
||||
| 'onPaste'
|
||||
| 'onPasteCapture'
|
||||
| 'onCompositionEnd'
|
||||
| 'onCompositionEndCapture'
|
||||
| 'onCompositionStart'
|
||||
| 'onCompositionStartCapture'
|
||||
| 'onCompositionUpdate'
|
||||
| 'onCompositionUpdateCapture'
|
||||
| 'onFocus'
|
||||
| 'onFocusCapture'
|
||||
| 'onBlur'
|
||||
| 'onBlurCapture'
|
||||
| 'onChange'
|
||||
| 'onChangeCapture'
|
||||
| 'onBeforeInput'
|
||||
| 'onBeforeInputCapture'
|
||||
| 'onInput'
|
||||
| 'onInputCapture'
|
||||
| 'onReset'
|
||||
| 'onResetCapture'
|
||||
| 'onSubmit'
|
||||
| 'onSubmitCapture'
|
||||
| 'onInvalid'
|
||||
| 'onInvalidCapture'
|
||||
| 'onLoad'
|
||||
| 'onLoadCapture'
|
||||
| 'onError'
|
||||
| 'onErrorCapture'
|
||||
| 'onKeyDown'
|
||||
| 'onKeyDownCapture'
|
||||
| 'onKeyPress'
|
||||
| 'onKeyPressCapture'
|
||||
| 'onKeyUp'
|
||||
| 'onKeyUpCapture'
|
||||
| 'onAbort'
|
||||
| 'onAbortCapture'
|
||||
| 'onCanPlay'
|
||||
| 'onCanPlayCapture'
|
||||
| 'onCanPlayThrough'
|
||||
| 'onCanPlayThroughCapture'
|
||||
| 'onDurationChange'
|
||||
| 'onDurationChangeCapture'
|
||||
| 'onEmptied'
|
||||
| 'onEmptiedCapture'
|
||||
| 'onEncrypted'
|
||||
| 'onEncryptedCapture'
|
||||
| 'onEnded'
|
||||
| 'onEndedCapture'
|
||||
| 'onLoadedData'
|
||||
| 'onLoadedDataCapture'
|
||||
| 'onLoadedMetadata'
|
||||
| 'onLoadedMetadataCapture'
|
||||
| 'onLoadStart'
|
||||
| 'onLoadStartCapture'
|
||||
| 'onPause'
|
||||
| 'onPauseCapture'
|
||||
| 'onPlay'
|
||||
| 'onPlayCapture'
|
||||
| 'onPlaying'
|
||||
| 'onPlayingCapture'
|
||||
| 'onProgress'
|
||||
| 'onProgressCapture'
|
||||
| 'onRateChange'
|
||||
| 'onRateChangeCapture'
|
||||
| 'onSeeked'
|
||||
| 'onSeekedCapture'
|
||||
| 'onSeeking'
|
||||
| 'onSeekingCapture'
|
||||
| 'onStalled'
|
||||
| 'onStalledCapture'
|
||||
| 'onSuspend'
|
||||
| 'onSuspendCapture'
|
||||
| 'onTimeUpdate'
|
||||
| 'onTimeUpdateCapture'
|
||||
| 'onVolumeChange'
|
||||
| 'onVolumeChangeCapture'
|
||||
| 'onWaiting'
|
||||
| 'onWaitingCapture'
|
||||
| 'onAuxClick'
|
||||
| 'onAuxClickCapture'
|
||||
| 'onClick'
|
||||
| 'onClickCapture'
|
||||
| 'onContextMenu'
|
||||
| 'onContextMenuCapture'
|
||||
| 'onDoubleClick'
|
||||
| 'onDoubleClickCapture'
|
||||
| 'onDrag'
|
||||
| 'onDragCapture'
|
||||
| 'onDragEnd'
|
||||
| 'onDragEndCapture'
|
||||
| 'onDragEnter'
|
||||
| 'onDragEnterCapture'
|
||||
| 'onDragExit'
|
||||
| 'onDragExitCapture'
|
||||
| 'onDragLeave'
|
||||
| 'onDragLeaveCapture'
|
||||
| 'onDragOver'
|
||||
| 'onDragOverCapture'
|
||||
| 'onDragStart'
|
||||
| 'onDragStartCapture'
|
||||
| 'onDrop'
|
||||
| 'onDropCapture'
|
||||
| 'onMouseDown'
|
||||
| 'onMouseDownCapture'
|
||||
| 'onMouseEnter'
|
||||
| 'onMouseLeave'
|
||||
| 'onMouseMove'
|
||||
| 'onMouseMoveCapture'
|
||||
| 'onMouseOut'
|
||||
| 'onMouseOutCapture'
|
||||
| 'onMouseOver'
|
||||
| 'onMouseOverCapture'
|
||||
| 'onMouseUp'
|
||||
| 'onMouseUpCapture'
|
||||
| 'onSelect'
|
||||
| 'onSelectCapture'
|
||||
| 'onTouchCancel'
|
||||
| 'onTouchCancelCapture'
|
||||
| 'onTouchEnd'
|
||||
| 'onTouchEndCapture'
|
||||
| 'onTouchMove'
|
||||
| 'onTouchMoveCapture'
|
||||
| 'onTouchStart'
|
||||
| 'onTouchStartCapture'
|
||||
| 'onPointerDown'
|
||||
| 'onPointerDownCapture'
|
||||
| 'onPointerMove'
|
||||
| 'onPointerMoveCapture'
|
||||
| 'onPointerUp'
|
||||
| 'onPointerUpCapture'
|
||||
| 'onPointerCancel'
|
||||
| 'onPointerCancelCapture'
|
||||
| 'onPointerEnter'
|
||||
| 'onPointerEnterCapture'
|
||||
| 'onPointerLeave'
|
||||
| 'onPointerLeaveCapture'
|
||||
| 'onPointerOver'
|
||||
| 'onPointerOverCapture'
|
||||
| 'onPointerOut'
|
||||
| 'onPointerOutCapture'
|
||||
| 'onGotPointerCapture'
|
||||
| 'onGotPointerCaptureCapture'
|
||||
| 'onLostPointerCapture'
|
||||
| 'onLostPointerCaptureCapture'
|
||||
| 'onScroll'
|
||||
| 'onScrollCapture'
|
||||
| 'onWheel'
|
||||
| 'onWheelCapture'
|
||||
| 'onAnimationStart'
|
||||
| 'onAnimationStartCapture'
|
||||
| 'onAnimationEnd'
|
||||
| 'onAnimationEndCapture'
|
||||
| 'onAnimationIteration'
|
||||
| 'onAnimationIterationCapture'
|
||||
| 'onTransitionEnd'
|
||||
| 'onTransitionEndCapture'
|
||||
| 'twoToneColor';
|
||||
27
src/components/BasicShop/BasicComponents/Icon/index.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import React, { memo } from 'react';
|
||||
import * as Icon from '@ant-design/icons';
|
||||
import IconImg from 'assets/icon.png';
|
||||
import { AntdIconProps } from '@ant-design/icons/lib/components/AntdIcon';
|
||||
import { AntdIconType } from './icon';
|
||||
import { IIconConfig } from './schema';
|
||||
|
||||
interface IconType extends IIconConfig {
|
||||
isTpl?: boolean;
|
||||
}
|
||||
const XIcon = memo((props: IconType) => {
|
||||
const { color, size, type, spin, isTpl } = props;
|
||||
|
||||
const MyIcon: React.ForwardRefExoticComponent<Pick<AntdIconProps, AntdIconType> &
|
||||
React.RefAttributes<HTMLSpanElement>> = Icon[type];
|
||||
|
||||
return isTpl ? (
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<img style={{ verticalAlign: '-20px', width: '82px' }} src={IconImg} alt={type} />
|
||||
图标
|
||||
</div>
|
||||
) : (
|
||||
<MyIcon twoToneColor={color} style={{ fontSize: size }} spin={spin} />
|
||||
);
|
||||
});
|
||||
|
||||
export default XIcon;
|
||||
144
src/components/BasicShop/BasicComponents/Icon/schema.ts
Normal file
@ -0,0 +1,144 @@
|
||||
import {
|
||||
ICardPickerConfigType,
|
||||
IColorConfigType,
|
||||
INumberConfigType,
|
||||
ISwitchConfigType,
|
||||
TCardPickerDefaultType,
|
||||
TColorDefaultType,
|
||||
TNumberDefaultType,
|
||||
TSwitchDefaultType,
|
||||
} from '@/components/PanelComponents/FormEditor/types';
|
||||
|
||||
export type TIconEditData = Array<
|
||||
IColorConfigType | INumberConfigType | ISwitchConfigType | ICardPickerConfigType<IconTypes>
|
||||
>;
|
||||
export interface IIconConfig {
|
||||
color: TColorDefaultType;
|
||||
size: TNumberDefaultType;
|
||||
spin: TSwitchDefaultType;
|
||||
type: TCardPickerDefaultType<IconTypes>;
|
||||
}
|
||||
|
||||
export interface IIconSchema {
|
||||
editData: TIconEditData;
|
||||
config: IIconConfig;
|
||||
}
|
||||
|
||||
export type IconTypes =
|
||||
| 'AccountBookTwoTone'
|
||||
| 'AlertTwoTone'
|
||||
| 'ApiTwoTone'
|
||||
| 'AppstoreTwoTone'
|
||||
| 'AudioTwoTone'
|
||||
| 'BankTwoTone'
|
||||
| 'BellTwoTone'
|
||||
| 'BookTwoTone'
|
||||
| 'BugTwoTone'
|
||||
| 'BuildTwoTone'
|
||||
| 'BulbTwoTone'
|
||||
| 'CalculatorTwoTone'
|
||||
| 'CalendarTwoTone'
|
||||
| 'CameraTwoTone'
|
||||
| 'CarTwoTone'
|
||||
| 'CarryOutTwoTone'
|
||||
| 'CiCircleTwoTone'
|
||||
| 'CloudTwoTone'
|
||||
| 'CodeTwoTone'
|
||||
| 'CrownTwoTone'
|
||||
| 'CustomerServiceTwoTone'
|
||||
| 'DollarCircleTwoTone'
|
||||
| 'EnvironmentTwoTone'
|
||||
| 'ExperimentTwoTone'
|
||||
| 'FireTwoTone'
|
||||
| 'GiftTwoTone'
|
||||
| 'InsuranceTwoTone'
|
||||
| 'LikeTwoTone'
|
||||
| 'LockTwoTone'
|
||||
| 'MailTwoTone'
|
||||
| 'MessageTwoTone'
|
||||
| 'PhoneTwoTone'
|
||||
| 'PictureTwoTone'
|
||||
| 'PlaySquareTwoTone'
|
||||
| 'RedEnvelopeTwoTone'
|
||||
| 'ShopTwoTone'
|
||||
| 'TrademarkCircleTwoTone'
|
||||
| 'StarTwoTone'
|
||||
| 'SafetyCertificateTwoTone'
|
||||
| 'SettingTwoTone'
|
||||
| 'RocketTwoTone';
|
||||
|
||||
const Icon: IIconSchema = {
|
||||
editData: [
|
||||
{
|
||||
key: 'color',
|
||||
name: '颜色',
|
||||
type: 'Color',
|
||||
},
|
||||
{
|
||||
key: 'size',
|
||||
name: '大小',
|
||||
type: 'Number',
|
||||
},
|
||||
{
|
||||
key: 'spin',
|
||||
name: '旋转动画',
|
||||
type: 'Switch',
|
||||
},
|
||||
{
|
||||
key: 'type',
|
||||
name: '图标类型',
|
||||
type: 'CardPicker',
|
||||
icons: [
|
||||
'AccountBookTwoTone',
|
||||
'AlertTwoTone',
|
||||
'ApiTwoTone',
|
||||
'AppstoreTwoTone',
|
||||
'AudioTwoTone',
|
||||
'BankTwoTone',
|
||||
'BellTwoTone',
|
||||
'BookTwoTone',
|
||||
'BugTwoTone',
|
||||
'BuildTwoTone',
|
||||
'BulbTwoTone',
|
||||
'CalculatorTwoTone',
|
||||
'CalendarTwoTone',
|
||||
'CameraTwoTone',
|
||||
'CarTwoTone',
|
||||
'CarryOutTwoTone',
|
||||
'CiCircleTwoTone',
|
||||
'CloudTwoTone',
|
||||
'CodeTwoTone',
|
||||
'CrownTwoTone',
|
||||
'CustomerServiceTwoTone',
|
||||
'DollarCircleTwoTone',
|
||||
'EnvironmentTwoTone',
|
||||
'ExperimentTwoTone',
|
||||
'FireTwoTone',
|
||||
'GiftTwoTone',
|
||||
'InsuranceTwoTone',
|
||||
'LikeTwoTone',
|
||||
'LockTwoTone',
|
||||
'MailTwoTone',
|
||||
'MessageTwoTone',
|
||||
'PhoneTwoTone',
|
||||
'PictureTwoTone',
|
||||
'PlaySquareTwoTone',
|
||||
'RedEnvelopeTwoTone',
|
||||
'ShopTwoTone',
|
||||
'TrademarkCircleTwoTone',
|
||||
'StarTwoTone',
|
||||
'SafetyCertificateTwoTone',
|
||||
'SettingTwoTone',
|
||||
'RocketTwoTone',
|
||||
],
|
||||
},
|
||||
],
|
||||
config: {
|
||||
color: 'rgba(74,144,226,1)',
|
||||
size: 36,
|
||||
spin: false,
|
||||
type: 'CarTwoTone',
|
||||
},
|
||||
};
|
||||
|
||||
export default Icon;
|
||||
@ -0,0 +1,5 @@
|
||||
const template = {
|
||||
type: 'Icon',
|
||||
h: 23,
|
||||
};
|
||||
export default template;
|
||||
20
src/components/BasicShop/BasicComponents/Image/index.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import React, { memo } from 'react';
|
||||
import { IImageConfig } from './schema';
|
||||
const Image = memo((props: IImageConfig) => {
|
||||
const { imgUrl, round = 0 } = props;
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
borderRadius: round,
|
||||
width: '100%',
|
||||
textAlign: 'center',
|
||||
overflow: 'hidden',
|
||||
position: 'absolute',
|
||||
}}
|
||||
>
|
||||
<img src={imgUrl && imgUrl[0].url} alt="" style={{ width: '100%' }} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default Image;
|
||||
49
src/components/BasicShop/BasicComponents/Image/schema.ts
Normal file
@ -0,0 +1,49 @@
|
||||
import {
|
||||
INumberConfigType,
|
||||
IUploadConfigType,
|
||||
TNumberDefaultType,
|
||||
TUploadDefaultType,
|
||||
} from '@/components/PanelComponents/FormEditor/types';
|
||||
import { baseConfig, baseDefault, ICommonBaseType } from '../../common';
|
||||
|
||||
export type TImageEditData = Array<IUploadConfigType | INumberConfigType>;
|
||||
export interface IImageConfig extends ICommonBaseType {
|
||||
imgUrl: TUploadDefaultType;
|
||||
round: TNumberDefaultType;
|
||||
}
|
||||
|
||||
export interface IImageSchema {
|
||||
editData: TImageEditData;
|
||||
config: IImageConfig;
|
||||
}
|
||||
|
||||
const Image: IImageSchema = {
|
||||
editData: [
|
||||
...baseConfig,
|
||||
{
|
||||
key: 'imgUrl',
|
||||
name: '上传',
|
||||
type: 'Upload',
|
||||
isCrop: false,
|
||||
},
|
||||
{
|
||||
key: 'round',
|
||||
name: '圆角',
|
||||
type: 'Number',
|
||||
},
|
||||
],
|
||||
config: {
|
||||
imgUrl: [
|
||||
{
|
||||
uid: '001',
|
||||
name: 'image.png',
|
||||
status: 'done',
|
||||
url: 'http://io.nainor.com/uploads/4_1740bf4535c.png',
|
||||
},
|
||||
],
|
||||
round: 0,
|
||||
...baseDefault,
|
||||
},
|
||||
};
|
||||
|
||||
export default Image;
|
||||
@ -0,0 +1,5 @@
|
||||
const template = {
|
||||
type: 'Image',
|
||||
h: 188,
|
||||
};
|
||||
export default template;
|
||||
19
src/components/BasicShop/BasicComponents/List/index.less
Normal file
@ -0,0 +1,19 @@
|
||||
.list {
|
||||
margin: 20px auto;
|
||||
width: 94%;
|
||||
.sourceList {
|
||||
.sourceItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
.imgWrap {
|
||||
}
|
||||
.content {
|
||||
margin-left: 12px;
|
||||
.tit {
|
||||
line-height: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
110
src/components/BasicShop/BasicComponents/List/index.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import React, { memo } from 'react';
|
||||
import styles from './index.less';
|
||||
import { IListConfig } from './schema';
|
||||
const List = memo((props: IListConfig) => {
|
||||
const { round, sourceData, imgSize, fontSize, color } = props;
|
||||
return (
|
||||
<>
|
||||
{props.isTpl && (
|
||||
<div className={styles.list}>
|
||||
<div className={styles.sourceList}>
|
||||
{sourceData.map((item, i) => {
|
||||
return (
|
||||
<div className={styles.sourceItem} key={i}>
|
||||
<div className={styles.imgWrap}>
|
||||
<img
|
||||
src={
|
||||
item.imgUrl[0]
|
||||
? item.imgUrl[0].url
|
||||
: 'http://io.nainor.com/uploads/01_173e15d3493.png'
|
||||
}
|
||||
alt={item.desc}
|
||||
style={{
|
||||
width: parseFloat(imgSize),
|
||||
height: imgSize,
|
||||
objectFit: 'cover',
|
||||
borderRadius: round,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<a
|
||||
className={styles.tit}
|
||||
style={{ fontSize, color }}
|
||||
href={item.link ? item.link : '#'}
|
||||
>
|
||||
{item.title}
|
||||
<div
|
||||
className={styles.desc}
|
||||
style={{ fontSize: fontSize * 0.8, color: 'rgba(0,0,0, .3)' }}
|
||||
>
|
||||
{item.desc}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!props.isTpl && (
|
||||
<div
|
||||
className={styles.list}
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
position: 'absolute',
|
||||
width: `${props.baseWidth}%`,
|
||||
height: `${props.baseHeight}%`,
|
||||
borderRadius: props.baseRadius,
|
||||
transform: `translate(${props.baseLeft}px,${props.baseTop}px)
|
||||
scale(${props.baseScale / 100})
|
||||
rotate(${props.baseRotate}deg)`,
|
||||
}}
|
||||
>
|
||||
<div className={styles.sourceList}>
|
||||
{sourceData.map((item, i) => {
|
||||
return (
|
||||
<div className={styles.sourceItem} key={i}>
|
||||
<div className={styles.imgWrap}>
|
||||
<img
|
||||
src={
|
||||
item.imgUrl[0]
|
||||
? item.imgUrl[0].url
|
||||
: 'http://io.nainor.com/uploads/01_173e15d3493.png'
|
||||
}
|
||||
alt={item.desc}
|
||||
style={{
|
||||
width: parseFloat(imgSize),
|
||||
height: imgSize,
|
||||
objectFit: 'cover',
|
||||
borderRadius: round,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<a
|
||||
className={styles.tit}
|
||||
style={{ fontSize, color }}
|
||||
href={item.link ? item.link : '#'}
|
||||
>
|
||||
{item.title}
|
||||
<div
|
||||
className={styles.desc}
|
||||
style={{ fontSize: fontSize * 0.8, color: 'rgba(0,0,0, .3)' }}
|
||||
>
|
||||
{item.desc}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default List;
|
||||
119
src/components/BasicShop/BasicComponents/List/schema.ts
Normal file
@ -0,0 +1,119 @@
|
||||
import {
|
||||
IColorConfigType,
|
||||
IDataListConfigType,
|
||||
INumberConfigType,
|
||||
ISelectConfigType,
|
||||
TColorDefaultType,
|
||||
TDataListDefaultType,
|
||||
TNumberDefaultType,
|
||||
TSelectDefaultType,
|
||||
} from '@/components/PanelComponents/FormEditor/types';
|
||||
import { baseConfig, baseDefault, ICommonBaseType } from '../../common';
|
||||
export type TListSelectKeyType = '60' | '80' | '100' | '120' | '150';
|
||||
export type TListEditData = Array<
|
||||
IColorConfigType | IDataListConfigType | INumberConfigType | ISelectConfigType<TListSelectKeyType>
|
||||
>;
|
||||
export interface IListConfig extends ICommonBaseType {
|
||||
sourceData: TDataListDefaultType;
|
||||
round: TNumberDefaultType;
|
||||
imgSize: TSelectDefaultType<TListSelectKeyType>;
|
||||
fontSize: TNumberDefaultType;
|
||||
color: TColorDefaultType;
|
||||
}
|
||||
|
||||
export interface IListSchema {
|
||||
editData: TListEditData;
|
||||
config: IListConfig;
|
||||
}
|
||||
|
||||
const List: IListSchema = {
|
||||
editData: [
|
||||
...baseConfig,
|
||||
{
|
||||
key: 'sourceData',
|
||||
name: '数据源',
|
||||
type: 'DataList',
|
||||
},
|
||||
{
|
||||
key: 'round',
|
||||
name: '圆角',
|
||||
type: 'Number',
|
||||
},
|
||||
{
|
||||
key: 'imgSize',
|
||||
name: '图片大小',
|
||||
type: 'Select',
|
||||
range: [
|
||||
{
|
||||
key: '60',
|
||||
text: '60 x 60',
|
||||
},
|
||||
{
|
||||
key: '80',
|
||||
text: '80 x 80',
|
||||
},
|
||||
{
|
||||
key: '100',
|
||||
text: '100 x 100',
|
||||
},
|
||||
{
|
||||
key: '120',
|
||||
text: '120 x 120',
|
||||
},
|
||||
{
|
||||
key: '150',
|
||||
text: '150 x 150',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'fontSize',
|
||||
name: '文字大小',
|
||||
type: 'Number',
|
||||
},
|
||||
{
|
||||
key: 'color',
|
||||
name: '文字颜色',
|
||||
type: 'Color',
|
||||
},
|
||||
],
|
||||
config: {
|
||||
sourceData: [
|
||||
{
|
||||
id: '1',
|
||||
title: '趣谈小课',
|
||||
desc: '致力于打造优质小课程',
|
||||
link: 'xxxxx',
|
||||
imgUrl: [
|
||||
{
|
||||
uid: '001',
|
||||
name: 'image.png',
|
||||
status: 'done',
|
||||
url: 'http://io.nainor.com/uploads/1_1740c6fbcd9.png',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: '趣谈小课',
|
||||
desc: '致力于打造优质小课程',
|
||||
link: 'xxxxx',
|
||||
imgUrl: [
|
||||
{
|
||||
uid: '002',
|
||||
name: 'image.png',
|
||||
status: 'done',
|
||||
url: 'http://io.nainor.com/uploads/1_1740c6fbcd9.png',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
round: 0,
|
||||
imgSize: '80',
|
||||
fontSize: 16,
|
||||
color: 'rgba(153,153,153,1)',
|
||||
...baseDefault,
|
||||
},
|
||||
};
|
||||
|
||||
export default List;
|
||||
@ -0,0 +1,5 @@
|
||||
const template = {
|
||||
type: 'List',
|
||||
h: 110,
|
||||
};
|
||||
export default template;
|
||||
15
src/components/BasicShop/BasicComponents/LongText/index.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import React, { memo } from 'react';
|
||||
import styles from './index.less';
|
||||
import { ILongTextConfig } from './schema';
|
||||
const LongText = memo((props: ILongTextConfig) => {
|
||||
const { text, fontSize, color, indent, lineHeight, textAlign } = props;
|
||||
return (
|
||||
<div
|
||||
className={styles.textWrap}
|
||||
style={{ color, textIndent: indent + 'px', fontSize, lineHeight, textAlign }}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
export default LongText;
|
||||
92
src/components/BasicShop/BasicComponents/LongText/schema.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import {
|
||||
IColorConfigType,
|
||||
INumberConfigType,
|
||||
ISelectConfigType,
|
||||
ITextAreaConfigType,
|
||||
TColorDefaultType,
|
||||
TNumberDefaultType,
|
||||
TSelectDefaultType,
|
||||
TTextAreaDefaultType,
|
||||
} from '@/components/PanelComponents/FormEditor/types';
|
||||
export type TLongTextSelectKeyType = 'left' | 'center' | 'right';
|
||||
|
||||
export type TLongTextEditData = Array<
|
||||
| ITextAreaConfigType
|
||||
| IColorConfigType
|
||||
| INumberConfigType
|
||||
| ISelectConfigType<TLongTextSelectKeyType>
|
||||
>;
|
||||
export interface ILongTextConfig {
|
||||
text: TTextAreaDefaultType;
|
||||
color: TColorDefaultType;
|
||||
fontSize: TNumberDefaultType;
|
||||
indent: TNumberDefaultType;
|
||||
lineHeight: TNumberDefaultType;
|
||||
textAlign: TSelectDefaultType<TLongTextSelectKeyType>;
|
||||
}
|
||||
|
||||
export interface ILongTextSchema {
|
||||
editData: TLongTextEditData;
|
||||
config: ILongTextConfig;
|
||||
}
|
||||
|
||||
const LongText: ILongTextSchema = {
|
||||
editData: [
|
||||
{
|
||||
key: 'text',
|
||||
name: '文字',
|
||||
type: 'TextArea',
|
||||
},
|
||||
{
|
||||
key: 'color',
|
||||
name: '标题颜色',
|
||||
type: 'Color',
|
||||
},
|
||||
{
|
||||
key: 'fontSize',
|
||||
name: '字体大小',
|
||||
type: 'Number',
|
||||
},
|
||||
{
|
||||
key: 'indent',
|
||||
name: '首行缩进',
|
||||
type: 'Number',
|
||||
range: [0, 100],
|
||||
},
|
||||
{
|
||||
key: 'textAlign',
|
||||
name: '对齐方式',
|
||||
type: 'Select',
|
||||
range: [
|
||||
{
|
||||
key: 'left',
|
||||
text: '左对齐',
|
||||
},
|
||||
{
|
||||
key: 'center',
|
||||
text: '居中对齐',
|
||||
},
|
||||
{
|
||||
key: 'right',
|
||||
text: '右对齐',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'lineHeight',
|
||||
name: '行高',
|
||||
type: 'Number',
|
||||
step: 0.1,
|
||||
},
|
||||
],
|
||||
config: {
|
||||
text: '我是长文本有一段故事,dooring可视化编辑器无限可能,赶快来体验吧,骚年们,奥利给~',
|
||||
color: 'rgba(60,60,60,1)',
|
||||
fontSize: 14,
|
||||
indent: 20,
|
||||
lineHeight: 1.8,
|
||||
textAlign: 'left',
|
||||
},
|
||||
};
|
||||
|
||||
export default LongText;
|
||||
@ -0,0 +1,5 @@
|
||||
const template = {
|
||||
type: 'LongText',
|
||||
h: 36,
|
||||
};
|
||||
export default template;
|
||||
13
src/components/BasicShop/BasicComponents/Notice/index.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import { NoticeBar } from 'zarm';
|
||||
import React, { memo } from 'react';
|
||||
import { INoticeConfig } from './schema';
|
||||
const Notice = memo((props: INoticeConfig) => {
|
||||
const { text, speed, theme, isClose = false } = props;
|
||||
return (
|
||||
<NoticeBar theme={theme === 'default' ? undefined : theme} closable={isClose} speed={speed}>
|
||||
<span style={{ color: 'inherit' }}>{text}</span>
|
||||
</NoticeBar>
|
||||
);
|
||||
});
|
||||
|
||||
export default Notice;
|
||||
81
src/components/BasicShop/BasicComponents/Notice/schema.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import {
|
||||
INumberConfigType,
|
||||
ISelectConfigType,
|
||||
ISwitchConfigType,
|
||||
ITextConfigType,
|
||||
TNumberDefaultType,
|
||||
TSelectDefaultType,
|
||||
TSwitchDefaultType,
|
||||
TTextDefaultType,
|
||||
} from '@/components/PanelComponents/FormEditor/types';
|
||||
|
||||
export type TNoticeSelectKeyType = 'default' | 'warning' | 'primary' | 'success' | 'danger';
|
||||
export type TNoticeEditData = Array<
|
||||
ITextConfigType | INumberConfigType | ISelectConfigType<TNoticeSelectKeyType> | ISwitchConfigType
|
||||
>;
|
||||
export interface INoticeConfig {
|
||||
text: TTextDefaultType;
|
||||
speed: TNumberDefaultType;
|
||||
theme: TSelectDefaultType<TNoticeSelectKeyType>;
|
||||
isClose: TSwitchDefaultType;
|
||||
}
|
||||
|
||||
export interface INoticeSchema {
|
||||
editData: TNoticeEditData;
|
||||
config: INoticeConfig;
|
||||
}
|
||||
|
||||
const Notice: INoticeSchema = {
|
||||
editData: [
|
||||
{
|
||||
key: 'text',
|
||||
name: '文本',
|
||||
type: 'Text',
|
||||
},
|
||||
{
|
||||
key: 'speed',
|
||||
name: '滚动速度',
|
||||
type: 'Number',
|
||||
},
|
||||
{
|
||||
key: 'theme',
|
||||
name: '主题',
|
||||
type: 'Select',
|
||||
range: [
|
||||
{
|
||||
key: 'default',
|
||||
text: '默认',
|
||||
},
|
||||
{
|
||||
key: 'warning',
|
||||
text: '警告',
|
||||
},
|
||||
{
|
||||
key: 'primary',
|
||||
text: '主要',
|
||||
},
|
||||
{
|
||||
key: 'success',
|
||||
text: '成功',
|
||||
},
|
||||
{
|
||||
key: 'danger',
|
||||
text: '危险',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'isClose',
|
||||
name: '是否可关闭',
|
||||
type: 'Switch',
|
||||
},
|
||||
],
|
||||
config: {
|
||||
text: '通知栏: 趣谈前端上线啦',
|
||||
speed: 50,
|
||||
theme: 'warning',
|
||||
isClose: false,
|
||||
},
|
||||
};
|
||||
|
||||
export default Notice;
|
||||
@ -0,0 +1,5 @@
|
||||
const template = {
|
||||
type: 'Notice',
|
||||
h: 20,
|
||||
};
|
||||
export default template;
|
||||
14
src/components/BasicShop/BasicComponents/Qrcode/index.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import React, { memo } from 'react';
|
||||
import { IQrcodeConfig } from './schema';
|
||||
|
||||
const Qrcode = memo((props: IQrcodeConfig) => {
|
||||
const { qrcode, text, color, fontSize = 14 } = props;
|
||||
return (
|
||||
<div style={{ width: '240px', margin: '16px auto' }}>
|
||||
<img src={qrcode && qrcode[0].url} alt={text} style={{ width: '100%' }} />
|
||||
<div style={{ textAlign: 'center', color, fontSize, paddingTop: '8px' }}>{text}</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default Qrcode;
|
||||
67
src/components/BasicShop/BasicComponents/Qrcode/schema.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import {
|
||||
IColorConfigType,
|
||||
INumberConfigType,
|
||||
ITextConfigType,
|
||||
IUploadConfigType,
|
||||
TColorDefaultType,
|
||||
TNumberDefaultType,
|
||||
TTextDefaultType,
|
||||
TUploadDefaultType,
|
||||
} from '@/components/PanelComponents/FormEditor/types';
|
||||
|
||||
export type TQrcodeEditData = Array<
|
||||
IUploadConfigType | ITextConfigType | IColorConfigType | INumberConfigType
|
||||
>;
|
||||
export interface IQrcodeConfig {
|
||||
qrcode: TUploadDefaultType;
|
||||
text: TTextDefaultType;
|
||||
color: TColorDefaultType;
|
||||
fontSize: TNumberDefaultType;
|
||||
}
|
||||
|
||||
export interface IQrcodeSchema {
|
||||
editData: TQrcodeEditData;
|
||||
config: IQrcodeConfig;
|
||||
}
|
||||
|
||||
const Qrcode: IQrcodeSchema = {
|
||||
editData: [
|
||||
{
|
||||
key: 'qrcode',
|
||||
name: '二维码',
|
||||
type: 'Upload',
|
||||
isCrop: true,
|
||||
cropRate: 1,
|
||||
},
|
||||
{
|
||||
key: 'text',
|
||||
name: '文字',
|
||||
type: 'Text',
|
||||
},
|
||||
{
|
||||
key: 'color',
|
||||
name: '文字颜色',
|
||||
type: 'Color',
|
||||
},
|
||||
{
|
||||
key: 'fontSize',
|
||||
name: '文字大小',
|
||||
type: 'Number',
|
||||
},
|
||||
],
|
||||
config: {
|
||||
qrcode: [
|
||||
{
|
||||
uid: '001',
|
||||
name: 'image.png',
|
||||
status: 'done',
|
||||
url: 'http://io.nainor.com/uploads/code_173e1705e0c.png',
|
||||
},
|
||||
],
|
||||
text: '二维码',
|
||||
color: 'rgba(153,153,153,1)',
|
||||
fontSize: 14,
|
||||
},
|
||||
};
|
||||
|
||||
export default Qrcode;
|
||||
@ -0,0 +1,5 @@
|
||||
const template = {
|
||||
type: 'Qrcode',
|
||||
h: 150,
|
||||
};
|
||||
export default template;
|
||||
@ -24,4 +24,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
64
src/components/BasicShop/BasicComponents/Tab/index.tsx
Normal file
@ -0,0 +1,64 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { Tabs } from 'zarm';
|
||||
import styles from './index.less';
|
||||
import { ITabConfig } from './schema';
|
||||
|
||||
const { Panel } = Tabs;
|
||||
|
||||
const XTab = (props: ITabConfig) => {
|
||||
const { tabs = ['分类一', '分类二'], activeColor, color, fontSize, sourceData } = props;
|
||||
|
||||
const tabWrapRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (tabWrapRef.current) {
|
||||
let res = tabWrapRef.current.querySelector('.za-tabs__line') as HTMLElement;
|
||||
if (res) {
|
||||
res.style.backgroundColor = activeColor;
|
||||
}
|
||||
}
|
||||
}, [activeColor]);
|
||||
|
||||
return (
|
||||
<div className={styles.tabWrap} ref={tabWrapRef}>
|
||||
<Tabs
|
||||
scrollThreshold={3}
|
||||
onChange={i => {
|
||||
console.log(i);
|
||||
}}
|
||||
>
|
||||
{tabs.map((item, i) => {
|
||||
return (
|
||||
<Panel title={item} key={i}>
|
||||
<div className={styles.content}>
|
||||
{sourceData
|
||||
.filter(item => item.type === i)
|
||||
.map((item, i) => {
|
||||
return (
|
||||
<div className={styles.item} key={i}>
|
||||
<a className={styles.imgWrap} href={item.link} title={item.desc}>
|
||||
<img
|
||||
src={
|
||||
item.imgUrl[0]
|
||||
? item.imgUrl[0].url
|
||||
: 'http://io.nainor.com/uploads/01_173e15d3493.png'
|
||||
}
|
||||
alt={item.title}
|
||||
/>
|
||||
<div className={styles.title} style={{ fontSize, color }}>
|
||||
{item.title}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default XTab;
|
||||
118
src/components/BasicShop/BasicComponents/Tab/schema.ts
Normal file
@ -0,0 +1,118 @@
|
||||
import {
|
||||
IColorConfigType,
|
||||
IDataListConfigType,
|
||||
IMutiTextConfigType,
|
||||
INumberConfigType,
|
||||
TColorDefaultType,
|
||||
TDataListDefaultType,
|
||||
TMutiTextDefaultType,
|
||||
TNumberDefaultType,
|
||||
} from '@/components/PanelComponents/FormEditor/types';
|
||||
|
||||
export type TTabEditData = Array<
|
||||
IMutiTextConfigType | IColorConfigType | INumberConfigType | IDataListConfigType
|
||||
>;
|
||||
export interface ITabConfig {
|
||||
tabs: TMutiTextDefaultType;
|
||||
color: TColorDefaultType;
|
||||
activeColor: TColorDefaultType;
|
||||
fontSize: TNumberDefaultType;
|
||||
imgSize: TNumberDefaultType;
|
||||
sourceData: TDataListDefaultType;
|
||||
}
|
||||
|
||||
export interface ITabSchema {
|
||||
editData: TTabEditData;
|
||||
config: ITabConfig;
|
||||
}
|
||||
|
||||
const Tab: ITabSchema = {
|
||||
editData: [
|
||||
{
|
||||
key: 'tabs',
|
||||
name: '项目类别',
|
||||
type: 'MutiText',
|
||||
},
|
||||
{
|
||||
key: 'activeColor',
|
||||
name: '激活颜色',
|
||||
type: 'Color',
|
||||
},
|
||||
{
|
||||
key: 'color',
|
||||
name: '文字颜色',
|
||||
type: 'Color',
|
||||
},
|
||||
{
|
||||
key: 'fontSize',
|
||||
name: '文字大小',
|
||||
type: 'Number',
|
||||
},
|
||||
{
|
||||
key: 'imgSize',
|
||||
name: '图片大小',
|
||||
type: 'Number',
|
||||
},
|
||||
{
|
||||
key: 'sourceData',
|
||||
name: '数据源',
|
||||
type: 'DataList',
|
||||
},
|
||||
],
|
||||
config: {
|
||||
tabs: ['类别一', '类别二'],
|
||||
color: 'rgba(153,153,153,1)',
|
||||
activeColor: 'rgba(0,102,204,1)',
|
||||
fontSize: 16,
|
||||
imgSize: 100,
|
||||
sourceData: [
|
||||
{
|
||||
id: '1',
|
||||
title: '趣谈小课1',
|
||||
desc: '致力于打造优质小课程',
|
||||
link: 'xxxxx',
|
||||
type: 0,
|
||||
imgUrl: [
|
||||
{
|
||||
uid: '001',
|
||||
name: 'image.png',
|
||||
status: 'done',
|
||||
url: 'http://io.nainor.com/uploads/1_1740c6fbcd9.png',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: '趣谈小课2',
|
||||
desc: '致力于打造优质小课程',
|
||||
link: 'xxxxx',
|
||||
type: 0,
|
||||
imgUrl: [
|
||||
{
|
||||
uid: '001',
|
||||
name: 'image.png',
|
||||
status: 'done',
|
||||
url: 'http://io.nainor.com/uploads/2_1740c7033a9.png',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: '趣谈小课3',
|
||||
desc: '致力于打造优质小课程',
|
||||
link: 'xxxxx',
|
||||
type: 1,
|
||||
imgUrl: [
|
||||
{
|
||||
uid: '001',
|
||||
name: 'image.png',
|
||||
status: 'done',
|
||||
url: 'http://io.nainor.com/uploads/1_1740c6fbcd9.png',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default Tab;
|
||||
5
src/components/BasicShop/BasicComponents/Tab/template.ts
Normal file
@ -0,0 +1,5 @@
|
||||
const template = {
|
||||
type: 'Tab',
|
||||
h: 130,
|
||||
};
|
||||
export default template;
|
||||
13
src/components/BasicShop/BasicComponents/Text/index.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import React, { memo } from 'react';
|
||||
import styles from './index.less';
|
||||
import { ITextConfig } from './schema';
|
||||
|
||||
const Text = memo((props: ITextConfig) => {
|
||||
const { align, text, fontSize, color, lineHeight } = props;
|
||||
return (
|
||||
<div className={styles.textWrap} style={{ color, textAlign: align, fontSize, lineHeight }}>
|
||||
{text}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
export default Text;
|
||||
78
src/components/BasicShop/BasicComponents/Text/schema.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import {
|
||||
IColorConfigType,
|
||||
INumberConfigType,
|
||||
ISelectConfigType,
|
||||
ITextConfigType,
|
||||
TColorDefaultType,
|
||||
TNumberDefaultType,
|
||||
TSelectDefaultType,
|
||||
TTextDefaultType,
|
||||
} from '@/components/PanelComponents/FormEditor/types';
|
||||
|
||||
export type TTextSelectKeyType = 'left' | 'right' | 'center';
|
||||
export type TTextEditData = Array<
|
||||
ITextConfigType | IColorConfigType | INumberConfigType | ISelectConfigType<TTextSelectKeyType>
|
||||
>;
|
||||
export interface ITextConfig {
|
||||
text: TTextDefaultType;
|
||||
color: TColorDefaultType;
|
||||
fontSize: TNumberDefaultType;
|
||||
align: TSelectDefaultType<TTextSelectKeyType>;
|
||||
lineHeight: TNumberDefaultType;
|
||||
}
|
||||
|
||||
export interface ITextSchema {
|
||||
editData: TTextEditData;
|
||||
config: ITextConfig;
|
||||
}
|
||||
const Text: ITextSchema = {
|
||||
editData: [
|
||||
{
|
||||
key: 'text',
|
||||
name: '文字',
|
||||
type: 'Text',
|
||||
},
|
||||
{
|
||||
key: 'color',
|
||||
name: '标题颜色',
|
||||
type: 'Color',
|
||||
},
|
||||
{
|
||||
key: 'fontSize',
|
||||
name: '字体大小',
|
||||
type: 'Number',
|
||||
},
|
||||
{
|
||||
key: 'align',
|
||||
name: '对齐方式',
|
||||
type: 'Select',
|
||||
range: [
|
||||
{
|
||||
key: 'left',
|
||||
text: '左对齐',
|
||||
},
|
||||
{
|
||||
key: 'center',
|
||||
text: '居中对齐',
|
||||
},
|
||||
{
|
||||
key: 'right',
|
||||
text: '右对齐',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'lineHeight',
|
||||
name: '行高',
|
||||
type: 'Number',
|
||||
},
|
||||
],
|
||||
config: {
|
||||
text: '我是文本',
|
||||
color: 'rgba(60,60,60,1)',
|
||||
fontSize: 18,
|
||||
align: 'center',
|
||||
lineHeight: 2,
|
||||
},
|
||||
};
|
||||
export default Text;
|
||||
@ -0,0 +1,5 @@
|
||||
const template = {
|
||||
type: 'Text',
|
||||
h: 20,
|
||||
};
|
||||
export default template;
|
||||
28
src/components/BasicShop/BasicComponents/schema.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import Carousel from './Carousel/schema';
|
||||
import Footer from './Footer/schema';
|
||||
import Form from './Form/schema';
|
||||
import Header from './Header/schema';
|
||||
import Icon from './Icon/schema';
|
||||
import Image from './Image/schema';
|
||||
import List from './List/schema';
|
||||
import LongText from './LongText/schema';
|
||||
import Notice from './Notice/schema';
|
||||
import Qrcode from './Qrcode/schema';
|
||||
import Tab from './Tab/schema';
|
||||
import Text from './Text/schema';
|
||||
|
||||
const basicSchema = {
|
||||
Carousel,
|
||||
Footer,
|
||||
Form,
|
||||
Header,
|
||||
Icon,
|
||||
Image,
|
||||
List,
|
||||
LongText,
|
||||
Notice,
|
||||
Qrcode,
|
||||
Tab,
|
||||
Text,
|
||||
};
|
||||
export default basicSchema;
|
||||
32
src/components/BasicShop/BasicComponents/template.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import Carousel from './Carousel/template';
|
||||
import Footer from './Footer/template';
|
||||
import Form from './Form/template';
|
||||
import Header from './Header/template';
|
||||
import Icon from './Icon/template';
|
||||
import Image from './Image/template';
|
||||
import List from './List/template';
|
||||
import LongText from './LongText/template';
|
||||
import Notice from './Notice/template';
|
||||
import Qrcode from './Qrcode/template';
|
||||
import Tab from './Tab/template';
|
||||
import Text from './Text/template';
|
||||
|
||||
const basicTemplate = [
|
||||
Carousel,
|
||||
Footer,
|
||||
Form,
|
||||
Header,
|
||||
Icon,
|
||||
Image,
|
||||
List,
|
||||
LongText,
|
||||
Notice,
|
||||
Qrcode,
|
||||
Tab,
|
||||
Text,
|
||||
];
|
||||
const BasicTemplate = basicTemplate.map(v => {
|
||||
return { ...v, category: 'base' };
|
||||
});
|
||||
|
||||
export default BasicTemplate;
|
||||
21
src/components/BasicShop/MediaComponents/Video/index.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React, { memo } from 'react';
|
||||
import { Player, BigPlayButton } from 'video-react';
|
||||
import './index.css';
|
||||
import { IVideoConfig } from './schema';
|
||||
|
||||
const VideoPlayer = memo((props: IVideoConfig) => {
|
||||
const { poster, url } = props;
|
||||
return (
|
||||
<div>
|
||||
<Player
|
||||
playsInline
|
||||
poster={poster[0].url}
|
||||
src={url || 'https://gossv.vcg.com/cmsUploadVideo/creative/1移轴/7月移轴.mp4'}
|
||||
>
|
||||
<BigPlayButton position="center" />
|
||||
</Player>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default VideoPlayer;
|
||||
45
src/components/BasicShop/MediaComponents/Video/schema.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import {
|
||||
ITextConfigType,
|
||||
IUploadConfigType,
|
||||
TTextDefaultType,
|
||||
TUploadDefaultType,
|
||||
} from '@/components/PanelComponents/FormEditor/types';
|
||||
|
||||
export type TVideoEditData = Array<IUploadConfigType | ITextConfigType>;
|
||||
export interface IVideoConfig {
|
||||
poster: TUploadDefaultType;
|
||||
url: TTextDefaultType;
|
||||
}
|
||||
|
||||
export interface IVideoSchema {
|
||||
editData: TVideoEditData;
|
||||
config: IVideoConfig;
|
||||
}
|
||||
|
||||
const Video: IVideoSchema = {
|
||||
editData: [
|
||||
{
|
||||
key: 'poster',
|
||||
name: '视频封面',
|
||||
type: 'Upload',
|
||||
},
|
||||
{
|
||||
key: 'url',
|
||||
name: '视频链接',
|
||||
type: 'Text',
|
||||
},
|
||||
],
|
||||
config: {
|
||||
poster: [
|
||||
{
|
||||
uid: '001',
|
||||
name: 'image.png',
|
||||
status: 'done',
|
||||
url: 'http://io.nainor.com/uploads/1_1740c6fbcd9.png',
|
||||
},
|
||||
],
|
||||
url: '',
|
||||
},
|
||||
};
|
||||
|
||||
export default Video;
|
||||
@ -0,0 +1,5 @@
|
||||
const template = {
|
||||
type: 'Video',
|
||||
h: 107,
|
||||
};
|
||||
export default template;
|
||||
6
src/components/BasicShop/MediaComponents/schema.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import Video from './Video/schema';
|
||||
|
||||
const mediaSchema = {
|
||||
Video,
|
||||
};
|
||||
export default mediaSchema;
|
||||
9
src/components/BasicShop/MediaComponents/template.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import Video from './Video/template';
|
||||
|
||||
const mediaTemplate = [Video];
|
||||
|
||||
const MediaTemplate = mediaTemplate.map(v => {
|
||||
return { ...v, category: 'media' };
|
||||
});
|
||||
|
||||
export default MediaTemplate;
|
||||
13
src/components/BasicShop/VisualComponents/Area/index.less
Normal file
@ -0,0 +1,13 @@
|
||||
.chartWrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
.chartTitle {
|
||||
text-align: center;
|
||||
}
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
canvas {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
76
src/components/BasicShop/VisualComponents/Area/index.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
import { Chart } from '@antv/f2';
|
||||
import React, { memo, useEffect, useRef } from 'react';
|
||||
// import { uuid } from 'utils/tool';
|
||||
import AreaImg from '@/assets/area.png';
|
||||
|
||||
import styles from './index.less';
|
||||
import { IChartConfig } from './schema';
|
||||
|
||||
interface XChartProps extends IChartConfig {
|
||||
isTpl: boolean;
|
||||
}
|
||||
|
||||
const XLine = (props: XChartProps) => {
|
||||
const { isTpl, data, color, size, paddingTop, title } = props;
|
||||
const chartRef = useRef(null);
|
||||
useEffect(() => {
|
||||
if (!isTpl) {
|
||||
const chart = new Chart({
|
||||
el: chartRef.current || undefined,
|
||||
pixelRatio: window.devicePixelRatio, // 指定分辨率
|
||||
});
|
||||
|
||||
// step 2: 处理数据
|
||||
const dataX = data.map(item => ({ ...item, value: Number(item.value), a: '1' }));
|
||||
|
||||
// Step 2: 载入数据源
|
||||
chart.source(dataX, {
|
||||
percent: {
|
||||
formatter: function formatter(val) {
|
||||
return val * 100 + '%';
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
chart.tooltip({
|
||||
showCrosshairs: true,
|
||||
});
|
||||
|
||||
chart.scale({
|
||||
name: {
|
||||
range: [0, 1],
|
||||
},
|
||||
value: {
|
||||
tickCount: 5,
|
||||
min: 0,
|
||||
},
|
||||
});
|
||||
|
||||
chart.axis('name', {
|
||||
label: function label(text, index, total) {
|
||||
const textCfg: any = {};
|
||||
if (index === 0) {
|
||||
textCfg.textAlign = 'left';
|
||||
} else if (index === total - 1) {
|
||||
textCfg.textAlign = 'right';
|
||||
}
|
||||
return textCfg;
|
||||
},
|
||||
});
|
||||
|
||||
chart.area().position('name*value');
|
||||
chart.line().position('name*value');
|
||||
chart.render();
|
||||
}
|
||||
}, [data, isTpl]);
|
||||
return (
|
||||
<div className={styles.chartWrap}>
|
||||
<div className={styles.chartTitle} style={{ color, fontSize: size, paddingTop }}>
|
||||
{title}
|
||||
</div>
|
||||
{isTpl ? <img src={AreaImg} alt="dooring chart" /> : <canvas ref={chartRef}></canvas>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(XLine);
|
||||
82
src/components/BasicShop/VisualComponents/Area/schema.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import {
|
||||
IColorConfigType,
|
||||
INumberConfigType,
|
||||
ITableConfigType,
|
||||
ITextConfigType,
|
||||
TColorDefaultType,
|
||||
TNumberDefaultType,
|
||||
TTableDefaultType,
|
||||
TTextDefaultType,
|
||||
} from '@/components/PanelComponents/FormEditor/types';
|
||||
|
||||
export type TChartEditData = Array<
|
||||
ITextConfigType | INumberConfigType | IColorConfigType | ITableConfigType
|
||||
>;
|
||||
export interface IChartConfig {
|
||||
title: TTextDefaultType;
|
||||
size: TNumberDefaultType;
|
||||
color: TColorDefaultType;
|
||||
paddingTop: TNumberDefaultType;
|
||||
data: TTableDefaultType;
|
||||
}
|
||||
|
||||
export interface IChartSchema {
|
||||
editData: TChartEditData;
|
||||
config: IChartConfig;
|
||||
}
|
||||
|
||||
const Chart: IChartSchema = {
|
||||
editData: [
|
||||
{
|
||||
key: 'title',
|
||||
name: '标题',
|
||||
type: 'Text',
|
||||
},
|
||||
{
|
||||
key: 'size',
|
||||
name: '标题大小',
|
||||
type: 'Number',
|
||||
},
|
||||
{
|
||||
key: 'color',
|
||||
name: '标题颜色',
|
||||
type: 'Color',
|
||||
},
|
||||
{
|
||||
key: 'paddingTop',
|
||||
name: '上边距',
|
||||
type: 'Number',
|
||||
},
|
||||
{
|
||||
key: 'data',
|
||||
name: '数据源',
|
||||
type: 'Table',
|
||||
},
|
||||
],
|
||||
config: {
|
||||
title: '面积图',
|
||||
size: 14,
|
||||
color: 'rgba(0,0,0,1)',
|
||||
paddingTop: 10,
|
||||
data: [
|
||||
{
|
||||
name: 'A',
|
||||
value: 20,
|
||||
},
|
||||
{
|
||||
name: 'B',
|
||||
value: 60,
|
||||
},
|
||||
{
|
||||
name: 'C',
|
||||
value: 20,
|
||||
},
|
||||
{
|
||||
name: 'D',
|
||||
value: 80,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default Chart;
|
||||
@ -0,0 +1,5 @@
|
||||
const template = {
|
||||
type: 'Area',
|
||||
h: 108,
|
||||
};
|
||||
export default template;
|
||||
13
src/components/BasicShop/VisualComponents/Chart/index.less
Normal file
@ -0,0 +1,13 @@
|
||||
.chartWrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
.chartTitle {
|
||||
text-align: center;
|
||||
}
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
canvas {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
49
src/components/BasicShop/VisualComponents/Chart/index.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import { Chart } from '@antv/f2';
|
||||
import React, { memo, useEffect, useRef } from 'react';
|
||||
// import { uuid } from 'utils/tool';
|
||||
import ChartImg from '@/assets/chart.png';
|
||||
|
||||
import styles from './index.less';
|
||||
import { IChartConfig } from './schema';
|
||||
|
||||
interface XChartProps extends IChartConfig {
|
||||
isTpl: boolean;
|
||||
}
|
||||
|
||||
const XChart = (props: XChartProps) => {
|
||||
const { isTpl, data, color, size, paddingTop, title } = props;
|
||||
const chartRef = useRef(null);
|
||||
useEffect(() => {
|
||||
if (!isTpl) {
|
||||
const chart = new Chart({
|
||||
el: chartRef.current || undefined,
|
||||
pixelRatio: window.devicePixelRatio, // 指定分辨率
|
||||
});
|
||||
|
||||
// step 2: 处理数据
|
||||
const dataX = data.map(item => ({ ...item, value: Number(item.value) }));
|
||||
|
||||
// Step 2: 载入数据源
|
||||
chart.source(dataX);
|
||||
|
||||
// Step 3:创建图形语法,绘制柱状图,由 genre 和 sold 两个属性决定图形位置,genre 映射至 x 轴,sold 映射至 y 轴
|
||||
chart
|
||||
.interval()
|
||||
.position('name*value')
|
||||
.color('name');
|
||||
|
||||
// Step 4: 渲染图表
|
||||
chart.render();
|
||||
}
|
||||
}, [data, isTpl]);
|
||||
return (
|
||||
<div className={styles.chartWrap}>
|
||||
<div className={styles.chartTitle} style={{ color, fontSize: size, paddingTop }}>
|
||||
{title}
|
||||
</div>
|
||||
{isTpl ? <img src={ChartImg} alt="dooring chart" /> : <canvas ref={chartRef}></canvas>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(XChart);
|
||||