@@ -0,0 +1,18 @@ | |||
VUE_APP_PUBLIC_PATH=/ | |||
VUE_APP_NAME=捷配管理后台 | |||
VUE_APP_ROUTES_KEY=admin.routes | |||
VUE_APP_PERMISSIONS_KEY=admin.permissions | |||
VUE_APP_ROLES_KEY=admin.roles | |||
VUE_APP_USER_KEY=admin.user | |||
VUE_APP_SETTING_KEY=admin.setting | |||
VUE_APP_TBAS_KEY=admin.tabs | |||
VUE_APP_TBAS_TITLES_KEY=admin.tabs.titles | |||
# api服务的baseUrls | |||
VUE_APP_API_IDS=https://ids.justfit.com | |||
VUE_APP_API_BASE_URL_API=https://agentapi.justfit.com | |||
VUE_APP_API_COMMON_BASE_URL=https://api.allpcb.com | |||
VUE_APP_API_OSS_BASE_URL=https://member.justfit.com | |||
VUE_APP_API_ALLPCB_BASE_URL=https://www.allpcb.com | |||
VUE_APP_YFZX_CSZ=115ecffc-261a-464d-c9c4-3a0343848dd6 |
@@ -0,0 +1,8 @@ | |||
# webpack devServer的proxy配置 | |||
VUE_APP_API_DEV_SERVER_PROXY_ENABLED=true | |||
VUE_APP_API_DEV_SERVER_PROXY_BASE=/proxy | |||
VUE_APP_API_IDS=/ids | |||
VUE_APP_API_BASE_URL_API=/ | |||
VUE_APP_API_OFFER=/ | |||
VUE_APP_YFZX_CSZ=de89fee0-d722-9d95-9520-3a00f6ff7b35 |
@@ -0,0 +1,8 @@ | |||
NODE_ENV=production | |||
# api服务的baseUrls | |||
VUE_APP_API_IDS=http://192.168.19.4:44330 | |||
VUE_APP_API_BASE_URL_API=http://192.168.19.4:44332 | |||
VUE_APP_YFZX_CSZ=de89fee0-d722-9d95-9520-3a00f6ff7b35 |
@@ -0,0 +1,63 @@ | |||
############################################################################### | |||
# Set default behavior to automatically normalize line endings. | |||
############################################################################### | |||
* text=auto | |||
############################################################################### | |||
# Set default behavior for command prompt diff. | |||
# | |||
# This is need for earlier builds of msysgit that does not have it on by | |||
# default for csharp files. | |||
# Note: This is only used by command line | |||
############################################################################### | |||
#*.cs diff=csharp | |||
############################################################################### | |||
# Set the merge driver for project and solution files | |||
# | |||
# Merging from the command prompt will add diff markers to the files if there | |||
# are conflicts (Merging from VS is not affected by the settings below, in VS | |||
# the diff markers are never inserted). Diff markers may cause the following | |||
# file extensions to fail to load in VS. An alternative would be to treat | |||
# these files as binary and thus will always conflict and require user | |||
# intervention with every merge. To do so, just uncomment the entries below | |||
############################################################################### | |||
#*.sln merge=binary | |||
#*.csproj merge=binary | |||
#*.vbproj merge=binary | |||
#*.vcxproj merge=binary | |||
#*.vcproj merge=binary | |||
#*.dbproj merge=binary | |||
#*.fsproj merge=binary | |||
#*.lsproj merge=binary | |||
#*.wixproj merge=binary | |||
#*.modelproj merge=binary | |||
#*.sqlproj merge=binary | |||
#*.wwaproj merge=binary | |||
############################################################################### | |||
# behavior for image files | |||
# | |||
# image files are treated as binary by default. | |||
############################################################################### | |||
#*.jpg binary | |||
#*.png binary | |||
#*.gif binary | |||
############################################################################### | |||
# diff behavior for common document formats | |||
# | |||
# Convert binary document formats to text before diffing them. This feature | |||
# is only available from the command line. Turn it on by uncommenting the | |||
# entries below. | |||
############################################################################### | |||
#*.doc diff=astextplain | |||
#*.DOC diff=astextplain | |||
#*.docx diff=astextplain | |||
#*.DOCX diff=astextplain | |||
#*.dot diff=astextplain | |||
#*.DOT diff=astextplain | |||
#*.pdf diff=astextplain | |||
#*.PDF diff=astextplain | |||
#*.rtf diff=astextplain | |||
#*.RTF diff=astextplain |
@@ -0,0 +1,20 @@ | |||
.DS_Store | |||
node_modules/ | |||
dist/ | |||
admindb/ | |||
npm-debug.log* | |||
yarn-debug.log* | |||
yarn-error.log* | |||
/test/unit/coverage/ | |||
/test/e2e/reports/ | |||
selenium-debug.log | |||
# Editor directories and files | |||
.idea | |||
.vscode | |||
*.suo | |||
*.ntvs* | |||
*.njsproj | |||
*.sln | |||
package-lock.json | |||
.env.production.local |
@@ -0,0 +1,4 @@ | |||
{ | |||
"singleQuote": true, | |||
"semi": false | |||
} |
@@ -0,0 +1,28 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<projectDescription> | |||
<name>html</name> | |||
<comment></comment> | |||
<projects> | |||
</projects> | |||
<buildSpec> | |||
<buildCommand> | |||
<name>com.aptana.ide.core.unifiedBuilder</name> | |||
<arguments> | |||
</arguments> | |||
</buildCommand> | |||
</buildSpec> | |||
<natures> | |||
<nature>com.aptana.projects.webnature</nature> | |||
</natures> | |||
<filteredResources> | |||
<filter> | |||
<id>1586918228372</id> | |||
<name></name> | |||
<type>26</type> | |||
<matcher> | |||
<id>org.eclipse.ui.ide.multiFilter</id> | |||
<arguments>1.0-name-matches-false-false-node_modules</arguments> | |||
</matcher> | |||
</filter> | |||
</filteredResources> | |||
</projectDescription> |
@@ -0,0 +1,21 @@ | |||
MIT License | |||
Copyright (c) 2018 iczer | |||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to deal | |||
in the Software without restriction, including without limitation the rights | |||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
copies of the Software, and to permit persons to whom the Software is | |||
furnished to do so, subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in all | |||
copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
SOFTWARE. |
@@ -0,0 +1,53 @@ | |||
[简体中文](./README.md) | English | |||
<h1 align="center">Vue Antd Admin</h1> | |||
<div align="center"> | |||
[Ant Design Pro](https://github.com/ant-design/ant-design-pro)'s implementation with Vue. | |||
An out-of-box UI solution for enterprise applications as a React boilerplate. | |||
[](https://github.com/iczer/vue-antd-admin/blob/master/LICENSE) | |||
[](https://david-dm.org/iczer/vue-antd-admin) | |||
[](https://david-dm.org/iczer/vue-antd-admin?type=dev) | |||
[](https://github.com/iczer/vue-antd-admin/releases/latest) | |||
 | |||
Multiple theme modes available: | |||
 | |||
</div> | |||
- Preview:https://iczer.gitee.io/vue-antd-admin | |||
- Documentation:https://iczer.gitee.io/vue-antd-admin-docs | |||
- FAQ:https://iczer.gitee.io/vue-antd-admin-docs/start/faq.html | |||
- Mirror Repo in China:https://gitee.com/iczer/vue-antd-admin | |||
## Browsers support | |||
Modern browsers and IE10. | |||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera | | |||
| --- | --- | --- | --- | --- | | |||
| IE10, Edge | last 2 versions | last 2 versions | last 2 versions | last 2 versions | | |||
## Usage | |||
### clone | |||
```bash | |||
$ git clone https://github.com/iczer/vue-antd-admin.git | |||
``` | |||
### yarn | |||
```bash | |||
$ yarn install | |||
$ yarn serve | |||
``` | |||
### or npm | |||
``` | |||
$ npm install | |||
$ npm run serve | |||
``` | |||
More instructions at [documentation](https://iczer.gitee.io/vue-antd-admin-docs). | |||
## Contributing | |||
Any type of contribution is welcome, here are some examples of how you may contribute to this project: :star2:: | |||
- Use Vue Antd Admin in your daily work. | |||
- Submit [Issue](https://github.com/iczer/vue-antd-admin/issues) to report :bug: or ask questions. | |||
- Propose [Pull Request](https://github.com/iczer/vue-antd-admin/pulls) to improve our code. | |||
- Join the community and share your experiences with us. QQ Group: 812277510、610090280(已满) |
@@ -0,0 +1,13 @@ | |||
const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV) | |||
const plugins = [] | |||
if (IS_PROD) { | |||
plugins.push('transform-remove-console') | |||
} | |||
module.exports = { | |||
presets: [ | |||
'@vue/cli-plugin-babel/preset' | |||
], | |||
plugins | |||
} |
@@ -0,0 +1,101 @@ | |||
{ | |||
"name": "vue-antd-admin", | |||
"version": "0.7.2", | |||
"homepage": "https://iczer.github.io/vue-antd-admin", | |||
"private": true, | |||
"scripts": { | |||
"serve": "vue-cli-service serve", | |||
"build": "vue-cli-service build --dest dist_捷配后台_生产", | |||
"build:test": "vue-cli-service build --mode test --dest dist_捷配后台_测试", | |||
"lint": "vue-cli-service lint" | |||
}, | |||
"dependencies": { | |||
"@antv/data-set": "^0.11.4", | |||
"@antv/x6": "^1.29.0", | |||
"animate.css": "^4.1.0", | |||
"ant-design-vue": "1.7.2", | |||
"axios": "^0.21.1", | |||
"clipboard": "^2.0.6", | |||
"codemirror": "^5.55.0", | |||
"core-js": "^3.6.5", | |||
"date-fns": "^2.14.0", | |||
"echarts": "^5.3.2", | |||
"enquire.js": "^2.1.6", | |||
"highlight.js": "^10.2.1", | |||
"js-cookie": "^2.2.1", | |||
"mockjs": "^1.1.0", | |||
"nprogress": "^0.2.0", | |||
"plupload": "^2.3.7", | |||
"qrcodejs2": "^0.0.2", | |||
"register-service-worker": "^1.7.1", | |||
"smooth-scrollbar": "^8.5.2", | |||
"sortablejs": "^1.10.2", | |||
"uuid": "^8.3.2", | |||
"v-viewer": "^1.6.4", | |||
"viser-vue": "^2.4.8", | |||
"vue": "^2.6.11", | |||
"vue-clipboard2": "^0.3.3", | |||
"vue-count-to": "^1.0.13", | |||
"vue-i18n": "^8.18.2", | |||
"vue-quill-editor": "^3.0.6", | |||
"vue-router": "^3.3.4", | |||
"vuedraggable": "^2.23.2", | |||
"vuex": "^3.4.0" | |||
}, | |||
"devDependencies": { | |||
"@ant-design/colors": "^4.0.1", | |||
"@vue/cli-plugin-babel": "^4.4.0", | |||
"@vue/cli-plugin-eslint": "^4.4.0", | |||
"@vue/cli-service": "^4.4.0", | |||
"@vuepress/plugin-back-to-top": "^1.5.2", | |||
"babel-eslint": "^8.0.2", | |||
"babel-plugin-transform-remove-console": "^6.9.4", | |||
"babel-polyfill": "^6.26.0", | |||
"compression-webpack-plugin": "^2.0.0", | |||
"deepmerge": "^4.2.2", | |||
"eslint": "^6.7.2", | |||
"eslint-plugin-vue": "^6.2.2", | |||
"fast-deep-equal": "^3.1.3", | |||
"gh-pages": "^3.1.0", | |||
"less-loader": "^6.1.1", | |||
"style-resources-loader": "^1.3.2", | |||
"vue-cli-plugin-style-resources-loader": "^0.1.4", | |||
"vue-template-compiler": "^2.6.11", | |||
"vuepress": "^1.5.2", | |||
"webpack-theme-color-replacer": "^1.3.12", | |||
"whatwg-fetch": "^3.0.0" | |||
}, | |||
"eslintConfig": { | |||
"root": true, | |||
"env": { | |||
"node": true | |||
}, | |||
"extends": [ | |||
"plugin:vue/essential", | |||
"eslint:recommended" | |||
], | |||
"parserOptions": { | |||
"parser": "babel-eslint" | |||
}, | |||
"rules": { | |||
"no-unused-vars": 0, | |||
"no-undef": 1, | |||
"no-irregular-whitespace": 1, | |||
"no-multiple-empty-lines": [ | |||
1, | |||
{ | |||
"max": 1 | |||
} | |||
], | |||
"indent": [ | |||
2, | |||
2 | |||
] | |||
} | |||
}, | |||
"browserslist": [ | |||
"> 1%", | |||
"last 2 versions", | |||
"not ie <= 10" | |||
] | |||
} |
@@ -0,0 +1,33 @@ | |||
<!DOCTYPE html> | |||
<html lang="en" class="beauty-scroll"> | |||
<head> | |||
<meta charset="utf-8"> | |||
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |||
<meta name="viewport" content="width=device-width,initial-scale=1.0"> | |||
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> | |||
<title> | |||
<%= process.env.VUE_APP_NAME %> | |||
</title> | |||
<!-- require cdn assets css --> | |||
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %> | |||
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" /> | |||
<% } %> | |||
<script src="./static/iconfont.js"></script> | |||
</head> | |||
<body> | |||
<noscript> | |||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> | |||
</noscript> | |||
<div id="popContainer" class="beauty-scroll" style="height: 100vh; overflow-y: scroll;background-color: #f0f2f5;"> | |||
<div id="app"></div> | |||
</div> | |||
<!-- require cdn assets js --> | |||
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %> | |||
<script type="text/javascript" src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script> | |||
<% } %> | |||
<!-- built files will be auto injected --> | |||
</body> | |||
</html> |
@@ -0,0 +1,117 @@ | |||
<template> | |||
<a-config-provider :locale="locale" :get-popup-container="popContainer"> | |||
<router-view/> | |||
</a-config-provider> | |||
</template> | |||
<script> | |||
import { enquireScreen } from './utils/util' | |||
import {mapState, mapMutations} from 'vuex' | |||
import themeUtil from '@/utils/themeUtil'; | |||
import {getI18nKey} from '@/utils/routerUtil' | |||
import {checkAuthorization} from '@/utils/request'; | |||
export default { | |||
name: 'App', | |||
data() { | |||
return { | |||
locale: {} | |||
} | |||
}, | |||
created () { | |||
this.setHtmlTitle() | |||
this.setLanguage(this.lang) | |||
enquireScreen(isMobile => this.setDevice(isMobile)) | |||
window.SYS_CONFIG = { | |||
OSS_Extranet_URL: { | |||
Name: 'oss外网访问地址', | |||
Code: 'OSS_Extranet_URL', | |||
Value: 'http://192.168.16.100:9015' | |||
}, | |||
} | |||
}, | |||
mounted() { | |||
this.setWeekModeTheme(this.weekMode) | |||
/* if (checkAuthorization()) { | |||
this.$store.dispatch('account/refreshPermissions'); | |||
if (this.$route.path == '/loginjmp') { | |||
location.href = '/#/main'; | |||
} | |||
} else { | |||
this.$router.push('/loginOA'); | |||
}*/ | |||
window.addEventListener('scroll', this.handleScroll, true); | |||
}, | |||
watch: { | |||
weekMode(val) { | |||
this.setWeekModeTheme(val) | |||
}, | |||
lang(val) { | |||
this.setLanguage(val) | |||
this.setHtmlTitle() | |||
}, | |||
$route() { | |||
this.setHtmlTitle() | |||
}, | |||
'theme.mode': function(val) { | |||
let closeMessage = this.$message.loading(`您选择了主题模式 ${val}, 正在切换...`) | |||
themeUtil.changeThemeColor(this.theme.color, val).then(closeMessage) | |||
}, | |||
'theme.color': function(val) { | |||
let closeMessage = this.$message.loading(`您选择了主题色 ${val}, 正在切换...`) | |||
themeUtil.changeThemeColor(val, this.theme.mode).then(closeMessage) | |||
}, | |||
'layout': function() { | |||
window.dispatchEvent(new Event('resize')) | |||
} | |||
}, | |||
computed: { | |||
...mapState('setting', ['layout', 'theme', 'weekMode', 'lang']) | |||
}, | |||
methods: { | |||
...mapMutations('setting', ['setDevice']), | |||
handleScroll() { | |||
const dom = document.getElementById('popContainer'); | |||
let scrollTop = dom.scrollTop; | |||
this.$bus.$emit('scrollTop', scrollTop); | |||
}, | |||
setWeekModeTheme(weekMode) { | |||
if (weekMode) { | |||
document.body.classList.add('week-mode') | |||
} else { | |||
document.body.classList.remove('week-mode') | |||
} | |||
}, | |||
setLanguage(lang) { | |||
this.$i18n.locale = lang | |||
switch (lang) { | |||
case 'CN': | |||
this.locale = require('ant-design-vue/es/locale-provider/zh_CN').default | |||
break | |||
case 'HK': | |||
this.locale = require('ant-design-vue/es/locale-provider/zh_TW').default | |||
break | |||
case 'US': | |||
default: | |||
this.locale = require('ant-design-vue/es/locale-provider/en_US').default | |||
break | |||
} | |||
}, | |||
setHtmlTitle() { | |||
const route = this.$route | |||
const key = route.path === '/' ? 'home.name' : getI18nKey(route.matched[route.matched.length - 1].path) | |||
document.title = process.env.VUE_APP_NAME + ' | ' + this.$t(key) | |||
}, | |||
popContainer() { | |||
return document.getElementById('popContainer') | |||
} | |||
}, | |||
beforeDestroy() { | |||
window.removeEventListener('scroll', this.handleScroll) | |||
} | |||
} | |||
</script> | |||
<style lang="less" scoped> | |||
</style> |
@@ -0,0 +1,25 @@ | |||
import {loadRoutes, loadGuards, setAppOptions} from '@/utils/routerUtil' | |||
import {loadInterceptors} from '@/utils/request' | |||
import guards from '@/router/guards' | |||
import interceptors from '@/utils/axios-interceptors' | |||
/** | |||
* 启动引导方法 | |||
* 应用启动时需要执行的操作放在这里 | |||
* @param router 应用的路由实例 | |||
* @param store 应用的 vuex.store 实例 | |||
* @param i18n 应用的 vue-i18n 实例 | |||
* @param i18n 应用的 message 实例 | |||
*/ | |||
function bootstrap({router, store, i18n, message}) { | |||
// 设置应用配置 | |||
setAppOptions({router, store, i18n}) | |||
// 加载 axios 拦截器 | |||
loadInterceptors(interceptors, {router, store, i18n, message}) | |||
// 加载路由 | |||
loadRoutes() | |||
// 加载路由守卫 | |||
loadGuards(guards, {router, store, i18n, message}) | |||
} | |||
export default bootstrap |
@@ -0,0 +1,118 @@ | |||
<template> | |||
<CustomDrawer ref="addPayType" title="新增收货地址" :destroyOnClose="true"> | |||
<a-form-model ref="ruleForm" :model="form" :rules="rules" layout="vertical"> | |||
<a-form-model-item label="联系人" prop="linkName"> | |||
<a-input v-model="form.linkName" /> | |||
</a-form-model-item> | |||
<a-form-model-item label="联系电话" prop="linkPhone"> | |||
<a-input v-model="form.linkPhone" /> | |||
</a-form-model-item> | |||
<a-form-model-item label="收货地址" prop="address"> | |||
<AreaSelect :areaData="form.address" ref="AreaSelect" @ok="getAddress" /> | |||
</a-form-model-item> | |||
<a-form-model-item label="详细地址" prop="detailAddress"> | |||
<a-textarea | |||
v-model="form.detailAddress" | |||
placeholder="请输入详细地址" | |||
:auto-size="{ minRows: 3, maxRows: 5 }" | |||
/> | |||
</a-form-model-item> | |||
<a-form-model-item label="默认使用"> | |||
<a-checkbox v-model="form.isEnable">默认使用</a-checkbox> | |||
</a-form-model-item> | |||
</a-form-model> | |||
<a-space :size="10"> | |||
<a-button type="primary" :loading="loading" @click="handleSave()">确认</a-button> | |||
<a-button @click="$refs.addPayType.hideDrawer()">取消</a-button> | |||
</a-space> | |||
</CustomDrawer> | |||
</template> | |||
<script> | |||
import { getProjectPaymentType } from '@/services/dropDown'; | |||
import { addAddress } from '@/services/fileManagement/address'; | |||
import AreaSelect from '@/components/customer/AreaSelect'; | |||
export default { | |||
components: { | |||
AreaSelect | |||
}, | |||
data() { | |||
return { | |||
form: {}, | |||
rules: { | |||
linkName: [{ required: true, message: '请输入联系人', trigger: 'blur' }], | |||
linkPhone: [{ required: true, message: '请输入联系电话', trigger: 'blur' }], | |||
address: [{ required: true, message: '请选择收货地址', trigger: 'change' }], | |||
detailAddress: [{ required: true, message: '请输入详细地址', trigger: 'blur' }], | |||
}, | |||
paymentTypes: [], | |||
companyId: '', | |||
loading: false, | |||
selectedOptions: [] | |||
}; | |||
}, | |||
methods: { | |||
showDrawer(companyId) { | |||
this.companyId = companyId; | |||
this.form = { | |||
isEnable: true, | |||
address: [] | |||
}; | |||
this.getDropDown(); | |||
this.$refs.addPayType.showDrawer(); | |||
}, | |||
getDropDown() { | |||
getProjectPaymentType() | |||
.then(res => { | |||
const arr = []; | |||
for (let i in res) { | |||
arr.push({ | |||
type: Number(i), | |||
name: res[i] | |||
}) | |||
} | |||
this.paymentTypes = arr; | |||
}) | |||
}, | |||
getAddress(value, selectedOptions) { | |||
this.form.address = value; | |||
this.selectedOptions = selectedOptions; | |||
}, | |||
handleSave() { | |||
this.$refs.ruleForm.validate((valid) => { | |||
if (valid) { | |||
const params = { | |||
companyId: this.companyId, | |||
linkName: this.form.linkName, | |||
linkPhone: this.form.linkPhone, | |||
provinceCode: this.selectedOptions[0].code, | |||
cityCode: this.selectedOptions[1].code, | |||
province: this.selectedOptions[0].name, | |||
city: this.selectedOptions[1].name, | |||
detailAddress: this.form.detailAddress, | |||
isDefault: this.form.isEnable | |||
} | |||
this.loading = true; | |||
addAddress(params) | |||
.then(res => { | |||
const data = { | |||
update: false, | |||
id: res.id | |||
}; | |||
if (this.form.isEnable) { | |||
data.update = true | |||
} | |||
this.$emit('ok', data); | |||
this.$refs.addPayType.hideDrawer(); | |||
}) | |||
.finally(() => { | |||
this.loading = false; | |||
}); | |||
} | |||
}); | |||
}, | |||
}, | |||
}; | |||
</script> | |||
<style></style> |
@@ -0,0 +1,193 @@ | |||
<template> | |||
<CustomDrawer ref="addressList" title="选择收货地址" :destroyOnClose="true"> | |||
<a-card | |||
v-for="(type, index) in addressList" | |||
:key="index" | |||
:class="type.isDefault && model == 1 ? 'cardCls active' : 'cardCls'" | |||
> | |||
<a-row class="rowCls"> | |||
<a-col :span="12"> | |||
<a-row> | |||
<a-col :span="10">联系人:</a-col> | |||
<a-col :span="12" class="conCls">{{ type.linkName }}</a-col> | |||
</a-row> | |||
</a-col> | |||
<a-col :span="12"> | |||
<a-row> | |||
<a-col :span="10">联系电话:</a-col> | |||
<a-col :span="12" class="conCls">{{ type.linkPhone }}</a-col> | |||
</a-row> | |||
</a-col> | |||
<a-col :span="12"> | |||
<a-row> | |||
<a-col :span="10">收货地址:</a-col> | |||
<a-col :span="12" class="conCls">{{ type.province }}-{{ type.city }}</a-col> | |||
</a-row> | |||
</a-col> | |||
<a-col :span="12"> | |||
<a-row> | |||
<a-col :span="10">详细地址:</a-col> | |||
<a-col :span="12" class="conCls">{{ type.detailAddress }}</a-col> | |||
</a-row> | |||
</a-col> | |||
</a-row> | |||
<div class="btnCls" v-if="!type.isDefault && model == 1"> | |||
<span @click="handleUse(type)">使用</span> | |||
<a-popconfirm | |||
placement="topRight" | |||
title="确认删除该收货地址?" | |||
ok-text="确认" | |||
cancel-text="取消" | |||
@confirm="handleDel(type)" | |||
> | |||
<span>删除</span> | |||
</a-popconfirm> | |||
</div> | |||
<div class="btnCls" v-if="model == 2"> | |||
<span @click="handleSelect(type)">使用</span> | |||
<a-popconfirm | |||
placement="topRight" | |||
title="确认删除该收货地址?" | |||
ok-text="确认" | |||
cancel-text="取消" | |||
@confirm="handleDel(type)" | |||
> | |||
<span>删除</span> | |||
</a-popconfirm> | |||
</div> | |||
<div class="activeCls" v-if="type.isDefault && model == 1"></div> | |||
</a-card> | |||
<a-button type="dashed" @click="addPayType()" icon="plus">新增</a-button> | |||
<AddressAdd ref="AddressAdd" @ok="getList" /> | |||
</CustomDrawer> | |||
</template> | |||
<script> | |||
import AddressAdd from './AddressAdd'; | |||
import { getAddressList, deleteAddress, updateAddress } from '@/services/fileManagement/address'; | |||
export default { | |||
props: { | |||
model: { | |||
type: Number,// 1:收货地址管理,2:选择收货地址 | |||
default: 1 | |||
} | |||
}, | |||
components: { AddressAdd }, | |||
data() { | |||
return { | |||
form: {}, | |||
rules: {}, | |||
payTypeArr: [], | |||
monthArr: [], | |||
addressList: [], | |||
id: '', | |||
}; | |||
}, | |||
methods: { | |||
//使用 | |||
handleUse(row) { | |||
updateAddress(row.id, { | |||
...row, | |||
companyId: this.id, | |||
isDefault: true | |||
}) | |||
.then(res => { | |||
this.$message.success('操作成功'); | |||
this.getList(); | |||
}) | |||
}, | |||
// 选择 | |||
handleSelect(row) { | |||
this.$emit('ok', row); | |||
this.$refs.addressList.hideDrawer(); | |||
}, | |||
//删除 | |||
handleDel(row) { | |||
deleteAddress(row.id, this.id) | |||
.then(res => { | |||
this.$message.success('删除成功'); | |||
this.getList(); | |||
}) | |||
}, | |||
showDrawer({id}) { | |||
this.id = id; | |||
this.getList(); | |||
this.$refs.addressList.showDrawer(); | |||
}, | |||
// 获取列表 | |||
getList() { | |||
getAddressList(this.id) | |||
.then(res => { | |||
this.addressList = res; | |||
}) | |||
}, | |||
// 新增 | |||
addPayType() { | |||
this.$refs.AddressAdd.showDrawer(this.id); | |||
}, | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="less"> | |||
.rowCls { | |||
line-height: 40px; | |||
color: #999999; | |||
} | |||
.conCls { | |||
font-size: 14px; | |||
color: #333333; | |||
} | |||
.cardCls { | |||
border: 1px solid #dddddd; | |||
border-radius: 4px; | |||
margin-bottom: 10px; | |||
position: relative; | |||
&.active { | |||
border-color: #f90; | |||
} | |||
.btnCls { | |||
display: none; | |||
position: absolute; | |||
right: 0; | |||
bottom: 5px; | |||
span { | |||
padding: 10px; | |||
color: #f90; | |||
cursor: pointer; | |||
} | |||
} | |||
&:hover { | |||
border-color: #f90; | |||
.btnCls { | |||
display: block; | |||
} | |||
} | |||
} | |||
.activeCls { | |||
position: absolute; | |||
right: 0; | |||
bottom: 0; | |||
&:before { | |||
content: ""; | |||
position: absolute; | |||
right: 0; | |||
bottom: 0; | |||
border: 12px solid #f90; | |||
border-top-color: transparent; | |||
border-left-color: transparent; | |||
} | |||
&:after { | |||
content: ""; | |||
width: 5px; | |||
height: 10px; | |||
position: absolute; | |||
right: 4px; | |||
bottom: 5px; | |||
border: 1px solid #fff; | |||
border-top-color: transparent; | |||
border-left-color: transparent; | |||
transform: rotate(45deg); | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,172 @@ | |||
import {isDef, isRegExp, remove} from '@/utils/util' | |||
const patternTypes = [String, RegExp, Array] | |||
function matches (pattern, name) { | |||
if (Array.isArray(pattern)) { | |||
if (pattern.indexOf(name) > -1) { | |||
return true | |||
} else { | |||
for (let item of pattern) { | |||
if (isRegExp(item) && item.test(name)) { | |||
return true | |||
} | |||
} | |||
return false | |||
} | |||
} else if (typeof pattern === 'string') { | |||
return pattern.split(',').indexOf(name) > -1 | |||
} else if (isRegExp(pattern)) { | |||
return pattern.test(name) | |||
} | |||
/* istanbul ignore next */ | |||
return false | |||
} | |||
function getComponentName (opts) { | |||
return opts && (opts.Ctor.options.name || opts.tag) | |||
} | |||
function getComponentKey (vnode) { | |||
const {componentOptions, key} = vnode | |||
return key == null | |||
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '') | |||
: key + componentOptions.Ctor.cid | |||
} | |||
function getFirstComponentChild (children) { | |||
if (Array.isArray(children)) { | |||
for (let i = 0; i < children.length; i++) { | |||
const c = children[i] | |||
if (isDef(c) && (isDef(c.componentOptions) || c.isAsyncPlaceholder)) { | |||
return c | |||
} | |||
} | |||
} | |||
} | |||
function pruneCache (keepAliveInstance, filter) { | |||
const { cache, keys, _vnode } = keepAliveInstance | |||
for (const key in cache) { | |||
const cachedNode = cache[key] | |||
if (cachedNode) { | |||
const name = getComponentName(cachedNode.componentOptions) | |||
const componentKey = getComponentKey(cachedNode) | |||
if (name && !filter(name, componentKey)) { | |||
pruneCacheEntry(cache, key, keys, _vnode) | |||
} | |||
} | |||
} | |||
} | |||
function pruneCacheEntry2(cache, key, keys) { | |||
const cached = cache[key] | |||
if (cached) { | |||
cached.componentInstance.$destroy() | |||
} | |||
cache[key] = null | |||
remove(keys, key) | |||
} | |||
function pruneCacheEntry (cache, key, keys, current) { | |||
const cached = cache[key] | |||
if (cached && (!current || cached.tag !== current.tag)) { | |||
cached.componentInstance.$destroy() | |||
} | |||
cache[key] = null | |||
remove(keys, key) | |||
} | |||
export default { | |||
name: 'AKeepAlive', | |||
abstract: true, | |||
model: { | |||
prop: 'clearCaches', | |||
event: 'clear', | |||
}, | |||
props: { | |||
include: patternTypes, | |||
exclude: patternTypes, | |||
excludeKeys: patternTypes, | |||
max: [String, Number], | |||
clearCaches: Array | |||
}, | |||
watch: { | |||
clearCaches: function(val) { | |||
if (val && val.length > 0) { | |||
const {cache, keys} = this | |||
val.forEach(key => { | |||
pruneCacheEntry2(cache, key, keys) | |||
}) | |||
this.$emit('clear', []) | |||
} | |||
} | |||
}, | |||
created() { | |||
this.cache = Object.create(null) | |||
this.keys = [] | |||
}, | |||
destroyed () { | |||
for (const key in this.cache) { | |||
pruneCacheEntry(this.cache, key, this.keys) | |||
} | |||
}, | |||
mounted () { | |||
this.$watch('include', val => { | |||
pruneCache(this, (name) => matches(val, name)) | |||
}) | |||
this.$watch('exclude', val => { | |||
pruneCache(this, (name) => !matches(val, name)) | |||
}) | |||
this.$watch('excludeKeys', val => { | |||
pruneCache(this, (name, key) => !matches(val, key)) | |||
}) | |||
}, | |||
render () { | |||
const slot = this.$slots.default | |||
const vnode = getFirstComponentChild(slot) | |||
const componentOptions = vnode && vnode.componentOptions | |||
if (componentOptions) { | |||
// check pattern | |||
const name = getComponentName(componentOptions) | |||
const componentKey = getComponentKey(vnode) | |||
const { include, exclude, excludeKeys } = this | |||
if ( | |||
// not included | |||
(include && (!name || !matches(include, name))) || | |||
// excluded | |||
(exclude && name && matches(exclude, name)) || | |||
(excludeKeys && componentKey && matches(excludeKeys, componentKey)) | |||
) { | |||
return vnode | |||
} | |||
const { cache, keys } = this | |||
const key = vnode.key == null | |||
// same constructor may get registered as different local components | |||
// so cid alone is not enough (#3269) | |||
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '') | |||
: vnode.key + componentOptions.Ctor.cid | |||
if (cache[key]) { | |||
vnode.componentInstance = cache[key].componentInstance | |||
// make current key freshest | |||
remove(keys, key) | |||
keys.push(key) | |||
} else { | |||
cache[key] = vnode | |||
keys.push(key) | |||
// prune oldest entry | |||
if (this.max && keys.length > parseInt(this.max)) { | |||
pruneCacheEntry(cache, keys[0], keys, this._vnode) | |||
} | |||
} | |||
vnode.data.keepAlive = true | |||
} | |||
return vnode || (slot && slot[0]) | |||
} | |||
} |
@@ -0,0 +1,78 @@ | |||
<template> | |||
<a-card :loading="loading" :body-style="{padding: '20px 24px 8px'}" :bordered="false"> | |||
<div class="chart-card-header"> | |||
<div class="meta"> | |||
<span class="chart-card-title">{{title}}</span> | |||
<span class="chart-card-action"> | |||
<slot name="action"></slot> | |||
</span> | |||
</div> | |||
<div class="total"><span>{{total}}</span></div> | |||
</div> | |||
<div class="chart-card-content"> | |||
<div class="content-fix"> | |||
<slot></slot> | |||
</div> | |||
</div> | |||
<div class="chart-card-footer"> | |||
<slot name="footer"></slot> | |||
</div> | |||
</a-card> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'ChartCard', | |||
props: ['title', 'total', 'loading'] | |||
} | |||
</script> | |||
<style scoped lang="less"> | |||
.chart-card-header{ | |||
position: relative; | |||
overflow: hidden; | |||
width: 100%; | |||
} | |||
.chart-card-header .meta{ | |||
position: relative; | |||
overflow: hidden; | |||
width: 100%; | |||
color: @text-color-second; | |||
font-size: 14px; | |||
line-height: 22px; | |||
} | |||
.chart-card-action{ | |||
cursor: pointer; | |||
position: absolute; | |||
top: 0; | |||
right: 0; | |||
} | |||
.total { | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
word-break: break-all; | |||
white-space: nowrap; | |||
margin-top: 4px; | |||
margin-bottom: 0; | |||
font-size: 30px; | |||
line-height: 38px; | |||
height: 38px; | |||
} | |||
.chart-card-footer{ | |||
border-top: 1px solid @border-color-base; | |||
padding-top: 9px; | |||
margin-top: 8px; | |||
} | |||
.chart-card-content{ | |||
margin-bottom: 12px; | |||
position: relative; | |||
height: 46px; | |||
width: 100%; | |||
} | |||
.chart-card-content .content-fix{ | |||
position: absolute; | |||
left: 0; | |||
bottom: 0; | |||
width: 100%; | |||
} | |||
</style> |
@@ -0,0 +1,59 @@ | |||
<template> | |||
<div class="bar"> | |||
<h4>{{title}}</h4> | |||
<div class="chart"> | |||
<v-chart :force-fit="true" height="312" :data="data" :padding="[24, 0, 0, 0]"> | |||
<v-tooltip /> | |||
<v-axis /> | |||
<v-bar position="x*y"/> | |||
</v-chart> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
const data = [] | |||
for (let i = 0; i < 12; i += 1) { | |||
data.push({ | |||
x: `${i + 1}月`, | |||
y: Math.floor(Math.random() * 1000) + 200 | |||
}) | |||
} | |||
const tooltip = [ | |||
'x*y', | |||
(x, y) => ({ | |||
name: x, | |||
value: y | |||
}) | |||
] | |||
const scale = [{ | |||
dataKey: 'x', | |||
min: 2 | |||
}, { | |||
dataKey: 'y', | |||
title: '时间', | |||
min: 1, | |||
max: 22 | |||
}] | |||
export default { | |||
name: 'Bar', | |||
props: ['title'], | |||
data () { | |||
return { | |||
data, | |||
scale, | |||
tooltip | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped lang="less"> | |||
.bar{ | |||
position: relative; | |||
.chart{ | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,67 @@ | |||
<template> | |||
<div class="mini-chart"> | |||
<div class="chart-content" :style="{height: 46}"> | |||
<v-chart :force-fit="true" :height="height" :data="data" :padding="[36, 5, 18, 5]"> | |||
<v-tooltip /> | |||
<v-smooth-area position="x*y" /> | |||
</v-chart> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import {format} from 'date-fns' | |||
const data = [] | |||
const beginDay = new Date().getTime() | |||
const fakeY = [7, 5, 4, 2, 4, 7, 5, 6, 5, 9, 6, 3, 1, 5, 3, 6, 5] | |||
for (let i = 0; i < fakeY.length; i += 1) { | |||
data.push({ | |||
x: format(new Date(beginDay + 1000 * 60 * 60 * 24 * i), 'yyyy-MM-dd'), | |||
y: fakeY[i] | |||
}) | |||
} | |||
const tooltip = [ | |||
'x*y', | |||
(x, y) => ({ | |||
name: x, | |||
value: y | |||
}) | |||
] | |||
const scale = [{ | |||
dataKey: 'x', | |||
min: 2 | |||
}, { | |||
dataKey: 'y', | |||
title: '时间', | |||
min: 1, | |||
max: 22 | |||
}] | |||
export default { | |||
name: 'MiniArea', | |||
data () { | |||
return { | |||
data, | |||
scale, | |||
tooltip, | |||
height: 100 | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped> | |||
.mini-chart { | |||
position: relative; | |||
width: 100% | |||
} | |||
.mini-chart .chart-content{ | |||
position: absolute; | |||
bottom: -28px; | |||
width: 100%; | |||
} | |||
</style> |
@@ -0,0 +1,59 @@ | |||
<template> | |||
<div class="mini-chart"> | |||
<div class="chart-content" :style="{height: 46}"> | |||
<v-chart :force-fit="true" :height="height" :data="data" :padding="[36, 5, 18, 5]"> | |||
<v-tooltip /> | |||
<v-bar position="x*y" /> | |||
</v-chart> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import {format} from 'date-fns' | |||
const data = [] | |||
const beginDay = new Date().getTime() | |||
const fakeY = [7, 5, 4, 2, 4, 7, 5, 6, 5, 9, 6, 3, 1, 5, 3, 6, 5] | |||
for (let i = 0; i < fakeY.length; i += 1) { | |||
data.push({ | |||
x: format(new Date(beginDay + 1000 * 60 * 60 * 24 * i), 'yyyy-MM-dd'), | |||
y: fakeY[i] | |||
}) | |||
} | |||
const tooltip = [ | |||
'x*y', | |||
(x, y) => ({ | |||
name: x, | |||
value: y | |||
}) | |||
] | |||
const scale = [{ | |||
dataKey: 'x', | |||
min: 2 | |||
}, { | |||
dataKey: 'y', | |||
title: '时间', | |||
min: 1, | |||
max: 22 | |||
}] | |||
export default { | |||
name: 'MiniBar', | |||
data () { | |||
return { | |||
data, | |||
scale, | |||
tooltip, | |||
height: 100 | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="less" scoped> | |||
@import "index.less"; | |||
</style> |
@@ -0,0 +1,56 @@ | |||
<template> | |||
<div class="mini-progress"> | |||
<a-tooltip :title="'目标值:' + target + '%'"> | |||
<div class="target" :style="{left: target + '%'}"> | |||
<span :style="{backgroundColor: color}" /> | |||
<span :style="{backgroundColor: color}" /> | |||
</div> | |||
</a-tooltip> | |||
<div class="wrap"> | |||
<div class="progress" :style="{backgroundColor: color, width: percent + '%', height: height}" /> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'MiniProgress', | |||
props: ['target', 'color', 'percent', 'height'] | |||
} | |||
</script> | |||
<style lang="less" scoped> | |||
.mini-progress { | |||
padding: 5px 0; | |||
position: relative; | |||
width: 100%; | |||
.wrap { | |||
background-color: @layout-bg-color; | |||
position: relative; | |||
} | |||
.progress { | |||
transition: all 0.4s cubic-bezier(0.08, 0.82, 0.17, 1) 0s; | |||
border-radius: 1px 0 0 1px; | |||
background-color: #13C2C2; | |||
width: 0; | |||
height: 100%; | |||
} | |||
.target { | |||
position: absolute; | |||
top: 0; | |||
bottom: 0; | |||
span { | |||
border-radius: 100px; | |||
position: absolute; | |||
top: 0; | |||
left: 0; | |||
height: 4px; | |||
width: 2px; | |||
} | |||
span:last-child { | |||
top: auto; | |||
bottom: 0; | |||
} | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,80 @@ | |||
<template> | |||
<v-chart :forceFit="true" height="400" :data="data" :padding="[20, 20, 95, 20]" :scale="scale"> | |||
<v-tooltip /> | |||
<v-axis :dataKey="axis1Opts.dataKey" :line="axis1Opts.line" :tickLine="axis1Opts.tickLine" :grid="axis1Opts.grid" /> | |||
<v-axis :dataKey="axis2Opts.dataKey" :line="axis2Opts.line" :tickLine="axis2Opts.tickLine" :grid="axis2Opts.grid" /> | |||
<v-legend dataKey="user" marker="circle" :offset="30" /> | |||
<v-coord type="polar" radius="0.8" /> | |||
<v-line position="item*score" color="user" :size="2" /> | |||
<v-point position="item*score" color="user" :size="4" shape="circle" /> | |||
</v-chart> | |||
</template> | |||
<script> | |||
const DataSet = require('@antv/data-set') | |||
const sourceData = [ | |||
{item: '引用', a: 70, b: 30, c: 40}, | |||
{item: '口碑', a: 60, b: 70, c: 40}, | |||
{item: '产量', a: 50, b: 60, c: 40}, | |||
{item: '贡献', a: 40, b: 50, c: 40}, | |||
{item: '热度', a: 60, b: 70, c: 40}, | |||
{item: '引用', a: 70, b: 50, c: 40} | |||
] | |||
const dv = new DataSet.View().source(sourceData) | |||
dv.transform({ | |||
type: 'fold', | |||
fields: ['a', 'b', 'c'], | |||
key: 'user', | |||
value: 'score' | |||
}) | |||
const scale = [{ | |||
dataKey: 'score', | |||
min: 0, | |||
max: 80 | |||
}] | |||
const data = dv.rows | |||
const axis1Opts = { | |||
dataKey: 'item', | |||
line: null, | |||
tickLine: null, | |||
grid: { | |||
lineStyle: { | |||
lineDash: null | |||
}, | |||
hideFirstLine: false | |||
} | |||
} | |||
const axis2Opts = { | |||
dataKey: 'score', | |||
line: null, | |||
tickLine: null, | |||
grid: { | |||
type: 'polygon', | |||
lineStyle: { | |||
lineDash: null | |||
} | |||
} | |||
} | |||
export default { | |||
name: 'Radar', | |||
data () { | |||
return { | |||
sourceData, | |||
data, | |||
axis1Opts, | |||
axis2Opts, | |||
scale | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped> | |||
</style> |
@@ -0,0 +1,59 @@ | |||
<template> | |||
<div class="rank"> | |||
<h4 class="title">{{title}}</h4> | |||
<ul class="list"> | |||
<li :key="index" v-for="(item, index) in list"> | |||
<span :class="index < 3 ? 'active' : null">{{index + 1}}</span> | |||
<span >{{item.name}}</span> | |||
<span >{{item.total}}</span> | |||
</li> | |||
</ul> | |||
</div> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'RankingList', | |||
props: ['title', 'list'] | |||
} | |||
</script> | |||
<style lang="less" scoped> | |||
.rank{ | |||
padding: 0 32px 32px 72px; | |||
.title{ | |||
} | |||
.list{ | |||
margin: 25px 0 0; | |||
padding: 0; | |||
list-style: none; | |||
li { | |||
margin-top: 16px; | |||
span { | |||
color: @text-color-second; | |||
font-size: 14px; | |||
line-height: 22px; | |||
} | |||
span:first-child { | |||
background-color: @layout-bg-color; | |||
border-radius: 20px; | |||
display: inline-block; | |||
font-size: 12px; | |||
font-weight: 600; | |||
margin-right: 24px; | |||
height: 20px; | |||
line-height: 20px; | |||
width: 20px; | |||
text-align: center; | |||
} | |||
span.active { | |||
background-color: #314659 !important; | |||
color: @text-color-inverse !important; | |||
} | |||
span:last-child { | |||
float: right; | |||
} | |||
} | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,79 @@ | |||
<template> | |||
<div class="chart-trend"> | |||
{{term}} | |||
<span>{{rate}}%</span> | |||
<span :class="['chart-trend-icon', trend]" style=""><a-icon :type="'caret-' + trend" /></span> | |||
</div> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'Trend', | |||
props: { | |||
term: { | |||
type: String, | |||
required: true | |||
}, | |||
target: { | |||
type: Number, | |||
required: false, | |||
default: 0 | |||
}, | |||
value: { | |||
type: Number, | |||
required: false, | |||
default: 0 | |||
}, | |||
isIncrease: { | |||
type: Boolean, | |||
required: false, | |||
default: null | |||
}, | |||
percent: { | |||
type: Number, | |||
required: false, | |||
default: null | |||
}, | |||
scale: { | |||
type: Number, | |||
required: false, | |||
default: 2 | |||
} | |||
}, | |||
data () { | |||
return { | |||
trend: this.isIncrease ? 'up' : 'down', | |||
rate: this.percent | |||
} | |||
}, | |||
created () { | |||
this.trend = this.caulateTrend() | |||
this.rate = this.caulateRate() | |||
}, | |||
methods: { | |||
caulateRate () { | |||
return (this.percent === null ? Math.abs(this.value - this.target) * 100 / this.target : this.percent).toFixed(this.scale) | |||
}, | |||
caulateTrend () { | |||
let isIncrease = this.isIncrease === null ? this.value >= this.target : this.isIncrease | |||
return isIncrease ? 'up' : 'down' | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="less" scoped> | |||
.chart-trend{ | |||
display: inline-block; | |||
font-size: 14px; | |||
.chart-trend-icon{ | |||
font-size: 12px; | |||
&.up{ | |||
color: @red-6; | |||
} | |||
&.down{ | |||
color: @green-6; | |||
} | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,9 @@ | |||
.mini-chart{ | |||
position: relative; | |||
width: 100%; | |||
.chart-content{ | |||
position: absolute; | |||
bottom: -28px; | |||
width: 100%; | |||
} | |||
} |
@@ -0,0 +1,157 @@ | |||
<template> | |||
<div class="theme-color" :style="{backgroundColor: color}" @click="toggle"> | |||
<a-icon v-if="sChecked" type="check" /> | |||
</div> | |||
</template> | |||
<script> | |||
const Group = { | |||
name: 'ColorCheckboxGroup', | |||
props: { | |||
defaultValues: { | |||
type: Array, | |||
required: false, | |||
default: () => [] | |||
}, | |||
multiple: { | |||
type: Boolean, | |||
required: false, | |||
default: false | |||
} | |||
}, | |||
data () { | |||
return { | |||
values: [], | |||
options: [] | |||
} | |||
}, | |||
computed: { | |||
colors () { | |||
let colors = [] | |||
this.options.forEach(item => { | |||
if (item.sChecked) { | |||
colors.push(item.color) | |||
} | |||
}) | |||
return colors | |||
} | |||
}, | |||
provide () { | |||
return { | |||
groupContext: this | |||
} | |||
}, | |||
watch: { | |||
values(value) { | |||
this.$emit('change', value, this.colors) | |||
} | |||
}, | |||
methods: { | |||
handleChange (option) { | |||
if (!option.checked) { | |||
if (this.values.indexOf(option.value) > -1) { | |||
this.values = this.values.filter(item => item != option.value) | |||
} | |||
} else { | |||
if (!this.multiple) { | |||
this.values = [option.value] | |||
this.options.forEach(item => { | |||
if (item.value != option.value) { | |||
item.sChecked = false | |||
} | |||
}) | |||
} else { | |||
this.values.push(option.value) | |||
} | |||
} | |||
} | |||
}, | |||
render (h) { | |||
const clear = h('div', {attrs: {style: 'clear: both'}}) | |||
return h( | |||
'div', | |||
{}, | |||
[this.$slots.default, clear] | |||
) | |||
} | |||
} | |||
export default { | |||
name: 'ColorCheckbox', | |||
Group: Group, | |||
props: { | |||
color: { | |||
type: String, | |||
required: true | |||
}, | |||
value: { | |||
type: [String, Number], | |||
required: true | |||
}, | |||
checked: { | |||
type: Boolean, | |||
required: false, | |||
default: false | |||
} | |||
}, | |||
data () { | |||
return { | |||
sChecked: this.initChecked() | |||
} | |||
}, | |||
computed: { | |||
}, | |||
inject: ['groupContext'], | |||
watch: { | |||
'sChecked': function () { | |||
const value = { | |||
value: this.value, | |||
color: this.color, | |||
checked: this.sChecked | |||
} | |||
this.$emit('change', value) | |||
const groupContext = this.groupContext | |||
if (groupContext) { | |||
groupContext.handleChange(value) | |||
} | |||
} | |||
}, | |||
created () { | |||
const groupContext = this.groupContext | |||
if (groupContext) { | |||
groupContext.options.push(this) | |||
} | |||
}, | |||
methods: { | |||
toggle () { | |||
if (this.groupContext.multiple || !this.sChecked) { | |||
this.sChecked = !this.sChecked | |||
} | |||
}, | |||
initChecked() { | |||
let groupContext = this.groupContext | |||
if (!groupContext) { | |||
return this.checked | |||
}else if (groupContext.multiple) { | |||
return groupContext.defaultValues.indexOf(this.value) > -1 | |||
} else { | |||
return groupContext.defaultValues[0] == this.value | |||
} | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="less" scoped> | |||
.theme-color{ | |||
float: left; | |||
width: 20px; | |||
height: 20px; | |||
border-radius: 2px; | |||
cursor: pointer; | |||
margin-right: 8px; | |||
text-align: center; | |||
color: @base-bg-color; | |||
font-weight: bold; | |||
} | |||
</style> |
@@ -0,0 +1,161 @@ | |||
<template> | |||
<a-tooltip :title="title" :overlayStyle="{zIndex: 2001}"> | |||
<div class="img-check-box" @click="toggle"> | |||
<img :src="img" /> | |||
<div v-if="sChecked" class="check-item"> | |||
<a-icon type="check" /> | |||
</div> | |||
</div> | |||
</a-tooltip> | |||
</template> | |||
<script> | |||
const Group = { | |||
name: 'ImgCheckboxGroup', | |||
props: { | |||
multiple: { | |||
type: Boolean, | |||
required: false, | |||
default: false | |||
}, | |||
defaultValues: { | |||
type: Array, | |||
required: false, | |||
default: () => [] | |||
} | |||
}, | |||
data () { | |||
return { | |||
values: [], | |||
options: [] | |||
} | |||
}, | |||
provide () { | |||
return { | |||
groupContext: this | |||
} | |||
}, | |||
watch: { | |||
'values': function (value) { | |||
this.$emit('change', value) | |||
// // 此条件是为解决单选时,触发两次chang事件问题 | |||
// if (!(newVal.length === 1 && oldVal.length === 1 && newVal[0] === oldVal[0])) { | |||
// this.$emit('change', this.values) | |||
// } | |||
} | |||
}, | |||
methods: { | |||
handleChange (option) { | |||
if (!option.checked) { | |||
if (this.values.indexOf(option.value) > -1) { | |||
this.values = this.values.filter(item => item != option.value) | |||
} | |||
} else { | |||
if (!this.multiple) { | |||
this.values = [option.value] | |||
this.options.forEach(item => { | |||
if (item.value != option.value) { | |||
item.sChecked = false | |||
} | |||
}) | |||
} else { | |||
this.values.push(option.value) | |||
} | |||
} | |||
} | |||
}, | |||
render (h) { | |||
return h( | |||
'div', | |||
{ | |||
attrs: {style: 'display: flex'} | |||
}, | |||
[this.$slots.default] | |||
) | |||
} | |||
} | |||
export default { | |||
name: 'ImgCheckbox', | |||
Group, | |||
props: { | |||
checked: { | |||
type: Boolean, | |||
required: false, | |||
default: false | |||
}, | |||
img: { | |||
type: String, | |||
required: true | |||
}, | |||
value: { | |||
required: true | |||
}, | |||
title: String | |||
}, | |||
data () { | |||
return { | |||
sChecked: this.initChecked() | |||
} | |||
}, | |||
inject: ['groupContext'], | |||
watch: { | |||
'sChecked': function () { | |||
const option = { | |||
value: this.value, | |||
checked: this.sChecked | |||
} | |||
this.$emit('change', option) | |||
const groupContext = this.groupContext | |||
if (groupContext) { | |||
groupContext.handleChange(option) | |||
} | |||
} | |||
}, | |||
created () { | |||
const groupContext = this.groupContext | |||
if (groupContext) { | |||
this.sChecked = groupContext.defaultValues.length > 0 ? groupContext.defaultValues.indexOf(this.value) >= 0 : this.sChecked | |||
groupContext.options.push(this) | |||
} | |||
}, | |||
methods: { | |||
toggle () { | |||
if (this.groupContext.multiple || !this.sChecked) { | |||
this.sChecked = !this.sChecked | |||
} | |||
}, | |||
initChecked() { | |||
let groupContext = this.groupContext | |||
if (!groupContext) { | |||
return this.checked | |||
}else if (groupContext.multiple) { | |||
return groupContext.defaultValues.indexOf(this.value) > -1 | |||
} else { | |||
return groupContext.defaultValues[0] == this.value | |||
} | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="less" scoped> | |||
.img-check-box{ | |||
margin-right: 16px; | |||
position: relative; | |||
border-radius: 4px; | |||
cursor: pointer; | |||
.check-item{ | |||
position: absolute; | |||
top: 0; | |||
right: 0; | |||
width: 100%; | |||
padding-top: 15px; | |||
padding-left: 24px; | |||
height: 100%; | |||
color: @primary-color; | |||
font-size: 14px; | |||
font-weight: bold; | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,7 @@ | |||
import ColorCheckbox from '@/components/checkbox/ColorCheckbox' | |||
import ImgCheckbox from '@/components/checkbox/ImgCheckbox' | |||
export { | |||
ColorCheckbox, | |||
ImgCheckbox | |||
} |
@@ -0,0 +1,46 @@ | |||
<template> | |||
<a-cascader :options="options" :fieldNames="fieldNames" v-model="area" placeholder="请选择省市" @change="onChange" /> | |||
</template> | |||
<script> | |||
import { getAreaList } from '@/services/dropDown'; | |||
export default { | |||
props: { | |||
areaData: { | |||
type: Array, | |||
default: function() { | |||
return []; | |||
} | |||
} | |||
}, | |||
watch: { | |||
areaData(n) { | |||
this.area = n; | |||
} | |||
}, | |||
data() { | |||
return { | |||
area: [], | |||
fieldNames: { | |||
label: 'name', | |||
value: 'code', | |||
children: 'child' | |||
}, | |||
options: [], | |||
}; | |||
}, | |||
mounted() { | |||
this.getList(); | |||
}, | |||
methods: { | |||
onChange(value, selectedOptions) { | |||
this.$emit('ok', value, selectedOptions) | |||
}, | |||
getList() { | |||
getAreaList() | |||
.then(res => { | |||
this.options = res; | |||
}) | |||
} | |||
}, | |||
}; | |||
</script> |
@@ -0,0 +1,64 @@ | |||
<template> | |||
<div :style="`height: ${height}px`"> | |||
<a-card ref="buttonBoxSelf" :class="{ 'button-box-fixed': isFixed }"> | |||
<slot></slot> | |||
</a-card> | |||
</div> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'ButtonBox', | |||
data() { | |||
return { | |||
height: 0, | |||
isFixed: false, | |||
timer: null | |||
} | |||
}, | |||
mounted() { | |||
const dom = this.$refs.buttonBoxSelf.$el; | |||
this.height = dom.offsetHeight; | |||
let offsetTop = dom.offsetTop; | |||
if (dom.offsetTop == 0) { | |||
const parent = this.$refs.buttonBoxSelf.$parent.$parent.$el; | |||
offsetTop = parent.offsetTop; | |||
} | |||
this.$bus.$on('scrollTop', scrollTop => { | |||
if(this.timer){ | |||
clearTimeout(this.timer) | |||
} | |||
this.timer = setTimeout(() => { | |||
if (offsetTop == 0) { | |||
// 滚动距离为包裹层的高度 | |||
if (scrollTop >= 20) { | |||
this.isFixed = true; | |||
} else { | |||
this.isFixed = false; | |||
} | |||
return; | |||
} | |||
// 滚动距离为包裹层距离内容层的高度 | |||
if (scrollTop >= offsetTop) { | |||
this.isFixed = true; | |||
} else { | |||
this.isFixed = false; | |||
} | |||
}, 10) | |||
}); | |||
}, | |||
methods: { | |||
} | |||
} | |||
</script> | |||
<style scoped> | |||
.button-box-fixed { | |||
position: fixed; | |||
top: 152px; | |||
z-index: 3; | |||
width: calc(100% - 120px); | |||
box-shadow: 0 6px 12px 0 rgb(0 0 0 / 5%); | |||
} | |||
</style> |
@@ -0,0 +1,107 @@ | |||
<template> | |||
<div class="business-box" :class="{'left': position == 'left', 'right': position == 'right',}" ref="business" @click.stop="stopPop"> | |||
<div class="business-info"> | |||
<div class="business-label">角色</div> | |||
<div class="business-value">{{getCustomerType(type)}}</div> | |||
<div class="business-label">姓名</div> | |||
<div class="business-value">{{fullName|empty}} <a v-if="type != 1 && showBtn" href="javascript:;" @click.stop="changeBusiness">更换</a></div> | |||
<div class="business-label">工号</div> | |||
<div class="business-value">{{employeeNo|empty}}</div> | |||
<div class="business-label">联系方式</div> | |||
<div class="business-value">{{info.phoneNumber|empty}}</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
export default { | |||
props: { | |||
info: { | |||
type: Object, | |||
default: function() { | |||
return {} | |||
} | |||
}, | |||
type: { | |||
type: Number, | |||
default: 2 | |||
}, | |||
position: { | |||
type: String, | |||
default: 'left' | |||
}, | |||
showBtn: { | |||
type: Boolean, | |||
default: true | |||
} | |||
}, | |||
computed: { | |||
fullName() { | |||
const surName = this.info.surname || ''; | |||
const name = this.info.name || ''; | |||
return `${surName}${name}`; | |||
}, | |||
employeeNo() { | |||
return this.info.extraProperties?.EmployeeNo || ''; | |||
}, | |||
}, | |||
methods: { | |||
stopPop() { | |||
// 阻止冒泡 | |||
}, | |||
changeBusiness() { | |||
this.$emit('change'); | |||
}, | |||
getCustomerType(){ | |||
if(this.type==1) | |||
return '商务人员' | |||
if(this.type==2) | |||
return '项目经理' | |||
if(this.type==3) | |||
return '投单人' | |||
if(this.type==4) | |||
return '审核人' | |||
}, | |||
} | |||
} | |||
</script> | |||
<style lang="less" scoped> | |||
.business-box { | |||
position: absolute; | |||
top: 0; | |||
display: none; | |||
&.left { | |||
padding-right: 7px; | |||
right: 40px; | |||
} | |||
&.right { | |||
padding-left: 7px; | |||
left: 40px; | |||
} | |||
} | |||
.business-info { | |||
width: 140px; | |||
height: 266px; | |||
padding: 20px; | |||
box-sizing: border-box; | |||
background: #fff; | |||
box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.16); | |||
border-radius: 4px; | |||
.business-label { | |||
font-size: 14px; | |||
line-height: 20px; | |||
color: #999; | |||
margin-bottom: 6px; | |||
} | |||
.business-value { | |||
font-size: 14px; | |||
line-height: 20px; | |||
color: #333; | |||
margin-bottom: 15px; | |||
&:last-child { | |||
margin-bottom: 0; | |||
} | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,70 @@ | |||
<template> | |||
<a-card> | |||
<div class="customer-header"> | |||
<div class="customer-header-left"> | |||
<img | |||
:src="basicInfo.logo" | |||
v-if="basicInfo.logo" | |||
alt="" | |||
width="64" | |||
height="64" | |||
/> | |||
<SvgIcon iconClass="icon-liebiaomorenwu" v-else /> | |||
</div> | |||
<div class="customer-header-right"> | |||
<div class="company-name">{{ basicInfo.name }}</div> | |||
<div class="company-info"> | |||
<span>客户编号:{{ basicInfo.code }}</span> | |||
<span | |||
>创建时间:{{ | |||
basicInfo.creationTime | moment("YYYY年MM月DD日") | |||
}}</span | |||
> | |||
</div> | |||
</div> | |||
</div> | |||
</a-card> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'CustomerIntroduce', | |||
props: { | |||
basicInfo: {} | |||
} | |||
} | |||
</script> | |||
<style lang="less" scoped> | |||
.customer-header { | |||
display: flex; | |||
.customer-header-left { | |||
margin-right: 20px; | |||
font-size: 64px; | |||
line-height: 64px; | |||
height: 64px; | |||
display: flex; | |||
align-items: center; | |||
border-radius: 4px; | |||
overflow: hidden; | |||
img { | |||
object-fit: cover; | |||
} | |||
} | |||
.company-name { | |||
font-size: 20px; | |||
line-height: 26px; | |||
color: #333; | |||
font-weight: 500; | |||
margin-bottom: 10px; | |||
} | |||
.company-info { | |||
font-size: 14px; | |||
line-height: 20px; | |||
color: #333; | |||
} | |||
.company-info span { | |||
margin-right: 25px; | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,173 @@ | |||
<template> | |||
<div> | |||
<p class="mb-10 titleCls">{{ title }}</p> | |||
<a-card class="fileCls"> | |||
<div class="file"> | |||
<div | |||
class="fileItem" | |||
v-for="(file, index) in structureOptions" | |||
:key="index" | |||
@click="showList(file)" | |||
> | |||
<div> | |||
<SvgIcon | |||
v-show="file.fileCount > 0" | |||
iconClass="icon-wenjian" | |||
></SvgIcon> | |||
<SvgIcon | |||
v-show="file.fileCount == 0" | |||
iconClass="icon-zanwuwenjian" | |||
></SvgIcon> | |||
</div> | |||
<p>{{ file.name || file.displayText }}</p> | |||
</div> | |||
</div> | |||
</a-card> | |||
<FileListDrawer | |||
ref="fileListDrawer" | |||
:title="fileListTitle" | |||
:fileArr="fileArr" | |||
:uploadFlag="uploadFlag" | |||
@ok="refresh" | |||
/> | |||
</div> | |||
</template> | |||
<script> | |||
import { getFileStructure } from '@/services/fileManagement/product' | |||
import FileListDrawer from '@/components/drawer/FileListDrawer' | |||
export default { | |||
name: 'FileList', | |||
components: { | |||
FileListDrawer, | |||
}, | |||
props: { | |||
title: { | |||
type: String, | |||
default: '文件清单', | |||
}, | |||
code: { | |||
type: String, | |||
}, | |||
dictCode: { | |||
type: String, | |||
}, | |||
dictionaryCode: { | |||
type: String, | |||
}, | |||
}, | |||
data() { | |||
return { | |||
fileListTitle: '', | |||
fileTypeOptions: [], | |||
structureOptions: [], | |||
fileArr: [], | |||
fileCenters: [], | |||
fileCode: null, | |||
fileDictCode: null, | |||
uploadFlag: true, | |||
} | |||
}, | |||
mounted() { | |||
this.getStructure() | |||
}, | |||
methods: { | |||
//报价详情中异步获取文件列表 | |||
showModal(code, dictCode, flag) { | |||
console.log(flag) | |||
this.uploadFlag = flag || false | |||
this.fileCode = code | |||
this.fileDictCode = dictCode | |||
this.getStructure() | |||
}, | |||
//刷新列表 | |||
refresh() { | |||
this.getStructure() | |||
}, | |||
//展示文件列表 | |||
showList(row) { | |||
if (row.fileCount == 0) { | |||
return | |||
} | |||
this.fileListTitle = row.name | |||
this.$refs.fileListDrawer.showDrawer(this.code || this.fileCode, row.type) | |||
}, | |||
// 获取文件夹结构 | |||
getStructure() { | |||
if ( | |||
(this.code && this.dictCode) || | |||
(this.fileCode && this.fileDictCode) | |||
) { | |||
getFileStructure({ | |||
dictCode: this.dictCode || this.fileDictCode, | |||
code: this.code || this.fileCode, | |||
}).then((res) => { | |||
this.structureOptions = res | |||
}) | |||
} | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style lang="less" scoped> | |||
.titleCls { | |||
font-size: 16px; | |||
color: #333333; | |||
} | |||
.fileCls { | |||
padding: 12px; | |||
box-sizing: border-box; | |||
p { | |||
font-size: 16px; | |||
line-height: 20px; | |||
color: #333333; | |||
} | |||
.file { | |||
display: flex; | |||
align-items: center; | |||
flex-wrap: wrap; | |||
} | |||
.fileItem { | |||
display: flex; | |||
flex-direction: column; | |||
align-items: center; | |||
justify-content: center; | |||
// margin-right: 50px; | |||
// width: 10%; | |||
flex-shrink: 0; | |||
// margin-bottom: 20px; | |||
width: 140px; | |||
padding: 10px; | |||
box-sizing: border-box; | |||
cursor: pointer; | |||
p { | |||
font-size: 14px; | |||
color: #333333; | |||
margin: 12px 0 0; | |||
padding: 0; | |||
} | |||
div { | |||
width: 55px; | |||
height: 50px; | |||
overflow: hidden; | |||
.icon { | |||
width: 50px; | |||
height: 50px; | |||
} | |||
img { | |||
width: 100%; | |||
height: 100%; | |||
object-fit: contain; | |||
} | |||
} | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,216 @@ | |||
<template> | |||
<div v-show="structureOptions.length"> | |||
<p class="mb-10 titleCls">{{ title }}</p> | |||
<a-card class="fileCls"> | |||
<div class="file"> | |||
<div | |||
class="fileItem" | |||
v-for="(file, index) in structureOptions" | |||
:key="index" | |||
@click="showList(file)" | |||
> | |||
<div> | |||
<SvgIcon | |||
v-show="file.fileCount > 0" | |||
iconClass="icon-wenjian" | |||
></SvgIcon> | |||
<SvgIcon | |||
v-show="file.fileCount == 0" | |||
iconClass="icon-zanwuwenjian" | |||
></SvgIcon> | |||
</div> | |||
<p>{{ file.name || file.displayText }}</p> | |||
</div> | |||
</div> | |||
</a-card> | |||
<FileListDrawer | |||
ref="fileListDrawer" | |||
:title="fileListTitle" | |||
:fileArr="fileArr" | |||
:uploadFlag="uploadFlag" | |||
@ok="refresh" | |||
/> | |||
</div> | |||
</template> | |||
<script> | |||
import { getFileStructure } from '@/services/fileManagement/product' | |||
import FileListDrawer from '@/components/drawer/FileListDrawer' | |||
export default { | |||
name: 'FileList', | |||
components: { | |||
FileListDrawer, | |||
}, | |||
props: { | |||
title: { | |||
type: String, | |||
default: '文件清单', | |||
}, | |||
code: { | |||
type: String, | |||
}, | |||
dictCode: { | |||
type: String, | |||
}, | |||
dictionaryCode: { | |||
type: String, | |||
}, | |||
}, | |||
data() { | |||
return { | |||
fileListTitle: '', | |||
fileTypeOptions: [], | |||
structureOptions: [], | |||
fileArr: [], | |||
fileCenters: [], | |||
fileCode: null, | |||
fileDictCode: null, | |||
uploadFlag: true, | |||
type:null, | |||
priceFileArr:[], | |||
} | |||
}, | |||
mounted() { | |||
this.getStructure() | |||
}, | |||
methods: { | |||
//报价详情中异步获取文件列表 | |||
showModal(code, dictCode,arr) { | |||
this.uploadFlag = false | |||
this.fileCode = code | |||
this.fileCenters = arr | |||
this.fileDictCode = dictCode | |||
this.getStructure() | |||
}, | |||
//刷新列表 | |||
refresh() { | |||
this.getStructure() | |||
}, | |||
//对象数组去重 | |||
removeDuplicateObj(arr){ | |||
let obj = {}; | |||
arr = arr.reduce((newArr, next) => { | |||
obj[next.type] ? '' : (obj[next.type] = true && newArr.push(next)); | |||
return newArr; | |||
}, []); | |||
return arr; | |||
}, | |||
filterData(){ | |||
let arr = this.fileCenters.map(item=>{ | |||
return { | |||
type:item.type, | |||
typeValue:item.typeValue | |||
} | |||
}) | |||
arr = this.removeDuplicateObj(arr); | |||
this.priceFileArr = []; | |||
arr.forEach(item=>{ | |||
let list = this.fileCenters.filter(data=>data.type==item.type); | |||
this.priceFileArr.push({ | |||
type:item.type, | |||
typeValue:item.typeValue, | |||
children:list | |||
}) | |||
}) | |||
let typeArr = this.structureOptions.map(item=>item.type); | |||
this.priceFileArr.forEach(item=>{ | |||
let index = typeArr.indexOf(item.type); | |||
if(index>-1){ | |||
this.structureOptions[index]['fileCount'] = item.children.length; | |||
this.$forceUpdate(); | |||
} | |||
}) | |||
}, | |||
//展示文件列表 | |||
showList(row) { | |||
if (row.fileCount == 0) { | |||
return | |||
} | |||
this.fileListTitle = row.name | |||
let arr = this.fileCenters.filter(item=>item.type==row.type); | |||
this.$refs.fileListDrawer.showDrawer(null,null,arr) | |||
}, | |||
// 获取文件夹结构 | |||
getStructure() { | |||
if ( | |||
(this.code && this.dictCode) || | |||
(this.fileCode && this.fileDictCode) | |||
) { | |||
getFileStructure({ | |||
dictCode: this.dictCode || this.fileDictCode, | |||
code: this.code || this.fileCode, | |||
}).then((res) => { | |||
let data = [...res] | |||
this.structureOptions = data.map(item=>{ | |||
item.fileCount = 0 | |||
return item | |||
}) | |||
this.filterData(); | |||
}) | |||
} | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style lang="less" scoped> | |||
.titleCls { | |||
font-size: 16px; | |||
color: #333333; | |||
} | |||
.fileCls { | |||
padding: 12px; | |||
box-sizing: border-box; | |||
p { | |||
font-size: 16px; | |||
line-height: 20px; | |||
color: #333333; | |||
} | |||
.file { | |||
display: flex; | |||
align-items: center; | |||
flex-wrap: wrap; | |||
} | |||
.fileItem { | |||
display: flex; | |||
flex-direction: column; | |||
align-items: center; | |||
justify-content: center; | |||
// margin-right: 50px; | |||
// width: 10%; | |||
flex-shrink: 0; | |||
// margin-bottom: 20px; | |||
width: 140px; | |||
padding: 10px; | |||
box-sizing: border-box; | |||
cursor: pointer; | |||
p { | |||
font-size: 14px; | |||
color: #333333; | |||
margin: 12px 0 0; | |||
padding: 0; | |||
} | |||
div { | |||
width: 55px; | |||
height: 50px; | |||
overflow: hidden; | |||
.icon { | |||
width: 50px; | |||
height: 50px; | |||
} | |||
img { | |||
width: 100%; | |||
height: 100%; | |||
object-fit: contain; | |||
} | |||
} | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,100 @@ | |||
<template> | |||
<div | |||
class="openclose-box" | |||
:class="{ | |||
'openclose-card-open': isOpen && card, | |||
'openclose-card-close': !isOpen && card, | |||
'openclose-box-open': isOpen && !card, | |||
'openclose-box-close': !isOpen && !card | |||
}" | |||
> | |||
<div class="openclose-btn" :class="{'openclose-btn-box': !card}" @click="isOpen = !isOpen"> | |||
<a-space :size="5"> | |||
<SvgIcon class="openclose-icon" iconClass="icon-zhankai" v-if="!isOpen" /> | |||
<SvgIcon class="openclose-icon" iconClass="icon-shouqi" v-else /> | |||
<span>{{ isOpen ? '收起' : '展开' }}</span> | |||
</a-space> | |||
</div> | |||
<a-card v-if="card"> | |||
<slot></slot> | |||
</a-card> | |||
<slot v-else></slot> | |||
</div> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'OpenCloseBox', | |||
props: { | |||
open: { | |||
type: Boolean, | |||
default: false | |||
}, | |||
card: { | |||
type: Boolean, | |||
default: true | |||
} | |||
}, | |||
data() { | |||
return { | |||
isOpen: this.open | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="less" scoped> | |||
.openclose-box { | |||
position: relative; | |||
/deep/ .ant-card-body { | |||
padding: 20px 18px; | |||
} | |||
.openclose-btn { | |||
font-size: 14px; | |||
line-height: 16px; | |||
color: #333; | |||
width: 100%; | |||
height: 56px; | |||
position: absolute; | |||
top: 0; | |||
right: 0; | |||
padding-right: 18px; | |||
display: flex; | |||
justify-content: flex-end; | |||
align-items: center; | |||
z-index: 1; | |||
user-select: none; | |||
cursor: pointer; | |||
.openclose-icon { | |||
color: #999; | |||
} | |||
&:hover { | |||
color: #f90; | |||
.openclose-icon { | |||
color: #f90; | |||
} | |||
} | |||
} | |||
.openclose-btn-box { | |||
height: 48px; | |||
} | |||
} | |||
.openclose-card-open { | |||
/deep/ .ant-card-body { | |||
height: auto; | |||
} | |||
} | |||
.openclose-card-close { | |||
/deep/ .ant-card-body { | |||
height: 56px; | |||
overflow: hidden; | |||
} | |||
} | |||
.openclose-box-open { | |||
height: auto; | |||
} | |||
.openclose-box-close { | |||
height: 48px; | |||
overflow: hidden; | |||
} | |||
</style> |
@@ -0,0 +1,65 @@ | |||
<template> | |||
<a-tooltip | |||
placement="topLeft" | |||
:title="content" | |||
:get-popup-container="getPopupContainer" | |||
> | |||
{{ text }} | |||
</a-tooltip> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'StringSubstr', | |||
props: { | |||
content: { | |||
type: String, | |||
default: '', | |||
}, | |||
length: { | |||
type: Number, | |||
default: 30, | |||
}, | |||
}, | |||
data() { | |||
return { | |||
text: '', | |||
} | |||
}, | |||
watch: { | |||
content() { | |||
this.getText() | |||
}, | |||
}, | |||
mounted() { | |||
this.getText() | |||
}, | |||
methods: { | |||
getText() { | |||
if ( | |||
typeof this.content == 'string' && | |||
this.content.length > this.length | |||
) { | |||
this.text = `${this.content.substring(0, this.length)}...` | |||
} else { | |||
this.text = this.content | |||
} | |||
}, | |||
// 气泡框不可见时自动调整位置 | |||
getPopupContainer(trigger) { | |||
return trigger.parentElement | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style> | |||
.ant-tooltip-arrow::before { | |||
display: none; | |||
} | |||
.ant-tooltip-inner { | |||
background-color: #fff !important; | |||
color: #333 !important; | |||
border-color: #dddddd !important; | |||
} | |||
</style> |
@@ -0,0 +1,175 @@ | |||
<template> | |||
<a-modal | |||
:class="[modalClass, simpleClass]" | |||
:visible="visible" | |||
v-bind="$props" | |||
:footer="footer" | |||
:bodyStyle="{padding:0}" | |||
@ok="handleOk" | |||
@cancel="handleCancel"> | |||
<div class="ant-modal-body" :style="bodyStyle"> | |||
<slot></slot> | |||
</div> | |||
<div class="ant-modal-footer relative" v-if="footer === null"> | |||
<slot name="footer"></slot> | |||
</div> | |||
<div v-if="!title && title !== ''" slot="title"> | |||
<slot name="title"></slot> | |||
</div> | |||
</a-modal> | |||
</template> | |||
<script> | |||
import props from './props.js' | |||
var mouseDownX = 0 | |||
var mouseDownY = 0 | |||
var deltaX = 0 | |||
var deltaY = 0 | |||
var sumX = 0 | |||
var sumY = 0 | |||
var header = null | |||
var contain = null | |||
var modalContent = null | |||
var onmousedown = false | |||
export default { | |||
name: 'DragModal', | |||
mixins: [props], | |||
props: { | |||
// 容器的类名 | |||
modalClass: { | |||
type: String, | |||
default: () => { | |||
return 'modal-box' | |||
} | |||
}, | |||
visible: { | |||
type: Boolean, | |||
default: () => { | |||
return false | |||
} | |||
}, | |||
title: { | |||
type: String, | |||
default: () => { | |||
return undefined | |||
} | |||
}, | |||
width: { | |||
type: [String, Number], | |||
default: () => { | |||
return '50%' | |||
} | |||
}, | |||
footer: { | |||
type: Boolean, | |||
default: () => { | |||
return undefined | |||
} | |||
} | |||
}, | |||
data () { | |||
return { | |||
} | |||
}, | |||
computed: { | |||
simpleClass () { | |||
return Math.random().toString(36).substring(2) | |||
} | |||
}, | |||
watch: { | |||
visible () { | |||
this.$nextTick(() => { | |||
this.initialEvent(this.visible) | |||
}) | |||
} | |||
}, | |||
mounted () { | |||
this.$nextTick(() => { | |||
this.initialEvent(this.visible) | |||
}) | |||
}, | |||
created () {}, | |||
beforeDestroy () { | |||
this.removeMove() | |||
window.removeEventListener('mouseup', this.removeUp, false) | |||
}, | |||
methods: { | |||
handleOk (e) { | |||
this.resetNum() | |||
this.$emit('ok', e) | |||
}, | |||
handleCancel (e) { | |||
this.resetNum() | |||
this.$emit('cancel', e) | |||
}, | |||
resetNum () { | |||
mouseDownX = 0 | |||
mouseDownY = 0 | |||
deltaX = 0 | |||
deltaY = 0 | |||
sumX = 0 | |||
sumY = 0 | |||
}, | |||
handleMove (event) { | |||
const delta1X = event.pageX - mouseDownX | |||
const delta1Y = event.pageY - mouseDownY | |||
deltaX = delta1X | |||
deltaY = delta1Y | |||
// console.log('delta1X:' + delta1X, 'sumX:' + sumX, 'delta1Y:' + delta1Y, 'sumY:' + sumY) | |||
modalContent.style.transform = `translate(${delta1X + sumX}px, ${delta1Y + sumY}px)` | |||
}, | |||
initialEvent (visible) { | |||
// console.log('--------- 初始化') | |||
// console.log('simpleClass===>', this.simpleClass) | |||
// console.log('document===>', document) | |||
if (visible) { | |||
setTimeout(() => { | |||
window.removeEventListener('mouseup', this.removeUp, false) | |||
contain = document.getElementsByClassName(this.simpleClass)[0] | |||
header = contain.getElementsByClassName('ant-modal-header')[0] | |||
modalContent = contain.getElementsByClassName('ant-modal-content')[0] | |||
modalContent.style.left = 0 | |||
modalContent.style.transform = 'translate(0px,0px)' | |||
// console.log('初始化-header:', header) | |||
// console.log('初始化-contain:', contain) | |||
// console.log('初始化-modalContent:', modalContent) | |||
header.style.cursor = 'all-scroll' | |||
// contain.onmousedown = (e) => { | |||
header.onmousedown = (e) => { | |||
onmousedown = true | |||
mouseDownX = e.pageX | |||
mouseDownY = e.pageY | |||
document.body.onselectstart = () => false | |||
window.addEventListener('mousemove', this.handleMove, false) | |||
} | |||
window.addEventListener('mouseup', this.removeUp, false) | |||
}, 0) | |||
} | |||
}, | |||
removeMove () { | |||
window.removeEventListener('mousemove', this.handleMove, false) | |||
}, | |||
removeUp (e) { | |||
// console.log('removeUp') | |||
document.body.onselectstart = () => true | |||
if (onmousedown && !(e.pageX === mouseDownX && e.pageY === mouseDownY)) { | |||
onmousedown = false | |||
sumX = sumX + deltaX | |||
sumY = sumY + deltaY | |||
// console.log('sumX:' + sumX, 'sumY:' + sumY) | |||
} | |||
this.removeMove() | |||
} | |||
} | |||
} | |||
</script> |
@@ -0,0 +1,30 @@ | |||
export default { | |||
props: [ | |||
'afterClose', // Modal 完全关闭后的回调 function 无 | |||
'bodyStyle', // Modal body 样式 object {} | |||
'cancelText', // 取消按钮文字 string| slot 取消 | |||
'centered', // 垂直居中展示 Modal Boolean false | |||
'closable', // 是否显示右上角的关闭按钮 boolean true | |||
'closeIcon', // 自定义关闭图标 VNode | slot - 1.5.0 | |||
'confirmLoading', // 确定按钮 loading boolean 无 | |||
'destroyOnClose', // 关闭时销毁 Modal 里的子元素 boolean false | |||
// 'footer', // 底部内容,当不需要默认底部按钮时,可以设为 :footer="null" string|slot 确定取消按钮 | |||
'forceRender', // 强制渲染 Modal boolean false | |||
'getContainer', // 指定 Modal 挂载的 HTML 节点 (instance): HTMLElement () => document.body | |||
'keyboard', // 是否支持键盘 esc 关闭 boolean true | |||
'mask', // 是否展示遮罩 Boolean true | |||
'maskClosable', // 点击蒙层是否允许关闭 boolean true | |||
'maskStyle', // 遮罩样式 object {} | |||
'okText', // 确认按钮文字 string|slot 确定 | |||
'okType', // 确认按钮类型 string primary | |||
'okButtonProps', // ok 按钮 props, 遵循 jsx规范 {props: ButtonProps, on: {}} - | |||
'cancelButtonProps', // cancel 按钮 props, 遵循 jsx规范 {props: ButtonProps, on: {}} - | |||
'title', // 标题 string|slot 无 | |||
'visible', // (v-model) 对话框是否可见 boolean 无 | |||
'width', // 宽度 string|number 520 | |||
'wrapClassName', // 对话框外层容器的类名 string - | |||
'zIndex', // 设置 Modal 的 z-index Number 1000 | |||
'dialogStyle', // 可用于设置浮层的样式,调整浮层位置等 object - 1.6.1 | |||
'dialogClass' // 可用于设置浮层的类名 string | |||
] | |||
} |
@@ -0,0 +1,190 @@ | |||
<template> | |||
<a-drawer | |||
:placement="placement" | |||
:closable="closable" | |||
:destroyOnClose="destroyOnClose" | |||
:getContainer="getContainer" | |||
:maskClosable="maskClosable" | |||
:mask="mask" | |||
:maskStyle="{ ...defaultMaskStyle, ...maskStyle }" | |||
:wrapClassName="wrapClassName" | |||
:wrapStyle="wrapStyle" | |||
:drawerStyle="drawerStyle" | |||
:headerStyle="{ ...defaultHeaderStyle, ...headerStyle }" | |||
:bodyStyle="{ ...defaultBodyStyle, ...bodyStyle }" | |||
:visible="visible" | |||
:after-visible-change="afterVisibleChange" | |||
:width="width" | |||
:height="height" | |||
:zIndex="zIndex" | |||
:keyboard="keyboard" | |||
@close="hideDrawer" | |||
> | |||
<div slot="title" class="title-wrap"> | |||
<span v-if="title!='自定义'">{{ title }}</span> | |||
<slot v-else name="title"></slot> | |||
<div class="drawer-close" @click="hideDrawer"> | |||
<SvgIcon iconClass="icon-guanbi" /> | |||
</div> | |||
</div> | |||
<slot></slot> | |||
</a-drawer> | |||
</template> | |||
<script> | |||
export default { | |||
name: 'CustomDrawer', | |||
props: { | |||
// 是否显示右上角的关闭按钮 | |||
closable: { | |||
type: Boolean, | |||
default: false, | |||
}, | |||
// 关闭时销毁 Drawer 里的子元素 | |||
destroyOnClose: { | |||
type: Boolean, | |||
default: true, | |||
}, | |||
// 指定 Drawer 挂载的 HTML 节点 | |||
getContainer: { | |||
type: [HTMLElement, () => HTMLElement, String], | |||
default: 'body', | |||
}, | |||
// 点击蒙层是否允许关闭 | |||
maskClosable: { | |||
type: Boolean, | |||
default: true, | |||
}, | |||
// 是否展示遮罩 | |||
mask: { | |||
type: Boolean, | |||
default: true, | |||
}, | |||
// 遮罩样式 | |||
maskStyle: { | |||
type: Object, | |||
default: function () { | |||
return {}; | |||
}, | |||
}, | |||
// 标题 | |||
title: { | |||
type: String, | |||
default: '标题', | |||
}, | |||
// 对话框外层容器的类名 | |||
wrapClassName: { | |||
type: String, | |||
default: '', | |||
}, | |||
// 可用于设置 Drawer 最外层容器的样式,和 drawerStyle 的区别是作用节点包括 mask | |||
wrapStyle: { | |||
type: Object, | |||
default: function () { | |||
return {}; | |||
}, | |||
}, | |||
// 用于设置 Drawer 弹出层的样式 | |||
drawerStyle: { | |||
type: Object, | |||
default: function () { | |||
return {}; | |||
}, | |||
}, | |||
// 用于设置 Drawer 头部的样式 | |||
headerStyle: { | |||
type: Object, | |||
default: function () { | |||
return {}; | |||
}, | |||
}, | |||
// 可用于设置 Drawer 内容部分的样式 | |||
bodyStyle: { | |||
type: Object, | |||
default: function () { | |||
return {}; | |||
}, | |||
}, | |||
// 宽度 | |||
width: { | |||
type: [Number, String], | |||
default: 600, | |||
}, | |||
// 高度, 在 placement 为 top 或 bottom 时使用 | |||
height: { | |||
type: [Number, String], | |||
default: 256, | |||
}, | |||
// 设置 Drawer 的 z-index | |||
zIndex: { | |||
type: Number, | |||
default: 1000, | |||
}, | |||
// 抽屉的方向 'top' | 'right' | 'bottom' | 'left' | |||
placement: { | |||
type: String, | |||
default: 'right', | |||
}, | |||
// 是否支持键盘 esc 关闭 | |||
keyboard: { | |||
type: Boolean, | |||
default: true, | |||
}, | |||
}, | |||
data() { | |||
return { | |||
visible: false, | |||
defaultMaskStyle: { | |||
background: 'rgba(0,0,0,0.2)', | |||
}, | |||
defaultHeaderStyle: { | |||
fontSize: '16px', | |||
fontWeight: '500', | |||
color: '#333333', | |||
padding: '40px 30px 25px', | |||
borderBottom: 'none', | |||
}, | |||
defaultBodyStyle: { | |||
padding: '0 30px 40px', | |||
}, | |||
}; | |||
}, | |||
methods: { | |||
// 切换抽屉时动画结束后的回调 | |||
afterVisibleChange(val) {}, | |||
showDrawer() { | |||
this.visible = true; | |||
}, | |||
// 点击遮罩层或右上角叉或取消按钮的回调 | |||
hideDrawer() { | |||
this.visible = false; | |||
}, | |||
}, | |||
}; | |||
</script> | |||
<style lang="less" scoped> | |||
.title-wrap { | |||
display: flex; | |||
justify-content: space-between; | |||
align-items: center; | |||
height: 28px; | |||
} | |||
.drawer-close { | |||
width: 28px; | |||
height: 28px; | |||
color: #666; | |||
border-radius: 4px; | |||
cursor: pointer; | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
position: absolute; | |||
top: 40px; | |||
right: 30px; | |||
z-index: 99; | |||
&:hover { | |||
background: #E8E9E9; | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,159 @@ | |||
<template> | |||
<CustomDrawer ref="assembleDrawer" :title="title"> | |||
<template v-if="code || type"> | |||
<div class="drawerCls" v-for="(file, index) in fileList" :key="index"> | |||
<div class="imgCls"> | |||
<SvgIcon iconClass="icon-wenjianyangshi"></SvgIcon> | |||
</div> | |||
<div class="conCls"> | |||
<div> | |||
<UploadFile | |||
v-if="uploadFlag" | |||
:id="'fileId' + index" | |||
@ok="getFile($event, file)" | |||
:iconFlag="true" | |||
style="display: inline-block; margin-right: 10px" | |||
/> | |||
<a | |||
class="nameCls" | |||
href="javascript:;" | |||
@click="downLoad(file.url)" | |||
>{{ file.name }}</a | |||
> | |||
</div> | |||
<div class="conText"><span>创建人</span>{{ file.creator }}</div> | |||
<div class="conText"> | |||
<span>创建时间</span>{{ file.creationTime | moment }} | |||
</div> | |||
</div> | |||
<div class="rightCls"> | |||
<div @click="handleDel(file, index)" style="cursor: pointer" v-if="checkPermission('Integrations.FileCenterManagement.Delete')"> | |||
<SvgIcon iconClass="icon-shanchu" /> | |||
</div> | |||
<div></div> | |||
<!-- <span>更新时间</span>{{file.lastModificationTime|moment}} --> | |||
</div> | |||
</div> | |||
</template> | |||
<template v-else> | |||
<div class="drawerCls" v-for="(file, index) in fileArr" :key="index"> | |||
<div class="imgCls"> | |||
<SvgIcon iconClass="icon-wenjianyangshi"></SvgIcon> | |||
</div> | |||
<div class="conCls"> | |||
<div> | |||
<UploadFile | |||
v-if="uploadFlag" | |||
:id="'fileId' + index" | |||
@ok="getFile($event, file)" | |||
:iconFlag="true" | |||
style="display: inline-block; margin-right: 10px" | |||
/> | |||
<a | |||
class="nameCls" | |||
href="javascript:;" | |||
@click="downLoad(file.url)" | |||
>{{ file.name }}</a | |||
> | |||
</div> | |||
<div class="conText"><span>创建人</span>{{ file.creator }}</div> | |||
<div class="conText"> | |||
<span>创建时间</span>{{ file.creationTime | moment }} | |||
</div> | |||
</div> | |||
<div class="rightCls"> | |||
<div></div> | |||
<div></div> | |||
<!-- <span>更新时间</span>{{file.lastModificationTime|moment}} --> | |||
</div> | |||
</div> | |||
</template> | |||
</CustomDrawer> | |||
</template> | |||
<script> | |||
import { checkPermission } from '@/utils/abp'; | |||
import { | |||
getFileCenter, | |||
putFileCenter, | |||
delFileCenter, | |||
} from '@/services/fileManagement/product' | |||
import UploadFile from '@/components/upload/UploadFileSingle' | |||
export default { | |||
components: { UploadFile }, | |||
props: { | |||
uploadFlag: { | |||
type: Boolean, | |||
default: true, | |||
}, | |||
title: { | |||
type: String, | |||
default: '文件列表', | |||
}, | |||
}, | |||
data() { | |||
return { | |||
fileList: [], | |||
type: null, | |||
code: null, | |||
fileArr:[] | |||
} | |||
}, | |||
methods: { | |||
checkPermission, | |||
getFile(row, file) { | |||
var obj = { | |||
type: this.type, | |||
name: row.name, | |||
url: row.path, | |||
} | |||
//如果有id,则更新 | |||
putFileCenter(file.id, obj).then(() => { | |||
this.$message.success('文件更新成功') | |||
this.getFileList(this.code, this.type) | |||
}) | |||
}, | |||
//下载文件 | |||
downLoad(url) { | |||
url && window.open(url) | |||
}, | |||
showDrawer(code, type,arr) { | |||
this.type = type | |||
this.code = code | |||
if (code || type) { | |||
this.getFileList(code, type) | |||
}else{ | |||
this.fileArr = arr; | |||
} | |||
this.$refs.assembleDrawer.showDrawer() | |||
}, | |||
getFileList(code, type) { | |||
getFileCenter(code, type).then((res) => { | |||
this.fileList = res | |||
}) | |||
}, | |||
hideDrawer() { | |||
this.$refs.assembleDrawer.hideDrawer() | |||
}, | |||
handleDel(file, index) { | |||
this.$confirm({ | |||
title: '确定删除该文件?', | |||
onOk: () => { | |||
delFileCenter(file.id).then(() => { | |||
this.$message.success('删除成功') | |||
this.fileList.splice(index, 1) | |||
this.$emit('ok') | |||
}) | |||
}, | |||
}) | |||
}, | |||
}, | |||
} | |||
</script> | |||
<style lang="less" scoped> | |||
@import '@/pages/customerManagement/product/style/product.less'; | |||
.nameCls { | |||
margin-left: 0 !important; | |||
} | |||
</style> |
@@ -0,0 +1,69 @@ | |||
<template> | |||
<div class="exception-page"> | |||
<div class="img"> | |||
<img :src="config[type].img" /> | |||
</div> | |||
<div class="content"> | |||
<h1>{{config[type].title}}</h1> | |||
<div class="desc">{{config[type].desc}}</div> | |||
<div class="action"> | |||
<a-button type="primary" @click="backHome">返回首页</a-button> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import Config from './typeConfig' | |||
export default { | |||
name: 'ExceptionPage', | |||
props: ['type', 'homeRoute'], | |||
data () { | |||
return { | |||
config: Config | |||
} | |||
}, | |||
methods: { | |||
backHome() { | |||
if (this.homeRoute) { | |||
this.$router.push(this.homeRoute) | |||
} | |||
this.$emit('backHome', this.type) | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="less" scoped> | |||
.exception-page{ | |||
border-radius: 4px; | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
background-color: @base-bg-color; | |||
.img{ | |||
padding-right: 52px; | |||
zoom: 1; | |||
img{ | |||
max-width: 430px; | |||
} | |||
} | |||
.content{ | |||
h1{ | |||
color: #434e59; | |||
font-size: 72px; | |||
font-weight: 600; | |||
line-height: 72px; | |||
margin-bottom: 24px; | |||
} | |||
.desc{ | |||
color: @text-color-second; | |||
font-size: 20px; | |||
line-height: 28px; | |||
margin-bottom: 16px; | |||
} | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,19 @@ | |||
const config = { | |||
403: { | |||
img: 'https://gw.alipayobjects.com/zos/rmsportal/wZcnGqRDyhPOEYFcZDnb.svg', | |||
title: '403', | |||
desc: '抱歉,你无权访问该页面' | |||
}, | |||
404: { | |||
img: 'https://gw.alipayobjects.com/zos/rmsportal/KpnpchXsobRgLElEozzI.svg', | |||
title: '404', | |||
desc: '抱歉,你访问的页面不存在或仍在开发中' | |||
}, | |||
500: { | |||
img: 'https://gw.alipayobjects.com/zos/rmsportal/RVRUAYdCGeYNBWoKiIwB.svg', | |||
title: '500', | |||
desc: '抱歉,服务器出错了' | |||
} | |||
} | |||
export default config |
@@ -0,0 +1,130 @@ | |||
<template> | |||
<a-form | |||
:form="form" | |||
class="form-container" | |||
ref="sortTable" | |||
:style="{ width: banner ? banner + 'px' : '100%', margin: '0 auto' }" | |||
:class="config.tableStyle ? 'table' : ''" | |||
> | |||
<template v-for="(item, index) in data"> | |||
<FormGridRender | |||
v-if="item.type === 'Grid'" | |||
:mode="mode" | |||
:key="index" | |||
:formItem="item" | |||
:form="form" | |||
:config="{ ...config, gateway, userModel }" | |||
:banner="!!banner" | |||
:mock="mock" | |||
/> | |||
<FormItemRender | |||
v-else | |||
:mode="mode" | |||
:key="index" | |||
:formItem="item" | |||
:form="form" | |||
:config="{ ...config, gateway, userModel }" | |||
/> | |||
</template> | |||
</a-form> | |||
</template> | |||
<script> | |||
import FormGridRender from './form-render/FormGridRender'; | |||
import FormItemRender from './form-render/FormItemRender'; | |||
export default { | |||
components: { FormGridRender, FormItemRender }, | |||
props: [ | |||
'uiConf', | |||
'formData', | |||
'gateway', | |||
'userModel', | |||
'mode', | |||
'banner', | |||
'mock', | |||
], | |||
data() { | |||
return { | |||
data: [], | |||
config: {}, | |||
}; | |||
}, | |||
watch: { | |||
uiConf: { | |||
handler(v) { | |||
if (v) { | |||
this.data = v.data; | |||
this.config = v.config; | |||
} else { | |||
this.data = []; | |||
this.config = {}; | |||
} | |||
}, | |||
immediate: true, | |||
deep: true, | |||
}, | |||
}, | |||
mounted() { | |||
this.$emit('renderFinish'); | |||
}, | |||
beforeCreate() { | |||
console.log('beforeCreate'); | |||
this.form = this.$form.createForm(this); | |||
this.$emit('getform', this.form); | |||
}, | |||
}; | |||
</script> | |||
<style lang="less"> | |||
@table-border: 1px solid @border-color-base; | |||
.form-container { | |||
&.table { | |||
border: @table-border; | |||
border-right: 0; | |||
.form-item-render { | |||
border-bottom: @table-border; | |||
border-right: @table-border; | |||
&:last-child { | |||
border-bottom: 0; | |||
} | |||
.ant-form-item { | |||
margin-bottom: 0; | |||
margin-top: 5px; | |||
.ant-form-explain { | |||
font-size: 12px; | |||
min-height: 16px; | |||
} | |||
.ant-form-item-control { | |||
padding-bottom: 16px; | |||
} | |||
.ant-form-explain { | |||
position: absolute; | |||
bottom: 0; | |||
} | |||
} | |||
} | |||
.form-grid-render { | |||
border-bottom: @table-border; | |||
border-right: @table-border; | |||
&:last-child { | |||
border-bottom: 0; | |||
} | |||
.col-item { | |||
align-self: stretch; | |||
min-height: 40px; | |||
border-right: @table-border; | |||
.form-item-render { | |||
border-right: 0; | |||
} | |||
&:last-child { | |||
border-right: 0; | |||
} | |||
} | |||
.form-grid-render { | |||
border-right: 0; | |||
border-bottom: @table-border; | |||
} | |||
} | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,495 @@ | |||
<template> | |||
<div class="form-builder-container"> | |||
<div class="builder-container header"> | |||
<div class="list header-item"> | |||
<span>表单控件仓库</span> | |||
<span class="tips">拖拽控件到中间</span> | |||
</div> | |||
<div class="builder header-item"> | |||
<span> | |||
<a-button type="link">普通布局</a-button> | |||
<!-- <a-button type="link" disabled>移动端布局</a-button> --> | |||
</span> | |||
<span> | |||
<a-input placeholder="请输入属性名称" v-model="propName" /> | |||
</span> | |||
<span> | |||
<a-button type="link" @click="onFormSave">保存</a-button> | |||
<a-button type="link" @click="onFormConfig">表单配置数据</a-button> | |||
<a-button type="link" @click="onGlobalSettingClick">全局设置</a-button> | |||
<a-button type="link" @click="onPreviewClick">预览</a-button> | |||
</span> | |||
</div> | |||
<div class="config header-item"> | |||
<span>控件设置</span> | |||
<span>{{activeItem ? activeItem.name : '未选中表单控件'}}</span> | |||
</div> | |||
</div> | |||
<div class="builder-container main"> | |||
<div class="list" ref="listArea"> | |||
<FormList @drag="onDrag" /> | |||
</div> | |||
<div class="builder" ref="builderArea" :class="[draging ? 'draging' : '']"> | |||
<a-form :form="form" class="form-container" ref="sortTable"> | |||
<template v-for="(item,index) in data"> | |||
<FormGridEditor | |||
v-if="item.type === 'Grid'" | |||
:key="renderKey+'_'+index" | |||
:index="index" | |||
:formItem="item" | |||
:form="form" | |||
:config="{...config,gateway,userModel}" | |||
:activeKey="activeKey" | |||
@active="onActive" | |||
@copy="onGridCopy" | |||
@delete="onDelete" | |||
/> | |||
<FormItemEditor | |||
v-else | |||
:activeKey="activeKey" | |||
:key="renderKey+'_'+index" | |||
:index="index" | |||
:formItem="item" | |||
:form="form" | |||
:config="{...config,gateway,userModel}" | |||
@active="onActive" | |||
@copy="onItemCopy" | |||
@delete="onDelete" | |||
/> | |||
</template> | |||
</a-form> | |||
</div> | |||
<div class="config" ref="configArea"> | |||
<FormSetting :activeItem="activeItem" /> | |||
</div> | |||
</div> | |||
<previewModal | |||
v-if="previewModalVisible" | |||
:visible="previewModalVisible" | |||
:uiConf="{config: config,data:data}" | |||
:gateway="gateway" | |||
:userModel="userModel" | |||
@close="previewModalVisible = false" | |||
/> | |||
<GlobalSettingModal | |||
v-if="globalSettingVisible" | |||
v-model="config" | |||
:visible="globalSettingVisible" | |||
@close="globalSettingVisible = false" | |||
/> | |||
<FormConfigData | |||
v-if="formConfigVisible" | |||
:visible="formConfigVisible" | |||
:uiConf="{config: config,data:data}" | |||
:dbConf="dbConf" | |||
@close="formConfigVisible = false" | |||
/> | |||
</div> | |||
</template> | |||
<script> | |||
import Sortable from 'sortablejs'; | |||
import Scrollbar from 'smooth-scrollbar'; | |||
import FormList from './form-list/FormList'; | |||
import formItemList from './form-item/form-item.config'; | |||
import FormItemEditor from './form-editor/FormItemEditor'; | |||
import FormGridEditor from './form-editor/FormGridEditor'; | |||
import FormSetting from './form-setting/FormSetting'; | |||
import previewModal from './form-builder-modal/PreviewModal'; | |||
import GlobalSettingModal from './form-builder-modal/GlobalSetting'; | |||
import FormConfigData from './form-builder-modal/FormConfigData'; | |||
import { | |||
getFormConfig, | |||
recursiveReplace, | |||
arrayObject2ArrayElement, | |||
getFormList | |||
} from './utils/form-config.util'; | |||
const defaultConfig = { | |||
labelCol: 5, | |||
wraperCol: 12, | |||
labelAlign: 'right', | |||
labelVertical: 'top', | |||
tableStyle: false | |||
}; | |||
export default { | |||
components: { | |||
FormList, | |||
FormItemEditor, | |||
FormSetting, | |||
FormGridEditor, | |||
previewModal, | |||
GlobalSettingModal, | |||
FormConfigData | |||
}, | |||
props: { | |||
value: { | |||
type: Object, | |||
default: () => ({ config: { ...defaultConfig }, list: [] }) | |||
}, | |||
gateway: { | |||
type: String, | |||
default: '', | |||
required: true | |||
}, | |||
userModel: { | |||
type: Object, | |||
default: () => ({}), | |||
required: true | |||
} | |||
}, | |||
watch: { | |||
value: { | |||
handler(val) { | |||
console.log(val, 'builder'); | |||
if (val) { | |||
this.data = val.list || []; | |||
this.config = { ...defaultConfig, ...val.config }; | |||
} else { | |||
this.data = []; | |||
this.config = { ...defaultConfig }; | |||
} | |||
}, | |||
immediate: true | |||
}, | |||
data: { | |||
handler(v) { | |||
this.dbConf = getFormList(v); | |||
this.$emit('change', { config: this.config, data: v }); | |||
}, | |||
deep: true | |||
}, | |||
config: { | |||
handler(v) { | |||
this.$emit('change', { config: v, data: this.data }); | |||
}, | |||
deep: true | |||
} | |||
}, | |||
data() { | |||
return { | |||
formConfigVisible: false, | |||
globalSettingVisible: false, | |||
previewModalVisible: false, | |||
data: [], | |||
config: { ...defaultConfig }, | |||
scroll: null, | |||
draging: false, | |||
activeItem: null, | |||
activeKey: null, | |||
previewForm: null, | |||
renderKey: 0, | |||
dbConf: [], | |||
propName: '' | |||
}; | |||
}, | |||
beforeCreate() { | |||
this.form = this.$form.createForm(this); | |||
}, | |||
mounted() { | |||
Scrollbar.init(this.$refs.listArea); | |||
Scrollbar.init(this.$refs.builderArea); | |||
Scrollbar.init(this.$refs.configArea); | |||
let isAdd = false; | |||
new Sortable(this.$refs.sortTable.$el, { | |||
group: { name: 'component' }, | |||
animation: 200, | |||
onStart: e => {}, | |||
onEnd: e => {}, | |||
onSort: e => { | |||
//添加也会触发onSort, 用个变量去来区分 | |||
if (!isAdd) { | |||
console.log('排序 进入', 'e'); | |||
const tempData = this.data[e.oldIndex]; | |||
const newData = this.data[e.newIndex]; | |||
console.log(tempData, newData, e.newIndex, e.oldIndex); | |||
if (!tempData.key || !newData.key) return; | |||
if (e.newIndex !== e.oldIndex) { | |||
this.data.splice(e.oldIndex, 1); | |||
this.data.splice(e.newIndex, 0, tempData); | |||
} | |||
this.emitChange(); | |||
console.log('排序 进入', this.data); | |||
} | |||
isAdd = false; | |||
}, | |||
onAdd: e => { | |||
console.log('builder onAdd'); | |||
isAdd = true; | |||
let defaultConfig = null; | |||
if (e.item.dataset.source == 'formList') { | |||
//左侧新增的 | |||
defaultConfig = getFormConfig(e.item.dataset.id); | |||
} else { | |||
//不是从左侧进来的 数据 | |||
defaultConfig = e.item.__vue__.formItem; | |||
} | |||
console.log(defaultConfig, 'eeeeeeeee'); | |||
this.data.splice(e.newIndex, 0, defaultConfig); | |||
this.activeKey = defaultConfig.key; | |||
this.activeItem = defaultConfig; | |||
e.item.parentNode.removeChild(e.item); | |||
this.emitChange(); | |||
console.log(this.data, 'ddddddddddddata'); | |||
}, | |||
onRemove: e => { | |||
console.log('builder remove'); | |||
//拖动时候的remove事件,不等于删除,删除时任何地方都没有了,remove的时候有个地方会add | |||
// 做移除的时候,add的地方会先触发,如果不设定时器,add的地方会取不到e.item.__vue__ | |||
isAdd = true | |||
setTimeout(() => { | |||
this.data.splice(e.oldIndex, 1); | |||
console.log(this.data, 'datatatatata'); | |||
this.emitChange(); | |||
}, 100); | |||
} | |||
}); | |||
}, | |||
methods: { | |||
onFormSave() { | |||
this.$emit('getData', {config: this.config, data: this.data}) | |||
}, | |||
onFormConfig() { | |||
this.formConfigVisible = true; | |||
}, | |||
onGlobalSettingClick() { | |||
this.globalSettingVisible = true; | |||
}, | |||
onPreviewClick() { | |||
this.previewModalVisible = true; | |||
}, | |||
emitChange() { | |||
this.renderKey += 1; | |||
this.$emit('change', { | |||
config: this.config, | |||
data: this.data | |||
}); | |||
}, | |||
onDelete(e) { | |||
this.data.splice(e.index, 1); | |||
this.activeItem = {}; | |||
this.activeKey = null; | |||
this.emitChange(); | |||
}, | |||
onItemCopy(e) { | |||
let item = JSON.parse(JSON.stringify(e.data)); | |||
let key = Date.now(); | |||
item['key'] = key; | |||
item['model'] = item.type + '_' + key; | |||
this.data.splice(e.index + 1, 0, item); | |||
this.activeItem = item; | |||
this.activeKey = key; | |||
this.emitChange(); | |||
}, | |||
onGridCopy(e) { | |||
//栅格复制,需要更新内部所有的key | |||
let item = JSON.parse(JSON.stringify(e.data)); | |||
// 替换替换栅格里的所有key | |||
let key = Date.now(); | |||
recursiveReplace(item, key, 0); | |||
this.data.splice(e.index + 1, 0, item); | |||
this.activeItem = item; | |||
this.activeKey = key; | |||
this.emitChange(); | |||
}, | |||
onDrag(bool) { | |||
this.draging = bool; | |||
}, | |||
onActive(e) { | |||
if (e) { | |||
this.activeItem = e; | |||
this.activeKey = e.key; | |||
} else { | |||
this.activeItem = {}; | |||
this.activeKey = null; | |||
} | |||
} | |||
} | |||
}; | |||
</script> | |||
<style lang="less" scoped> | |||
@border: 1px solid @border-color-base; | |||
/deep/ .preview-modal { | |||
height: 100%; | |||
.ant-modal { | |||
height: 100%; | |||
top: 0; | |||
padding-bottom: 0; | |||
.ant-modal-content { | |||
height: 100%; | |||
overflow: hidden; | |||
.ant-modal-body { | |||
padding: 0; | |||
height: calc(100% - 55px); | |||
.top-toolbox { | |||
padding: @padding-md; | |||
height: 50px; | |||
border-bottom: 1px solid @border-color-base; | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
background-color: #f1f1f1; | |||
.item { | |||
padding: 0 @padding-md * 2; | |||
/deep/ button { | |||
margin-right: @padding-md; | |||
} | |||
} | |||
} | |||
.preview-content { | |||
height: calc(100% - 66px); | |||
padding: @padding-md / 2; | |||
overflow: auto; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
.form-builder-container { | |||
height: 100%; | |||
.builder-container { | |||
display: flex; | |||
&.header { | |||
height: 45px; | |||
border-bottom: @border; | |||
.header-item { | |||
align-items: center; | |||
display: flex; | |||
justify-content: space-between; | |||
&:not(.builder) { | |||
padding: 0 @padding-md; | |||
} | |||
span:nth-child(1) { | |||
font-weight: bold; | |||
} | |||
} | |||
} | |||
.list, | |||
.config { | |||
flex: 0 0 290px; | |||
.tips { | |||
font-size: 12px; | |||
color: #d1d1d1; | |||
} | |||
} | |||
.builder { | |||
flex: 1; | |||
border-left: @border; | |||
border-right: @border; | |||
} | |||
&.main { | |||
height: calc(100% - 45px); | |||
.builder { | |||
background-image: linear-gradient( | |||
90deg, | |||
rgba(0, 0, 0, 0.02) 10%, | |||
transparent 0 | |||
), | |||
linear-gradient(rgba(0, 0, 0, 0.02) 10%, transparent 0); | |||
background-size: 10px 10px; | |||
/deep/ .scroll-content { | |||
height: 100%; | |||
.form-container { | |||
min-height: 100%; | |||
padding-bottom: 50px; | |||
} | |||
} | |||
&.draging { | |||
background-image: linear-gradient( | |||
90deg, | |||
@primary-1 10%, | |||
transparent 0 | |||
), | |||
linear-gradient(@primary-1 10%, transparent 0); | |||
padding-top: 20px; | |||
} | |||
/deep/ .component-item { | |||
width: 100%; | |||
height: 5px; | |||
background-color: @primary-color; | |||
margin-left: 0; | |||
.component-content { | |||
display: none; | |||
} | |||
} | |||
} | |||
/deep/ .form-item-editor { | |||
position: relative; | |||
padding: 0 5px; | |||
.overlay { | |||
position: absolute; | |||
left: 0; | |||
top: 0; | |||
width: 100%; | |||
height: 100%; | |||
cursor: move; | |||
z-index: 19; | |||
} | |||
.form-item-copy { | |||
position: absolute; | |||
right: 16px; | |||
bottom: -4px; | |||
width: 20px; | |||
height: 20px; | |||
border: 1px solid @primary-color; | |||
border-radius: 50%; | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
background-color: #ffffff; | |||
color: @primary-color; | |||
z-index: 20; | |||
cursor: pointer; | |||
i, | |||
svg { | |||
font-size: 12px; | |||
} | |||
&:hover { | |||
background-color: @primary-color; | |||
color: #ffffff; | |||
} | |||
} | |||
.form-item-delete { | |||
position: absolute; | |||
right: 40px; | |||
bottom: -4px; | |||
width: 20px; | |||
height: 20px; | |||
border: 1px solid @error-color; | |||
border-radius: 50%; | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
background-color: #ffffff; | |||
cursor: pointer; | |||
color: @error-color; | |||
z-index: 20; | |||
i, | |||
svg { | |||
font-size: 12px; | |||
} | |||
&:hover { | |||
background-color: @error-color; | |||
color: #ffffff; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,96 @@ | |||
<template> | |||
<a-modal | |||
:width="width" | |||
title="表单配置" | |||
wrapClassName="preview-modal" | |||
v-model="modalVisible" | |||
:footer="null" | |||
:maskClosable="false" | |||
@cancel="$emit('close',false)" | |||
> | |||
<a-tabs @change="onChange"> | |||
<a-tab-pane key="1" tab="表单界面配置数据"> | |||
<div :style="{height:height+'px'}"> | |||
<textarea ref="uiDom" :value="strUiConf"></textarea> | |||
</div> | |||
</a-tab-pane> | |||
<a-tab-pane key="2" tab="表单字段配置数据"> | |||
<div :style="{height:height+'px'}"> | |||
<textarea ref="dbDom" :value="strDbConf"></textarea> | |||
</div> | |||
</a-tab-pane> | |||
</a-tabs> | |||
</a-modal> | |||
</template> | |||
<script> | |||
import * as CodeMirror from 'codemirror/lib/codemirror'; | |||
import 'codemirror/mode/javascript/javascript.js'; | |||
export default { | |||
props: ['visible', 'uiConf', 'dbConf'], | |||
data() { | |||
return { | |||
width: window.innerWidth, | |||
modalVisible: false, | |||
CodeMirrorEditorUi: null, | |||
height: window.innerHeight - 120, | |||
CodeMirrorEditorDb: null | |||
}; | |||
}, | |||
watch: { | |||
visible: { | |||
handler(v) { | |||
this.modalVisible = v; | |||
}, | |||
immediate: true | |||
} | |||
}, | |||
computed: { | |||
strUiConf() { | |||
return JSON.stringify(this.uiConf,null,'\t'); | |||
}, | |||
strDbConf() { | |||
return JSON.stringify(this.dbConf,null,'\t'); | |||
} | |||
}, | |||
mounted() { | |||
this.$nextTick(() => { | |||
this.CodeMirrorEditorUi = CodeMirror.fromTextArea(this.$refs.uiDom, { | |||
lineNumbers: true, //是否显示每一行的行数 | |||
cursorHeight: 1, | |||
mode: { name: 'text/javascript' }, | |||
theme: 'darcula', | |||
styleActiveLine: true, // 当前行背景高亮 | |||
smartIndent: true | |||
}); | |||
}); | |||
}, | |||
methods: { | |||
onChange(e) { | |||
if (e == 2 && !this.CodeMirrorEditorDb) { | |||
this.$nextTick(() => { | |||
this.CodeMirrorEditorDb = CodeMirror.fromTextArea(this.$refs.dbDom, { | |||
lineNumbers: true, //是否显示每一行的行数 | |||
cursorHeight: 1, | |||
mode: { name: 'text/javascript' }, | |||
theme: 'darcula', | |||
styleActiveLine: true, // 当前行背景高亮 | |||
smartIndent: true | |||
}); | |||
}); | |||
} | |||
} | |||
} | |||
}; | |||
</script> | |||
<style lang="less" scoped> | |||
@import "~codemirror/lib/codemirror.css"; | |||
@import "~codemirror/theme/darcula.css"; | |||
/deep/ .CodeMirror { | |||
height: 100%; | |||
} | |||
</style> |
@@ -0,0 +1,121 @@ | |||
<template> | |||
<a-modal | |||
:width="660" | |||
title="全局设置" | |||
wrapClassName="global-setting-modal" | |||
v-model="modalVisible" | |||
:maskClosable="false" | |||
@cancel="onCancel" | |||
@ok="onOk" | |||
> | |||
<div class="global-setting-list"> | |||
<div class="global-setting-item"> | |||
<div class="label">标签宽度</div> | |||
<div class="control"> | |||
<a-input-number v-model="globalConfig.labelCol" :min="0" :max="24" /> | |||
<span>与控件宽度之和不超过24</span> | |||
</div> | |||
</div> | |||
<div class="global-setting-item"> | |||
<div class="label">控件宽度</div> | |||
<div class="control"> | |||
<a-input-number v-model="globalConfig.wraperCol" :min="0" :max="24" /> | |||
<span>与标签宽度之和不超过24</span> | |||
</div> | |||
</div> | |||
<div class="global-setting-item"> | |||
<div class="label">标签横向对齐方式</div> | |||
<div class="control"> | |||
<a-radio-group v-model="globalConfig.labelAlign" buttonStyle="solid"> | |||
<a-radio-button value="left">左对齐</a-radio-button> | |||
<a-radio-button value="center">居中</a-radio-button> | |||
<a-radio-button value="right">右对齐</a-radio-button> | |||
</a-radio-group> | |||
</div> | |||
</div> | |||
<div class="global-setting-item"> | |||
<div class="label">标签竖向对齐方式</div> | |||
<div class="control"> | |||
<a-radio-group v-model="globalConfig.labelVertical" buttonStyle="solid"> | |||
<a-radio-button value="top">顶部对齐</a-radio-button> | |||
<a-radio-button value="middle">居中</a-radio-button> | |||
<a-radio-button value="bottom">底部对齐</a-radio-button> | |||
</a-radio-group> | |||
</div> | |||
</div> | |||
<div class="global-setting-item"> | |||
<div class="label">表格模式</div> | |||
<div class="control"> | |||
<a-switch v-model="globalConfig.tableStyle" /> | |||
<span>如果开启,响应式设置将会失效</span> | |||
</div> | |||
</div> | |||
</div> | |||
</a-modal> | |||
</template> | |||
<script> | |||
export default { | |||
model: { | |||
prop: 'value', | |||
event: 'change' | |||
}, | |||
props: ['visible', 'value'], | |||
data() { | |||
return { | |||
modalVisible: false, | |||
globalConfig: {} | |||
}; | |||
}, | |||
watch: { | |||
visible: { | |||
handler(v) { | |||
this.modalVisible = v; | |||
}, | |||
immediate: true | |||
}, | |||
value: { | |||
handler(v) { | |||
if (v) { | |||
this.globalConfig = JSON.parse(JSON.stringify(v)); | |||
} else { | |||
this.globalConfig = {}; | |||
} | |||
}, | |||
immediate: true, | |||
deep: true | |||
} | |||
}, | |||
methods: { | |||
onCancel() { | |||
this.$emit('close', false); | |||
}, | |||
onOk() { | |||
this.$emit('change', this.globalConfig); | |||
this.$emit('close', false); | |||
} | |||
} | |||
}; | |||
</script> | |||
<style lang="less" scoped> | |||
.global-setting-list { | |||
display: flex; | |||
flex-wrap: wrap; | |||
.global-setting-item { | |||
width: 50%; | |||
height: 90px; | |||
display: flex; | |||
flex-direction: column; | |||
justify-content: center; | |||
span { | |||
font-size: 12px; | |||
padding-left: @padding-md / 2; | |||
} | |||
.label { | |||
margin-bottom: @padding-md / 2; | |||
} | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,152 @@ | |||
<template> | |||
<a-modal | |||
:width="width" | |||
title="预览" | |||
:wrapClassName="'preview-modal preview-mock ' + layout" | |||
v-model="previewModalVisible" | |||
:footer="null" | |||
:maskClosable="false" | |||
@cancel="$emit('close',false)" | |||
> | |||
<div class="top-toolbox"> | |||
<span class="item"> | |||
<span>布局:</span> | |||
<a-select v-model="layout" style="width: 160px" > | |||
<a-select-option value="none">无</a-select-option> | |||
<a-select-option value="banner">banner (1200px)</a-select-option> | |||
<a-select-option value="xs" :disabled="uiConf && uiConf.config && uiConf.config.tableStyle">xs (<576px)</a-select-option> | |||
<a-select-option value="sm" :disabled="uiConf && uiConf.config && uiConf.config.tableStyle">sm (≥576px)</a-select-option> | |||
<a-select-option value="md" :disabled="uiConf && uiConf.config && uiConf.config.tableStyle">md (≥768px)</a-select-option> | |||
<a-select-option value="lg" :disabled="uiConf && uiConf.config && uiConf.config.tableStyle">lg (≥992px)</a-select-option> | |||
<a-select-option value="xl" :disabled="uiConf && uiConf.config && uiConf.config.tableStyle">xl (≥1200px)</a-select-option> | |||
<a-select-option value="xxl" :disabled="uiConf && uiConf.config && uiConf.config.tableStyle">xxl (≥1600px)</a-select-option> | |||
</a-select> | |||
</span> | |||
<span class="item"> | |||
<span>模式:</span> | |||
<a-select v-model="formMode" style="width: 160px"> | |||
<a-select-option value="edit">编辑模式</a-select-option> | |||
<a-select-option value="detail">详情模式</a-select-option> | |||
</a-select> | |||
</span> | |||
<span class="item" v-if="formMode == 'edit'"> | |||
<a-button type="primary" @click="onFormSubmit">提交表单</a-button> | |||
<a-button type="danger" ghost @click="onFormReset">重置</a-button> | |||
</span> | |||
</div> | |||
<div class="preview-content" :class="['mock-' + layout]"> | |||
<Form | |||
:uiConf="uiConf" | |||
:gateway="gateway" | |||
:userModel="userModel" | |||
@getform="getPreviewForm" | |||
:mode="formMode" | |||
:banner="layout == 'banner' ? 1200 : false" | |||
:mock="layout" | |||
/> | |||
</div> | |||
<a-modal title="表单验证结果" v-model="resultModalVisible" :footer="null"> | |||
<div class="result-item header">{{results?results:'表单验证不通过'}}</div> | |||
</a-modal> | |||
</a-modal> | |||
</template> | |||
<script> | |||
import Form from '../Form'; | |||
import message from 'ant-design-vue/es/message'; | |||
export default { | |||
props: ['uiConf', 'visible', 'gateway', 'userModel'], | |||
components: { Form }, | |||
data() { | |||
return { | |||
previewModalVisible: false, | |||
resultModalVisible: false, | |||
layout: 'none', | |||
formMode: 'edit', | |||
width: window.innerWidth, | |||
results: [] | |||
}; | |||
}, | |||
watch: { | |||
visible: { | |||
handler(v) { | |||
this.previewModalVisible = v; | |||
this.layout = 'none'; | |||
this.formMode = 'edit'; | |||
}, | |||
immediate: true | |||
} | |||
}, | |||
methods: { | |||
onGlobalSettingClick() {}, | |||
onFormSubmit() { | |||
if (this.previewForm) { | |||
this.previewForm.validateFields((err, values) => { | |||
if (err) { | |||
message.error('表单验证失败'); | |||
this.results = null; | |||
} else { | |||
message.success('表单验证通过'); | |||
this.results = values; | |||
} | |||
this.resultModalVisible = true; | |||
}); | |||
} | |||
}, | |||
onFormReset() { | |||
if (this.previewForm) { | |||
this.$confirm({ | |||
title: '提示', | |||
content: '确认要重置表单的值吗?', | |||
onOk: () => { | |||
this.previewForm.resetFields(); | |||
} | |||
}); | |||
} | |||
}, | |||
getPreviewForm(form) { | |||
this.previewForm = form; | |||
} | |||
} | |||
}; | |||
</script> | |||
<style lang="less" scoped> | |||
/deep/ .preview-mock { | |||
.ant-modal-body { | |||
background-color: rgba(0, 0, 0, 0.5); | |||
} | |||
&.none { | |||
.ant-modal-body { | |||
background-color: transparent; | |||
} | |||
} | |||
.preview-content { | |||
background-color: #fff; | |||
margin: 0 auto; | |||
padding: 0; | |||
height: calc(100% - 50px) !important; | |||
&.mock-banner { | |||
width: 1260px; | |||
} | |||
&.mock-xs { | |||
width: 576px; | |||
} | |||
&.mock-sm { | |||
width: 760px; | |||
} | |||
&.mock-md { | |||
width: 880px; | |||
} | |||
&.mock-lg { | |||
width: 1000px; | |||
} | |||
&.mock-xl { | |||
width: 1400px; | |||
} | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,251 @@ | |||
<template> | |||
<div | |||
class="form-grid-editor form-item-editor" | |||
:class="[activeKey === formItem.key ?'form-item-active':'']" | |||
@click.stop.prevent="onItemActive" | |||
> | |||
<a-row | |||
type="flex" | |||
:align="formItem.align || 'top'" | |||
:justify="formItem.justify || 'start'" | |||
class="form-grid-editor-row" | |||
> | |||
<a-col | |||
v-for="(colItem,index) in formItem.columns" | |||
class="col-item" | |||
:offset="colItem.offset" | |||
:span="colItem.span" | |||
:pull="colItem.pull" | |||
:push="colItem.push" | |||
:xs="colItem.isResponvive?colItem.xs:colItem.span" | |||
:sm="colItem.isResponvive?colItem.sm:colItem.span" | |||
:md="colItem.isResponvive?colItem.md:colItem.span" | |||
:lg="colItem.isResponvive?colItem.lg:colItem.span" | |||
:xl="colItem.isResponvive?colItem.xl:colItem.span" | |||
:xxl="colItem.isResponvive?colItem.xxl:colItem.span" | |||
:key="index" | |||
> | |||
<div class="col-border"> | |||
<div class="col-grid-list" :data-id="colItem.key" :data-index="index" ref="colbody"> | |||
<template v-for="(colListItem,cIndex) in colItem.list"> | |||
<FormGridEditor | |||
v-if="colListItem.type == 'Grid'" | |||
:key="renderKey+'_'+cIndex" | |||
:formItem="colListItem" | |||
:activeKey="activeKey" | |||
:form="form" | |||
:index="cIndex" | |||
:config="config" | |||
@active="onSubAcitve" | |||
@copy="onGridCopy($event,index)" | |||
@delete="onSubDelete($event,index)" | |||
/> | |||
<FormItemEditor | |||
v-else | |||
:key="renderKey+'_'+cIndex" | |||
:formItem="colListItem" | |||
:form="form" | |||
:activeKey="activeKey" | |||
:config="config" | |||
:index="cIndex" | |||
@active="onSubAcitve" | |||
@copy="onItemCopy($event,index)" | |||
@delete="onSubDelete($event,index)" | |||
/> | |||
</template> | |||
</div> | |||
</div> | |||
</a-col> | |||
<template v-if="activeKey === formItem.key"> | |||
<div class="active-bar"></div> | |||
<div class="form-item-copy" @click.stop.prevent="onCopy" title="复制"> | |||
<a-icon type="copy" /> | |||
</div> | |||
<div class="form-item-delete" @click.stop.prevent="onDelete" title="删除"> | |||
<a-icon type="minus" /> | |||
</div> | |||
</template> | |||
</a-row> | |||
</div> | |||
</template> | |||
<script> | |||
import FormItemEditor from './FormItemEditor'; | |||
import Sortable from 'sortablejs'; | |||
import { getFormConfig, recursiveReplace } from '../utils/form-config.util'; | |||
export default { | |||
name: 'FormGridEditor', | |||
props: ['formItem', 'form', 'config', 'activeKey', 'index'], | |||
components: { FormItemEditor }, | |||
data() { | |||
return { | |||
sortInstance: [], | |||
renderKey: 0 | |||
}; | |||
}, | |||
mounted() {}, | |||
watch: { | |||
formItem: { | |||
handler(v) { | |||
this.sortableInit(); | |||
}, | |||
immediate: true, | |||
deep: true | |||
} | |||
}, | |||
beforeDestroy() { | |||
this.destorySortInstance(); | |||
}, | |||
methods: { | |||
onItemCopy(e, colIndex) { | |||
let item = JSON.parse(JSON.stringify(e.data)); | |||
let key = Date.now(); | |||
item['key'] = key; | |||
item['model'] = item.type + '_' + key; | |||
this.formItem.columns[colIndex].list.splice(e.index + 1, 0, item); | |||
this.$emit('change', this.formItem); | |||
this.$emit('active', item); | |||
}, | |||
onSubDelete(e, colIndex) { | |||
console.log(this.formItem, '前'); | |||
this.formItem.columns[colIndex].list.splice(e.index, 1); | |||
console.log(this.formItem, '后'); | |||
this.$emit('change', this.formItem); | |||
this.$emit('active', null); | |||
}, | |||
onGridCopy(e, colIndex) { | |||
let item = JSON.parse(JSON.stringify(e.data)); | |||
let key = Date.now(); | |||
recursiveReplace(item, key, 0); | |||
this.formItem.columns[colIndex].list.splice(e.index + 1, 0, item); | |||
this.$emit('change', this.formItem); | |||
this.$emit('active', item); | |||
}, | |||
onCopy() { | |||
this.$emit('copy', { data: this.formItem, index: this.index }); | |||
}, | |||
onDelete() { | |||
this.$emit('delete', { data: this.formItem, index: this.index }); | |||
}, | |||
onItemActive() { | |||
console.log('我是在 grid里'); | |||
this.$emit('active', this.formItem); | |||
}, | |||
onSubAcitve(e) { | |||
console.log('我是在item里 grid'); | |||
this.$emit('active', e); | |||
}, | |||
destorySortInstance() { | |||
this.sortInstance.forEach(item => { | |||
if (item.destory) { | |||
item.destory(); | |||
} | |||
}); | |||
}, | |||
sortableInit() { | |||
this.$nextTick(() => { | |||
this.destorySortInstance(); //节约内存,摧毁已经实例过的desotry | |||
this.$refs.colbody.forEach(item => { | |||
let isAdd = false; | |||
let sortinstance = new Sortable(item, { | |||
group: { name: 'component' }, | |||
animation: 200, | |||
onSort: e => { | |||
if (!isAdd) { | |||
const tempData = this.formItem.columns[ | |||
Number(e.target.dataset.index) | |||
].list[e.oldIndex]; | |||
const newData = this.formItem.columns[ | |||
Number(e.target.dataset.index) | |||
].list[e.newIndex]; | |||
if (!newData) { | |||
//如果没有newData,则表示是从栅格里面拖出来的,拖到最后面去了 | |||
this.formItem.columns[ | |||
Number(e.target.dataset.index) | |||
].list.splice(e.oldIndex, 1); | |||
} else { | |||
if (!tempData.key) return; | |||
if (e.newIndex !== e.oldIndex) { | |||
this.formItem.columns[ | |||
Number(e.target.dataset.index) | |||
].list.splice(e.oldIndex, 1); | |||
this.formItem.columns[ | |||
Number(e.target.dataset.index) | |||
].list.splice(e.newIndex, 0, tempData); | |||
} | |||
} | |||
this.renderKey += 1; | |||
this.$emit('change', this.formItem); | |||
} | |||
isAdd = false; | |||
}, | |||
onAdd: e => { | |||
isAdd = true; | |||
console.log(e, 'eeee'); | |||
let defaultConfig = null; | |||
if (e.item.dataset.source == 'formList') { | |||
//左侧进入的 | |||
defaultConfig = getFormConfig(e.item.dataset.id); | |||
} else { | |||
//是从现有的控件拖过来的 | |||
console.log(e.item.__vue__.formItem, 'eeee'); | |||
defaultConfig = e.item.__vue__.formItem; | |||
} | |||
this.formItem.columns[Number(e.target.dataset.index)].list.splice( | |||
e.newIndex, | |||
0, | |||
defaultConfig | |||
); | |||
this.$emit('active', defaultConfig); | |||
this.$emit('change', this.formItem); | |||
e.item.parentNode.removeChild(e.item); | |||
}, | |||
onRemove: e => { | |||
// 做移除的时候,add的地方会先触发,如果不设定时器,add的地方会取不到e.item.__vue__ | |||
setTimeout(() => { | |||
this.formItem.columns[ | |||
Number(e.target.dataset.index) | |||
].list.splice(e.newIndex, 1); | |||
this.$emit('change', this.formItem); | |||
}, 100); | |||
} | |||
}); | |||
this.sortInstance.push(sortinstance); | |||
}); | |||
}); | |||
} | |||
} | |||
}; | |||
</script> | |||
<style lang="less" scoped> | |||
@table-border: 1px solid @border-color-base; | |||
.form-grid-editor { | |||
background-color: #f4f6fc; | |||
.col-border { | |||
border: 1px dashed @border-color-base; | |||
.col-grid-list { | |||
padding-bottom: 50px; | |||
} | |||
} | |||
&.form-item-active { | |||
background-color: @primary-1; | |||
.active-bar { | |||
position: absolute; | |||
left: 0; | |||
top: 0; | |||
height: 100%; | |||
width: 5px; | |||
background-color: @primary-color; | |||
} | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,55 @@ | |||
<template> | |||
<div class="form-item-editor" :class="[activeKey === formItem.key ?'form-item-active':'']"> | |||
<FormItemRender :formItem="formItem" :form="form" :config="config" mode="edit" /> | |||
<div class="overlay" @click.stop.prevent="onItemActive"></div> | |||
<template v-if="activeKey === formItem.key"> | |||
<div class="active-bar"></div> | |||
<div class="form-item-copy" @click.stop.prevent="onCopy" title="复制"> | |||
<a-icon type="copy" /> | |||
</div> | |||
<div class="form-item-delete" @click.stop.prevent="onDelete" title="删除"> | |||
<a-icon type="minus" /> | |||
</div> | |||
</template> | |||
</div> | |||
</template> | |||
<script> | |||
import FormItemRender from '../form-render/FormItemRender'; | |||
export default { | |||
props: ['formItem', 'form', 'config', 'activeKey', 'index'], | |||
components: { FormItemRender }, | |||
methods: { | |||
onItemActive() { | |||
console.log('我是在item里'); | |||
this.$emit('active', this.formItem); | |||
}, | |||
onCopy() { | |||
this.$emit('copy', { data: this.formItem, index: this.index }); | |||
}, | |||
onDelete() { | |||
console.log(1) | |||
this.$emit('delete', { data: this.formItem, index: this.index }); | |||
}, | |||
} | |||
}; | |||
</script> | |||
<style lang="less" scoped> | |||
.form-item-editor { | |||
background-color: #f4f6fc; | |||
&.form-item-active { | |||
background-color: @primary-1; | |||
.active-bar { | |||
position: absolute; | |||
left: 0; | |||
top: 0; | |||
height: 100%; | |||
width: 5px; | |||
background-color: @primary-color; | |||
} | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,93 @@ | |||
<template> | |||
<span class="hb-checkbox" :class="[formItem.orientation]"> | |||
<a-checkbox-group :options="options" :value="data" @change="onChange" v-if="mode !== 'detail'"> | |||
<a-checkbox | |||
v-for="(option,index) in options" | |||
:key="index" | |||
:value="option.value" | |||
>{{option.label}}</a-checkbox> | |||
</a-checkbox-group> | |||
<template v-else>{{ detailText }}</template> | |||
</span> | |||
</template> | |||
<script> | |||
import message from 'ant-design-vue/es/message'; | |||
export default { | |||
model: { | |||
prop: 'value', | |||
event: 'change' | |||
}, | |||
props: ['formItem', 'form', 'config', 'value', 'mode'], | |||
data() { | |||
return { | |||
options: [], | |||
data: [], | |||
radioStyle: {} | |||
}; | |||
}, | |||
computed:{ | |||
detailText(){ | |||
let names = []; | |||
this.options.forEach(item =>{ | |||
if(this.data.includes(item.value)){ | |||
names.push(item.label) | |||
} | |||
}) | |||
return names.join(','); | |||
} | |||
}, | |||
watch: { | |||
value: { | |||
handler(v) { | |||
if (v && typeof v === 'string') { | |||
this.data = v.split(','); | |||
} else { | |||
this.data = []; | |||
} | |||
}, | |||
immediate: true | |||
}, | |||
formItem: { | |||
handler(v) { | |||
if (v && Array.isArray(v.options)) { | |||
this.options = v.options; | |||
} else { | |||
this.options = []; | |||
} | |||
}, | |||
immediate: true, | |||
deep: true | |||
} | |||
}, | |||
methods: { | |||
onChange(e) { | |||
if ( | |||
this.formItem && | |||
this.formItem.maxCount >= 1 && | |||
e.length > this.formItem.maxCount | |||
) { | |||
//超出最大个数,提示,报错 | |||
message.error(`超出当前允许最大选择个数: ${this.formItem.maxCount}`); | |||
return; | |||
} | |||
this.$emit('change', e.join(',')); | |||
} | |||
} | |||
}; | |||
</script> | |||
<style lang="less" scoped> | |||
.hb-checkbox { | |||
&.vertical { | |||
/deep/ .ant-checkbox-wrapper { | |||
display: block; | |||
height: 30px; | |||
line-height: 30px; | |||
} | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,118 @@ | |||
import { | |||
ALIGN_OPTIONS, | |||
VERTICAL_OPTIONS, | |||
DEMO_OPTIONS, | |||
} from '../common.config' | |||
export default { | |||
name: '多选按钮', | |||
type: 'Checkbox', | |||
realForm: true, | |||
default_config: { | |||
title: '多选按钮', | |||
hideTitle: false, | |||
labelOverflow: 'ellipsis', | |||
defaultValue: '', | |||
labelBlock: false, | |||
labelAlign: null, | |||
labelVertical: null, | |||
labelCol: null, | |||
wraperCol: null, | |||
rules: [], | |||
placeholder: '请选择', | |||
options: JSON.parse(JSON.stringify(DEMO_OPTIONS)), | |||
orientation: 'horizontal', | |||
maxCount: 0 | |||
}, | |||
config_render_list: [{ | |||
fields: 'title', | |||
type: 'Text', | |||
title: '标题', | |||
placeholder: '字段别名' | |||
}, | |||
{ | |||
fields: 'hideTitle', | |||
type: 'Checkbox', | |||
title: '隐藏标题' | |||
}, | |||
{ | |||
fields: 'labelOverflow', | |||
type: 'Radio', | |||
options: [{ | |||
label: '省略号', | |||
value: 'ellipsis' | |||
}, | |||
{ | |||
label: '换行', | |||
value: 'break' | |||
}, | |||
], | |||
title: '标题溢出处理方式' | |||
}, | |||
{ | |||
fields: 'defaultValue', | |||
type: 'Text', | |||
title: '默认值' | |||
}, | |||
{ | |||
fields: 'maxCount', | |||
type: 'Number', | |||
title: '允许最多选择个数(大于1生效)' | |||
}, | |||
{ | |||
fields: 'options', | |||
type: 'EditOption', | |||
title: '选项设置' | |||
}, | |||
{ | |||
fields: 'orientation', | |||
type: 'Radio', | |||
title: '选项排列方向', | |||
options: [{ | |||
label: '横向', | |||
value: 'horizontal' | |||
}, { | |||
label: '竖向', | |||
value: 'vertical' | |||
}] | |||
}, | |||
{ | |||
fields: 'placeholder', | |||
type: 'Text', | |||
title: '提示文字', | |||
placeholder: '请输入提示文字' | |||
}, | |||
{ | |||
fields: 'rules', | |||
type: 'Rules', | |||
title: '校验规则' | |||
}, | |||
{ | |||
fields: 'labelCol', | |||
type: 'Number', | |||
title: '标题宽度' | |||
}, | |||
{ | |||
fields: 'wraperCol', | |||
type: 'Number', | |||
title: '控件宽度' | |||
}, | |||
{ | |||
fields: 'labelBlock', | |||
type: 'Checkbox', | |||
title: '标题独占一行' | |||
}, | |||
{ | |||
fields: 'labelAlign', | |||
type: 'Radio', | |||
options: ALIGN_OPTIONS, | |||
title: '标题对齐方式' | |||
}, | |||
{ | |||
fields: 'labelVertical', | |||
type: 'Radio', | |||
options: VERTICAL_OPTIONS, | |||
title: '标题竖向对齐方式' | |||
}, | |||
] | |||
} |
@@ -0,0 +1,55 @@ | |||
export const ALIGN_OPTIONS = [{ | |||
label: '左对齐', | |||
value: 'left' | |||
}, | |||
{ | |||
label: '居中', | |||
value: 'center' | |||
}, | |||
{ | |||
label: '右对齐', | |||
value: 'right' | |||
} | |||
] | |||
export const VERTICAL_OPTIONS = [{ | |||
label: '顶部对齐', | |||
value: 'top' | |||
}, | |||
{ | |||
label: '居中对齐', | |||
value: 'middle' | |||
}, | |||
{ | |||
label: '底部对齐', | |||
value: 'bottom' | |||
} | |||
] | |||
export const DEMO_OPTIONS = [{ | |||
label: '选项1', | |||
value: 'option1' | |||
}, | |||
{ | |||
label: '选项2', | |||
value: 'option2' | |||
}, | |||
{ | |||
label: '选项3', | |||
value: 'option3' | |||
} | |||
] | |||
export const DEMO_TAG_OPTIONS = ['标签1', '标签2', '标签3'] | |||
export const INPUTFIX_OPTIONS = [{ | |||
label: '文本', | |||
value: 'text' | |||
}, | |||
{ | |||
label: '图标', | |||
value: 'icon' | |||
} | |||
] | |||
export const hbConfigKey = ['title', 'hideTitle', 'labelOverflow', 'labelBlock', 'labelAlign', 'labelCol', 'wraperCol', 'rules'] |
@@ -0,0 +1,55 @@ | |||
<template> | |||
<span> | |||
<a-range-picker | |||
v-if="mode !== 'detail'" | |||
v-model="data" | |||
:showTime="!!formItem.showTime" | |||
@change="onChange" | |||
:placeholder="[formItem?formItem.placeholder:undefined,formItem?formItem.placeholder:undefined]" | |||
/> | |||
<template v-else>{{ detailText }}</template> | |||
</span> | |||
</template> | |||
<script> | |||
import moment from 'moment'; | |||
export default { | |||
model: { | |||
prop: 'value', | |||
event: 'change' | |||
}, | |||
props: ['formItem', 'form', 'config', 'value', 'mode'], | |||
data() { | |||
return { | |||
data: [] | |||
}; | |||
}, | |||
computed:{ | |||
detailText(){ | |||
return (this.value || []).join('~'); | |||
} | |||
}, | |||
watch: { | |||
value: { | |||
handler(v) { | |||
if (v && v.length >= 2) { | |||
this.data = [moment(v[0]), moment(v[1])]; | |||
} else { | |||
this.data = []; | |||
} | |||
}, | |||
immediate: true | |||
} | |||
}, | |||
methods: { | |||
onChange(e) { | |||
let format = | |||
this.formItem && this.formItem.showTime | |||
? 'YYYY-MM-DD HH:mm:ss' | |||
: 'YYYY-MM-DD'; | |||
this.$emit('change', [e[0].format(format), e[1].format(format)]); | |||
} | |||
} | |||
}; | |||
</script> |
@@ -0,0 +1,98 @@ | |||
import { | |||
ALIGN_OPTIONS, | |||
VERTICAL_OPTIONS, | |||
} from '../common.config' | |||
export default { | |||
name: '日期区间', | |||
type: 'DateRange', | |||
realForm: true, | |||
default_config: { | |||
title: '日期区间', | |||
hideTitle: false, | |||
labelOverflow: 'ellipsis', | |||
defaultValue: undefined, | |||
labelBlock: false, | |||
labelAlign: null, | |||
labelVertical: null, | |||
labelCol: null, | |||
wraperCol: undefined, | |||
rules: [], | |||
placeholder: '请选择', | |||
showTime: false, | |||
}, | |||
config_render_list: [{ | |||
fields: 'title', | |||
type: 'Text', | |||
title: '标题', | |||
placeholder: '字段别名' | |||
}, | |||
{ | |||
fields: 'hideTitle', | |||
type: 'Checkbox', | |||
title: '隐藏标题' | |||
}, | |||
{ | |||
fields: 'labelOverflow', | |||
type: 'Radio', | |||
options: [{ | |||
label: '省略号', | |||
value: 'ellipsis' | |||
}, | |||
{ | |||
label: '换行', | |||
value: 'break' | |||
}, | |||
], | |||
title: '标题溢出处理方式' | |||
}, | |||
{ | |||
fields: 'defaultValue', | |||
type: 'DateRange', | |||
title: '默认值' | |||
}, | |||
{ | |||
fields: 'showTime', | |||
type: 'Checkbox', | |||
title: '是否显示时间' | |||
}, | |||
{ | |||
fields: 'placeholder', | |||
type: 'Text', | |||
title: '提示文字', | |||
placeholder: '请输入提示文字' | |||
}, | |||
{ | |||
fields: 'rules', | |||
type: 'Rules', | |||
title: '校验规则' | |||
}, | |||
{ | |||
fields: 'labelCol', | |||
type: 'Number', | |||
title: '标题宽度' | |||
}, | |||
{ | |||
fields: 'wraperCol', | |||
type: 'Number', | |||
title: '控件宽度' | |||
}, | |||
{ | |||
fields: 'labelBlock', | |||
type: 'Checkbox', | |||
title: '标题独占一行' | |||
}, | |||
{ | |||
fields: 'labelAlign', | |||
type: 'Radio', | |||
options: ALIGN_OPTIONS, | |||
title: '标题对齐方式' | |||
}, | |||
{ | |||
fields: 'labelVertical', | |||
type: 'Radio', | |||
options: VERTICAL_OPTIONS, | |||
title: '标题竖向对齐方式' | |||
}, | |||
] | |||
} |
@@ -0,0 +1,49 @@ | |||
<template> | |||
<span> | |||
<a-date-picker | |||
v-if="mode !== 'detail'" | |||
v-model="data" | |||
:showTime="!!formItem.showTime" | |||
@change="onChange" | |||
:placeholder="formItem?formItem.placeholder:undefined" | |||
/> | |||
<template v-else>{{ value }}</template> | |||
</span> | |||
</template> | |||
<script> | |||
import moment from 'moment'; | |||
export default { | |||
model: { | |||
prop: 'value', | |||
event: 'change' | |||
}, | |||
props: ['formItem', 'form', 'config', 'value','mode'], | |||
data() { | |||
return { | |||
data: undefined | |||
}; | |||
}, | |||
watch: { | |||
value: { | |||
handler(v) { | |||
if (v) { | |||
this.data = moment(v); | |||
} else { | |||
this.data = undefined; | |||
} | |||
}, | |||
immediate: true | |||
} | |||
}, | |||
methods: { | |||
onChange(e) { | |||
let format = | |||
this.formItem && this.formItem.showTime | |||
? 'YYYY-MM-DD HH:mm:ss' | |||
: 'YYYY-MM-DD'; | |||
this.$emit('change', e.format(format)); | |||
} | |||
} | |||
}; | |||
</script> |
@@ -0,0 +1,98 @@ | |||
import { | |||
ALIGN_OPTIONS, | |||
VERTICAL_OPTIONS, | |||
} from '../common.config' | |||
export default { | |||
name: '日期时间', | |||
type: 'Datetime', | |||
realForm: true, | |||
default_config: { | |||
title: '日期时间', | |||
hideTitle: false, | |||
labelOverflow: 'ellipsis', | |||
defaultValue: undefined, | |||
labelBlock: false, | |||
labelAlign: null, | |||
labelVertical: null, | |||
labelCol: null, | |||
wraperCol: undefined, | |||
rules: [], | |||
placeholder: '请选择', | |||
showTime: false | |||
}, | |||
config_render_list: [{ | |||
fields: 'title', | |||
type: 'Text', | |||
title: '标题', | |||
placeholder: '字段别名' | |||
}, | |||
{ | |||
fields: 'hideTitle', | |||
type: 'Checkbox', | |||
title: '隐藏标题' | |||
}, | |||
{ | |||
fields: 'labelOverflow', | |||
type: 'Radio', | |||
options: [{ | |||
label: '省略号', | |||
value: 'ellipsis' | |||
}, | |||
{ | |||
label: '换行', | |||
value: 'break' | |||
}, | |||
], | |||
title: '标题溢出处理方式' | |||
}, | |||
{ | |||
fields: 'defaultValue', | |||
type: 'Datetime', | |||
title: '默认值' | |||
}, | |||
{ | |||
fields: 'showTime', | |||
type: 'Checkbox', | |||
title: '是否显示时间' | |||
}, | |||
{ | |||
fields: 'placeholder', | |||
type: 'Text', | |||
title: '提示文字', | |||
placeholder: '请输入提示文字' | |||
}, | |||
{ | |||
fields: 'rules', | |||
type: 'Rules', | |||
title: '校验规则' | |||
}, | |||
{ | |||
fields: 'labelCol', | |||
type: 'Number', | |||
title: '标题宽度' | |||
}, | |||
{ | |||
fields: 'wraperCol', | |||
type: 'Number', | |||
title: '控件宽度' | |||
}, | |||
{ | |||
fields: 'labelBlock', | |||
type: 'Checkbox', | |||
title: '标题独占一行' | |||
}, | |||
{ | |||
fields: 'labelAlign', | |||
type: 'Radio', | |||
options: ALIGN_OPTIONS, | |||
title: '标题对齐方式' | |||
}, | |||
{ | |||
fields: 'labelVertical', | |||
type: 'Radio', | |||
options: VERTICAL_OPTIONS, | |||
title: '标题竖向对齐方式' | |||
}, | |||
] | |||
} |