├── .gitignore
├── README.md
├── babel.config.js
├── package.json
├── public
├── favicon.ico
└── index.html
├── src
├── App.vue
├── assets
│ └── logo.png
├── main.js
├── page
│ ├── DynamicFormTable.vue
│ ├── components
│ │ ├── DynamicForm.vue
│ │ └── DynamicTable.vue
│ └── utils
│ │ ├── ListCompare.js
│ │ ├── dynamicRenderingList.js
│ │ ├── flattenResult.js
│ │ ├── getIDsAndProps.js
│ │ ├── helper.js
│ │ └── index.js
└── plugins
│ └── element.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw?
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## 前言
4 | 不知道大家有没有一个同感?天下产品一大抄,简直比程序员的CV大法还厉害!
5 |
6 | 产品一张图,交互全凭自己意会,比如产品经理常说的一句话:"你参考一下某夕夕,某猫,某东",实际上我们没有它们的后台账号,他就点了两下给我们看。
7 |
8 | ## 效果
9 | 言归正传,想必每一家电商公司,都有自己的商品中心,今天又懒得加班了,我带你实现商品中心的SKU,效果大致如下:
10 |
11 | 
12 |
13 | 看到这个效果,首先要找准算法方向,一是全组合,二是对比,大致从以下两点入手:
14 |
15 | ### 算法一:多数组实现全组合,要求如下:
16 |
17 | 
18 |
19 | ### 算法二:两数组对比,要求如下:
20 | 
21 |
22 | ### 大佬看到这么清晰的要求,估计想法:"好简单!"。其实很多东西别人帮你理清楚了当然就觉得简单,实际操作时还是会棘手的。不过,我想在这儿讲个故事,就是下图啦!
23 |
24 | 
25 |
26 | ### 算法一的实现如下:
27 | 
28 |
29 | ### 算法二的实现如下:
30 | 
31 |
32 |
33 | ## 源代码
34 | ### 目录结构大致如下:
35 | 
36 |
37 | # dynamic-form-table
38 | ## Project setup
39 | ```
40 | yarn install
41 | ```
42 |
43 | ### Compiles and hot-reloads for development
44 | ```
45 | yarn run serve
46 | ```
47 |
48 | ### Compiles and minifies for production
49 | ```
50 | yarn run build
51 | ```
52 |
53 | ### Run your tests
54 | ```
55 | yarn run test
56 | ```
57 |
58 | ### Lints and fixes files
59 | ```
60 | yarn run lint
61 | ```
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dynamic-form-table",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "core-js": "^3.6.4",
12 | "element-ui": "^2.4.5",
13 | "vue": "^2.6.11"
14 | },
15 | "devDependencies": {
16 | "@vue/cli-plugin-babel": "^4.2.0",
17 | "@vue/cli-plugin-eslint": "^4.2.0",
18 | "@vue/cli-service": "^4.2.0",
19 | "babel-eslint": "^10.0.3",
20 | "eslint": "^6.7.2",
21 | "eslint-plugin-vue": "^6.1.2",
22 | "vue-cli-plugin-element": "^1.0.1",
23 | "vue-template-compiler": "^2.6.11"
24 | },
25 | "eslintConfig": {
26 | "root": true,
27 | "env": {
28 | "node": true
29 | },
30 | "extends": [
31 | "plugin:vue/essential",
32 | "eslint:recommended"
33 | ],
34 | "parserOptions": {
35 | "parser": "babel-eslint"
36 | },
37 | "rules": {
38 | "no-console":"off",
39 | "no-irregular-whitespace":"off"
40 | }
41 | },
42 | "browserslist": [
43 | "> 1%",
44 | "last 2 versions"
45 | ]
46 | }
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TJH0420/Dynamic-Form-Table/efdcbac0666953bced389a89074facea5483d4ab/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
17 |
18 |
28 |
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TJH0420/Dynamic-Form-Table/efdcbac0666953bced389a89074facea5483d4ab/src/assets/logo.png
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import './plugins/element.js'
4 |
5 | Vue.config.productionTip = false
6 |
7 | new Vue({
8 | render: h => h(App),
9 | }).$mount('#app')
10 |
--------------------------------------------------------------------------------
/src/page/DynamicFormTable.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
动态表单
4 |
9 | 动态表格
10 |
18 |
19 | 传输给后台
20 |
21 |
22 |
23 |
24 |
183 |
184 |
185 |
187 |
--------------------------------------------------------------------------------
/src/page/components/DynamicForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
59 |
60 |
61 |
98 |
99 |
101 |
--------------------------------------------------------------------------------
/src/page/components/DynamicTable.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
20 |
21 | {{scope.row[scope.column.property]}}
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
150 |
151 |
152 |
154 |
--------------------------------------------------------------------------------
/src/page/utils/ListCompare.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * @description 重新选择后比较 相同保留 多的添加 少的删除
4 | *
5 | * @param list1 旧表
6 | * @param list2 新表
7 | */
8 | export default function ListCompare(list1 = [], list2 = []) {
9 | const hash = new Map();
10 | list2.forEach(({ tableEnName }, index) => hash.set(tableEnName, index));
11 | const result = []; // 相同的保留
12 | result.push(...list1.filter(({ tableEnName }) => hash.has(tableEnName)));
13 | hash.clear();
14 | list1.forEach(({ tableEnName }, index) => hash.set(tableEnName, index));
15 | result.push(...list2.filter(({ tableEnName }) => !hash.has(tableEnName)));
16 | return result;
17 | }
--------------------------------------------------------------------------------
/src/page/utils/dynamicRenderingList.js:
--------------------------------------------------------------------------------
1 | import { deepCloneHandler, hasOwnProperty } from './helper'
2 |
3 | /**
4 | * @description 根据选择的表单值 排列组合成 动态渲染出list
5 | *
6 | * @param defaultSelectedList 表单选择的结果List
7 | * @param defaultTableColumnList 默认表头
8 | * @param nameEn 英语名
9 | * @param nameLo 本地名
10 | */
11 | export default function dynamicRenderingList({ defaultSelectedList = [], defaultTableColumnList = [], nameEn = '', nameLo = '' }) {
12 | const defaultSelectedListCopy = deepCloneHandler(defaultSelectedList);
13 | console.log('defaultSelectedListCopy', defaultSelectedListCopy)
14 | //判断arr内attributeSeleted为空情况
15 | if (isAllEmpty(defaultSelectedListCopy)) {
16 | return [];
17 | }
18 |
19 | // 组合成 tableShowList
20 | let queue = [];
21 | getQueue({}, 0, defaultSelectedListCopy, queue);
22 |
23 | //对str适当处理
24 | setQueue(queue, defaultTableColumnList, nameEn, nameLo)
25 | return queue;
26 | }
27 |
28 | function isAllEmpty(arr) {
29 | let emptyNum = 0;
30 | arr.forEach(item => {
31 | if (!item.attributeSeleted.length) {
32 | item.attributeSeleted.push("");
33 | emptyNum++;
34 | }
35 | });
36 | console.log(emptyNum === arr.length);
37 | return emptyNum === arr.length ? true : false;
38 | }
39 |
40 | function setQueue(queue, defaultTableColumnList, nameEn, nameLo) {
41 | queue.forEach(item => {
42 | let tmp = "";
43 | for (let name in item) {
44 | if (hasOwnProperty(item, name)) tmp += item[name];
45 | }
46 | defaultTableColumnList.forEach(e => {
47 | if (e.prop === "tableEnName") item[e.prop] = nameEn + tmp;
48 | else if (e.prop === "tableLocalName") item[e.prop] = nameLo + tmp;
49 | else item[e.prop] = "";
50 | });
51 | });
52 | }
53 |
54 | function getQueue(obj, i, arr, queue) {
55 | let arrLen = arr.length
56 | for (let j = 0; j < arr[i].attributeSeleted.length; j++) {
57 | if (i < arrLen - 1) {
58 | obj[arr[i].attributeId] = arr[i].attributeSeleted[j];
59 | getQueue(obj, i + 1, arr, queue);
60 | } else {
61 | obj[arr[i].attributeId] = arr[i].attributeSeleted[j];
62 | console.log("here", obj);
63 | queue.push(deepCloneHandler(obj));
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/page/utils/flattenResult.js:
--------------------------------------------------------------------------------
1 | import { deepCloneHandler } from './helper'
2 | /**
3 | * @description 结果扁平化 和 表单结果填充
4 | *
5 | * @param resultList 返回的结果
6 | * @param defaultFormList 动态生成表单的List
7 | */
8 | export default function flattenResult(resultList = [], defaultFormList = []) {
9 | const copyResultList = deepCloneHandler(resultList);
10 | const copyDefaultFormList = deepCloneHandler(defaultFormList);
11 | const map = new Map();
12 | const valHash = new Set()
13 | copyResultList.forEach(item => {
14 | // 把值都拿出来 O(m*n)
15 | const { tableListIDs = [] } = item;
16 | tableListIDs.forEach(({ attributeId: id, attributeValues: value }) => {
17 | // 有值没存过
18 | if (map.has(id) && !valHash.has(value)) {
19 | valHash.add(value)
20 | map.get(id).push(value);
21 | } else if (!map.has(id)) {
22 | valHash.add(value)
23 | map.set(id, [value]);
24 | }
25 | item[id] = value;
26 | });
27 | delete item.tableListIDs;
28 | });
29 | // 表单结果填充
30 | copyDefaultFormList.forEach(item => {
31 | if (map.has(item.attributeId)) {
32 | item.attributeSeleted = [...map.get(item.attributeId)];
33 | }
34 | });
35 | return { copyResultList, copyDefaultFormList };
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/src/page/utils/getIDsAndProps.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @description 获得转换 和 获得动态表头
3 | *
4 | * @param defaultFormList 动态生成表单的List
5 | */
6 | export default function getIDsAndProps(defaultFormList = []) {
7 | const defaultTableIDs = [];
8 | const defaultTableProps = [];
9 | defaultFormList.forEach(element => {
10 | // 获得转换defaultTableIDs
11 | defaultTableIDs.push(element.attributeId);
12 | // 获得动态表头defaultTableProps
13 | defaultTableProps.push({
14 | attributeId: element.attributeId,
15 | attributeName: element.attributeName
16 | });
17 | });
18 | return { defaultTableIDs, defaultTableProps };
19 | }
--------------------------------------------------------------------------------
/src/page/utils/helper.js:
--------------------------------------------------------------------------------
1 | export const deepCloneHandler = arr => {
2 | return JSON.parse(JSON.stringify(arr));
3 | };
4 |
5 | export const hasOwnProperty = () => Object.prototype.hasOwnProperty.call;
--------------------------------------------------------------------------------
/src/page/utils/index.js:
--------------------------------------------------------------------------------
1 | import dynamicRenderingList from './dynamicRenderingList'
2 | import ListCompare from './ListCompare'
3 | import { deepCloneHandler, hasOwnProperty } from './helper'
4 | import flattenResult from './flattenResult'
5 | import getIDsAndProps from './getIDsAndProps'
6 |
7 | export {
8 | dynamicRenderingList,
9 | ListCompare,
10 | deepCloneHandler,
11 | hasOwnProperty,
12 | flattenResult,
13 | getIDsAndProps
14 | }
15 |
16 | export default {
17 | dynamicRenderingList,
18 | ListCompare,
19 | deepCloneHandler,
20 | hasOwnProperty,
21 | flattenResult,
22 | getIDsAndProps
23 | }
--------------------------------------------------------------------------------
/src/plugins/element.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Element from 'element-ui'
3 | import 'element-ui/lib/theme-chalk/index.css'
4 |
5 | Vue.use(Element)
6 |
--------------------------------------------------------------------------------