[fix] init
This commit is contained in:
commit
1311e08484
4
.babelrc
Normal file
4
.babelrc
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"presets": ["@babel/preset-env", "@vue/babel-preset-jsx"],
|
||||||
|
"plugins": ["@babel/plugin-transform-runtime"]
|
||||||
|
}
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/node_modules
|
||||||
|
yarn-error.log
|
||||||
|
yarn.lock
|
5
.idea/.gitignore
vendored
Normal file
5
.idea/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
12
.idea/cl-crud2-main.iml
Normal file
12
.idea/cl-crud2-main.iml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
58
.idea/codeStyles/Project.xml
Normal file
58
.idea/codeStyles/Project.xml
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<HTMLCodeStyleSettings>
|
||||||
|
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
||||||
|
<option name="HTML_ENFORCE_QUOTES" value="true" />
|
||||||
|
</HTMLCodeStyleSettings>
|
||||||
|
<JSCodeStyleSettings version="0">
|
||||||
|
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||||
|
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||||
|
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
||||||
|
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||||
|
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||||
|
</JSCodeStyleSettings>
|
||||||
|
<TypeScriptCodeStyleSettings version="0">
|
||||||
|
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||||
|
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||||
|
<option name="ENFORCE_TRAILING_COMMA" value="Remove" />
|
||||||
|
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||||
|
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||||
|
</TypeScriptCodeStyleSettings>
|
||||||
|
<VueCodeStyleSettings>
|
||||||
|
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
|
||||||
|
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
|
||||||
|
</VueCodeStyleSettings>
|
||||||
|
<editorconfig>
|
||||||
|
<option name="ENABLED" value="false" />
|
||||||
|
</editorconfig>
|
||||||
|
<codeStyleSettings language="HTML">
|
||||||
|
<option name="SOFT_MARGINS" value="100" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="JavaScript">
|
||||||
|
<option name="SOFT_MARGINS" value="100" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="TypeScript">
|
||||||
|
<option name="SOFT_MARGINS" value="100" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="Vue">
|
||||||
|
<option name="SOFT_MARGINS" value="100" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="4" />
|
||||||
|
<option name="TAB_SIZE" value="4" />
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
|
</state>
|
||||||
|
</component>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/cl-crud2-main.iml" filepath="$PROJECT_DIR$/.idea/cl-crud2-main.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
9
.prettierrc
Normal file
9
.prettierrc
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"tabWidth": 4,
|
||||||
|
"useTabs": true,
|
||||||
|
"semi": true,
|
||||||
|
"jsxBracketSameLine": true,
|
||||||
|
"singleQuote": false,
|
||||||
|
"printWidth": 100,
|
||||||
|
"trailingComma": "none"
|
||||||
|
}
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"liveServer.settings.port": 5501
|
||||||
|
}
|
46
README.md
Normal file
46
README.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#### Browser
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/cl-crud2@0.3.3/dist/cl-crud2.min.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Document
|
||||||
|
|
||||||
|
[https://docs-admin-pro.cool-js.com/#/front/crud](https://docs-admin-pro.cool-js.com/#/front/crud)
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
- 0.4.1 处理 cl-dialog 关闭按钮点击刷新页面问题
|
||||||
|
- 0.4.0 弃用 cl-dialog, destroy-on-close,改用 key 缓存
|
||||||
|
- 0.3.8 添加 saveButtonText, closeButtonText 字典
|
||||||
|
- 0.3.7 cl-upsert 添加 showLoading, hiddenLoading
|
||||||
|
- 0.3.5 添加 dict.label
|
||||||
|
- 0.3.4 优化 cl-dialog cl-form 周期
|
||||||
|
- 0.3.3 解决 cl-form 作用域改变问题
|
||||||
|
- 0.3.0 解决 cl-form 默认值不监听问题,对组件添加响应式布局
|
||||||
|
- 0.2.11 解决 cl-query props.list 不更新问题
|
||||||
|
- 0.2.9 优化 cl-form, cl-adv-search 对表单操作错误的问题
|
||||||
|
- 0.2.8 优化
|
||||||
|
- 0.2.7 解决 cl-form props 异常
|
||||||
|
- 0.2.6 解决 cl-dialog 关闭异常问题
|
||||||
|
- 0.2.5 过滤 cl-upsert, cl-form 事件(submit) hidden = true 的值
|
||||||
|
- 0.2.4 解决 cl-upsert opList hiddenOp 异常
|
||||||
|
- 0.2.3 解决 cl-form form.done 异常
|
||||||
|
- 0.2.2 解决 cl-search-key field 取值错误
|
||||||
|
- 0.2.1 解决 cl-dialog.props.fullscreen 异常
|
||||||
|
- 0.2.0 解决 uniapp cloud 命名冲突问题
|
||||||
|
- 0.1.9 cl-crud 添加 doLayout 方法
|
||||||
|
- 0.1.8 解决 cl-form resetForm 异常;hidden 添加 isEdit 参数
|
||||||
|
- 0.1.7 cl-form cl-table 添加缓存处理
|
||||||
|
- 0.1.6 upsert.hidden adv-search.hidden 添加 @[prop] | function 方式
|
||||||
|
- 0.1.5 解决 cl-upser onOpen 参数缺少问题
|
||||||
|
- 0.1.4 解决 rowAppend 参数无法传递异常
|
||||||
|
- 0.1.3 添加 cl-table 的 emit 事件, cl-table 添加 component 方式渲染
|
||||||
|
- 0.1.2 解决 clearForm 后,监听事件失效问题
|
||||||
|
- 0.1.1 table.column.dict 支持 el-tag 标签
|
||||||
|
- 0.1.0 重写 permission 设置途径
|
||||||
|
- 0.0.20 setPermission 返回 this
|
||||||
|
- 0.0.19 添加 setPermission
|
||||||
|
- 0.0.18 重构 el-adv-search, el-upsert, el-form 周期事件
|
||||||
|
- 0.0.1 初始化
|
3
dist/cl-crud2.min.js
vendored
Normal file
3
dist/cl-crud2.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
31
dist/cl-crud2.min.js.LICENSE.txt
vendored
Normal file
31
dist/cl-crud2.min.js.LICENSE.txt
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/*!
|
||||||
|
* The buffer module from node.js, for the browser.
|
||||||
|
*
|
||||||
|
* @author Feross Aboukhadijeh <http://feross.org>
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* is-plain-object <https://github.com/jonschlinkert/is-plain-object>
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014-2017, Jon Schlinkert.
|
||||||
|
* Released under the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* isobject <https://github.com/jonschlinkert/isobject>
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014-2017, Jon Schlinkert.
|
||||||
|
* Released under the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* shallow-clone <https://github.com/jonschlinkert/shallow-clone>
|
||||||
|
*
|
||||||
|
* Copyright (c) 2015-present, Jon Schlinkert.
|
||||||
|
* Released under the MIT License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
||||||
|
|
||||||
|
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
1
dist/cl-crud2.min.js.map
vendored
Normal file
1
dist/cl-crud2.min.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
938
example/index.html
Normal file
938
example/index.html
Normal file
@ -0,0 +1,938 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||||
|
<title>CRUD Example</title>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
|
||||||
|
<script src="./vue2.js"></script>
|
||||||
|
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
|
||||||
|
<script src="../dist/cl-crud2.min.js"></script>
|
||||||
|
<style>
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#app {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<cl-crud ref="crud" @load="onLoad">
|
||||||
|
<el-row type="flex" align="middle">
|
||||||
|
<cl-refresh-btn></cl-refresh-btn>
|
||||||
|
<cl-add-btn></cl-add-btn>
|
||||||
|
<cl-multi-delete-btn></cl-multi-delete-btn>
|
||||||
|
<cl-query
|
||||||
|
:list="[
|
||||||
|
{
|
||||||
|
label: '启用',
|
||||||
|
value: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '禁用',
|
||||||
|
value: 0
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
></cl-query>
|
||||||
|
<cl-filter label="状态">
|
||||||
|
<el-select
|
||||||
|
size="mini"
|
||||||
|
v-model="selects.status"
|
||||||
|
@change="val => {
|
||||||
|
refresh({
|
||||||
|
status: val,
|
||||||
|
page: 1
|
||||||
|
})
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<el-option value="" label="全部"></el-option>
|
||||||
|
<el-option value="0" label="禁用"></el-option>
|
||||||
|
<el-option value="1" label="启用"></el-option>
|
||||||
|
</el-select>
|
||||||
|
</cl-filter>
|
||||||
|
<el-button size="mini" @click="openForm">自定义测试表单</el-button>
|
||||||
|
<el-button size="mini" @click="openDialog">自定义对话框</el-button>
|
||||||
|
<cl-flex1></cl-flex1>
|
||||||
|
<cl-search-key
|
||||||
|
field="name"
|
||||||
|
:field-list="[
|
||||||
|
{
|
||||||
|
label: '姓名',
|
||||||
|
value: 'name'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '身份证',
|
||||||
|
value: 'idCard'
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
></cl-search-key>
|
||||||
|
<cl-adv-btn></cl-adv-btn>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row>
|
||||||
|
<cl-table ref="table" v-bind="table.props" v-on="table.on"> </cl-table>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row>
|
||||||
|
<cl-pagination></cl-pagination>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 高级搜索 -->
|
||||||
|
<cl-adv-search ref="adv-search" v-bind="advSearch.props" v-on="advSearch.on">
|
||||||
|
</cl-adv-search>
|
||||||
|
|
||||||
|
<!-- 编辑、新增 -->
|
||||||
|
<cl-upsert
|
||||||
|
v-model="upsert.form"
|
||||||
|
ref="upsert"
|
||||||
|
v-bind="upsert.props"
|
||||||
|
v-on="upsert.on"
|
||||||
|
>
|
||||||
|
</cl-upsert>
|
||||||
|
|
||||||
|
<!-- 自定义表单 -->
|
||||||
|
<cl-form ref="form">
|
||||||
|
<!-- 动态增减表单验证 -->
|
||||||
|
<template #slot-validate="{scope}">
|
||||||
|
<el-form-item
|
||||||
|
v-for="(item, index) in scope.vads"
|
||||||
|
:key="index"
|
||||||
|
:prop="'vads.' + index + '.val'"
|
||||||
|
:rules="{ required: true, message: '请输入' }"
|
||||||
|
>
|
||||||
|
<el-input v-model="item.val"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-button @click="addVad(scope.vads)">添加行</el-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 测试设置表单值 -->
|
||||||
|
<template #slot-var="{ scope }">
|
||||||
|
<el-input v-model="scope._name"></el-input>
|
||||||
|
<el-button @click="setFormValue(scope)">设置</el-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 内嵌crud -->
|
||||||
|
<template #slot-crud="{scope}">
|
||||||
|
<cl-crud @load="onUpsertCrudLoad">
|
||||||
|
<cl-table
|
||||||
|
:props="{
|
||||||
|
'max-height': '300px'
|
||||||
|
}"
|
||||||
|
:columns="[
|
||||||
|
{
|
||||||
|
label: '姓名',
|
||||||
|
prop: 'name'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '存款',
|
||||||
|
prop: 'price',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '创建时间',
|
||||||
|
prop: 'createTime'
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
></cl-table>
|
||||||
|
</cl-crud>
|
||||||
|
</template>
|
||||||
|
</cl-form>
|
||||||
|
|
||||||
|
<!-- 自定义对话框 -->
|
||||||
|
<cl-dialog
|
||||||
|
title="自定义对话框"
|
||||||
|
:visible.sync="dialog.visible"
|
||||||
|
v-bind="dialog.props"
|
||||||
|
v-on="dialog.on"
|
||||||
|
>
|
||||||
|
<p>自定义对话框</p>
|
||||||
|
<test></test>
|
||||||
|
</cl-dialog>
|
||||||
|
</cl-crud>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 表格数据
|
||||||
|
const userList = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "刘一",
|
||||||
|
process: 42.2,
|
||||||
|
createTime: "2019年09月02日",
|
||||||
|
price: 75.99,
|
||||||
|
salesRate: 52.2,
|
||||||
|
status: 1,
|
||||||
|
images: [
|
||||||
|
"https://cool-comm.oss-cn-shenzhen.aliyuncs.com/show/imgs/chat/avatar/1.jpg"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "陈二",
|
||||||
|
process: 35.2,
|
||||||
|
createTime: "2019年09月05日",
|
||||||
|
price: 242.1,
|
||||||
|
salesRate: 72.1,
|
||||||
|
status: 1,
|
||||||
|
images: [
|
||||||
|
"https://cool-comm.oss-cn-shenzhen.aliyuncs.com/show/imgs/chat/avatar/2.jpg"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "张三",
|
||||||
|
process: 10.2,
|
||||||
|
createTime: "2019年09月12日",
|
||||||
|
price: 74.11,
|
||||||
|
salesRate: 23.9,
|
||||||
|
status: 0,
|
||||||
|
images: [
|
||||||
|
"https://cool-comm.oss-cn-shenzhen.aliyuncs.com/show/imgs/chat/avatar/3.jpg"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "李四",
|
||||||
|
process: 75.5,
|
||||||
|
createTime: "2019年09月13日",
|
||||||
|
price: 276.64,
|
||||||
|
salesRate: 47.2,
|
||||||
|
status: 0,
|
||||||
|
images: [
|
||||||
|
"https://cool-comm.oss-cn-shenzhen.aliyuncs.com/show/imgs/chat/avatar/4.jpg"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: "王五",
|
||||||
|
process: 25.4,
|
||||||
|
createTime: "2019年09月18日",
|
||||||
|
price: 160.23,
|
||||||
|
salesRate: 28.3,
|
||||||
|
status: 1,
|
||||||
|
images: [
|
||||||
|
"https://cool-comm.oss-cn-shenzhen.aliyuncs.com/show/imgs/chat/avatar/5.jpg"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
// 网络请求
|
||||||
|
const testService = {
|
||||||
|
page: p => {
|
||||||
|
console.log("GET[page]", p);
|
||||||
|
return Promise.resolve({
|
||||||
|
list: userList,
|
||||||
|
pagination: {
|
||||||
|
page: p.page,
|
||||||
|
size: p.size,
|
||||||
|
total: 5
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
info: d => {
|
||||||
|
console.log("GET[info]", d);
|
||||||
|
return new Promise(resolve => {
|
||||||
|
resolve({
|
||||||
|
id: 1,
|
||||||
|
name: d.id,
|
||||||
|
price: 100,
|
||||||
|
ids: "0,3,2"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
add: d => {
|
||||||
|
console.log("POST[add]", d);
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
delete: d => {
|
||||||
|
console.log("POST[delete]", d);
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
update: d => {
|
||||||
|
console.log("POST[update]", d);
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 注册组件
|
||||||
|
Vue.use(CRUD, {
|
||||||
|
crud: {
|
||||||
|
event: {
|
||||||
|
refresh: (data, { app }) => {
|
||||||
|
app.refresh(data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dict: {
|
||||||
|
label: {
|
||||||
|
add: "添加",
|
||||||
|
update: "更新",
|
||||||
|
delete: "移除",
|
||||||
|
multiDelete: "批量移除",
|
||||||
|
advSearch: "过滤",
|
||||||
|
saveButtonText: "确认"
|
||||||
|
// closeButtonText: "取消"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.component("test", {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
text: ""
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
console.log("test load");
|
||||||
|
setInterval(() => {
|
||||||
|
this.text = Math.random();
|
||||||
|
}, 1000);
|
||||||
|
},
|
||||||
|
|
||||||
|
render(h) {
|
||||||
|
return h("p", this.text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
selects: {
|
||||||
|
status: ""
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
on: {
|
||||||
|
"row-click": row => {
|
||||||
|
console.log("行点击", row);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
type: "selection",
|
||||||
|
align: "center",
|
||||||
|
width: 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "姓名",
|
||||||
|
prop: "name",
|
||||||
|
align: "center",
|
||||||
|
"min-width": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "存款",
|
||||||
|
prop: "price",
|
||||||
|
sortable: true,
|
||||||
|
align: "center",
|
||||||
|
"min-width": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "状态",
|
||||||
|
prop: "status",
|
||||||
|
align: "center",
|
||||||
|
"min-width": 120,
|
||||||
|
dict: [
|
||||||
|
{
|
||||||
|
label: "启用",
|
||||||
|
value: 1,
|
||||||
|
type: "primary"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "禁用",
|
||||||
|
value: 0,
|
||||||
|
type: "danger"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "创建时间",
|
||||||
|
prop: "createTime",
|
||||||
|
align: "center",
|
||||||
|
"min-width": 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "操作",
|
||||||
|
type: "op",
|
||||||
|
align: "center",
|
||||||
|
layout: [
|
||||||
|
"edit",
|
||||||
|
"delete",
|
||||||
|
({ h }) => {
|
||||||
|
return h(
|
||||||
|
"el-button",
|
||||||
|
{
|
||||||
|
props: {
|
||||||
|
type: "text",
|
||||||
|
size: "mini"
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
click: () => {
|
||||||
|
this.$refs["crud"].rowAppend({
|
||||||
|
name: "icssoa append"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"追加"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
upsert: {
|
||||||
|
on: {
|
||||||
|
open(isEdit, data) {
|
||||||
|
console.log("cl-upsert 打开", isEdit, data.name);
|
||||||
|
},
|
||||||
|
|
||||||
|
close() {
|
||||||
|
console.log("cl-upsert 关闭");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
props: {
|
||||||
|
width: "1000px",
|
||||||
|
"label-position": "top"
|
||||||
|
},
|
||||||
|
|
||||||
|
onOpen: (isEdit, data, { done, submit, close }) => {
|
||||||
|
console.log("cl-upsert 打开钩子", isEdit, data);
|
||||||
|
},
|
||||||
|
|
||||||
|
onClose(done) {
|
||||||
|
console.log("cl-upsert 关闭钩子");
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
|
||||||
|
onInfo(data, { next, done, close }) {
|
||||||
|
console.log("cl-upsert 详情钩子", data);
|
||||||
|
next(data);
|
||||||
|
},
|
||||||
|
|
||||||
|
onSubmit(isEdit, data, { next, close, done }) {
|
||||||
|
console.log("cl-upsert 提交钩子", `是否编辑 ${isEdit}`, data);
|
||||||
|
next(data);
|
||||||
|
},
|
||||||
|
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: "姓名",
|
||||||
|
prop: "name",
|
||||||
|
component: {
|
||||||
|
name: "el-input"
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
required: true,
|
||||||
|
message: "姓名不能为空"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "是否显示存款",
|
||||||
|
prop: "isPrice",
|
||||||
|
flex: false,
|
||||||
|
component: {
|
||||||
|
name: "el-switch"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "存款",
|
||||||
|
prop: "price",
|
||||||
|
hidden: "@isPrice",
|
||||||
|
component: {
|
||||||
|
name: "el-input-number"
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
required: true,
|
||||||
|
message: "存款不能为空"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
op: {
|
||||||
|
layout: [
|
||||||
|
"close",
|
||||||
|
"save",
|
||||||
|
({ h }) => {
|
||||||
|
return h(
|
||||||
|
"el-button",
|
||||||
|
{
|
||||||
|
props: {
|
||||||
|
size: "small"
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
click: () => {
|
||||||
|
// this.$refs["upsert"].setForm(
|
||||||
|
// "name",
|
||||||
|
// "神仙都没用"
|
||||||
|
// );
|
||||||
|
this.upsert.form.name = "神仙都没用";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"设置名称"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
hdr: {
|
||||||
|
opList: ["fullscreen", "close"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
name: "xxxx"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
advSearch: {
|
||||||
|
on: {
|
||||||
|
open(data) {
|
||||||
|
console.log("adv-search 打开", data);
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
console.log("adv-search 关闭");
|
||||||
|
},
|
||||||
|
reset() {
|
||||||
|
console.log("adv-search 重置");
|
||||||
|
},
|
||||||
|
clear() {
|
||||||
|
console.log("adv-search 清空");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
onOpen(data, { next }) {
|
||||||
|
console.log("adv-search 打开钩子", data);
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
onClose(done) {
|
||||||
|
console.log("adv-search 关闭钩子");
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
onSearch(data, { next, close }) {
|
||||||
|
console.log("adv-search 搜索钩子", data);
|
||||||
|
next(data);
|
||||||
|
},
|
||||||
|
opList: ["search", "reset", "clear", "close"],
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: "金额",
|
||||||
|
prop: "price",
|
||||||
|
value: 100,
|
||||||
|
component: {
|
||||||
|
name: "el-input-number",
|
||||||
|
props: {
|
||||||
|
min: 1,
|
||||||
|
max: 1000000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "销售率",
|
||||||
|
prop: "salesRate",
|
||||||
|
component: {
|
||||||
|
name: "el-input-number",
|
||||||
|
props: {
|
||||||
|
precision: 2,
|
||||||
|
min: 0,
|
||||||
|
max: 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dialog: {
|
||||||
|
visible: false,
|
||||||
|
props: {
|
||||||
|
title: "自定义对话框",
|
||||||
|
props: {
|
||||||
|
"before-close"(done) {
|
||||||
|
console.log("dialog before-close");
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
done();
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
open() {
|
||||||
|
console.log("dialog open");
|
||||||
|
},
|
||||||
|
closed() {
|
||||||
|
console.log("dialog closed");
|
||||||
|
},
|
||||||
|
opened() {
|
||||||
|
console.log("dialog opened");
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
console.log("dialog close");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
openForm() {
|
||||||
|
const { open, setForm } = this.$refs["form"];
|
||||||
|
|
||||||
|
const rd = Math.random();
|
||||||
|
|
||||||
|
open({
|
||||||
|
props: {
|
||||||
|
title: "自定义表单",
|
||||||
|
"label-width": "150px",
|
||||||
|
width: "1000px"
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
qs: [1],
|
||||||
|
_name: "羊姜" + rd
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
label: "表单名称",
|
||||||
|
prop: "name",
|
||||||
|
component: {
|
||||||
|
name: "test"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
props: {
|
||||||
|
"label-width": "0px"
|
||||||
|
},
|
||||||
|
component: ({ h }) => {
|
||||||
|
return h(
|
||||||
|
"el-divider",
|
||||||
|
{
|
||||||
|
props: {
|
||||||
|
"content-position": "left"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"测试设置表单值"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: {
|
||||||
|
name: "slot-var"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
props: {
|
||||||
|
"label-width": "0px"
|
||||||
|
},
|
||||||
|
component: ({ h }) => {
|
||||||
|
return h(
|
||||||
|
"el-divider",
|
||||||
|
{
|
||||||
|
props: {
|
||||||
|
"content-position": "left"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"测试验证el-select 清空表单"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "类型",
|
||||||
|
prop: "type",
|
||||||
|
component: {
|
||||||
|
name: "el-select",
|
||||||
|
on: {
|
||||||
|
change: v => {
|
||||||
|
setForm("name", v);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: "羊姜",
|
||||||
|
value: "羊姜"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "神仙都没用",
|
||||||
|
value: "神仙都没用"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
props: {
|
||||||
|
"label-width": "0px"
|
||||||
|
},
|
||||||
|
component: ({ h }) => {
|
||||||
|
return h(
|
||||||
|
"el-divider",
|
||||||
|
{
|
||||||
|
props: {
|
||||||
|
"content-position": "left"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"测试内嵌CRUD"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
props: {
|
||||||
|
"label-width": "0px"
|
||||||
|
},
|
||||||
|
component: {
|
||||||
|
name: "slot-crud"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
props: {
|
||||||
|
"label-width": "0px"
|
||||||
|
},
|
||||||
|
component: ({ h }) => {
|
||||||
|
return h(
|
||||||
|
"el-divider",
|
||||||
|
{
|
||||||
|
props: {
|
||||||
|
"content-position": "left"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"测试验证规则"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: "vads",
|
||||||
|
value: [],
|
||||||
|
label: "动态增减表单验证",
|
||||||
|
component: {
|
||||||
|
name: "slot-validate"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
props: {
|
||||||
|
"label-width": "0px"
|
||||||
|
},
|
||||||
|
component: ({ h }) => {
|
||||||
|
return h(
|
||||||
|
"el-divider",
|
||||||
|
{
|
||||||
|
props: {
|
||||||
|
"content-position": "left"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"测试显隐"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "奇术",
|
||||||
|
prop: "qs",
|
||||||
|
value: [],
|
||||||
|
component: {
|
||||||
|
name: "el-select",
|
||||||
|
attrs: {
|
||||||
|
placeholder: "请选择奇术"
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
multiple: true
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: "烟水还魂",
|
||||||
|
value: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "雨恨云愁",
|
||||||
|
value: 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "技能",
|
||||||
|
prop: "jn",
|
||||||
|
value: 1,
|
||||||
|
component: {
|
||||||
|
name: "el-select",
|
||||||
|
attrs: {
|
||||||
|
placeholder: "请选择技能"
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: "飞羽箭",
|
||||||
|
value: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "落星式",
|
||||||
|
value: 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "五行",
|
||||||
|
prop: "wx",
|
||||||
|
value: 0,
|
||||||
|
hidden: ({ scope }) => {
|
||||||
|
return scope.jn == 1;
|
||||||
|
},
|
||||||
|
component: {
|
||||||
|
name: "el-radio-group",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: "水",
|
||||||
|
value: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "火",
|
||||||
|
value: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "雷",
|
||||||
|
value: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "风",
|
||||||
|
value: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "土",
|
||||||
|
value: 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "雨润",
|
||||||
|
prop: "s1",
|
||||||
|
hidden: ({ scope }) => {
|
||||||
|
return scope.wx != 0;
|
||||||
|
},
|
||||||
|
component: ({ h }) => {
|
||||||
|
return h("p", "以甘甜雨露的滋润使人精力充沛");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "风雪冰天",
|
||||||
|
prop: "s2",
|
||||||
|
hidden: ({ scope }) => {
|
||||||
|
return scope.wx != 0;
|
||||||
|
},
|
||||||
|
component: ({ h }) => {
|
||||||
|
return h("p", "召唤漫天风雪,对敌方造成巨大的杀伤力");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "三昧真火",
|
||||||
|
prop: "h",
|
||||||
|
hidden: ({ scope }) => {
|
||||||
|
return scope.wx != 1;
|
||||||
|
},
|
||||||
|
component: ({ h }) => {
|
||||||
|
return h("p", "召唤三昧真火焚烧敌方的仙术");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "惊雷闪",
|
||||||
|
prop: "l",
|
||||||
|
hidden: ({ scope }) => {
|
||||||
|
return scope.wx != 2;
|
||||||
|
},
|
||||||
|
component: ({ h }) => {
|
||||||
|
return h(
|
||||||
|
"p",
|
||||||
|
"召唤惊雷无数,对敌方全体进行攻击,是十分强力的仙术"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "如沐春风",
|
||||||
|
prop: "f",
|
||||||
|
hidden: ({ scope }) => {
|
||||||
|
return scope.wx != 3;
|
||||||
|
},
|
||||||
|
component: ({ h }) => {
|
||||||
|
return h("p", "温暖柔和的复苏春风,使人回复活力");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "艮山壁障",
|
||||||
|
prop: "t",
|
||||||
|
hidden: ({ scope }) => {
|
||||||
|
return scope.wx != 4;
|
||||||
|
},
|
||||||
|
component: ({ h }) => {
|
||||||
|
return h(
|
||||||
|
"p",
|
||||||
|
"以艮山之灵形成一道壁障,受此壁障守护者刀枪不入"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
on: {
|
||||||
|
open: (data, { close, submit, done }) => {
|
||||||
|
console.log("cl-form open", data);
|
||||||
|
},
|
||||||
|
|
||||||
|
close(done) {
|
||||||
|
done();
|
||||||
|
console.log("cl-form close");
|
||||||
|
},
|
||||||
|
|
||||||
|
submit: (data, { close, done, next }) => {
|
||||||
|
console.log("cl-form submit", data);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$message.success("提交成功");
|
||||||
|
close();
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
openDialog() {
|
||||||
|
this.dialog.visible = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
setFormValue(scope) {
|
||||||
|
this.$refs["form"].setForm("_name", "神仙");
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoad({ ctx, app }) {
|
||||||
|
ctx.service(testService)
|
||||||
|
.permission(() => {
|
||||||
|
return {
|
||||||
|
add: true,
|
||||||
|
update: true,
|
||||||
|
delete: true
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.done();
|
||||||
|
|
||||||
|
app.refresh();
|
||||||
|
},
|
||||||
|
|
||||||
|
refresh(params) {
|
||||||
|
this.$refs["crud"].refresh(params);
|
||||||
|
},
|
||||||
|
|
||||||
|
onUpsertCrudLoad({ ctx, app }) {
|
||||||
|
ctx.service(testService).done();
|
||||||
|
app.refresh();
|
||||||
|
},
|
||||||
|
|
||||||
|
addVad(list) {
|
||||||
|
list.push({
|
||||||
|
val: ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
12014
example/vue2.js
Normal file
12014
example/vue2.js
Normal file
File diff suppressed because it is too large
Load Diff
47
package.json
Normal file
47
package.json
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"name": "cl-crud2",
|
||||||
|
"version": "0.4.1",
|
||||||
|
"description": "cool-admin: cl-crud、cl-form、cl-dialog",
|
||||||
|
"main": "dist/cl-crud2.min.js",
|
||||||
|
"scripts": {
|
||||||
|
"dist": "webpack --config webpack.config.js",
|
||||||
|
"dev": "webpack -w"
|
||||||
|
},
|
||||||
|
"author": "cool-team-official",
|
||||||
|
"license": "ISC",
|
||||||
|
"keywords": [
|
||||||
|
"crud",
|
||||||
|
"admin",
|
||||||
|
"vue",
|
||||||
|
"element-ui",
|
||||||
|
"upsert"
|
||||||
|
],
|
||||||
|
"homepage": "https://github.com/cool-team-official/cl-crud2",
|
||||||
|
"files": [
|
||||||
|
"dist",
|
||||||
|
"src",
|
||||||
|
"webpack.config.js",
|
||||||
|
"example"
|
||||||
|
],
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.9.6",
|
||||||
|
"@babel/plugin-transform-runtime": "^7.9.6",
|
||||||
|
"@babel/preset-env": "^7.10.2",
|
||||||
|
"@vue/babel-preset-jsx": "^1.1.2",
|
||||||
|
"babel-loader": "^8.1.0",
|
||||||
|
"css-loader": "^3.2.0",
|
||||||
|
"progress-bar-webpack-plugin": "^2.1.0",
|
||||||
|
"style-loader": "^1.0.0",
|
||||||
|
"stylus": "^0.54.7",
|
||||||
|
"stylus-loader": "^3.0.2",
|
||||||
|
"terser-webpack-plugin": "^3.0.1",
|
||||||
|
"typescript": "^3.9.3",
|
||||||
|
"webpack": "^4.41.2",
|
||||||
|
"webpack-cli": "^3.3.11"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"array.prototype.flat": "^1.2.3",
|
||||||
|
"clone-deep": "^4.0.1"
|
||||||
|
},
|
||||||
|
"sideEffects": false
|
||||||
|
}
|
70
src/app.js
Normal file
70
src/app.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { deepMerge, isFunction } from "@/utils";
|
||||||
|
import { __plugins, __inst } from "@/global";
|
||||||
|
|
||||||
|
export const bootstrap = (that) => {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const { conf, refresh, event, id, fn } = that;
|
||||||
|
|
||||||
|
const app = {
|
||||||
|
refresh(d) {
|
||||||
|
return isFunction(d) ? d(that.params, refresh) : refresh(d);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ctx = (data) => {
|
||||||
|
deepMerge(that, data);
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.id = id;
|
||||||
|
|
||||||
|
ctx.conf = (d) => {
|
||||||
|
deepMerge(conf, d);
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.service = (d) => {
|
||||||
|
that.service = d;
|
||||||
|
|
||||||
|
if (fn.permission) {
|
||||||
|
that.permission = fn.permission(that);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.permission = (x) => {
|
||||||
|
if (isFunction(x)) {
|
||||||
|
that.permission = x(that);
|
||||||
|
} else {
|
||||||
|
deepMerge(that.permission, x);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.set = (key, value) => {
|
||||||
|
deepMerge(that[key], value);
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
};
|
||||||
|
|
||||||
|
["on", "once"].forEach((n) => {
|
||||||
|
ctx[n] = (name, cb) => {
|
||||||
|
event[name] = {
|
||||||
|
mode: n,
|
||||||
|
callback: cb
|
||||||
|
};
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.done = () => {
|
||||||
|
that.done();
|
||||||
|
};
|
||||||
|
|
||||||
|
return { ctx, app };
|
||||||
|
};
|
423
src/assets/css/index.styl
Normal file
423
src/assets/css/index.styl
Normal file
@ -0,0 +1,423 @@
|
|||||||
|
.cl-crud {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
padding: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #fff;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&>.el-row {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cl-flex1 {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cl-search-key {
|
||||||
|
display: flex;
|
||||||
|
margin-left: 10px;
|
||||||
|
|
||||||
|
&__input {
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__select {
|
||||||
|
width: 150px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__button {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cl-adv-btn {
|
||||||
|
& > .el-button {
|
||||||
|
margin-left: 10px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cl-table {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.el-table {
|
||||||
|
.el-loading-mask {
|
||||||
|
.el-loading-spinner {
|
||||||
|
.el-icon-loading {
|
||||||
|
font-size: 25px;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-loading-text {
|
||||||
|
color: #666;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.el-loading-parent--relative {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__op {
|
||||||
|
.el-dropdown-link {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cl-query {
|
||||||
|
display: inline-flex;
|
||||||
|
margin: 0 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: 0;
|
||||||
|
background-color: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
outline: none;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #666;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #6FA8FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
color: #409EFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 15px;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
span {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cl-filter {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 10px;
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-right: 10px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-select {
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input-number {
|
||||||
|
&__decrease, &__increase {
|
||||||
|
border: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .el-row {
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
min-height: 33px;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
height: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgba(144, 147, 153, 0.3);
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cl-adv-search {
|
||||||
|
&__container {
|
||||||
|
height: calc(100% - 50px);
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 10px 20px;
|
||||||
|
|
||||||
|
.el-form-item__content {
|
||||||
|
&>div {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
height: 40px;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-drawer {
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
span {
|
||||||
|
outline: none;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__close-btn {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cl-form {
|
||||||
|
.el-form-item {
|
||||||
|
.el-input-number {
|
||||||
|
&__decrease, &__increase {
|
||||||
|
border: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-item {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
&__prepend {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__component {
|
||||||
|
&.is-flex {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&>div {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__append {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__collapse {
|
||||||
|
height: 33px;
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
.el-divider {
|
||||||
|
margin: 16px 0;
|
||||||
|
|
||||||
|
&__text {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cl-dialog {
|
||||||
|
.el-dialog {
|
||||||
|
&__header {
|
||||||
|
padding: 10px !important;
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 1px solid #f7f7f7;
|
||||||
|
|
||||||
|
.el-dialog__title {
|
||||||
|
font-size: 15px;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-dialog__headerbtn {
|
||||||
|
display: none;
|
||||||
|
top: 13px;
|
||||||
|
|
||||||
|
.el-dialog__close {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-slot {
|
||||||
|
&.is-drag {
|
||||||
|
-moz-user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
-khtml-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__body {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__footer {
|
||||||
|
padding-bottom: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
height: 25px;
|
||||||
|
line-height: 25px;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
display: block;
|
||||||
|
font-size: 15px;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__headerbtn {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 9;
|
||||||
|
|
||||||
|
.minimize, .maximize, .close {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 25px;
|
||||||
|
width: 40px;
|
||||||
|
border: 0;
|
||||||
|
background-color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.hidden-header {
|
||||||
|
.el-dialog__header {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cl-crud__op-dropdown-menu {
|
||||||
|
.el-button {
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Element-ui Theme
|
||||||
|
.el-message {
|
||||||
|
&.el-message--success, &.el-message--error, &.el-message--info, &.el-message--warning {
|
||||||
|
min-width: auto;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
border: 0;
|
||||||
|
padding: 12px 20px 12px 15px;
|
||||||
|
|
||||||
|
.el-message__content {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-table {
|
||||||
|
&__header {
|
||||||
|
th {
|
||||||
|
padding: 0 !important;
|
||||||
|
background-color: #ebeef5 !important;
|
||||||
|
height: 36px;
|
||||||
|
line-height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell {
|
||||||
|
color: $color-main;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__column {
|
||||||
|
&-filter-trigger {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-column--selection {
|
||||||
|
.cell {
|
||||||
|
padding: 0 14px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-table-filter {
|
||||||
|
margin-top: 5px !important;
|
||||||
|
|
||||||
|
.el-checkbox__label {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 768px) {
|
||||||
|
.el-message-box {
|
||||||
|
width: 90% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-table {
|
||||||
|
&__body {
|
||||||
|
&-wrapper {
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
height: 6px;
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
src/common/index.js
Normal file
11
src/common/index.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
Promise.prototype.done = function (cb) {
|
||||||
|
let P = this.constructor;
|
||||||
|
|
||||||
|
return this.then(
|
||||||
|
(value) => P.resolve(cb()).then(() => value),
|
||||||
|
(reason) =>
|
||||||
|
P.resolve(cb()).then(() => {
|
||||||
|
throw reason;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
28
src/components/add-btn.js
Normal file
28
src/components/add-btn.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
export default {
|
||||||
|
name: "cl-add-btn",
|
||||||
|
componentName: "ClAddBtn",
|
||||||
|
inject: ["crud"],
|
||||||
|
props: {
|
||||||
|
// el-button props
|
||||||
|
props: Object
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
this.crud.getPermission("add") && (
|
||||||
|
<el-button
|
||||||
|
{...{
|
||||||
|
props: {
|
||||||
|
size: "mini",
|
||||||
|
type: "primary",
|
||||||
|
...this.props
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
click: this.crud.rowAdd
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{this.$slots.default || this.crud.dict.label.add}
|
||||||
|
</el-button>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
28
src/components/adv-btn.js
Normal file
28
src/components/adv-btn.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
export default {
|
||||||
|
name: "cl-adv-btn",
|
||||||
|
componentName: "ClAdvBtn",
|
||||||
|
inject: ["crud"],
|
||||||
|
props: {
|
||||||
|
// el-button props
|
||||||
|
props: Object
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div class="cl-adv-btn">
|
||||||
|
<el-button
|
||||||
|
{...{
|
||||||
|
props: {
|
||||||
|
size: "mini",
|
||||||
|
...this.props
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
click: this.crud.openAdvSearch
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<i class="el-icon-search" />
|
||||||
|
{this.$slots.default || this.crud.dict.label.advSearch}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
258
src/components/adv-search.js
Normal file
258
src/components/adv-search.js
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
import { cloneDeep } from "@/utils";
|
||||||
|
import { renderNode } from "@/utils/vnode";
|
||||||
|
import Parse from "@/utils/parse";
|
||||||
|
import { Form, Emitter, Screen } from "@/mixins";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "cl-adv-search",
|
||||||
|
componentName: "ClAdvSearch",
|
||||||
|
inject: ["crud"],
|
||||||
|
mixins: [Emitter, Screen, Form],
|
||||||
|
props: {
|
||||||
|
// Bind value
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Form items
|
||||||
|
items: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
// el-drawer props
|
||||||
|
props: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Op button ['search', 'reset', 'clear', 'close']
|
||||||
|
opList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => ["close", "search"]
|
||||||
|
},
|
||||||
|
// Hooks by open { data, { next } }
|
||||||
|
onOpen: Function,
|
||||||
|
// Hooks by close { done }
|
||||||
|
onClose: Function,
|
||||||
|
// Hooks by search { data, { next, close } }
|
||||||
|
onSearch: Function
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
form: {},
|
||||||
|
visible: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value: {
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
handler(val) {
|
||||||
|
console.log(val)
|
||||||
|
this.form = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.$on("crud.open", this.open);
|
||||||
|
this.$on("crud.close", this.close);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// Open drawer
|
||||||
|
open() {
|
||||||
|
this.items.map((e) => {
|
||||||
|
if (this.form[e.prop] === undefined) {
|
||||||
|
this.$set(this.form, e.prop, e.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Open event
|
||||||
|
const next = (data) => {
|
||||||
|
this.visible = true;
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
// Merge data
|
||||||
|
Object.assign(this.form, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit("open", this.form);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.onOpen) {
|
||||||
|
this.onOpen(this.form, { next });
|
||||||
|
} else {
|
||||||
|
next(null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Close drawer
|
||||||
|
close() {
|
||||||
|
// Close event
|
||||||
|
const done = () => {
|
||||||
|
this.visible = false;
|
||||||
|
this.$emit("close");
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.onClose) {
|
||||||
|
this.onClose(done);
|
||||||
|
} else {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Reset data
|
||||||
|
reset() {
|
||||||
|
this.resetForm()
|
||||||
|
this.$emit("reset");
|
||||||
|
},
|
||||||
|
|
||||||
|
// Clear data
|
||||||
|
clear() {
|
||||||
|
for (let i in this.form) {
|
||||||
|
this.form[i] = undefined
|
||||||
|
}
|
||||||
|
this.clearForm()
|
||||||
|
this.$emit("clear");
|
||||||
|
},
|
||||||
|
|
||||||
|
// Search data
|
||||||
|
search() {
|
||||||
|
const params = cloneDeep(this.form);
|
||||||
|
|
||||||
|
// Search event
|
||||||
|
const next = (params) => {
|
||||||
|
this.crud.refresh({
|
||||||
|
...params,
|
||||||
|
page: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
this.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.onSearch) {
|
||||||
|
this.onSearch(params, { next, close: this.close });
|
||||||
|
} else {
|
||||||
|
next(params);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Render form
|
||||||
|
renderForm() {
|
||||||
|
return (
|
||||||
|
<el-form
|
||||||
|
ref="form"
|
||||||
|
class="cl-form"
|
||||||
|
{...{
|
||||||
|
props: {
|
||||||
|
size: "small",
|
||||||
|
"label-width": "100px",
|
||||||
|
'label-position': this.isFullscreen ? 'top' : '',
|
||||||
|
disabled: this.saving,
|
||||||
|
model: this.form,
|
||||||
|
...this.props
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<el-row
|
||||||
|
v-loading={this.loading}
|
||||||
|
{...{
|
||||||
|
attrs: {
|
||||||
|
...this["v-loading"]
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{this.items.map((e, i) => {
|
||||||
|
return (
|
||||||
|
!Parse("hidden", {
|
||||||
|
value: e.hidden,
|
||||||
|
scope: this.form
|
||||||
|
}) && (
|
||||||
|
<el-col
|
||||||
|
{...{
|
||||||
|
props: {
|
||||||
|
key: i,
|
||||||
|
span: 24,
|
||||||
|
...e
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<el-form-item
|
||||||
|
{...{
|
||||||
|
props: {
|
||||||
|
...e
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{renderNode(e.component, {
|
||||||
|
prop: e.prop,
|
||||||
|
scope: this.form,
|
||||||
|
$scopedSlots: this.$scopedSlots
|
||||||
|
})}
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const ButtonText = {
|
||||||
|
search: "搜索",
|
||||||
|
reset: "重置",
|
||||||
|
clear: "清空",
|
||||||
|
close: "取消"
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="cl-adv-search">
|
||||||
|
<el-drawer
|
||||||
|
{...{
|
||||||
|
props: {
|
||||||
|
visible: this.visible,
|
||||||
|
title: "高级搜索",
|
||||||
|
direction: "rtl",
|
||||||
|
size: this.isFullscreen ? '100%' : "500px",
|
||||||
|
...this.props
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
"update:visible": () => {
|
||||||
|
this.close();
|
||||||
|
},
|
||||||
|
...this.on
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<div class="cl-adv-search__container">{this.renderForm()}</div>
|
||||||
|
|
||||||
|
<div class="cl-adv-search__footer">
|
||||||
|
{this.opList.map((e) => {
|
||||||
|
if (ButtonText[e]) {
|
||||||
|
return (
|
||||||
|
<el-button
|
||||||
|
{...{
|
||||||
|
props: {
|
||||||
|
size: this.props.size || "small",
|
||||||
|
type: e === "search" ? "primary" : ""
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
click: this[e]
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{ButtonText[e]}
|
||||||
|
</el-button>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return renderNode(e, {
|
||||||
|
scope: this.form,
|
||||||
|
$scopedSlots: this.$scopedSlots
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
338
src/components/crud.js
Normal file
338
src/components/crud.js
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
import { deepMerge, isArray, isString, isObject, isFunction } from "@/utils";
|
||||||
|
import { bootstrap } from "@/app";
|
||||||
|
import { __inst, __crud } from "@/global";
|
||||||
|
import { Emitter } from "@/mixins";
|
||||||
|
|
||||||
|
require("@/assets/css/index.styl");
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "cl-crud",
|
||||||
|
componentName: "ClCrud",
|
||||||
|
|
||||||
|
props: {
|
||||||
|
name: String,
|
||||||
|
onDelete: Function,
|
||||||
|
onRefresh: Function
|
||||||
|
},
|
||||||
|
|
||||||
|
mixins: [Emitter],
|
||||||
|
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
crud: this
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
service: null,
|
||||||
|
loading: false,
|
||||||
|
selection: [],
|
||||||
|
test: {
|
||||||
|
refreshRd: null,
|
||||||
|
sortLock: false,
|
||||||
|
process: false
|
||||||
|
},
|
||||||
|
permission: {
|
||||||
|
update: true,
|
||||||
|
page: true,
|
||||||
|
info: true,
|
||||||
|
list: true,
|
||||||
|
add: true,
|
||||||
|
delete: true
|
||||||
|
},
|
||||||
|
dict: {
|
||||||
|
api: {
|
||||||
|
list: "list",
|
||||||
|
add: "add",
|
||||||
|
update: "update",
|
||||||
|
delete: "delete",
|
||||||
|
info: "info",
|
||||||
|
page: "page"
|
||||||
|
},
|
||||||
|
pagination: {
|
||||||
|
page: "page",
|
||||||
|
size: "size"
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
keyWord: "keyWord",
|
||||||
|
query: "query"
|
||||||
|
},
|
||||||
|
sort: {
|
||||||
|
order: "order",
|
||||||
|
prop: "prop"
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
add: "新增",
|
||||||
|
delete: "删除",
|
||||||
|
multiDelete: "删除",
|
||||||
|
update: "编辑",
|
||||||
|
refresh: "刷新",
|
||||||
|
advSearch: "高级搜索",
|
||||||
|
saveButtonText: "保存",
|
||||||
|
closeButtonText: "关闭"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
page: 1,
|
||||||
|
size: 20
|
||||||
|
},
|
||||||
|
fn: {
|
||||||
|
permission: null
|
||||||
|
},
|
||||||
|
events: {}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.$on("table.selection-change", ({ selection }) => {
|
||||||
|
this.selection = selection;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
// Merge crud data
|
||||||
|
const res = bootstrap(deepMerge(this, __crud));
|
||||||
|
|
||||||
|
// Loaded
|
||||||
|
this.$emit("load", res);
|
||||||
|
|
||||||
|
// Register event
|
||||||
|
for (let i in this.events) {
|
||||||
|
let event = this.events[i];
|
||||||
|
let mode = null;
|
||||||
|
let callback = null;
|
||||||
|
|
||||||
|
if (isObject(event)) {
|
||||||
|
mode = event.mode;
|
||||||
|
callback = event.callback;
|
||||||
|
} else {
|
||||||
|
mode = "on";
|
||||||
|
callback = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!["on", "once"].includes(mode)) {
|
||||||
|
return console.error(`Event[${i}].mode must be (on / once)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isFunction(callback)) {
|
||||||
|
return console.error(`Event[${i}].callback is not a function`);
|
||||||
|
}
|
||||||
|
|
||||||
|
__inst[`$${mode}`](i, (data) => {
|
||||||
|
callback(data, res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Window onresize
|
||||||
|
window.removeEventListener("resize", function () { });
|
||||||
|
window.addEventListener("resize", () => {
|
||||||
|
this.doLayout();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
// Get service permission
|
||||||
|
getPermission(key) {
|
||||||
|
switch (key) {
|
||||||
|
case "edit":
|
||||||
|
case "update":
|
||||||
|
return this.permission["update"];
|
||||||
|
default:
|
||||||
|
return this.permission[key];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Upsert add
|
||||||
|
rowAdd() {
|
||||||
|
this.broadcast("cl-upsert", "crud.add");
|
||||||
|
},
|
||||||
|
|
||||||
|
// Upsert edit
|
||||||
|
rowEdit(data) {
|
||||||
|
this.broadcast("cl-upsert", "crud.edit", data);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Upsert append
|
||||||
|
rowAppend(data) {
|
||||||
|
this.broadcast("cl-upsert", "crud.append", data);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Upsert close
|
||||||
|
rowClose() {
|
||||||
|
this.broadcast("cl-upsert", "crud.close");
|
||||||
|
},
|
||||||
|
|
||||||
|
// Row delete
|
||||||
|
rowDelete(...selection) {
|
||||||
|
// Get request function
|
||||||
|
const reqName = this.dict.api.delete;
|
||||||
|
|
||||||
|
let params = {
|
||||||
|
ids: selection.map((e) => e.id).join(",")
|
||||||
|
};
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
const next = (params) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.$confirm(`此操作将永久删除选中数据,是否继续?`, "提示", {
|
||||||
|
type: "warning"
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
if (res === "confirm") {
|
||||||
|
// Validate
|
||||||
|
if (!this.service[reqName]) {
|
||||||
|
return reject(`Request function '${reqName}' is not fount`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
this.service[reqName](params)
|
||||||
|
.then((res) => {
|
||||||
|
this.$message.success(`删除成功`);
|
||||||
|
this.refresh();
|
||||||
|
resolve(res);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.$message.error(err);
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => null);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.onDelete) {
|
||||||
|
this.onDelete(selection, { next });
|
||||||
|
} else {
|
||||||
|
next(params);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Multi delete
|
||||||
|
deleteMulti() {
|
||||||
|
this.rowDelete.apply(this, this.selection || []);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Open advSearch
|
||||||
|
openAdvSearch() {
|
||||||
|
this.broadcast("cl-adv-search", "crud.open");
|
||||||
|
},
|
||||||
|
|
||||||
|
// close advSearch
|
||||||
|
closeAdvSearch() {
|
||||||
|
this.broadcast("cl-adv-search", "crud.close");
|
||||||
|
},
|
||||||
|
|
||||||
|
// Refresh params replace
|
||||||
|
paramsReplace(params) {
|
||||||
|
const { pagination, search, sort } = this.dict;
|
||||||
|
let a = { ...params };
|
||||||
|
let b = { ...pagination, ...search, ...sort };
|
||||||
|
|
||||||
|
for (let i in b) {
|
||||||
|
if (a.hasOwnProperty(i)) {
|
||||||
|
if (i != b[i]) {
|
||||||
|
a[`_${b[i]}`] = a[i];
|
||||||
|
|
||||||
|
delete a[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i in a) {
|
||||||
|
if (i[0] === "_") {
|
||||||
|
a[i.substr(1)] = a[i];
|
||||||
|
|
||||||
|
delete a[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return a;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Service refresh
|
||||||
|
refresh(newParams = {}) {
|
||||||
|
// 设置参数
|
||||||
|
let params = this.paramsReplace(Object.assign(this.params, newParams));
|
||||||
|
|
||||||
|
// Loading
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
// 预防脏数据
|
||||||
|
let rd = (this.test.refreshRd = Math.random());
|
||||||
|
|
||||||
|
// 完成事件
|
||||||
|
const done = () => {
|
||||||
|
this.loading = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 渲染
|
||||||
|
const render = (list, pagination) => {
|
||||||
|
this.broadcast("cl-table", "crud.refresh", { list });
|
||||||
|
this.broadcast("cl-pagination", "crud.refresh", pagination);
|
||||||
|
done();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 请求执行
|
||||||
|
const next = (params) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reqName = this.dict.api.page;
|
||||||
|
|
||||||
|
if (!this.service[reqName]) {
|
||||||
|
done();
|
||||||
|
return reject(`Request function '${reqName}' is not fount`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.service[reqName](params)
|
||||||
|
.then((res) => {
|
||||||
|
if (rd != this.test.refreshRd) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isString(res)) {
|
||||||
|
return reject("Response error");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isArray(res)) {
|
||||||
|
render(res);
|
||||||
|
} else if (isObject(res)) {
|
||||||
|
render(res.list, res.pagination);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(res);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
this.$message.error(err);
|
||||||
|
reject(err);
|
||||||
|
})
|
||||||
|
.done(() => {
|
||||||
|
done();
|
||||||
|
this.test.sortLock = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.onRefresh) {
|
||||||
|
return this.onRefresh(params, { next, done, render });
|
||||||
|
} else {
|
||||||
|
return next(params);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Layout again
|
||||||
|
doLayout() {
|
||||||
|
this.broadcast("ClTable", "resize");
|
||||||
|
},
|
||||||
|
|
||||||
|
done() {
|
||||||
|
// Done render
|
||||||
|
this.test.process = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return <div class="cl-crud">{this.$slots.default}</div>;
|
||||||
|
}
|
||||||
|
};
|
332
src/components/dialog.js
Normal file
332
src/components/dialog.js
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
import { renderNode } from "@/utils/vnode";
|
||||||
|
import { isBoolean } from "@/utils";
|
||||||
|
import { Screen } from '@/mixins'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "cl-dialog",
|
||||||
|
componentName: "ClDialog",
|
||||||
|
props: {
|
||||||
|
visible: Boolean,
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: "对话框"
|
||||||
|
},
|
||||||
|
drag: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
opList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => ["fullscreen", "close"]
|
||||||
|
},
|
||||||
|
hiddenOp: Boolean
|
||||||
|
},
|
||||||
|
mixins: [Screen],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
cacheKey: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
"props.fullscreen"(f) {
|
||||||
|
if (this.$el && this.$el.querySelector) {
|
||||||
|
const el = this.$el.querySelector(".el-dialog");
|
||||||
|
|
||||||
|
if (el) {
|
||||||
|
if (f) {
|
||||||
|
el.style = {
|
||||||
|
top: 0,
|
||||||
|
left: 0
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
el.style.marginBottom = "50px";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set header cursor state
|
||||||
|
el.querySelector(".el-dialog__header").style.cursor = f ? "text" : "move";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.crud) {
|
||||||
|
// Fullscreen change event
|
||||||
|
this.crud.$emit("fullscreen-change");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
visible: {
|
||||||
|
immediate: true,
|
||||||
|
handler(f) {
|
||||||
|
if (f) {
|
||||||
|
this.dragEvent();
|
||||||
|
} else {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.changeFullscreen(false);
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open() {
|
||||||
|
this.cacheKey++;
|
||||||
|
this.$emit("update:visible", true);
|
||||||
|
this.$emit("open");
|
||||||
|
},
|
||||||
|
|
||||||
|
onOpened() {
|
||||||
|
this.$emit('opened')
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeClose() {
|
||||||
|
if (this.props['before-close']) {
|
||||||
|
this.props['before-close'](this.close)
|
||||||
|
} else {
|
||||||
|
this.close()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.$emit("update:visible", false);
|
||||||
|
},
|
||||||
|
|
||||||
|
onClose() {
|
||||||
|
this.$emit("close");
|
||||||
|
this.close();
|
||||||
|
},
|
||||||
|
|
||||||
|
onClosed() {
|
||||||
|
this.$emit('closed')
|
||||||
|
},
|
||||||
|
|
||||||
|
// Change dialog fullscreen status
|
||||||
|
changeFullscreen(f) {
|
||||||
|
this.$set(this.props, "fullscreen", isBoolean(f) ? f : !this.props.fullscreen);
|
||||||
|
this.$emit("update:props:fullscreen", this.props.fullscreen);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Drag event
|
||||||
|
dragEvent() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
const dlg = this.$el.querySelector(".el-dialog");
|
||||||
|
const hdr = this.$el.querySelector(".el-dialog__header");
|
||||||
|
|
||||||
|
if (!hdr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hdr.onmousedown = (e) => {
|
||||||
|
// Props
|
||||||
|
const { fullscreen, top = "15vh" } = this.props;
|
||||||
|
|
||||||
|
// Body size
|
||||||
|
const { clientWidth, clientHeight } = document.body;
|
||||||
|
|
||||||
|
// Try drag
|
||||||
|
const isDrag = (() => {
|
||||||
|
if (fullscreen) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.drag) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine height of the box is too large
|
||||||
|
let marginTop = 0;
|
||||||
|
|
||||||
|
if (["vh", "%"].some((e) => top.includes(e))) {
|
||||||
|
marginTop = clientHeight * (parseInt(top) / 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (top.includes("px")) {
|
||||||
|
marginTop = top;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dlg.clientHeight > clientHeight - 50 - marginTop) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Set header cursor state
|
||||||
|
if (!isDrag) {
|
||||||
|
return (hdr.style.cursor = "text");
|
||||||
|
} else {
|
||||||
|
hdr.style.cursor = "move";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set el-dialog style, hidden scroller
|
||||||
|
dlg.style.marginTop = 0;
|
||||||
|
dlg.style.marginBottom = 0;
|
||||||
|
dlg.style.top = dlg.style.top || top;
|
||||||
|
|
||||||
|
// Distance
|
||||||
|
const dis = {
|
||||||
|
left: e.clientX - hdr.offsetLeft,
|
||||||
|
top: e.clientY - hdr.offsetTop
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calc left and top of the box
|
||||||
|
const box = (() => {
|
||||||
|
const { left, top } =
|
||||||
|
dlg.currentStyle || window.getComputedStyle(dlg, null);
|
||||||
|
|
||||||
|
if (left.includes("%")) {
|
||||||
|
return {
|
||||||
|
top: +clientHeight * (+top.replace(/\%/g, "") / 100),
|
||||||
|
left: +clientWidth * (+left.replace(/\%/g, "") / 100)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
top: +top.replace(/\px/g, ""),
|
||||||
|
left: +left.replace(/\px/g, "")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Screen limit
|
||||||
|
const pad = 5;
|
||||||
|
const minLeft = -(clientWidth - dlg.clientWidth) / 2 + pad;
|
||||||
|
const maxLeft =
|
||||||
|
(dlg.clientWidth >= clientWidth / 2
|
||||||
|
? dlg.clientWidth / 2 - (dlg.clientWidth - clientWidth / 2)
|
||||||
|
: dlg.clientWidth / 2 + clientWidth / 2 - dlg.clientWidth) - pad;
|
||||||
|
|
||||||
|
const minTop = pad;
|
||||||
|
const maxTop = clientHeight - dlg.clientHeight - pad;
|
||||||
|
|
||||||
|
// Start move
|
||||||
|
document.onmousemove = function (e) {
|
||||||
|
let left = e.clientX - dis.left + box.left;
|
||||||
|
let top = e.clientY - dis.top + box.top;
|
||||||
|
|
||||||
|
if (left < minLeft) {
|
||||||
|
left = minLeft;
|
||||||
|
} else if (left >= maxLeft) {
|
||||||
|
left = maxLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (top < minTop) {
|
||||||
|
top = minTop;
|
||||||
|
} else if (top >= maxTop) {
|
||||||
|
top = maxTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set dialog top and left
|
||||||
|
dlg.style.top = top + "px";
|
||||||
|
dlg.style.left = left + "px";
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clear event
|
||||||
|
document.onmouseup = function () {
|
||||||
|
document.onmousemove = null;
|
||||||
|
document.onmouseup = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Header
|
||||||
|
headerRender() {
|
||||||
|
return this.hiddenOp ? null : (
|
||||||
|
<div
|
||||||
|
class="cl-dialog__header"
|
||||||
|
{...{
|
||||||
|
on: {
|
||||||
|
dblclick: () => {
|
||||||
|
this.changeFullscreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{/* title */}
|
||||||
|
<span class="cl-dialog__title">{this.title}</span>
|
||||||
|
{/* op button */}
|
||||||
|
<div class="cl-dialog__headerbtn">
|
||||||
|
{this.opList.map((vnode) => {
|
||||||
|
// Fullscreen
|
||||||
|
if (vnode === "fullscreen") {
|
||||||
|
// Hidden fullscreen btn
|
||||||
|
if (this.screen === 'xs') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show diff icon
|
||||||
|
if (this.props.fullscreen) {
|
||||||
|
return (
|
||||||
|
<button type="button" class="minimize" on-click={this.changeFullscreen}>
|
||||||
|
<i class="el-icon-minus" />
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<button type="button" class="maximize" on-click={this.changeFullscreen}>
|
||||||
|
<i class="el-icon-full-screen" />
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Close
|
||||||
|
else if (vnode === "close") {
|
||||||
|
return (
|
||||||
|
<button type="button" class="close" on-click={this.beforeClose}>
|
||||||
|
<i class="el-icon-close" />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Custom node render
|
||||||
|
else {
|
||||||
|
return renderNode(vnode, {
|
||||||
|
$scopedSlots: this.$scopedSlots
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<el-dialog
|
||||||
|
custom-class={`cl-dialog ${this.hiddenOp ? "hidden-header" : ""}`}
|
||||||
|
{...{
|
||||||
|
props: {
|
||||||
|
...this.props,
|
||||||
|
fullscreen: this.isFullscreen ? true : this.props.fullscreen,
|
||||||
|
visible: this.visible,
|
||||||
|
"show-close": false
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
open: this.open,
|
||||||
|
opened: this.onOpened,
|
||||||
|
close: this.onClose,
|
||||||
|
closed: this.onClosed
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{/* header */}
|
||||||
|
<template slot="title">{this.headerRender()}</template>
|
||||||
|
{/* container */}
|
||||||
|
<div class="cl-dialog__container" key={this.cacheKey}>
|
||||||
|
{this.$slots.default}
|
||||||
|
</div>
|
||||||
|
{/* footer */}
|
||||||
|
<div class="cl-dialog__footer" slot="footer">
|
||||||
|
{this.$slots.footer}
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
13
src/components/error-message.js
Normal file
13
src/components/error-message.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export default {
|
||||||
|
name: "cl-error-message",
|
||||||
|
|
||||||
|
props: {
|
||||||
|
title: String
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return () => {
|
||||||
|
return <el-alert title={this.title} type="error"></el-alert>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
18
src/components/filter.js
Normal file
18
src/components/filter.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export default {
|
||||||
|
name: "cl-filter",
|
||||||
|
componentName: "ClFilter",
|
||||||
|
props: {
|
||||||
|
label: String
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div class="cl-filter">
|
||||||
|
<span class="cl-filter__label" v-show={this.label}>
|
||||||
|
{this.label}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{this.$slots.default}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
7
src/components/flex1.js
Normal file
7
src/components/flex1.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default {
|
||||||
|
name: "cl-flex1",
|
||||||
|
componentName: "ClFlex1",
|
||||||
|
render() {
|
||||||
|
return <div class="cl-flex1">{this.$slots.default}</div>;
|
||||||
|
}
|
||||||
|
};
|
375
src/components/form.js
Normal file
375
src/components/form.js
Normal file
@ -0,0 +1,375 @@
|
|||||||
|
import { deepMerge, isFunction, cloneDeep } from "@/utils";
|
||||||
|
import { renderNode } from "@/utils/vnode";
|
||||||
|
import Parse from "@/utils/parse";
|
||||||
|
import { Form, Emitter, Screen } from "@/mixins";
|
||||||
|
import { __inst } from "@/global";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "cl-form",
|
||||||
|
componentName: "ClForm",
|
||||||
|
mixins: [Emitter, Screen, Form],
|
||||||
|
props: {
|
||||||
|
// Bind value
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
saving: false,
|
||||||
|
loading: false,
|
||||||
|
form: {},
|
||||||
|
conf: {
|
||||||
|
on: {
|
||||||
|
open: null,
|
||||||
|
submit: null,
|
||||||
|
close: null
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
fullscreen: false,
|
||||||
|
"close-on-click-modal": false,
|
||||||
|
"append-to-body": true,
|
||||||
|
},
|
||||||
|
op: {
|
||||||
|
hidden: false,
|
||||||
|
saveButtonText: "保存",
|
||||||
|
closeButtonText: "取消",
|
||||||
|
layout: ["close", "save"]
|
||||||
|
},
|
||||||
|
hdr: {
|
||||||
|
hidden: false,
|
||||||
|
opList: ["fullscreen", "close"]
|
||||||
|
},
|
||||||
|
items: [],
|
||||||
|
_data: {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value: {
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
handler(val) {
|
||||||
|
this.form = val;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
immediate: true,
|
||||||
|
handler(val) {
|
||||||
|
this.$emit("input", val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open(options = {}) {
|
||||||
|
// Merge conf
|
||||||
|
for (let i in this.conf) {
|
||||||
|
if (i == "items") {
|
||||||
|
this.conf.items = cloneDeep(options.items || []);
|
||||||
|
} else {
|
||||||
|
deepMerge(this.conf[i], options[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show dialog
|
||||||
|
this.visible = true;
|
||||||
|
|
||||||
|
// Preset form
|
||||||
|
if (options.form) {
|
||||||
|
for (let i in options.form) {
|
||||||
|
this.$set(this.form, i, options.form[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set form data by items
|
||||||
|
this.conf.items.map((e) => {
|
||||||
|
if (e.prop) {
|
||||||
|
// Priority use form data
|
||||||
|
this.$set(this.form, e.prop, this.form[e.prop] || cloneDeep(e.value));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Open callback
|
||||||
|
const { open } = this.conf.on;
|
||||||
|
|
||||||
|
if (open) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
open(this.form, {
|
||||||
|
close: this.close,
|
||||||
|
submit: this.submit,
|
||||||
|
done: this.done
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeClose() {
|
||||||
|
if (this.conf.on.close) {
|
||||||
|
this.conf.on.close(this.close);
|
||||||
|
} else {
|
||||||
|
this.close()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.visible = false;
|
||||||
|
this.clear();
|
||||||
|
this.done();
|
||||||
|
},
|
||||||
|
|
||||||
|
done() {
|
||||||
|
this.saving = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
for (let i in this.form) {
|
||||||
|
delete this.form[i]
|
||||||
|
}
|
||||||
|
this.clearForm()
|
||||||
|
},
|
||||||
|
|
||||||
|
submit() {
|
||||||
|
// Validate form
|
||||||
|
this.$refs["form"].validate(async (valid) => {
|
||||||
|
if (valid) {
|
||||||
|
this.saving = true;
|
||||||
|
|
||||||
|
// Hooks event
|
||||||
|
const { submit } = this.conf.on;
|
||||||
|
|
||||||
|
// Get mount variable
|
||||||
|
const { $refs } = __inst;
|
||||||
|
|
||||||
|
// Hooks by onSubmit
|
||||||
|
if (isFunction(submit)) {
|
||||||
|
let d = cloneDeep(this.form);
|
||||||
|
|
||||||
|
// Filter hidden data
|
||||||
|
this.conf.items.forEach((e) => {
|
||||||
|
if (e._hidden) {
|
||||||
|
delete d[e.prop];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
submit(d, {
|
||||||
|
done: this.done,
|
||||||
|
close: this.close,
|
||||||
|
$refs
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.error("on[submit] is not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
showLoading() {
|
||||||
|
this.loading = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
hiddenLoading() {
|
||||||
|
this.loading = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
collapseItem(item) {
|
||||||
|
if (item.collapse !== undefined) {
|
||||||
|
item.collapse = !item.collapse;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
formRender() {
|
||||||
|
const { props, items } = this.conf;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<el-form
|
||||||
|
ref="form"
|
||||||
|
class="cl-form"
|
||||||
|
{...{
|
||||||
|
props: {
|
||||||
|
size: "small",
|
||||||
|
"label-width": "100px",
|
||||||
|
"label-position": this.isFullscreen ? "top" : "",
|
||||||
|
disabled: this.saving,
|
||||||
|
model: this.form,
|
||||||
|
...props
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<el-row gutter={10} v-loading={this.loading}>
|
||||||
|
{items.map((e, i) => {
|
||||||
|
// Is hidden
|
||||||
|
e._hidden = Parse("hidden", {
|
||||||
|
value: e.hidden,
|
||||||
|
scope: this.form,
|
||||||
|
data: this.conf._data
|
||||||
|
});
|
||||||
|
|
||||||
|
// Is flex
|
||||||
|
if (e.flex === undefined) {
|
||||||
|
e.flex = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
!e._hidden && (
|
||||||
|
<el-col
|
||||||
|
key={`form-item-${i}`}
|
||||||
|
{...{
|
||||||
|
props: {
|
||||||
|
key: i,
|
||||||
|
span: 24,
|
||||||
|
...e
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{e.component && (
|
||||||
|
<el-form-item
|
||||||
|
{...{
|
||||||
|
props: {
|
||||||
|
label: e.label,
|
||||||
|
prop: e.prop,
|
||||||
|
rules: e.rules,
|
||||||
|
...e.props
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{/* Redefine label */}
|
||||||
|
<template slot="label">
|
||||||
|
<span
|
||||||
|
on-click={() => {
|
||||||
|
this.collapseItem(e);
|
||||||
|
}}>
|
||||||
|
{e.label}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
{/* Form item */}
|
||||||
|
<div class="cl-form-item">
|
||||||
|
{/* Component */}
|
||||||
|
{["prepend", "component", "append"].map(
|
||||||
|
(name) => {
|
||||||
|
return (
|
||||||
|
e[name] && (
|
||||||
|
<div
|
||||||
|
class={[
|
||||||
|
`cl-form-item__${name}`,
|
||||||
|
{
|
||||||
|
"is-flex": e.flex
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
v-show={!e.collapse}>
|
||||||
|
{renderNode(e[name], {
|
||||||
|
prop: e.prop,
|
||||||
|
scope: this.form,
|
||||||
|
$scopedSlots: this
|
||||||
|
.$scopedSlots
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Collapse button */}
|
||||||
|
<div
|
||||||
|
class="cl-form-item__collapse"
|
||||||
|
v-show={e.collapse}
|
||||||
|
on-click={() => {
|
||||||
|
this.collapseItem(e);
|
||||||
|
}}>
|
||||||
|
<el-divider content-position="center">
|
||||||
|
点击展开,查看更多
|
||||||
|
<i class="el-icon-arrow-down"></i>
|
||||||
|
</el-divider>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
)}
|
||||||
|
</el-col>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
footerRender() {
|
||||||
|
const { hidden, layout, saveButtonText, closeButtonText } = this.conf.op;
|
||||||
|
const { size = "small" } = this.conf.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
!hidden &&
|
||||||
|
layout.map((vnode) => {
|
||||||
|
if (vnode == "save") {
|
||||||
|
return (
|
||||||
|
<el-button
|
||||||
|
{...{
|
||||||
|
props: {
|
||||||
|
size,
|
||||||
|
type: "success",
|
||||||
|
disabled: this.loading,
|
||||||
|
loading: this.saving
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
click: this.submit
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{saveButtonText}
|
||||||
|
</el-button>
|
||||||
|
);
|
||||||
|
} else if (vnode == "close") {
|
||||||
|
return (
|
||||||
|
<el-button
|
||||||
|
{...{
|
||||||
|
props: {
|
||||||
|
size
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
click: this.beforeClose
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{closeButtonText}
|
||||||
|
</el-button>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return renderNode(vnode, {
|
||||||
|
scope: this.form,
|
||||||
|
$scopedSlots: this.$scopedSlots
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { props, hdr } = this.conf;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="cl-form">
|
||||||
|
<cl-dialog
|
||||||
|
visible={this.visible}
|
||||||
|
{...{
|
||||||
|
props: {
|
||||||
|
title: props.title,
|
||||||
|
opList: hdr.opList,
|
||||||
|
props: {
|
||||||
|
...props,
|
||||||
|
'before-close': this.beforeClose
|
||||||
|
}
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
'update:visible': (v) => (this.visible = v),
|
||||||
|
"update:props:fullscreen": (v) => (props.fullscreen = v)
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<div class="cl-form__container">{this.formRender()}</div>
|
||||||
|
<div class="cl-form__footer" slot="footer">
|
||||||
|
{this.footerRender()}
|
||||||
|
</div>
|
||||||
|
</cl-dialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
35
src/components/index.js
Normal file
35
src/components/index.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import Crud from './crud'
|
||||||
|
import AddBtn from "./add-btn";
|
||||||
|
import AdvBtn from "./adv-btn";
|
||||||
|
import AdvSearch from "./adv-search";
|
||||||
|
import Flex from "./flex1";
|
||||||
|
import Form from "./form";
|
||||||
|
import MultiDeleteBtn from "./multi-delete-btn";
|
||||||
|
import Pagination from "./pagination";
|
||||||
|
import Query from "./query";
|
||||||
|
import RefreshBtn from "./refresh-btn";
|
||||||
|
import SearchKey from "./search-key";
|
||||||
|
import Table from "./table";
|
||||||
|
import Upsert from "./upsert";
|
||||||
|
import Dialog from "./dialog";
|
||||||
|
import Filter from "./filter";
|
||||||
|
import ErrorMessage from "./error-message";
|
||||||
|
|
||||||
|
export {
|
||||||
|
Crud,
|
||||||
|
AddBtn,
|
||||||
|
AdvBtn,
|
||||||
|
AdvSearch,
|
||||||
|
Flex,
|
||||||
|
Form,
|
||||||
|
MultiDeleteBtn,
|
||||||
|
Pagination,
|
||||||
|
Query,
|
||||||
|
RefreshBtn,
|
||||||
|
SearchKey,
|
||||||
|
Table,
|
||||||
|
Upsert,
|
||||||
|
Dialog,
|
||||||
|
Filter,
|
||||||
|
ErrorMessage
|
||||||
|
};
|
29
src/components/multi-delete-btn.js
Normal file
29
src/components/multi-delete-btn.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
export default {
|
||||||
|
name: "cl-multi-delete-btn",
|
||||||
|
componentName: "ClMultiDeleteBtn",
|
||||||
|
inject: ["crud"],
|
||||||
|
props: {
|
||||||
|
// el-button props
|
||||||
|
props: Object
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
this.crud.getPermission("delete") && (
|
||||||
|
<el-button
|
||||||
|
{...{
|
||||||
|
props: {
|
||||||
|
size: "mini",
|
||||||
|
type: "danger",
|
||||||
|
disabled: this.crud.selection.length == 0,
|
||||||
|
...this.props
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
click: this.crud.deleteMulti
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{this.$slots.default || this.crud.dict.label.multiDelete || this.crud.dict.label.delete}
|
||||||
|
</el-button>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
72
src/components/pagination.js
Normal file
72
src/components/pagination.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
export default {
|
||||||
|
name: "cl-pagination",
|
||||||
|
componentName: "ClPagination",
|
||||||
|
inject: ["crud"],
|
||||||
|
props: {
|
||||||
|
props: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
on: Object
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
total: 0,
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 20
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
props: {
|
||||||
|
immediate: true,
|
||||||
|
handler: "setPagination"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.$on("crud.refresh", this.setPagination);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
currentChange(index) {
|
||||||
|
this.crud.refresh({
|
||||||
|
page: index
|
||||||
|
});
|
||||||
|
},
|
||||||
|
sizeChange(size) {
|
||||||
|
this.crud.refresh({
|
||||||
|
page: 1,
|
||||||
|
size
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setPagination(res) {
|
||||||
|
if (res) {
|
||||||
|
this.currentPage = res.currentPage || res.page || 1;
|
||||||
|
this.pageSize = res.pageSize || res.size || 20;
|
||||||
|
this.total = res.total | 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<el-pagination
|
||||||
|
{...{
|
||||||
|
on: {
|
||||||
|
"size-change": this.sizeChange,
|
||||||
|
"current-change": this.currentChange,
|
||||||
|
...this.on
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
background: true,
|
||||||
|
layout: "total, sizes, prev, pager, next, jumper",
|
||||||
|
"page-sizes": [10, 20, 30, 40, 50, 100],
|
||||||
|
...this.props,
|
||||||
|
total: this.total,
|
||||||
|
"current-page": this.currentPage,
|
||||||
|
"page-size": this.pageSize
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
107
src/components/query.js
Normal file
107
src/components/query.js
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
export default {
|
||||||
|
name: "cl-query",
|
||||||
|
componentName: "ClQuery",
|
||||||
|
inject: ["crud"],
|
||||||
|
props: {
|
||||||
|
value: null,
|
||||||
|
multiple: Boolean,
|
||||||
|
list: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
callback: Function,
|
||||||
|
field: {
|
||||||
|
type: String,
|
||||||
|
default: "query"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
list2: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
value: {
|
||||||
|
immediate: true,
|
||||||
|
handler: 'setList'
|
||||||
|
},
|
||||||
|
|
||||||
|
list() {
|
||||||
|
this.setList(this.value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
setList(val) {
|
||||||
|
let arr = [];
|
||||||
|
|
||||||
|
if (val instanceof Array) {
|
||||||
|
arr = val;
|
||||||
|
} else {
|
||||||
|
arr = [val];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.multiple) {
|
||||||
|
arr.splice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.list2 = (this.list || []).map((e) => {
|
||||||
|
this.$set(
|
||||||
|
e,
|
||||||
|
"active",
|
||||||
|
arr.some((v) => v === e.value)
|
||||||
|
);
|
||||||
|
return e;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
selectRow(item) {
|
||||||
|
if (item.active) {
|
||||||
|
item.active = false;
|
||||||
|
} else {
|
||||||
|
if (this.multiple) {
|
||||||
|
item.active = true;
|
||||||
|
} else {
|
||||||
|
this.list2.map((e) => {
|
||||||
|
e.active = e.value == item.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const selects = this.list2.filter((e) => e.active).map((e) => e.value);
|
||||||
|
const value = this.multiple ? selects : selects[0];
|
||||||
|
|
||||||
|
if (this.callback) {
|
||||||
|
this.callback(value);
|
||||||
|
} else {
|
||||||
|
this.crud.refresh({
|
||||||
|
[this.field]: value
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$emit('change', value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div class="cl-query">
|
||||||
|
{this.list2.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={index}
|
||||||
|
class={{ "is-active": item.active }}
|
||||||
|
on-click={(event) => {
|
||||||
|
this.selectRow(item);
|
||||||
|
event.preventDefault();
|
||||||
|
}}>
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
25
src/components/refresh-btn.js
Normal file
25
src/components/refresh-btn.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
export default {
|
||||||
|
name: "cl-refresh-btn",
|
||||||
|
componentName: "ClRefreshBtn",
|
||||||
|
inject: ["crud"],
|
||||||
|
props: {
|
||||||
|
// el-button props
|
||||||
|
props: Object
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<el-button
|
||||||
|
{...{
|
||||||
|
props: {
|
||||||
|
size: "mini",
|
||||||
|
...this.props
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
click: this.crud.refresh
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{this.$slots.default || "刷新"}
|
||||||
|
</el-button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
128
src/components/search-key.js
Normal file
128
src/components/search-key.js
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
export default {
|
||||||
|
name: "cl-search-key",
|
||||||
|
componentName: "ClSearchKey",
|
||||||
|
inject: ["crud"],
|
||||||
|
props: {
|
||||||
|
// 绑定值
|
||||||
|
value: [String, Number],
|
||||||
|
// 选中字段
|
||||||
|
field: {
|
||||||
|
type: String,
|
||||||
|
default: "keyWord"
|
||||||
|
},
|
||||||
|
// 字段列表
|
||||||
|
fieldList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
// 搜索时的钩子
|
||||||
|
onSearch: Function,
|
||||||
|
// 输入框占位内容
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: "请输入关键字"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
field2: null,
|
||||||
|
value2: ""
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
field: {
|
||||||
|
immediate: true,
|
||||||
|
handler(val) {
|
||||||
|
this.field2 = val;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
value: {
|
||||||
|
immediate: true,
|
||||||
|
handler(val) {
|
||||||
|
this.value2 = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
selectList() {
|
||||||
|
return this.fieldList.map((e, i) => {
|
||||||
|
return <el-option key={i} label={e.label} value={e.value} />;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onKeyup({ keyCode }) {
|
||||||
|
if (keyCode === 13) {
|
||||||
|
this.search();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
search() {
|
||||||
|
let params = {};
|
||||||
|
|
||||||
|
this.fieldList.forEach((e) => {
|
||||||
|
params[e.value] = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const next = (params2) => {
|
||||||
|
this.crud.refresh({
|
||||||
|
page: 1,
|
||||||
|
...params,
|
||||||
|
[this.field2]: this.value2,
|
||||||
|
...params2
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.onSearch) {
|
||||||
|
this.onSearch(params, { next });
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onInput(val) {
|
||||||
|
this.$emit("input", val);
|
||||||
|
this.$emit("change", val);
|
||||||
|
},
|
||||||
|
|
||||||
|
onNameChange() {
|
||||||
|
this.$emit("field-change", this.field2);
|
||||||
|
this.onInput("");
|
||||||
|
this.value2 = "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div class="cl-search-key">
|
||||||
|
<el-select
|
||||||
|
class="cl-search-key__select"
|
||||||
|
filterable
|
||||||
|
size="mini"
|
||||||
|
v-model={this.field2}
|
||||||
|
v-show={this.selectList.length > 0}
|
||||||
|
on-change={this.onNameChange}>
|
||||||
|
{this.selectList}
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
<el-input
|
||||||
|
class="cl-search-key__input"
|
||||||
|
v-model={this.value2}
|
||||||
|
placeholder={this.placeholder}
|
||||||
|
nativeOnKeyup={this.onKeyup}
|
||||||
|
on-input={this.onInput}
|
||||||
|
clearable
|
||||||
|
size="mini"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<el-button
|
||||||
|
class="cl-search-key__button"
|
||||||
|
type="primary"
|
||||||
|
size="mini"
|
||||||
|
on-click={this.search}>
|
||||||
|
搜索
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
450
src/components/table.js
Normal file
450
src/components/table.js
Normal file
@ -0,0 +1,450 @@
|
|||||||
|
import { renderNode } from "@/utils/vnode";
|
||||||
|
import { isNull } from "@/utils";
|
||||||
|
import { Emitter } from "@/mixins";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "cl-table",
|
||||||
|
componentName: "ClTable",
|
||||||
|
inject: ["crud"],
|
||||||
|
mixins: [Emitter],
|
||||||
|
props: {
|
||||||
|
columns: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
maxHeight: null,
|
||||||
|
data: [],
|
||||||
|
emit: {}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
// Get default sort
|
||||||
|
const { order, prop } = this.props["default-sort"] || {};
|
||||||
|
|
||||||
|
// Set request params
|
||||||
|
this.crud.params.order = !order ? "" : order === "descending" ? "desc" : "asc";
|
||||||
|
this.crud.params.prop = prop;
|
||||||
|
|
||||||
|
// Crud event
|
||||||
|
this.$on("crud.resize", () => {
|
||||||
|
this.calcMaxHeight();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Crud refresh
|
||||||
|
this.$on("crud.refresh", ({ list }) => {
|
||||||
|
this.data = list;
|
||||||
|
|
||||||
|
});
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.emptyRender();
|
||||||
|
this.calcMaxHeight();
|
||||||
|
this.bindEmit();
|
||||||
|
this.bindMethods()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
columnRender() {
|
||||||
|
return this.columns
|
||||||
|
.filter((e) => !e.hidden)
|
||||||
|
.map((item, index) => {
|
||||||
|
const deep = (item) => {
|
||||||
|
let params = {
|
||||||
|
props: item,
|
||||||
|
on: item.on
|
||||||
|
};
|
||||||
|
|
||||||
|
// If op
|
||||||
|
if (item.type === "op") {
|
||||||
|
return this.opRender(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default
|
||||||
|
if (!item.type || item.type === "expand") {
|
||||||
|
params.scopedSlots = {
|
||||||
|
default: (scope) => {
|
||||||
|
// Column-slot
|
||||||
|
let slot = this.$scopedSlots[`column-${item.prop}`];
|
||||||
|
|
||||||
|
let newScope = {
|
||||||
|
...scope,
|
||||||
|
...item
|
||||||
|
};
|
||||||
|
|
||||||
|
let value = scope.row[item.prop];
|
||||||
|
|
||||||
|
if (slot) {
|
||||||
|
// Use slot
|
||||||
|
return slot({
|
||||||
|
scope: newScope
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// If component
|
||||||
|
if (item.component) {
|
||||||
|
return renderNode(item.component, {
|
||||||
|
prop: item.prop,
|
||||||
|
scope: newScope.row
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Formatter
|
||||||
|
else if (item.formatter) {
|
||||||
|
return item.formatter(
|
||||||
|
newScope.row,
|
||||||
|
newScope.column,
|
||||||
|
newScope.row[item.prop],
|
||||||
|
newScope.$index
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Dict tag
|
||||||
|
else if (item.dict) {
|
||||||
|
let data = item.dict.find((d) => d.value == value);
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
// Use el-tag
|
||||||
|
return (
|
||||||
|
<el-tag
|
||||||
|
{...{
|
||||||
|
props: {
|
||||||
|
size: "small",
|
||||||
|
"disable-transitions": true,
|
||||||
|
effect: "dark",
|
||||||
|
...data
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{data.label}
|
||||||
|
</el-tag>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Empty text
|
||||||
|
else if (isNull(value)) {
|
||||||
|
return scope.emptyText;
|
||||||
|
}
|
||||||
|
// Value
|
||||||
|
else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
header: (scope) => {
|
||||||
|
let slot = this.$scopedSlots[`header-${item.prop}`];
|
||||||
|
|
||||||
|
if (slot) {
|
||||||
|
return slot({
|
||||||
|
scope
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return scope.column.label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Children element
|
||||||
|
const childrenEl = item.children ? item.children.map(deep) : null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<el-table-column key={`crud-table-column-${index}`} {...params}>
|
||||||
|
{childrenEl}
|
||||||
|
</el-table-column>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return deep(item);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
opRender(item) {
|
||||||
|
const { rowEdit, rowDelete, getPermission } = this.crud;
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const render = (scope) => {
|
||||||
|
// Use op layout
|
||||||
|
return (item.layout || ["edit", "delete"]).map((vnode) => {
|
||||||
|
if (["edit", "update", "delete"].includes(vnode)) {
|
||||||
|
// Get permission
|
||||||
|
const perm = getPermission(vnode);
|
||||||
|
|
||||||
|
if (perm) {
|
||||||
|
let clickEvent = () => { };
|
||||||
|
let buttonText = null;
|
||||||
|
|
||||||
|
switch (vnode) {
|
||||||
|
case "edit":
|
||||||
|
case "update":
|
||||||
|
clickEvent = rowEdit;
|
||||||
|
buttonText = this.crud.dict.label.update;
|
||||||
|
break;
|
||||||
|
case "delete":
|
||||||
|
clickEvent = rowDelete;
|
||||||
|
buttonText = this.crud.dict.label.delete;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<el-button
|
||||||
|
size="mini"
|
||||||
|
type="text"
|
||||||
|
on-click={() => {
|
||||||
|
clickEvent(scope.row);
|
||||||
|
}}>
|
||||||
|
{buttonText}
|
||||||
|
</el-button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Use custom render
|
||||||
|
return renderNode(vnode, { scope, $scopedSlots: this.$scopedSlots });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<el-table-column
|
||||||
|
{...{
|
||||||
|
props: {
|
||||||
|
label: "操作",
|
||||||
|
width: "160px",
|
||||||
|
...item
|
||||||
|
},
|
||||||
|
scopedSlots: {
|
||||||
|
default: (scope) => {
|
||||||
|
let el = null;
|
||||||
|
|
||||||
|
// Dropdown op
|
||||||
|
if (item.name == "dropdown-menu") {
|
||||||
|
const slot = this.$scopedSlots["table-op-dropdown-menu"];
|
||||||
|
const { width } = item["dropdown-menu"] || {};
|
||||||
|
const items = render(scope).map((e) => {
|
||||||
|
return <el-dropdown-item>{e}</el-dropdown-item>;
|
||||||
|
});
|
||||||
|
|
||||||
|
el = (
|
||||||
|
<el-dropdown
|
||||||
|
{...{
|
||||||
|
on,
|
||||||
|
props: {
|
||||||
|
trigger: "click",
|
||||||
|
...item.props
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{slot ? (
|
||||||
|
slot({ scope })
|
||||||
|
) : (
|
||||||
|
<span class="el-dropdown-link">
|
||||||
|
<span>更多操作</span>
|
||||||
|
<i class="el-icon-arrow-down el-icon--right"></i>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<el-dropdown-menu
|
||||||
|
style={{ width }}
|
||||||
|
class="cl-crud__op-dropdown-menu"
|
||||||
|
{...{ slot: "dropdown" }}>
|
||||||
|
{items}
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</el-dropdown>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
el = render(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div class="cl-table__op">{el}</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
emptyRender() {
|
||||||
|
const empty = this.$scopedSlots["table-empty"];
|
||||||
|
const scope = {
|
||||||
|
h: this.$createElement,
|
||||||
|
scope: this
|
||||||
|
};
|
||||||
|
|
||||||
|
if (empty) {
|
||||||
|
this.$scopedSlots.empty = () => {
|
||||||
|
return empty(scope)[0];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
appendRender() {
|
||||||
|
return this.$slots["append"];
|
||||||
|
},
|
||||||
|
|
||||||
|
changeSort(prop, order) {
|
||||||
|
if (order === "desc") {
|
||||||
|
order = "descending";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order === "asc") {
|
||||||
|
order = "ascending";
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$refs["table"].sort(prop, order);
|
||||||
|
},
|
||||||
|
|
||||||
|
sortChange({ prop, order }) {
|
||||||
|
if (order === "descending") {
|
||||||
|
order = "desc";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order === "ascending") {
|
||||||
|
order = "asc";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!order) {
|
||||||
|
prop = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.crud.test.sortLock) {
|
||||||
|
this.crud.refresh({
|
||||||
|
prop,
|
||||||
|
order,
|
||||||
|
page: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
selectionChange(selection) {
|
||||||
|
this.dispatch("cl-crud", "table.selection-change", { selection });
|
||||||
|
this.$emit("selection-change", selection);
|
||||||
|
},
|
||||||
|
|
||||||
|
bindEmit() {
|
||||||
|
const funcs = [
|
||||||
|
"select",
|
||||||
|
"select-all",
|
||||||
|
"cell-mouse-enter",
|
||||||
|
"cell-mouse-leave",
|
||||||
|
"cell-click",
|
||||||
|
"cell-dblclick",
|
||||||
|
"row-click",
|
||||||
|
"row-contextmenu",
|
||||||
|
"row-dblclick",
|
||||||
|
"header-click",
|
||||||
|
"header-contextmenu",
|
||||||
|
"filter-change",
|
||||||
|
"current-change",
|
||||||
|
"header-dragend",
|
||||||
|
"expand-change"
|
||||||
|
];
|
||||||
|
|
||||||
|
funcs.forEach((name) => {
|
||||||
|
this.emit[name] = (...args) => {
|
||||||
|
this.$emit.apply(this, [name, ...args]);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
bindMethods() {
|
||||||
|
[
|
||||||
|
"clearSelection",
|
||||||
|
"toggleRowSelection",
|
||||||
|
"toggleAllSelection",
|
||||||
|
"toggleRowExpansion",
|
||||||
|
"setCurrentRow",
|
||||||
|
"clearSort",
|
||||||
|
"clearFilter",
|
||||||
|
"doLayout",
|
||||||
|
"sort"
|
||||||
|
].forEach(e => {
|
||||||
|
this[e] = this.$refs["table"][e];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
calcMaxHeight() {
|
||||||
|
return this.$nextTick(() => {
|
||||||
|
const el = this.crud.$el.parentNode;
|
||||||
|
let { height = "" } = this.props || {};
|
||||||
|
|
||||||
|
if (el) {
|
||||||
|
let rows = el.querySelectorAll(".cl-crud .el-row");
|
||||||
|
|
||||||
|
if (!rows[0] || !rows[0].isConnected) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let h = 20;
|
||||||
|
|
||||||
|
for (let i = 0; i < rows.length; i++) {
|
||||||
|
let f = true;
|
||||||
|
|
||||||
|
for (let j = 0; j < rows[i].childNodes.length; j++) {
|
||||||
|
if (rows[i].childNodes[j].className == "cl-table") {
|
||||||
|
f = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (f) {
|
||||||
|
h += rows[i].clientHeight + 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let h1 = Number(String(height).replace("px", ""));
|
||||||
|
let h2 = el.clientHeight - h;
|
||||||
|
|
||||||
|
this.maxHeight = h1 > h2 ? h1 : h2;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div class="cl-table">
|
||||||
|
{
|
||||||
|
<el-table
|
||||||
|
ref="table"
|
||||||
|
data={this.data}
|
||||||
|
v-loading={this.crud.loading}
|
||||||
|
{...{
|
||||||
|
on: {
|
||||||
|
"selection-change": this.selectionChange,
|
||||||
|
"sort-change": this.sortChange,
|
||||||
|
...this.emit,
|
||||||
|
...this.on
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
"max-height": this.maxHeight + "px",
|
||||||
|
border: true,
|
||||||
|
size: "mini",
|
||||||
|
...this.props
|
||||||
|
},
|
||||||
|
scopedSlots: {
|
||||||
|
...this.$scopedSlots
|
||||||
|
},
|
||||||
|
slots: {
|
||||||
|
...this.$slots
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{this.columnRender()}
|
||||||
|
</el-table>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
338
src/components/upsert.js
Normal file
338
src/components/upsert.js
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
import { Emitter } from "@/mixins";
|
||||||
|
import { __inst } from "@/global";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "cl-upsert",
|
||||||
|
componentName: "ClUpsert",
|
||||||
|
inject: ["crud"],
|
||||||
|
mixins: [Emitter],
|
||||||
|
props: {
|
||||||
|
// Bind value
|
||||||
|
value: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Form items
|
||||||
|
items: Array,
|
||||||
|
// el-dialog attributes
|
||||||
|
props: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Edit sync
|
||||||
|
sync: Boolean,
|
||||||
|
// Hidden operation button
|
||||||
|
hiddenOp: Boolean,
|
||||||
|
// Op buttons
|
||||||
|
opList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => ["close", "save"]
|
||||||
|
},
|
||||||
|
// Op object
|
||||||
|
op: Object,
|
||||||
|
// Dialog header object
|
||||||
|
hdr: Object,
|
||||||
|
// Save button text
|
||||||
|
saveButtonText: String,
|
||||||
|
// Close button text
|
||||||
|
closeButtonText: String,
|
||||||
|
// Hook by open { isEdit, data, { submit, done, close } }
|
||||||
|
onOpen: Function,
|
||||||
|
// Hook by close { action, done }
|
||||||
|
onClose: Function,
|
||||||
|
// Hook by info { data, { next, done, close } }
|
||||||
|
onInfo: Function,
|
||||||
|
// Hook by submit { isEdit, data, { next, done, close } }
|
||||||
|
onSubmit: Function
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isEdit: false,
|
||||||
|
form: {}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value: {
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
handler(val) {
|
||||||
|
this.form = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.$on("crud.add", this.add);
|
||||||
|
this.$on("crud.append", this.append);
|
||||||
|
this.$on("crud.edit", this.edit);
|
||||||
|
this.$on("crud.close", this.close);
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.inject();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// Add
|
||||||
|
async add() {
|
||||||
|
this.isEdit = false;
|
||||||
|
this.form = {};
|
||||||
|
await this.open();
|
||||||
|
this.$emit("open", false, {});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Append data
|
||||||
|
async append(data) {
|
||||||
|
this.isEdit = false;
|
||||||
|
|
||||||
|
// Assign data
|
||||||
|
if (data) {
|
||||||
|
for (let i in data) {
|
||||||
|
this.$set(this.form, i, data[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.open();
|
||||||
|
this.$emit("open", false, this.form);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Edit
|
||||||
|
edit(data) {
|
||||||
|
const { showLoading, hiddenLoading } = this.$refs["form"];
|
||||||
|
|
||||||
|
// Is edit
|
||||||
|
this.isEdit = true;
|
||||||
|
// Start loading
|
||||||
|
showLoading();
|
||||||
|
|
||||||
|
// Async open form
|
||||||
|
if (!this.sync) {
|
||||||
|
this.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish
|
||||||
|
const done = (data) => {
|
||||||
|
// Assign data
|
||||||
|
Object.assign(this.form, data);
|
||||||
|
hiddenLoading();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Close
|
||||||
|
const close = () => {
|
||||||
|
hiddenLoading();
|
||||||
|
this.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Submit
|
||||||
|
const next = (data) => {
|
||||||
|
// Get Service and Dict
|
||||||
|
const { dict, service } = this.crud;
|
||||||
|
// Get api.info
|
||||||
|
const reqName = dict.api.info;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// Validate
|
||||||
|
if (!service[reqName]) {
|
||||||
|
reject(`Request function '${reqName}' is not fount!`);
|
||||||
|
hiddenLoading();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
service[reqName]({
|
||||||
|
id: data.id
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
// Finish
|
||||||
|
done(res);
|
||||||
|
resolve(res);
|
||||||
|
|
||||||
|
// Sync open form
|
||||||
|
if (this.sync) {
|
||||||
|
this.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback
|
||||||
|
this.$emit("open", this.isEdit, this.form);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.$message.error(err);
|
||||||
|
reject(err);
|
||||||
|
})
|
||||||
|
.done(() => {
|
||||||
|
hiddenLoading();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hook by onInfo
|
||||||
|
if (this.onInfo) {
|
||||||
|
this.onInfo(data, {
|
||||||
|
next,
|
||||||
|
done: (data) => {
|
||||||
|
done(data);
|
||||||
|
this.$emit("open", true, this.form);
|
||||||
|
},
|
||||||
|
close
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
next(data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Open
|
||||||
|
open() {
|
||||||
|
const { saveButtonText, closeButtonText } = this.crud.dict.label;
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.$refs["form"].open({
|
||||||
|
items: this.items,
|
||||||
|
props: {
|
||||||
|
title: this.isEdit ? "编辑" : "新增",
|
||||||
|
...this.props
|
||||||
|
},
|
||||||
|
op: {
|
||||||
|
hidden: this.hiddenOp,
|
||||||
|
layout: this.opList,
|
||||||
|
saveButtonText: this.saveButtonText || saveButtonText,
|
||||||
|
closeButtonText: this.closeButtonText || closeButtonText,
|
||||||
|
...this.op
|
||||||
|
},
|
||||||
|
hdr: {
|
||||||
|
...this.hdr
|
||||||
|
},
|
||||||
|
on: {
|
||||||
|
open: (data, { done, close }) => {
|
||||||
|
if (this.onOpen) {
|
||||||
|
this.onOpen(this.isEdit, this.form, {
|
||||||
|
submit: () => {
|
||||||
|
this.submit(this.form);
|
||||||
|
},
|
||||||
|
done,
|
||||||
|
close
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
submit: this.submit,
|
||||||
|
close: this.beforeClose
|
||||||
|
},
|
||||||
|
_data: {
|
||||||
|
isEdit: this.isEdit
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Close
|
||||||
|
close() {
|
||||||
|
this.$refs["form"].close();
|
||||||
|
this.$emit("close");
|
||||||
|
},
|
||||||
|
|
||||||
|
// Before close
|
||||||
|
beforeClose() {
|
||||||
|
if (this.onClose) {
|
||||||
|
this.onClose(this.close);
|
||||||
|
} else {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit form
|
||||||
|
* @param {*} data
|
||||||
|
*/
|
||||||
|
submit(data, { done }) {
|
||||||
|
// Get Service and Dict
|
||||||
|
const { dict, service } = this.crud;
|
||||||
|
|
||||||
|
// Submit
|
||||||
|
const next = (data) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// Judge update or add
|
||||||
|
const func = this.isEdit ? "update" : "add";
|
||||||
|
// Get request function
|
||||||
|
const reqName = dict.api[func];
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
if (!service[reqName]) {
|
||||||
|
done();
|
||||||
|
return reject(`Request function '${reqName}' is not fount!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
service[reqName](data)
|
||||||
|
.then((res) => {
|
||||||
|
this.$message.success("保存成功");
|
||||||
|
// Close
|
||||||
|
this.close("submit");
|
||||||
|
// Refresh
|
||||||
|
this.crud.refresh();
|
||||||
|
// Callback
|
||||||
|
resolve(res);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.$message.error(err);
|
||||||
|
reject(err);
|
||||||
|
})
|
||||||
|
.done(done);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Hook by onSubmit
|
||||||
|
if (this.onSubmit) {
|
||||||
|
// Get mount variable
|
||||||
|
const { $refs } = __inst;
|
||||||
|
|
||||||
|
this.onSubmit(this.isEdit, data, {
|
||||||
|
$refs,
|
||||||
|
done,
|
||||||
|
next,
|
||||||
|
close: () => {
|
||||||
|
this.close("submit");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
next(data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Inject form api
|
||||||
|
inject() {
|
||||||
|
const fns = [
|
||||||
|
"getForm",
|
||||||
|
"setForm",
|
||||||
|
"clearForm",
|
||||||
|
"setData",
|
||||||
|
"setOptions",
|
||||||
|
"toggleItem",
|
||||||
|
"hiddenItem",
|
||||||
|
"showItem",
|
||||||
|
"showLoading",
|
||||||
|
"hiddenLoading"
|
||||||
|
];
|
||||||
|
|
||||||
|
fns.forEach((e) => {
|
||||||
|
this[e] = this.$refs["form"][e];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div class="cl-upsert">
|
||||||
|
<cl-form
|
||||||
|
ref="form"
|
||||||
|
v-model={this.form}
|
||||||
|
{...{
|
||||||
|
scopedSlots: {
|
||||||
|
...this.$scopedSlots
|
||||||
|
}
|
||||||
|
}}></cl-form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
5
src/global.js
Normal file
5
src/global.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export let __crud = {};
|
||||||
|
export let __vue = {};
|
||||||
|
export let __inst = {};
|
||||||
|
export let __components = {};
|
||||||
|
export let __plugins = [];
|
33
src/index.js
Normal file
33
src/index.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import * as global from "./global";
|
||||||
|
import * as comps from "./components";
|
||||||
|
|
||||||
|
require("./common");
|
||||||
|
|
||||||
|
export const CRUD = {
|
||||||
|
version: "0.4.1",
|
||||||
|
|
||||||
|
install: function (Vue, options) {
|
||||||
|
const { crud, components, plugins } = options || {};
|
||||||
|
|
||||||
|
// 设置全局参数
|
||||||
|
global.__crud = crud;
|
||||||
|
global.__vue = Vue;
|
||||||
|
global.__components = components;
|
||||||
|
global.__plugins = plugins;
|
||||||
|
global.__inst = new Vue();
|
||||||
|
|
||||||
|
// 注册组件
|
||||||
|
for (let i in comps) {
|
||||||
|
Vue.component(comps[i].name, comps[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 挂载 $crud
|
||||||
|
Vue.prototype.$crud = {
|
||||||
|
emit: (name, callback) => {
|
||||||
|
global.__inst.$emit(name, callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CRUD;
|
34
src/mixins/emitter.js
Normal file
34
src/mixins/emitter.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
function broadcast(componentName, eventName, params) {
|
||||||
|
this.$children.forEach((child) => {
|
||||||
|
let name = child.$options._componentTag;
|
||||||
|
|
||||||
|
if (name === componentName) {
|
||||||
|
child.$emit.apply(child, [eventName].concat(params));
|
||||||
|
} else {
|
||||||
|
broadcast.apply(child, [componentName, eventName].concat([params]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
dispatch(componentName, eventName, params) {
|
||||||
|
let parent = this.$parent || this.$root;
|
||||||
|
let name = parent.$options._componentTag;
|
||||||
|
|
||||||
|
while (parent && (!name || name !== componentName)) {
|
||||||
|
parent = parent.$parent;
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
name = parent.$options._componentTag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (parent) {
|
||||||
|
parent.$emit.apply(parent, [eventName].concat(params));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
broadcast(componentName, eventName, params) {
|
||||||
|
broadcast.call(this, componentName, eventName, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
88
src/mixins/form.js
Normal file
88
src/mixins/form.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { dataset } from "@/utils";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
_set({ prop, options, hidden, path }, data) {
|
||||||
|
let conf = null
|
||||||
|
|
||||||
|
switch (this.$options._componentTag) {
|
||||||
|
case 'cl-adv-search':
|
||||||
|
conf = this
|
||||||
|
break
|
||||||
|
case 'cl-form':
|
||||||
|
conf = this.conf
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
let p = path;
|
||||||
|
|
||||||
|
if (prop) {
|
||||||
|
p = `items[prop:${prop}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options) {
|
||||||
|
p += `.component.options`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hidden) {
|
||||||
|
p += ".hidden";
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataset(conf, p, data);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Get form
|
||||||
|
getForm(prop) {
|
||||||
|
return prop ? this.form[prop] : this.form;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Set form
|
||||||
|
setForm(prop, value) {
|
||||||
|
// Add watch
|
||||||
|
this.$set(this.form, prop, value);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Set [props, on]
|
||||||
|
setData(path, value) {
|
||||||
|
this._set({ path }, value);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Set item component options
|
||||||
|
setOptions(prop, value) {
|
||||||
|
this._set({ options: true, prop }, value);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Toggle item is hide or show
|
||||||
|
toggleItem(prop, value) {
|
||||||
|
if (value === undefined) {
|
||||||
|
value = this._set({ prop, hidden: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
this._set({ hidden: true, prop }, !value);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Hidden item
|
||||||
|
hiddenItem(...props) {
|
||||||
|
props.forEach((prop) => {
|
||||||
|
this._set({ hidden: true, prop }, true);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Show item
|
||||||
|
showItem(...props) {
|
||||||
|
props.forEach((prop) => {
|
||||||
|
this._set({ hidden: true, prop }, false);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// Clear form data
|
||||||
|
clearForm() {
|
||||||
|
this.$refs["form"].clearValidate();
|
||||||
|
},
|
||||||
|
|
||||||
|
// Reset form data
|
||||||
|
resetForm() {
|
||||||
|
this.$refs['form'].resetFields()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
5
src/mixins/index.js
Normal file
5
src/mixins/index.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import Emitter from './emitter'
|
||||||
|
import Form from './form'
|
||||||
|
import Screen from './screen'
|
||||||
|
|
||||||
|
export { Emitter, Form, Screen }
|
34
src/mixins/screen.js
Normal file
34
src/mixins/screen.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
screen: 'full'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
isFullscreen() {
|
||||||
|
return this.screen === 'xs'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
const fn = () => {
|
||||||
|
const w = document.body.clientWidth
|
||||||
|
|
||||||
|
if (w < 768) {
|
||||||
|
this.screen = 'xs'
|
||||||
|
} else if (w < 992) {
|
||||||
|
this.screen = 'sm'
|
||||||
|
} else if (w < 1200) {
|
||||||
|
this.screen = 'md'
|
||||||
|
} else if (w < 1920) {
|
||||||
|
this.screen = 'xl'
|
||||||
|
} else {
|
||||||
|
this.screen = 'full'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', fn)
|
||||||
|
fn()
|
||||||
|
}
|
||||||
|
}
|
148
src/utils/index.js
Normal file
148
src/utils/index.js
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import cloneDeep from "clone-deep";
|
||||||
|
import flat from "array.prototype.flat";
|
||||||
|
import { __vue, __plugins, __inst } from "@/global";
|
||||||
|
|
||||||
|
export function throttle(fn, delay) {
|
||||||
|
let prev = Date.now();
|
||||||
|
return function () {
|
||||||
|
let args = arguments;
|
||||||
|
let context = this;
|
||||||
|
let now = Date.now();
|
||||||
|
|
||||||
|
if (now - prev > delay) {
|
||||||
|
fn.apply(context, args);
|
||||||
|
prev = Date.now();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isArray(value) {
|
||||||
|
if (typeof Array.isArray === "function") {
|
||||||
|
return Array.isArray(value);
|
||||||
|
} else {
|
||||||
|
return Object.prototype.toString.call(value) === "[object Array]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isObject(value) {
|
||||||
|
return Object.prototype.toString.call(value) === "[object Object]";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNumber(value) {
|
||||||
|
return !isNaN(Number(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFunction(value) {
|
||||||
|
return typeof value === "function";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isString(value) {
|
||||||
|
return typeof value === "string";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNull(value) {
|
||||||
|
return !value && value !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isBoolean(value) {
|
||||||
|
return typeof value === "boolean";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isEmpty(value) {
|
||||||
|
if (isArray(value)) {
|
||||||
|
return value.length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isObject(value)) {
|
||||||
|
return Object.keys(value).length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value === "" || value === undefined || value === null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clone(obj) {
|
||||||
|
return Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getParent(name) {
|
||||||
|
let parent = this.$parent;
|
||||||
|
|
||||||
|
while (parent) {
|
||||||
|
if (parent.$options.componentName !== name) {
|
||||||
|
parent = parent.$parent;
|
||||||
|
} else {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dataset(obj, key, value) {
|
||||||
|
const isGet = value === undefined;
|
||||||
|
let d = obj;
|
||||||
|
|
||||||
|
let arr = flat(
|
||||||
|
key.split(".").map((e) => {
|
||||||
|
if (e.includes("[")) {
|
||||||
|
return e.split("[").map((e) => e.replace(/"/g, ""));
|
||||||
|
} else {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
let e = arr[i];
|
||||||
|
let n = null;
|
||||||
|
|
||||||
|
if (e.includes("]")) {
|
||||||
|
let [k, v] = e.replace("]", "").split(":");
|
||||||
|
|
||||||
|
if (v) {
|
||||||
|
n = d.findIndex((x) => x[k] == v);
|
||||||
|
} else {
|
||||||
|
n = Number(n);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
n = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i != arr.length - 1) {
|
||||||
|
d = d[n];
|
||||||
|
} else {
|
||||||
|
if (isGet) {
|
||||||
|
return d[n];
|
||||||
|
} else {
|
||||||
|
__inst.$set(d, n, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("格式错误", `${key}`);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deepMerge(a, b) {
|
||||||
|
let k;
|
||||||
|
for (k in b) {
|
||||||
|
a[k] =
|
||||||
|
a[k] && a[k].toString() === "[object Object]" ? deepMerge(a[k], b[k]) : (a[k] = b[k]);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function contains(parent, node) {
|
||||||
|
if (document.documentElement.contains) {
|
||||||
|
return parent !== node && parent.contains(node);
|
||||||
|
} else {
|
||||||
|
while (node && (node = node.parentNode)) if (node === parent) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { cloneDeep, flat };
|
33
src/utils/parse.js
Normal file
33
src/utils/parse.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { isString, isBoolean, isFunction, isArray } from "./index";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* parse hidden
|
||||||
|
* 1 Boolean
|
||||||
|
* 2 Function({ scope })
|
||||||
|
* 3 :[prop] is bind form[prop] value
|
||||||
|
* @param {*} value
|
||||||
|
*/
|
||||||
|
export default function (method, { value, scope, data = {} }) {
|
||||||
|
if (data) {
|
||||||
|
data.isAdd = !data.isEdit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method === "hidden") {
|
||||||
|
if (isBoolean(value)) {
|
||||||
|
return value;
|
||||||
|
} else if (isString(value)) {
|
||||||
|
const prop = value.substring(1, value.length);
|
||||||
|
|
||||||
|
switch (value[0]) {
|
||||||
|
case "@":
|
||||||
|
return !scope[prop];
|
||||||
|
case ":":
|
||||||
|
return data[prop];
|
||||||
|
}
|
||||||
|
} else if (isFunction(value)) {
|
||||||
|
return value({ scope, ...data });
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
175
src/utils/vnode.js
Normal file
175
src/utils/vnode.js
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
import { isFunction, isString, cloneDeep, isObject } from "./index";
|
||||||
|
import { __inst, __plugins, __vue } from "@/global";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse JSX, filter params
|
||||||
|
* @param {*} vnode
|
||||||
|
* @param {{scope,prop,children}} options
|
||||||
|
*/
|
||||||
|
const parse_jsx = (vnode, options = {}) => {
|
||||||
|
const { scope, prop, $scopedSlots, children = [] } = options;
|
||||||
|
const h = __inst.$createElement;
|
||||||
|
|
||||||
|
if (vnode.name.indexOf("slot-") == 0) {
|
||||||
|
let rn = $scopedSlots[vnode.name];
|
||||||
|
|
||||||
|
if (rn) {
|
||||||
|
return rn({ scope });
|
||||||
|
} else {
|
||||||
|
return <cl-error-message title={`组件渲染失败,未找到插槽:${vnode.name}`} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vnode.render) {
|
||||||
|
if (!__inst.$root.$options.components[vnode.name]) {
|
||||||
|
__vue.component(vnode.name, cloneDeep(vnode));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid props prompts { type:null }
|
||||||
|
delete vnode.props;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = [
|
||||||
|
"class",
|
||||||
|
"style",
|
||||||
|
"props",
|
||||||
|
"attrs",
|
||||||
|
"domProps",
|
||||||
|
"on",
|
||||||
|
"nativeOn",
|
||||||
|
"directives",
|
||||||
|
"scopedSlots",
|
||||||
|
"slot",
|
||||||
|
"key",
|
||||||
|
"ref",
|
||||||
|
"refInFor"
|
||||||
|
];
|
||||||
|
|
||||||
|
// Avoid loop update
|
||||||
|
let data = cloneDeep(vnode);
|
||||||
|
|
||||||
|
for (let i in data) {
|
||||||
|
if (!keys.includes(i)) {
|
||||||
|
delete data[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scope) {
|
||||||
|
if (!data.attrs) {
|
||||||
|
data.attrs = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.on) {
|
||||||
|
data.on = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default value
|
||||||
|
data.attrs.value = scope[prop];
|
||||||
|
// Add input event
|
||||||
|
data.on.input = (val) => {
|
||||||
|
__inst.$set(scope, prop, val);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return h(vnode.name, cloneDeep(data), children);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render vNode
|
||||||
|
* @param {*} vnode
|
||||||
|
* @param {*} options
|
||||||
|
*/
|
||||||
|
export function renderNode(vnode, { prop, scope, $scopedSlots }) {
|
||||||
|
const h = __inst.$createElement;
|
||||||
|
|
||||||
|
if (!vnode) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When slot or tagName
|
||||||
|
if (isString(vnode)) {
|
||||||
|
return parse_jsx({ name: vnode }, { scope, $scopedSlots });
|
||||||
|
}
|
||||||
|
|
||||||
|
// When customeize render function
|
||||||
|
if (isFunction(vnode)) {
|
||||||
|
return vnode({ scope, h });
|
||||||
|
}
|
||||||
|
|
||||||
|
// When jsx
|
||||||
|
if (isObject(vnode)) {
|
||||||
|
if (vnode.context) {
|
||||||
|
return vnode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vnode.name) {
|
||||||
|
// Handle general component
|
||||||
|
const keys = ["el-select", "el-radio-group", "el-checkbox-group"]
|
||||||
|
|
||||||
|
if (keys.includes(vnode.name)) {
|
||||||
|
// Append component children
|
||||||
|
const children = (vnode.options || []).map((e, i) => {
|
||||||
|
if (vnode.name === 'el-select') {
|
||||||
|
let label, value;
|
||||||
|
|
||||||
|
if (isString(e)) {
|
||||||
|
label = value = e
|
||||||
|
} else if (isObject(e)) {
|
||||||
|
label = e.label
|
||||||
|
value = e.value
|
||||||
|
} else {
|
||||||
|
return <cl-error-message title={`组件渲染失败,options 参数错误`} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<el-option
|
||||||
|
{...{
|
||||||
|
props: {
|
||||||
|
key: i,
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
...e.props
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if (vnode.name === 'el-radio-group') {
|
||||||
|
return (
|
||||||
|
<el-radio {...{
|
||||||
|
props: {
|
||||||
|
key: i,
|
||||||
|
label: e.value,
|
||||||
|
...e.props
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{e.label}
|
||||||
|
</el-radio>
|
||||||
|
);
|
||||||
|
} else if (vnode.name === 'el-checkbox-group') {
|
||||||
|
return (
|
||||||
|
<el-checkbox {...{
|
||||||
|
props: {
|
||||||
|
key: i,
|
||||||
|
label: e.value,
|
||||||
|
...e.props
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
{e.label}
|
||||||
|
</el-checkbox>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return parse_jsx(vnode, { prop, scope, children });
|
||||||
|
} else {
|
||||||
|
return parse_jsx(vnode, { prop, scope, $scopedSlots });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return <cl-error-message title={`组件渲染失败,组件 name 不能为空`} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
webpack.config.js
Normal file
43
webpack.config.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
const path = require("path");
|
||||||
|
const TerserPlugin = require("terser-webpack-plugin");
|
||||||
|
|
||||||
|
const resolve = (dir) => path.resolve(__dirname, dir);
|
||||||
|
|
||||||
|
const webpackConfig = {
|
||||||
|
devtool: "source-map",
|
||||||
|
mode: "production",
|
||||||
|
entry: "./src/index.js",
|
||||||
|
output: {
|
||||||
|
path: resolve("./dist"),
|
||||||
|
filename: "cl-crud2.min.js",
|
||||||
|
libraryTarget: "umd"
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
loader: "babel-loader",
|
||||||
|
exclude: /node_modules/
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.styl$/,
|
||||||
|
loaders: ["style-loader", "css-loader", "stylus-loader"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": resolve("src")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
optimization: {
|
||||||
|
minimize: true,
|
||||||
|
minimizer: [
|
||||||
|
new TerserPlugin({
|
||||||
|
sourceMap: true
|
||||||
|
})
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = webpackConfig;
|
Loading…
Reference in New Issue
Block a user