feat: 初始化

This commit is contained in:
2026-03-24 15:29:49 +08:00
commit a3d00f78ce
353 changed files with 84957 additions and 0 deletions
+39
View File
@@ -0,0 +1,39 @@
[*]
charset=utf-8
end_of_line=crlf
insert_final_newline=false
indent_style=space
indent_size=2
[{*.ng,*.sht,*.html,*.shtm,*.shtml,*.htm}]
indent_style=space
indent_size=2
[{*.jhm,*.xslt,*.xul,*.rng,*.xsl,*.xsd,*.ant,*.tld,*.fxml,*.jrxml,*.xml,*.jnlp,*.wsdl}]
indent_style=space
indent_size=2
[{.babelrc,.stylelintrc,jest.config,.eslintrc,.prettierrc,*.json,*.jsb3,*.jsb2,*.bowerrc}]
indent_style=space
indent_size=2
[*.svg]
indent_style=space
indent_size=2
[*.js.map]
indent_style=space
indent_size=2
[*.less]
indent_style=space
indent_size=2
[*.vue]
indent_style=space
indent_size=2
[{.analysis_options,*.yml,*.yaml}]
indent_style=space
indent_size=2
+7
View File
@@ -0,0 +1,7 @@
# 本地环境
ENV = 'development'
# 接口地址
VUE_API_BASE_URL = http://127.0.0.1:9999/jshERP-boot
# 本地端口
VUE_PORT = 82
+7
View File
@@ -0,0 +1,7 @@
# 生产环境
ENV = 'production'
# 接口地址
VUE_API_BASE_URL = https://www.byhah.com/jshERP-boot
# 本地端口
VUE_PORT = 82
+1
View File
@@ -0,0 +1 @@
/src
+1
View File
@@ -0,0 +1 @@
public/* linguist-vendored
+22
View File
@@ -0,0 +1,22 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*
/package-lock.json
+5
View File
@@ -0,0 +1,5 @@
{
"printWidth": 120,
"semi": false,
"singleQuote": true
}
+104
View File
@@ -0,0 +1,104 @@
jshERP-web Vue
====
Overview
----
#### 前端技术
- 基础框架:[ant-design-vue](https://github.com/vueComponent/ant-design-vue) - Ant Design Of Vue 实现
- JavaScript框架:Vue
- Jeecg-boot 的前段UI框架
- Webpack
- node
- yarn
- eslint
- @vue/cli 3.2.1
- [vue-cropper](https://github.com/xyxiao001/vue-cropper) - 头像裁剪组件
- [@antv/g2](https://antv.alipay.com/zh-cn/index.html) - Alipay AntV 数据可视化图表
- [Viser-vue](https://viserjs.github.io/docs.html#/viser/guide/installation) - antv/g2 封装实现
项目运行
----
- 安装nodeJS
```
建议安装node-v20.17.0-x64版本 教程参考 https://blog.csdn.net/Coin_Collecter/article/details/136484312
```
- 安装yarn
```
npm install -g yarn
```
- 配镜像源(速度快)
```
yarn config set registry https://registry.npmmirror.com
```
- 安装依赖
```
yarn install
```
- 开发模式运行
```
yarn serve
```
- 编译发布项目
```
yarn build
```
其他说明
----
- 项目使用的 [vue-cli3](https://cli.vuejs.org/guide/), 请更新您的 cli
- 关闭 Eslint (不推荐) 移除 `package.json``eslintConfig` 整个节点代码
- 修改 Ant Design 配色,在文件 `vue.config.js` 中,其他 less 变量覆盖参考 [ant design](https://ant.design/docs/react/customize-theme-cn) 官方说明
```ecmascript 6
css: {
loaderOptions: {
less: {
modifyVars: {
/* less 变量覆盖,用于自定义 ant design 主题 */
'primary-color': '#F5222D',
'link-color': '#F5222D',
'border-radius-base': '4px',
},
javascriptEnabled: true,
}
}
}
```
附属文档
----
- [Ant Design Vue](https://vuecomponent.github.io/ant-design-vue/docs/vue/introduce-cn)
- [报表 viser-vue](https://viserjs.github.io/demo.html#/viser/bar/basic-bar)
- [Vue](https://cn.vuejs.org/v2/guide)
- [路由/菜单说明](https://github.com/zhangdaiscott/jeecg-boot/tree/master/ant-design-jeecg-vue/src/router/README.md)
- [ANTD 默认配置项](https://github.com/zhangdaiscott/jeecg-boot/tree/master/ant-design-jeecg-vue/src/defaultSettings.js)
- 其他待补充...
备注
----
> @vue/cli 升级后,eslint 规则更新了。由于影响到全部 .vue 文件,需要逐个验证。既暂时关闭部分原本不验证的规则,后期维护时,在逐步修正这些 rules
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
presets: [
['@vue/app',
{ useBuiltIns: 'entry' }]
]
}
+24
View File
@@ -0,0 +1,24 @@
'use strict'
const path = require('path')
function resolve (dir) {
return path.join(__dirname, '.', dir)
}
module.exports = {
context: path.resolve(__dirname, './'),
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'config': resolve('config'),
'@': resolve('src'),
'@views': resolve('src/views'),
'@comp': resolve('src/components'),
'@core': resolve('src/core'),
'@utils': resolve('src/utils'),
'@entry': resolve('src/entry'),
'@router': resolve('src/router'),
'@store': resolve('src/store')
}
},
}
+110
View File
@@ -0,0 +1,110 @@
{
"name": "jsh-erp-web",
"version": "3.5.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
"dependencies": {
"@antv/data-set": "^0.11.2",
"@tinymce/tinymce-vue": "^2.0.0",
"ant-design-vue": "1.5.2",
"area-data": "^5.0.6",
"axios": "^0.18.0",
"clipboard": "^2.0.4",
"codemirror": "^5.46.0",
"dayjs": "^1.8.0",
"enquire.js": "^2.1.6",
"intro.js": "^4.2.2",
"jquery": "^1.12.4",
"js-cookie": "^2.2.0",
"lodash.get": "^4.4.2",
"lodash.pick": "^4.4.0",
"md5": "^2.2.1",
"nprogress": "^0.2.0",
"viser-vue": "^2.4.4",
"vue": "^2.7.16",
"vue-area-linkage": "^5.1.0",
"vue-cropper": "^0.4.8",
"vue-draggable-resizable": "^2.3.0",
"vue-i18n": "^8.7.0",
"vue-loader": "^15.7.0",
"vue-ls": "^3.2.0",
"vue-photo-preview": "^1.1.3",
"vue-print-nb-jeecg": "^1.0.9",
"vue-router": "^3.0.1",
"vue-splitpane": "^1.0.4",
"vuedraggable": "^2.20.0",
"vuex": "^3.1.0"
},
"devDependencies": {
"@babel/polyfill": "^7.2.5",
"@vue/cli-plugin-babel": "^3.3.0",
"@vue/cli-plugin-eslint": "^3.3.0",
"@vue/cli-service": "^3.3.0",
"@vue/eslint-config-standard": "^4.0.0",
"babel-eslint": "^10.0.1",
"compression-webpack-plugin": "^3.1.0",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.1.0",
"html-webpack-plugin": "^4.2.0",
"less": "^3.9.0",
"less-loader": "^4.1.0",
"mini-css-extract-plugin": "^2.9.4",
"vue-template-compiler": "^2.6.10"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/strongly-recommended",
"@vue/standard"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {
"generator-star-spacing": "off",
"no-mixed-operators": 0,
"vue/max-attributes-per-line": [
2,
{
"singleline": 5,
"multiline": {
"max": 1,
"allowFirstLine": false
}
}
],
"vue/attribute-hyphenation": 0,
"vue/html-self-closing": 0,
"vue/component-name-in-template-casing": 0,
"vue/html-closing-bracket-spacing": 0,
"vue/singleline-html-element-content-newline": 0,
"vue/no-unused-components": 0,
"vue/multiline-html-element-content-newline": 0,
"vue/no-use-v-if-with-v-for": 0,
"vue/html-closing-bracket-newline": 0,
"vue/no-parsing-error": 0,
"no-console": 0,
"no-tabs": 0,
"indent": [
1,
4
]
}
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 10"
]
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+319
View File
@@ -0,0 +1,319 @@
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<title></title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta name="description" content="基于SpringBoot框架,立志为中小企业提供开源好用的ERP软件,目前专注进销存+财务功能。主要模块有零售管理、采购管理、销售管理、仓库管理、财务管理、报表查询、基础数据、系统管理等。" />
<meta name="keywords" content="erp,erp系统,进销存,进销存系统" />
<link rel="icon" href="<%= BASE_URL %>static/favicon.ico">
<style>
html,
body,
#app {
height: 100%;
margin: 0px;
padding: 0px;
}
.chromeframe {
margin: 0.2em 0;
background: #ccc;
color: #999;
padding: 0.2em 0;
}
#loader-wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999999;
}
#loader {
display: block;
position: relative;
left: 50%;
top: 50%;
width: 120px;
height: 120px;
margin: -75px 0 0 -75px;
border-radius: 50%;
border: 3px solid transparent;
/* COLOR 1 */
border-top-color: #999;
-webkit-animation: spin 2s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-ms-animation: spin 2s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-moz-animation: spin 2s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-o-animation: spin 2s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
animation: spin 2s linear infinite;
/* Chrome, Firefox 16+, IE 10+, Opera */
z-index: 1001;
}
#loader:before {
content: "";
position: absolute;
top: 5px;
left: 5px;
right: 5px;
bottom: 5px;
border-radius: 50%;
border: 3px solid transparent;
/* COLOR 2 */
border-top-color: #999;
-webkit-animation: spin 3s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-moz-animation: spin 3s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-o-animation: spin 3s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-ms-animation: spin 3s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
animation: spin 3s linear infinite;
/* Chrome, Firefox 16+, IE 10+, Opera */
}
#loader:after {
content: "";
position: absolute;
top: 15px;
left: 15px;
right: 15px;
bottom: 15px;
border-radius: 50%;
border: 3px solid transparent;
border-top-color: #999;
/* COLOR 3 */
-moz-animation: spin 1.5s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-o-animation: spin 1.5s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-ms-animation: spin 1.5s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
-webkit-animation: spin 1.5s linear infinite;
/* Chrome, Opera 15+, Safari 5+ */
animation: spin 1.5s linear infinite;
/* Chrome, Firefox 16+, IE 10+, Opera */
}
@-webkit-keyframes spin {
0% {
-webkit-transform: rotate(0deg);
/* Chrome, Opera 15+, Safari 3.1+ */
-ms-transform: rotate(0deg);
/* IE 9 */
transform: rotate(0deg);
/* Firefox 16+, IE 10+, Opera */
}
100% {
-webkit-transform: rotate(360deg);
/* Chrome, Opera 15+, Safari 3.1+ */
-ms-transform: rotate(360deg);
/* IE 9 */
transform: rotate(360deg);
/* Firefox 16+, IE 10+, Opera */
}
}
@keyframes spin {
0% {
-webkit-transform: rotate(0deg);
/* Chrome, Opera 15+, Safari 3.1+ */
-ms-transform: rotate(0deg);
/* IE 9 */
transform: rotate(0deg);
/* Firefox 16+, IE 10+, Opera */
}
100% {
-webkit-transform: rotate(360deg);
/* Chrome, Opera 15+, Safari 3.1+ */
-ms-transform: rotate(360deg);
/* IE 9 */
transform: rotate(360deg);
/* Firefox 16+, IE 10+, Opera */
}
}
#loader-wrapper .loader-section {
position: fixed;
top: 0;
width: 51%;
height: 100%;
background: #fff;
/* Old browsers */
z-index: 1000;
-webkit-transform: translateX(0);
/* Chrome, Opera 15+, Safari 3.1+ */
-ms-transform: translateX(0);
/* IE 9 */
transform: translateX(0);
/* Firefox 16+, IE 10+, Opera */
}
#loader-wrapper .loader-section.section-left {
left: 0;
}
#loader-wrapper .loader-section.section-right {
right: 0;
}
/* Loaded */
.loaded #loader-wrapper .loader-section.section-left {
-webkit-transform: translateX(-100%);
/* Chrome, Opera 15+, Safari 3.1+ */
-ms-transform: translateX(-100%);
/* IE 9 */
transform: translateX(-100%);
/* Firefox 16+, IE 10+, Opera */
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
}
.loaded #loader-wrapper .loader-section.section-right {
-webkit-transform: translateX(100%);
/* Chrome, Opera 15+, Safari 3.1+ */
-ms-transform: translateX(100%);
/* IE 9 */
transform: translateX(100%);
/* Firefox 16+, IE 10+, Opera */
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
}
.loaded #loader {
opacity: 0;
-webkit-transition: all 0.3s ease-out;
transition: all 0.3s ease-out;
}
.loaded #loader-wrapper {
visibility: hidden;
-webkit-transform: translateY(-100%);
/* Chrome, Opera 15+, Safari 3.1+ */
-ms-transform: translateY(-100%);
/* IE 9 */
transform: translateY(-100%);
/* Firefox 16+, IE 10+, Opera */
-webkit-transition: all 0.3s 1s ease-out;
transition: all 0.3s 1s ease-out;
}
/* JavaScript Turned Off */
.no-js #loader-wrapper {
display: none;
}
.no-js h1 {
color: #222222;
}
#loader-wrapper .load_title {
font-family: 'Open Sans';
color: #999;
font-size: 14px;
width: 100%;
text-align: center;
z-index: 9999999999999;
position: absolute;
top: 60%;
opacity: 1;
line-height: 30px;
}
#loader-wrapper .load_title span {
font-weight: normal;
font-style: italic;
font-size: 14px;
color: #999;
opacity: 0.5;
}
/* 滚动条优化 start */
::-webkit-scrollbar{
width:8px;
height:8px;
}
::-webkit-scrollbar-track{
background: #f6f6f6;
border-radius:2px;
}
::-webkit-scrollbar-thumb{
background: #cdcdcd;
border-radius:2px;
}
::-webkit-scrollbar-thumb:hover{
background: #747474;
}
::-webkit-scrollbar-corner {
background: #f6f6f6;
}
/* 滚动条优化 end */
</style>
<script>
function getPlatform(type) {
let res = '';
let ajax = new XMLHttpRequest();
let url = window._CONFIG['domianURL'] + '/platformConfig/getPlatform/' + type
ajax.onreadystatechange = function () {
if (ajax.readyState===4 &&ajax.status===200) {
res = ajax.responseText;
} else {
res = 'ERP系统';
}
}
ajax.open('get', url, false);
ajax.send(null);
return res;
}
window._CONFIG = {};
window._CONFIG['domianURL'] = '/jshERP-boot';
let statisticsCode = '1cd9bcbaae133f03a6eb19da6579aaba'
window.SYS_TITLE = getPlatform("name");
window.SYS_URL = getPlatform("url");
window._statistics = 'https://hm.baidu.com/hm.js?' + statisticsCode
document.title = window.SYS_TITLE;
//statistics
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = window._statistics;
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
</head>
<body>
<!-- built files will be auto injected -->
<div id="app">
<div id="loader-wrapper">
<div id="loader"></div>
<div class="loader-section section-left"></div>
<div class="loader-section section-right"></div>
<div class="load_title">
正在加载系统,请耐心等待
</div>
</div>
</div>
</body>
<!-- 全局配置-多语言切换-开始 -->
<script src="<%= BASE_URL %>static/translate.js"></script>
<script>
//设置本地语种(当前网页的语种)。如果不设置,默认就是 'chinese_simplified' 简体中文
translate.language.setLocal('chinese_simplified');
translate.service.use('client.edge');
//翻译自定义
translate.nomenclature.append('chinese_simplified','english',`
管伊佳ERP=GuanYiJia
`)
//开启html页面变化的监控,对变化部分会进行自动翻译
translate.listener.start();
//不显示语言选择标签
translate.selectLanguageTag.show = false;
//执行翻译初始化操作,显示出select语言选择
//translate.execute();
//VUE的渲染需要时间,所以留出一点点时间来进行翻译切换
document.addEventListener('DOMContentLoaded', function () {
//页面 DOM 已渲染完毕,当然最好是能监控到整个vue渲染完毕后触发最好
translate.execute();
//2秒后再一次,避免有遗漏
setTimeout(function(){
translate.execute();
},2000);
});
</script>
<!-- 全局配置-多语言切换-结束 -->
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

+18
View File
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

+43
View File
@@ -0,0 +1,43 @@
<template>
<a-config-provider :locale="locale">
<div id="app">
<router-view/>
</div>
</a-config-provider>
</template>
<script>
import zhCN from 'ant-design-vue/lib/locale-provider/zh_CN'
import enquireScreen from '@/utils/device'
export default {
data () {
return {
locale: zhCN,
}
},
created () {
let that = this
enquireScreen(deviceType => {
// tablet
if (deviceType === 0) {
that.$store.commit('TOGGLE_DEVICE', 'mobile')
that.$store.dispatch('setSidebar', false)
}
// mobile
else if (deviceType === 1) {
that.$store.commit('TOGGLE_DEVICE', 'mobile')
that.$store.dispatch('setSidebar', false)
}
else {
that.$store.commit('TOGGLE_DEVICE', 'desktop')
that.$store.dispatch('setSidebar', true)
}
})
}
}
</script>
<style>
#app {
height: 100%;
}
</style>
+30
View File
@@ -0,0 +1,30 @@
import Vue from 'vue'
/**
* 将一个请求分组
*
* @param getPromise 传入一个可以获取到Promise对象的方法
* @param groupId 分组ID,如果不传或者为空则不分组
* @param expire 过期时间,默认 半分钟
*/
export function httpGroupRequest(getPromise, groupId, expire = 1000 * 30) {
if (groupId == null || groupId === '') {
console.log("--------popup----------getFrom DB-------with---no--groupId ")
return getPromise()
}
if (Vue.ls.get(groupId)) {
console.log("---------popup--------getFrom Cache--------groupId = " + groupId)
return Promise.resolve(Vue.ls.get(groupId));
} else {
console.log("--------popup----------getFrom DB---------groupId = " + groupId)
}
// 还没有发出请求,就发出第一次的请求
return getPromise().then(res => {
Vue.ls.set(groupId, res, expire);
return Promise.resolve(res);
})
}
+213
View File
@@ -0,0 +1,213 @@
import { getAction, deleteAction, putAction, postAction, httpAction } from '@/api/manage'
//首页统计
const getBuyAndSaleStatistics = (params)=>getAction("/depotHead/getBuyAndSaleStatistics",params);
const buyOrSalePrice = (params)=>getAction("/depotItem/buyOrSalePrice",params);
//租户管理
const checkTenant = (params)=>getAction("/tenant/checkIsNameExist",params);
const addTenant = (params)=>postAction("/tenant/add",params);
const editTenant = (params)=>putAction("/tenant/update",params);
//角色管理
const addRole = (params)=>postAction("/role/add",params);
const editRole = (params)=>putAction("/role/update",params);
const checkRole = (params)=>getAction("/role/checkIsNameExist",params);
const roleAllList = (params)=>getAction("/role/allList",params);
const getTenantRoleList = (params)=>getAction("/role/tenantRoleList",params);
//用户管理
const registerUser = (params)=>postAction("/user/registerUser",params);
const addUser = (params)=>postAction("/user/addUser",params);
const editUser = (params)=>putAction("/user/updateUser",params);
const getUserList = (params)=>getAction("/user/getUserList",params);
const getUserBtnByCurrentUser = (params)=>getAction("/user/getUserBtnByCurrentUser",params);
const queryPermissionsByUser = (params)=>postAction("/function/findMenuByPNumber",params);
//机构管理
const queryOrganizationTreeList = (params)=>getAction("/organization/getOrganizationTree",params);
const getAllOrganizationTreeByUser = (params)=>getAction("/organization/getAllOrganizationTreeByUser",params);
const queryOrganizationById = (params)=>getAction("/organization/findById",params);
const checkOrganization = (params)=>getAction("/organization/checkIsNameExist",params);
//经手人管理
const addPerson = (params)=>postAction("/person/add",params);
const editPerson = (params)=>putAction("/person/update",params);
const checkPerson = (params)=>getAction("/person/checkIsNameExist",params);
const getPersonByType = (params)=>getAction("/person/getPersonByType",params);
const getPersonByNumType = (params)=>getAction("/person/getPersonByNumType",params);
//账户管理
const addAccount = (params)=>postAction("/account/add",params);
const editAccount = (params)=>putAction("/account/update",params);
const checkAccount = (params)=>getAction("/account/checkIsNameExist",params);
const getAccount = (params)=>getAction("/account/getAccount",params);
//收支项目
const addInOutItem = (params)=>postAction("/inOutItem/add",params);
const editInOutItem = (params)=>putAction("/inOutItem/update",params);
const checkInOutItem = (params)=>getAction("/inOutItem/checkIsNameExist",params);
const findInOutItemByParam = (params)=>getAction("/inOutItem/findBySelect",params);
//仓库信息
const addDepot = (params)=>postAction("/depot/add",params);
const editDepot = (params)=>putAction("/depot/update",params);
const checkDepot = (params)=>getAction("/depot/checkIsNameExist",params);
//商品属性
const addOrUpdateMaterialProperty = (params)=>postAction("/materialProperty/addOrUpdate",params);
//商品类型
const queryMaterialCategoryTreeList = (params)=>getAction("/materialCategory/getMaterialCategoryTree",params);
const queryMaterialCategoryById = (params)=>getAction("/materialCategory/findById",params);
const checkMaterialCategory = (params)=>getAction("/materialCategory/checkIsNameExist",params);
//商品管理
const addMaterial = (params)=>postAction("/material/add",params);
const editMaterial = (params)=>putAction("/material/update",params);
const checkMaterial = (params)=>getAction("/material/checkIsExist",params);
const getMaterialBySelect = (params)=>getAction("/material/findBySelect",params);
const getSerialMaterialBySelect = (params)=>getAction("/material/getMaterialEnableSerialNumberList",params);
const getMaterialByParam = (params)=>getAction("/material/getMaterialByParam",params);
const getMaterialByBarCode = (params)=>getAction("/material/getMaterialByBarCode",params);
const getMaxBarCode = (params)=>getAction("/material/getMaxBarCode",params);
const checkMaterialBarCode = (params)=>getAction("/materialsExtend/checkIsBarCodeExist",params);
const batchUpdateMaterial = (params)=>postAction("/material/batchUpdate",params);
const changeNameToPinYin = (params)=>postAction("/material/changeNameToPinYin",params);
//序列号
const batAddSerialNumber = (params)=>postAction("/serialNumber/batAddSerialNumber",params);
const getEnableSerialNumberList = (params)=>postAction("/serialNumber/getEnableSerialNumberList",params);
//多属性
const addMaterialAttribute = (params)=>postAction("/materialAttribute/add",params);
const editMaterialAttribute = (params)=>putAction("/materialAttribute/update",params);
const checkMaterialAttribute = (params)=>getAction("/materialAttribute/checkIsNameExist",params);
const getMaterialAttributeNameList = (params)=>getAction("/materialAttribute/getNameList",params);
const getMaterialAttributeValueListById = (params)=>getAction("/materialAttribute/getValueListById",params);
//功能管理
const addFunction = (params)=>postAction("/function/add",params);
const editFunction = (params)=>putAction("/function/update",params);
const checkFunction = (params)=>getAction("/function/checkIsNameExist",params);
const checkNumber = (params)=>getAction("/function/checkIsNumberExist",params);
//系统配置
const addSystemConfig = (params)=>postAction("/systemConfig/add",params);
const editSystemConfig = (params)=>putAction("/systemConfig/update",params);
const checkSystemConfig = (params)=>getAction("/systemConfig/checkIsNameExist",params);
const getCurrentSystemConfig = (params)=>getAction("/systemConfig/getCurrentInfo",params);
const fileSizeLimit = (params)=>getAction("/systemConfig/fileSizeLimit",params);
//平台参数
const addPlatformConfig = (params)=>postAction("/platformConfig/add",params);
const editPlatformConfig = (params)=>putAction("/platformConfig/update",params);
const getPlatformConfigByKey = (params)=>getAction("/platformConfig/getInfoByKey",params);
//用户|角色|模块关系
const addUserBusiness = (params)=>postAction("/userBusiness/add",params);
const editUserBusiness = (params)=>putAction("/userBusiness/update",params);
const checkUserBusiness = (params)=>getAction("/userBusiness/checkIsValueExist",params);
const updateBtnStrByRoleId = (params)=>postAction("/userBusiness/updateBtnStr",params);
const updateOneValueByKeyIdAndType = (params)=>postAction("/userBusiness/updateOneValueByKeyIdAndType",params);
//多单位
const addUnit = (params)=>postAction("/unit/add",params);
const editUnit = (params)=>putAction("/unit/update",params);
const checkUnit = (params)=>getAction("/unit/checkIsNameExist",params);
//供应商|客户|会员
const addSupplier = (params)=>postAction("/supplier/add",params);
const editSupplier = (params)=>putAction("/supplier/update",params);
const checkSupplier = (params)=>getAction("/supplier/checkIsNameAndTypeExist",params);
const findBySelectSup = (params)=>postAction("/supplier/findBySelect_sup",params);
const findBySelectCus = (params)=>postAction("/supplier/findBySelect_cus",params);
const findBySelectRetail = (params)=>postAction("/supplier/findBySelect_retail",params);
const findBySelectOrgan = (params)=>postAction("/supplier/findBySelect_organ",params);
//单据相关
const findBillDetailByNumber = (params)=>getAction("/depotHead/getDetailByNumber",params);
const waitBillCount = (params)=>getAction("/depotHead/waitBillCount",params);
const getNeedCount = (params)=>getAction("/depotHead/getNeedCount",params);
const batchAddDepotHeadAndDetail = (params)=>postAction("/depotHead/batchAddDepotHeadAndDetail",params);
const findStockByDepotAndBarCode = (params)=>getAction("/depotItem/findStockByDepotAndBarCode",params);
const getBatchNumberList = (params)=>getAction("/depotItem/getBatchNumberList",params);
const findFinancialDetailByNumber = (params)=>getAction("/accountHead/getDetailByNumber",params);
export {
getBuyAndSaleStatistics,
buyOrSalePrice,
checkTenant,
addTenant,
editTenant,
addRole,
editRole,
checkRole,
roleAllList,
getTenantRoleList,
registerUser,
addUser,
editUser,
getUserList,
getUserBtnByCurrentUser,
queryPermissionsByUser,
queryOrganizationTreeList,
getAllOrganizationTreeByUser,
queryOrganizationById,
checkOrganization,
addPerson,
editPerson,
checkPerson,
getPersonByType,
getPersonByNumType,
addAccount,
editAccount,
checkAccount,
getAccount,
addInOutItem,
editInOutItem,
checkInOutItem,
findInOutItemByParam,
addDepot,
editDepot,
checkDepot,
addOrUpdateMaterialProperty,
queryMaterialCategoryTreeList,
queryMaterialCategoryById,
checkMaterialCategory,
addMaterial,
editMaterial,
checkMaterial,
getMaterialBySelect,
getSerialMaterialBySelect,
getMaterialByParam,
getMaterialByBarCode,
getMaxBarCode,
checkMaterialBarCode,
batchUpdateMaterial,
changeNameToPinYin,
batAddSerialNumber,
getEnableSerialNumberList,
addMaterialAttribute,
editMaterialAttribute,
checkMaterialAttribute,
getMaterialAttributeNameList,
getMaterialAttributeValueListById,
addFunction,
editFunction,
checkFunction,
checkNumber,
addSystemConfig,
editSystemConfig,
checkSystemConfig,
getCurrentSystemConfig,
fileSizeLimit,
addPlatformConfig,
editPlatformConfig,
getPlatformConfigByKey,
addUserBusiness,
editUserBusiness,
checkUserBusiness,
updateBtnStrByRoleId,
updateOneValueByKeyIdAndType,
addUnit,
editUnit,
checkUnit,
addSupplier,
editSupplier,
checkSupplier,
findBySelectSup,
findBySelectCus,
findBySelectRetail,
findBySelectOrgan,
findBillDetailByNumber,
waitBillCount,
getNeedCount,
batchAddDepotHeadAndDetail,
findStockByDepotAndBarCode,
getBatchNumberList,
findFinancialDetailByNumber
}
+10
View File
@@ -0,0 +1,10 @@
const api = {
Login: '/user/login',
Logout: '/sys/logout',
ForgePassword: '/auth/forge-password',
Register: '/auth/register',
SendSms: '/account/sms',
// get my info
UserInfo: '/user/info'
}
export default api
+28
View File
@@ -0,0 +1,28 @@
import api from './index'
import { axios } from '@/utils/request'
/**
* login func
* parameter: {
* username: '',
* password: '',
* remember_me: true,
* captcha: '12345'
* }
* @param parameter
* @returns {*}
*/
export function login(parameter) {
return axios({
url: '/user/login',
method: 'post',
data: parameter
})
}
export function logout() {
return axios({
url: '/user/logout',
method: 'get'
})
}
+164
View File
@@ -0,0 +1,164 @@
import Vue from 'vue'
import { axios } from '@/utils/request'
const api = {
user: '/api/user',
role: '/api/role',
service: '/api/service',
permission: '/api/permission',
permissionNoPager: '/api/permission/no-pager',
exportExcelByParam: '/systemConfig/exportExcelByParam'
}
export default api
//post
export function postAction(url,parameter) {
return axios({
url: url,
method:'post' ,
data: parameter
})
}
//post method= {post | put}
export function httpAction(url,parameter,method) {
return axios({
url: url,
method:method ,
data: parameter
})
}
//put
export function putAction(url,parameter) {
return axios({
url: url,
method:'put',
data: parameter
})
}
//get
export function getAction(url,parameter) {
return axios({
url: url,
method: 'get',
params: parameter
})
}
//deleteAction
export function deleteAction(url,parameter) {
return axios({
url: url,
method: 'delete',
params: parameter
})
}
export function getUserList(parameter) {
return axios({
url: api.user,
method: 'get',
params: parameter
})
}
export function getRoleList(parameter) {
return axios({
url: api.role,
method: 'get',
params: parameter
})
}
export function getServiceList(parameter) {
return axios({
url: api.service,
method: 'get',
params: parameter
})
}
export function getPermissions(parameter) {
return axios({
url: api.permissionNoPager,
method: 'get',
params: parameter
})
}
// id == 0 add post
// id != 0 update put
export function saveService(parameter) {
return axios({
url: api.service,
method: parameter.id == 0 ? 'post' : 'put',
data: parameter
})
}
/**
* 下载文件 用于excel导出
* @param url
* @param parameter
* @returns {*}
*/
export function downFile(url,parameter){
return axios({
url: url,
params: parameter,
method: 'get',
responseType: 'blob'
})
}
/**
* 下载文件 用于excel导出
* @param url
* @param parameter
* @returns {*}
*/
export function downFilePost(parameter){
return axios({
url: api.exportExcelByParam,
data: parameter,
method: 'post',
responseType: 'blob'
})
}
/**
* 文件上传 用于富文本上传图片
* @param url
* @param parameter
* @returns {*}
*/
export function uploadAction(url,parameter){
return axios({
url: url,
data: parameter,
method:'post' ,
headers: {
'Content-Type': 'multipart/form-data', // 文件上传
},
})
}
/**
* 获取文件服务访问路径
* @param avatar
* @param subStr
* @returns {*}
*/
export function getFileAccessHttpUrl(avatar,subStr) {
if(!subStr) subStr = 'http'
if(avatar && avatar.startsWith(subStr)){
return avatar;
}else{
if(avatar && avatar.length>0 && avatar.indexOf('[')==-1){
return window._CONFIG['domianURL'] + "/" + avatar;
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

+40
View File
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>Group 5 Copy 5</title>
<desc>Created with Sketch.</desc>
<defs>
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="setting-copy-2" transform="translate(-1190.000000, -136.000000)">
<g id="Group-8" transform="translate(1167.000000, 0.000000)">
<g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
<mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use>
</mask>
<g id="Rectangle-18">
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
</g>
<rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="-1" y="0" width="49" height="10"></rect>
<rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="44"></rect>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

+259
View File
@@ -0,0 +1,259 @@
.area-zoom-in-top-enter-active,
.area-zoom-in-top-leave-active {
opacity: 1;
transform: scaleY(1);
}
.area-zoom-in-top-enter,
.area-zoom-in-top-leave-active {
opacity: 0;
transform: scaleY(0);
}
.area-select {
box-sizing: border-box;
margin: 0;
padding: 0;
color: rgba(0, 0, 0, 0.65);
font-size: 14px;
font-variant: tabular-nums;
line-height: 1.5;
list-style: none;
font-feature-settings: 'tnum';
position: relative;
outline: 0;
display: block;
background-color: #fff;
border: 1px solid #d9d9d9;
border-top-width: 1.02px;
border-radius: 4px;
outline: none;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
.area-select-wrap .area-select {
display: inline-block;
}
.area-select * {
box-sizing: border-box;
}
.area-select:hover {
border-color: #40a9ff;
border-right-width: 1px !important;
outline: 0;
}
.area-select:active {
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.area-select.small {
width: 126px;
}
.area-select.medium {
width: 160px;
}
.area-select.large {
width: 194px;
}
.area-select.is-disabled {
background: #eceff5;
cursor: not-allowed;
}
.area-select.is-disabled:hover {
border-color: #e1e2e6;
}
.area-select.is-disabled .area-selected-trigger {
cursor: not-allowed;
}
.area-select .area-selected-trigger {
position: relative;
display: block;
font-size: 14px;
cursor: pointer;
margin: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
height: 100%;
padding: 8px 20px 7px 12px;
}
.area-select .area-select-icon {
position: absolute;
top: 50%;
margin-top: -2px;
right: 6px;
content: "";
width: 0;
height: 0;
border: 6px solid transparent;
border-top-color: rgba(0, 0, 0, 0.25);
transition: all .3s linear;
transform-origin: center;
}
.area-select .area-select-icon.active {
margin-top: -8px;
transform: rotate(180deg);
}
.area-selectable-list-wrap {
position: absolute;
width: 100%;
max-height: 275px;
z-index: 15000;
background-color: #fff;
box-sizing: border-box;
overflow-x: auto;
margin: 2px 0;
border-radius: 4px;
outline: none;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
transition: opacity 0.15s, transform 0.3s !important;
transform-origin: center top !important;
}
.area-selectable-list {
position: relative;
margin: 0;
padding: 6px 0;
width: 100%;
font-size: 14px;
color: #565656;
list-style: none;
}
.area-selectable-list .area-select-option {
position: relative;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
padding: 0 15px 0 10px;
height: 32px;
line-height: 32px;
}
.area-selectable-list .area-select-option.hover {
background-color: #e6f7ff;
}
.area-selectable-list .area-select-option.selected {
color: rgba(0, 0, 0, 0.65);
font-weight: 600;
background-color: #efefef;
}
.cascader-menu-list-wrap {
position: absolute;
white-space: nowrap;
z-index: 15000;
background-color: #fff;
box-sizing: border-box;
overflow: hidden;
font-size: 0;
margin: 2px 0;
border-radius: 4px;
outline: none;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
transition: opacity 0.15s, transform 0.3s !important;
transform-origin: center top !important;
}
.cascader-menu-list {
position: relative;
margin: 0;
font-size: 14px;
color: #565656;
padding: 6px 0;
list-style: none;
display: inline-block;
height: 204px;
overflow-x: hidden;
overflow-y: auto;
min-width: 160px;
vertical-align: top;
background-color: #fff;
border-right: 1px solid #e4e7ed;
}
.cascader-menu-list:last-child {
border-right: none;
}
.cascader-menu-list .cascader-menu-option {
position: relative;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
padding: 0 15px 0 10px;
height: 32px;
line-height: 32px;
}
.cascader-menu-list .cascader-menu-option.hover,
.cascader-menu-list .cascader-menu-option:hover {
background-color: #e6f7ff;
}
.cascader-menu-list .cascader-menu-option.selected {
color: rgba(0, 0, 0, 0.65);
font-weight: 600;
background-color: #efefef;
}
.cascader-menu-list .cascader-menu-option.cascader-menu-extensible:after {
position: absolute;
top: 50%;
margin-top: -4px;
right: 5px;
content: "";
width: 0;
height: 0;
border: 4px solid transparent;
border-left-color: #a1a4ad;
}
.cascader-menu-list::-webkit-scrollbar,
.area-selectable-list-wrap::-webkit-scrollbar {
width: 8px;
background: transparent;
}
.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:decremen,
.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:end:decrement,
.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:increment,
.area-selectable-list-wrap::-webkit-scrollbar-button:vertical:start:increment,
.cascader-menu-list::-webkit-scrollbar-button:vertical:decremen,
.cascader-menu-list::-webkit-scrollbar-button:vertical:end:decrement,
.cascader-menu-list::-webkit-scrollbar-button:vertical:increment,
.cascader-menu-list::-webkit-scrollbar-button:vertical:start:increment {
display: none;
}
.cascader-menu-list::-webkit-scrollbar-thumb:vertical,
.area-selectable-list-wrap::-webkit-scrollbar-thumb:vertical {
background-color: #b8b8b8;
border-radius: 4px;
}
.cascader-menu-list::-webkit-scrollbar-thumb:vertical:hover,
.area-selectable-list-wrap::-webkit-scrollbar-thumb:vertical:hover {
background-color: #777;
}
+15
View File
@@ -0,0 +1,15 @@
/** [表格主题样式一] 表格强制列不换行 */
.j-table-force-nowrap {
td, th {
white-space: nowrap;
}
.ant-table-selection-column {
padding: 12px 22px !important;
}
/** 列自适应,弊端会导致列宽失效 */
&.ant-table-wrapper .ant-table-content {
overflow-x: auto;
}
}
+96
View File
@@ -0,0 +1,96 @@
/*列表上方操作按钮区域*/
.ant-card-body .table-operator {
margin-bottom: 0px;
}
/** Button按钮间距 */
.table-operator .ant-btn {
margin: 0 8px 8px 0;
}
.table-operator .ant-btn-group .ant-btn {
margin: 0;
}
.table-operator .ant-btn-group .ant-btn:last-child {
margin: 0 8px 8px 0;
}
/*列表td的padding设置 可以控制列表大小*/
.ant-table-tbody .ant-table-row td {
padding-top: 15px;
padding-bottom: 15px;
}
.depot-mask {
margin-top: 93px;
margin-left: 154px;
}
/*列表页面弹出modal*/
.ant-modal-cust-warp {
height: 100%
}
/*弹出modal Y轴滚动条*/
.ant-modal-cust-warp .ant-modal-body {
padding: 24px 24px 12px 24px;
height: calc(100% - 110px) !important;
overflow-y: auto
}
/*弹出modal 先有content后有body 故滚动条控制在body上*/
.ant-modal-cust-warp .ant-modal-content {
height: 90%;
overflow-y: hidden
}
/*文本框样式*/
.ant-modal-cust-warp .ant-form-item {
margin-bottom: 12px;
}
/*全屏模式*/
.ant-modal-cust-warp .fullscreen >.ant-modal-content {
height: 100vh;
border-radius: 0;
}
/*全屏模式*/
.ant-modal-cust-warp .fullscreen >.ant-modal-content >.ant-modal-body {
padding: 24px 24px 12px 24px;
height: calc(100% - 200px) !important;
overflow-y: auto
}
/*列表中有图片的加这个样式 参考用户管理*/
.anty-img-wrap {
height: 25px;
position: relative;
}
.anty-img-wrap > img {
max-height: 100%;
}
/*列表中范围查询样式*/
.query-group-cust{width: calc(50% - 10px)}
.query-group-split-cust:before{content:"~";width: 20px;display: inline-block;text-align: center}
/*erp风格子表外框padding设置*/
.ant-card-wider-padding.cust-erp-sub-tab>.ant-card-body{padding:5px 12px}
/* 内嵌子表背景颜色 */
.j-inner-table-wrapper /deep/ .ant-table-expanded-row .ant-table-wrapper .ant-table-tbody .ant-table-row {
background-color: #FFFFFF;
}
.ant-modal-mask {
background-color: rgba(0, 0, 0, 0.1) !important;
}
/* 拖拽 */
.table-draggable-handle {
/* width: 10px !important; */
height: 100% !important;
left: auto !important;
right: -5px;
cursor: col-resize;
touch-action: none;
border: none;
position: absolute;
transform: none !important;
bottom: 0;
}
.resize-table-th {
position: relative;
}
/* 单据下拉框-按钮 */
.dropdown-btn {
float:left;
padding: 4px 8px;
cursor: pointer;
}
+28
View File
@@ -0,0 +1,28 @@
/**
* 列表查询通用样式,移动端自适应
*/
.search{
margin-bottom: 54px;
}
.fold{
width: calc(100% - 216px);
display: inline-block
}
.operator{
margin-bottom: 18px;
}
@media screen and (max-width: 900px) {
.fold {
width: 100%;
}
}
.operator button {
margin-right: 5px;
}
i {
cursor: pointer;
}
.trcolor{
background-color: rgba(255, 192, 203, 0.31);
color:red;
}
+40
View File
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
<title>Group 5</title>
<desc>Created with Sketch.</desc>
<defs>
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge>
</filter>
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter>
</defs>
<g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="setting-copy-2" transform="translate(-1254.000000, -136.000000)">
<g id="Group-8" transform="translate(1167.000000, 0.000000)">
<g id="Group-5" filter="url(#filter-1)" transform="translate(89.000000, 137.000000)">
<mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use>
</mask>
<g id="Rectangle-18">
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
</g>
<rect id="Rectangle-18" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="16" height="44"></rect>
<rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="-1" y="0" width="49" height="10"></rect>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

+46
View File
@@ -0,0 +1,46 @@
<template>
<tooltip v-if="tips !== ''">
<template slot="title">{{ tips }}</template>
<avatar :size="avatarSize" :src="src" />
</tooltip>
<avatar v-else :size="avatarSize" :src="src" />
</template>
<script>
import Avatar from 'ant-design-vue/es/avatar'
import Tooltip from 'ant-design-vue/es/tooltip'
export default {
name: "AvatarItem",
components: {
Avatar,
Tooltip
},
props: {
tips: {
type: String,
default: '',
required: false
},
src: {
type: String,
default: ''
}
},
data () {
return {
size: this.$parent.size
}
},
computed: {
avatarSize () {
return this.size !== 'mini' && this.size || 20
}
},
watch: {
'$parent.size' (val) {
this.size = val
}
}
}
</script>
+91
View File
@@ -0,0 +1,91 @@
<!--
<template>
<div :class="[prefixCls]">
<ul>
<slot></slot>
<template v-for="item in filterEmpty($slots.default).slice(0, 3)"></template>
<template v-if="maxLength > 0 && filterEmpty($slots.default).length > maxLength">
<avatar-item :size="size">
<avatar :size="size !== 'mini' && size || 20" :style="excessItemsStyle">{{ `+${maxLength}` }}</avatar>
</avatar-item>
</template>
</ul>
</div>
</template>
-->
<script>
import Avatar from 'ant-design-vue/es/avatar'
import AvatarItem from './Item'
export default {
AvatarItem,
name: "AvatarList",
components: {
Avatar,
AvatarItem
},
props: {
prefixCls: {
type: String,
default: 'ant-pro-avatar-list'
},
/**
* 头像大小 类型: large、small 、mini, default
* 默认值: default
*/
size: {
type: [String, Number],
default: 'default'
},
/**
* 要显示的最大项目
*/
maxLength: {
type: Number,
default: 0
},
/**
* 多余的项目风格
*/
excessItemsStyle: {
type: Object,
default: () => {
return {
color: '#f56a00',
backgroundColor: '#fde3cf'
}
}
}
},
data () {
return {}
},
methods: {
getItems(items) {
const classString = {
[`${this.prefixCls}-item`]: true,
[`${this.size}`]: true
}
if (this.maxLength > 0) {
items = items.slice(0, this.maxLength)
items.push((<Avatar size={ this.size } style={ this.excessItemsStyle }>{`+${this.maxLength}`}</Avatar>))
}
const itemList = items.map((item) => (
<li class={ classString }>{ item }</li>
))
return itemList
}
},
render () {
const { prefixCls, size } = this.$props
const classString = {
[`${prefixCls}`]: true,
[`${size}`]: true,
}
}
}
</script>
+4
View File
@@ -0,0 +1,4 @@
import AvatarList from './List'
import "./index.less"
export default AvatarList
+60
View File
@@ -0,0 +1,60 @@
@import "../index";
@avatar-list-prefix-cls: ~"@{ant-pro-prefix}-avatar-list";
@avatar-list-item-prefix-cls: ~"@{ant-pro-prefix}-avatar-list-item";
.@{avatar-list-prefix-cls} {
display: inline-block;
ul {
list-style: none;
display: inline-block;
padding: 0;
margin: 0 0 0 8px;
font-size: 0;
}
}
.@{avatar-list-item-prefix-cls} {
display: inline-block;
font-size: @font-size-base;
margin-left: -8px;
width: @avatar-size-base;
height: @avatar-size-base;
:global {
.ant-avatar {
border: 1px solid #fff;
cursor: pointer;
}
}
&.large {
width: @avatar-size-lg;
height: @avatar-size-lg;
}
&.small {
width: @avatar-size-sm;
height: @avatar-size-sm;
}
&.mini {
width: 20px;
height: 20px;
:global {
.ant-avatar {
width: 20px;
height: 20px;
line-height: 20px;
.ant-avatar-string {
font-size: 12px;
line-height: 18px;
}
}
}
}
}
+105
View File
@@ -0,0 +1,105 @@
<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>
<div class="chart-card-content">
<div class="content-fix">
<slot></slot>
</div>
</div>
</a-card>
</template>
<script>
export default {
name: "ChartCard",
props: {
title: {
type: String,
default: ''
},
total: {
type: String,
default: ''
},
loading: {
type: Boolean,
default: false
}
}
}
</script>
<style lang="less" scoped>
.chart-card-header {
position: relative;
overflow: hidden;
width: 100%;
.meta {
position: relative;
overflow: hidden;
width: 100%;
color: rgba(0, 0, 0, .45);
font-size: 14px;
line-height: 22px;
}
}
.chart-card-action {
cursor: pointer;
position: absolute;
top: 0;
right: 0;
}
.chart-card-footer {
border-top: 1px solid #e8e8e8;
padding-top: 9px;
margin-top: 8px;
> * {
position: relative;
}
.field {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin: 0;
}
}
.chart-card-content {
margin-bottom: 12px;
position: relative;
height: 46px;
width: 100%;
.content-fix {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
}
}
.total {
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
white-space: nowrap;
color: #000;
margin-top: 4px;
margin-bottom: 0;
font-size: 30px;
line-height: 38px;
height: 38px;
}
</style>
+103
View File
@@ -0,0 +1,103 @@
<template>
<span>
{{ lastTime | format }}
</span>
</template>
<script>
function fixedZero(val) {
return val * 1 < 10 ? `0${val}` : val;
}
export default {
name: "CountDown",
props: {
format: {
type: Function,
default: undefined
},
target: {
type: [Date, Number],
required: true,
},
onEnd: {
type: Function,
default: () => {
}
}
},
data() {
return {
dateTime: '0',
originTargetTime: 0,
lastTime: 0,
timer: 0,
interval: 1000
}
},
filters: {
format(time) {
const hours = 60 * 60 * 1000;
const minutes = 60 * 1000;
const h = Math.floor(time / hours);
const m = Math.floor((time - h * hours) / minutes);
const s = Math.floor((time - h * hours - m * minutes) / 1000);
return `${fixedZero(h)}:${fixedZero(m)}:${fixedZero(s)}`
}
},
created() {
this.initTime()
this.tick()
},
methods: {
initTime() {
let lastTime = 0;
let targetTime = 0;
this.originTargetTime = this.target
try {
if (Object.prototype.toString.call(this.target) === '[object Date]') {
targetTime = this.target
} else {
targetTime = new Date(this.target).getTime()
}
} catch (e) {
throw new Error('invalid target prop')
}
lastTime = targetTime - new Date().getTime();
this.lastTime = lastTime < 0 ? 0 : lastTime
},
tick() {
const {onEnd} = this
this.timer = setTimeout(() => {
if (this.lastTime < this.interval) {
clearTimeout(this.timer)
this.lastTime = 0
if (typeof onEnd === 'function') {
onEnd();
}
} else {
this.lastTime -= this.interval
this.tick()
}
}, this.interval)
}
},
beforeUpdate () {
if (this.originTargetTime !== this.target) {
this.initTime()
}
},
beforeDestroy() {
clearTimeout(this.timer)
}
}
</script>
<style scoped>
</style>
+3
View File
@@ -0,0 +1,3 @@
import CountDown from './CountDown'
export default CountDown
+36
View File
@@ -0,0 +1,36 @@
<script>
export default {
name: 'Ellipsis',
props: {
prefixCls: {
type: String,
default: 'ant-pro-ellipsis'
},
tooltip: {
type: Boolean,
default: true,
},
length: {
type: Number,
default: 25,
},
lines: {
type: Number,
default: 1
},
fullWidthRecognition: {
type: Boolean,
default: false
}
},
methods: {},
render() {
const { tooltip, length } = this.$props
let text = ''
// 处理没有default插槽时的特殊情况
if (this.$slots.default) {
text = this.$slots.default.map(vNode => vNode.text).join('')
}
}
}
</script>
+3
View File
@@ -0,0 +1,3 @@
import Ellipsis from './Ellipsis'
export default Ellipsis
+54
View File
@@ -0,0 +1,54 @@
<template>
<div :class="[prefixCls]">
<slot name="subtitle">
<div :class="[`${prefixCls}-subtitle`]">{{ typeof subTitle === 'string' ? subTitle : subTitle() }}</div>
</slot>
<div class="number-info-value">
<span>{{ total }}</span>
<span class="sub-total">
{{ subTotal }}
<icon :type="`caret-${status}`" />
</span>
</div>
</div>
</template>
<script>
import Icon from 'ant-design-vue/es/icon'
export default {
name: 'NumberInfo',
props: {
prefixCls: {
type: String,
default: 'ant-pro-number-info'
},
total: {
type: Number,
required: true
},
subTotal: {
type: Number,
required: true
},
subTitle: {
type: [String, Function],
default: ''
},
status: {
type: String,
default: 'up'
}
},
components: {
Icon
},
data () {
return {}
}
}
</script>
<style lang="less" scoped>
/*@import "index";*/
</style>
+3
View File
@@ -0,0 +1,3 @@
import NumberInfo from './NumberInfo'
export default NumberInfo
+55
View File
@@ -0,0 +1,55 @@
@import "../index";
@numberInfo-prefix-cls: ~"@{ant-pro-prefix}-number-info";
.@{numberInfo-prefix-cls} {
.ant-pro-number-info-subtitle {
color: @text-color-secondary;
font-size: @font-size-base;
height: 22px;
line-height: 22px;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
white-space: nowrap;
}
.number-info-value {
margin-top: 4px;
font-size: 0;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
white-space: nowrap;
& > span {
color: @heading-color;
display: inline-block;
line-height: 32px;
height: 32px;
font-size: 24px;
margin-right: 32px;
}
.sub-total {
color: @text-color-secondary;
font-size: @font-size-lg;
vertical-align: top;
margin-right: 0;
i {
font-size: 12px;
transform: scale(0.82);
margin-left: 4px;
}
:global {
.anticon-caret-up {
color: @red-6;
}
.anticon-caret-down {
color: @green-6;
}
}
}
}
}
+41
View File
@@ -0,0 +1,41 @@
<template>
<div :class="[prefixCls, reverseColor && 'reverse-color' ]">
<span>
<slot name="term"></slot>
<span class="item-text">
<slot></slot>
</span>
</span>
<span :class="[flag]"><a-icon :type="`caret-${flag}`"/></span>
</div>
</template>
<script>
export default {
name: "Trend",
props: {
prefixCls: {
type: String,
default: 'ant-pro-trend'
},
/**
* 上升下降标识:up|down
*/
flag: {
type: String,
required: true
},
/**
* 颜色反转
*/
reverseColor: {
type: Boolean,
default: false
}
}
}
</script>
<style lang="less" scoped>
@import "index";
</style>
+3
View File
@@ -0,0 +1,3 @@
import Trend from './Trend.vue'
export default Trend
+42
View File
@@ -0,0 +1,42 @@
@import "../index";
@trend-prefix-cls: ~"@{ant-pro-prefix}-trend";
.@{trend-prefix-cls} {
display: inline-block;
font-size: @font-size-base;
line-height: 22px;
.up,
.down {
margin-left: 4px;
position: relative;
top: 1px;
i {
font-size: 12px;
transform: scale(0.83);
}
}
.item-text {
display: inline-block;
margin-left: 8px;
color: rgba(0,0,0,.85);
}
.up {
color: @red-6;
}
.down {
color: @green-6;
top: -1px;
}
&.reverse-color .up {
color: @green-6;
}
&.reverse-color .down {
color: @red-6;
}
}
+88
View File
@@ -0,0 +1,88 @@
<template>
<div :style="{ padding: '0' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart ref="chart" :forceFit="true" :height="height" :data="dataSource" :scale="scale">
<v-tooltip :shared="false"/>
<v-axis/>
<v-line position="x*y" :size="lineSize" :color="lineColor"/>
<v-area position="x*y" :color="color"/>
</v-chart>
</div>
</template>
<script>
import { triggerWindowResizeEvent } from '@/utils/util'
export default {
name: 'AreaChartTy',
props: {
// 图表数据
dataSource: {
type: Array,
required: true
},
// 图表标题
title: {
type: String,
default: ''
},
// x 轴别名
x: {
type: String,
default: 'x'
},
// y 轴别名
y: {
type: String,
default: 'y'
},
// Y轴最小值
min: {
type: Number,
default: 0
},
// Y轴最大值
max: {
type: Number,
default: null
},
// 图表高度
height: {
type: Number,
default: 254
},
// 线的粗细
lineSize: {
type: Number,
default: 2
},
// 面积的颜色
color: {
type: String,
default: ''
},
// 线的颜色
lineColor: {
type: String,
default: ''
}
},
computed: {
scale() {
return [
{ dataKey: 'x', title: this.x, alias: this.x },
{ dataKey: 'y', title: this.y, alias: this.y, min: this.min, max: this.max }
]
}
},
mounted() {
triggerWindowResizeEvent()
}
}
</script>
<style lang="less" scoped>
@import "chart";
</style>
+55
View File
@@ -0,0 +1,55 @@
<template>
<div :style="{ padding: '0 0 32px 32px' }">
<h3 :style="{ marginBottom: '20px' }">{{ title }}</h3>
<v-chart :forceFit="true" :height="height" :data="dataSource" :scale="scale" :padding="padding">
<v-tooltip/>
<v-axis/>
<v-bar position="x*y" :color="color"/>
</v-chart>
</div>
</template>
<script>
import { triggerWindowResizeEvent } from '@/utils/util'
import { DEFAULT_COLOR } from "@/store/mutation-types"
import Vue from 'vue'
export default {
name: 'Bar',
props: {
dataSource: {
type: Array,
required: true
},
yaxisText: {
type: String,
default: 'y'
},
title: {
type: String,
default: ''
},
height: {
type: Number,
default: 254
}
},
data() {
return {
padding: ['auto', 'auto', '40', '50'],
color: Vue.ls.get(DEFAULT_COLOR)
}
},
computed: {
scale() {
return [{
dataKey: 'y',
alias: this.yaxisText
}]
}
},
mounted() {
triggerWindowResizeEvent()
}
}
</script>
+60
View File
@@ -0,0 +1,60 @@
<template>
<div :style="{ padding: '0 50px 32px 0' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart :forceFit="true" :height="height" :data="data" :scale="scale" :padding=" padding" :onClick="handleClick">
<v-tooltip/>
<v-legend/>
<v-axis/>
<v-bar position="type*bar"/>
<v-line position="type*line" color="#2fc25b" :size="3"/>
</v-chart>
</div>
</template>
<script>
import { ChartEventMixins } from './mixins/ChartMixins'
export default {
name: 'BarAndLine',
mixins: [ChartEventMixins],
props: {
title: {
type: String,
default: ''
},
dataSource: {
type: Array,
default: () => [
{ type: '10:10', bar: 200, line: 1000 },
{ type: '10:15', bar: 600, line: 1000},
{ type: '10:20', bar: 200, line: 1000},
{ type: '10:25', bar: 900, line: 1000},
{ type: '10:30', bar: 200, line: 1000},
{ type: '10:35', bar: 200, line: 1000},
{ type: '10:40', bar: 100, line: 1000}
]
},
height: {
type: Number,
default: 400
}
},
data() {
return {
padding: { top:50, right:50, bottom:100, left:50 },
scale: [{
dataKey: 'bar',
min: 0
}, {
dataKey: 'line',
min: 0
}]
}
},
computed: {
data() {
return this.dataSource
}
}
}
</script>
+88
View File
@@ -0,0 +1,88 @@
<template>
<div :style="{ padding: '0 0 32px 32px' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart :data="data" :height="height" :force-fit="true" :onClick="handleClick">
<v-tooltip/>
<v-axis/>
<v-legend/>
<v-bar position="x*y" color="type" :adjust="adjust"/>
</v-chart>
</div>
</template>
<script>
import { DataSet } from '@antv/data-set'
import { ChartEventMixins } from './mixins/ChartMixins'
export default {
name: 'BarMultid',
mixins: [ChartEventMixins],
props: {
title: {
type: String,
default: ''
},
dataSource: {
type: Array,
default: () => [
{ type: 'Jeecg', 'Jan.': 18.9, 'Feb.': 28.8, 'Mar.': 39.3, 'Apr.': 81.4, 'May': 47, 'Jun.': 20.3, 'Jul.': 24, 'Aug.': 35.6 },
{ type: 'Jeebt', 'Jan.': 12.4, 'Feb.': 23.2, 'Mar.': 34.5, 'Apr.': 99.7, 'May': 52.6, 'Jun.': 35.5, 'Jul.': 37.4, 'Aug.': 42.4 }
]
},
fields: {
type: Array,
default: () => ['Jan.', 'Feb.', 'Mar.', 'Apr.', 'May', 'Jun.', 'Jul.', 'Aug.']
},
// 别名,需要的格式:[{field:'name',alias:'姓名'}, {field:'sex',alias:'性别'}]
aliases: {
type: Array,
default: () => []
},
height: {
type: Number,
default: 254
}
},
data() {
return {
adjust: [{
type: 'dodge',
marginRatio: 1 / 32
}]
}
},
computed: {
data() {
const dv = new DataSet.View().source(this.dataSource)
dv.transform({
type: 'fold',
fields: this.fields,
key: 'x',
value: 'y'
})
// bar 使用不了 - 和 / 所以替换下
let rows = dv.rows.map(row => {
if (typeof row.x === 'string') {
row.x = row.x.replace(/[-/]/g, '_')
}
return row
})
// 替换别名
rows.forEach(row => {
for (let item of this.aliases) {
if (item.field === row.type) {
row.type = item.alias
break
}
}
})
return rows
}
}
}
</script>
<style scoped>
</style>
+187
View File
@@ -0,0 +1,187 @@
<template>
<div :style="{ padding: '0 0 32px 32px' }">
<v-chart :forceFit="true" :height="300" :data="chartData" :scale="scale">
<v-coord type="polar" :startAngle="-202.5" :endAngle="22.5" :radius="0.75"></v-coord>
<v-axis
dataKey="value"
:zIndex="2"
:line="null"
:label="axisLabel"
:subTickCount="4"
:subTickLine="axisSubTickLine"
:tickLine="axisTickLine"
:grid="null"
></v-axis>
<v-axis dataKey="1" :show="false"></v-axis>
<v-series
gemo="point"
position="value*1"
shape="pointer"
color="#1890FF"
:active="false"
></v-series>
<v-guide
type="arc"
:zIndex="0"
:top="false"
:start="arcGuide1Start"
:end="arcGuide1End"
:vStyle="arcGuide1Style"
></v-guide>
<v-guide
type="arc"
:zIndex="1"
:start="arcGuide2Start"
:end="getArcGuide2End"
:vStyle="arcGuide2Style"
></v-guide>
<v-guide
type="html"
:position="htmlGuidePosition"
:html="getHtmlGuideHtml()"
></v-guide>
</v-chart>
</div>
</template>
<script>
import { registerShape } from 'viser-vue';
registerShape('point', 'pointer', {
draw(cfg, container) {
let point = cfg.points[0];
point = this.parsePoint(point);
const center = this.parsePoint({
x: 0,
y: 0,
});
container.addShape('line', {
attrs: {
x1: center.x,
y1: center.y,
x2: point.x,
y2: point.y + 15,
stroke: cfg.color,
lineWidth: 5,
lineCap: 'round',
}
});
return container.addShape('circle', {
attrs: {
x: center.x,
y: center.y,
r: 9.75,
stroke: cfg.color,
lineWidth: 4.5,
fill: '#fff',
}
});
}
});
const scale = [{
dataKey: 'value',
min: 0,
max: 9,
tickInterval: 1,
nice: false,
}];
const data = [
{ value: 7.0 },
];
export default {
name:"DashChartDemo",
props:{
datasource:{
type: Number,
default:7
},
title: {
type: String,
default: ''
}
},
created(){
if(!this.datasource){
this.chartData = data;
}else{
this.chartData = [
{ value: this.datasource },
];
}
this.getChartData()
},
watch: {
'datasource': function (val) {
this.chartData = [
{ value: val},
];
this.getChartData();
}
},
methods:{
getChartData(){
if(this.chartData && this.chartData.length>0){
this.abcd = this.chartData[0].value * 10
}else{
this.abcd = 70
}
},
getHtmlGuideHtml(){
return '<div style="width: 300px;text-align: center;">\n' +
'<p style="font-size: 14px;color: #545454;margin: 0;">'+this.title+'</p>\n' +
'<p style="font-size: 36px;color: #545454;margin: 0;">'+this.abcd+'%</p>\n' +
'</div>'
},
getArcGuide2End(){
return [this.chartData[0].value, 0.945]
}
},
data() {
return {
chartData:[],
height: 400,
scale: scale,
abcd:70,
axisLabel: {
offset: -16,
textStyle: {
fontSize: 18,
textAlign: 'center',
textBaseline: 'middle'
}
},
axisSubTickLine: {
length: -8,
stroke: '#fff',
strokeOpacity: 1,
},
axisTickLine: {
length: -17,
stroke: '#fff',
strokeOpacity: 1,
},
arcGuide1Start: [0, 0.945],
arcGuide1End: [9, 0.945],
arcGuide1Style: {
stroke: '#CBCBCB',
lineWidth: 18,
},
arcGuide2Start: [0, 0.945],
arcGuide2Style: {
stroke: '#1890FF',
lineWidth: 18,
},
htmlGuidePosition: ['50%', '100%'],
htmlGuideHtml: `
<div style="width: 300px;text-align: center;">
<p style="font-size: 14px;color: #545454;margin: 0;">${this.title}</p>
<p style="font-size: 36px;color: #545454;margin: 0;">${this.abcd}%</p>
</div>
`,
};
},
};
</script>
+61
View File
@@ -0,0 +1,61 @@
<template>
<div :style="{ padding: '0 0 32px 32px' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart
height="254"
:data="datasource"
:forceFit="true"
:padding="['auto', 'auto', '40', '50']">
<v-tooltip />
<v-axis />
<v-bar position="x*y"/>
</v-chart>
</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: {
type: String,
default: ''
}
},
mounted(){
this.datasource = data
},
data () {
return {
datasource:[],
scale,
tooltip
}
}
}
</script>
+94
View File
@@ -0,0 +1,94 @@
<template>
<div :style="{ padding: '0 0 32px 32px' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart :force-fit="true" :height="height" :data="data" :scale="scale" :onClick="handleClick">
<v-tooltip/>
<v-axis/>
<v-legend/>
<v-line position="type*y" color="x"/>
<v-point position="type*y" color="x" :size="4" :v-style="style" :shape="'circle'"/>
</v-chart>
</div>
</template>
<script>
import { DataSet } from '@antv/data-set'
import { ChartEventMixins } from './mixins/ChartMixins'
export default {
name: 'LineChartMultid',
mixins: [ChartEventMixins],
props: {
title: {
type: String,
default: ''
},
dataSource: {
type: Array,
default: () => [
{ type: 'Jan', jeecg: 7.0, jeebt: 3.9 },
{ type: 'Feb', jeecg: 6.9, jeebt: 4.2 },
{ type: 'Mar', jeecg: 9.5, jeebt: 5.7 },
{ type: 'Apr', jeecg: 14.5, jeebt: 8.5 },
{ type: 'May', jeecg: 18.4, jeebt: 11.9 },
{ type: 'Jun', jeecg: 21.5, jeebt: 15.2 },
{ type: 'Jul', jeecg: 25.2, jeebt: 17.0 },
{ type: 'Aug', jeecg: 26.5, jeebt: 16.6 },
{ type: 'Sep', jeecg: 23.3, jeebt: 14.2 },
{ type: 'Oct', jeecg: 18.3, jeebt: 10.3 },
{ type: 'Nov', jeecg: 13.9, jeebt: 6.6 },
{ type: 'Dec', jeecg: 9.6, jeebt: 4.8 }
]
},
fields: {
type: Array,
default: () => ['jeecg', 'jeebt']
},
// 别名,需要的格式:[{field:'name',alias:'姓名'}, {field:'sex',alias:'性别'}]
aliases:{
type: Array,
default: () => []
},
height: {
type: Number,
default: 254
}
},
data() {
return {
scale: [{
dataKey: 'x',
min: 0,
max: 1
}],
style: { stroke: '#fff', lineWidth: 1 }
}
},
computed: {
data() {
const dv = new DataSet.View().source(this.dataSource)
dv.transform({
type: 'fold',
fields: this.fields,
key: 'x',
value: 'y'
})
let rows = dv.rows
// 替换别名
rows.forEach(row => {
for (let item of this.aliases) {
if (item.field === row.x) {
row.x = item.alias
break
}
}
})
return rows
}
}
}
</script>
<style scoped>
</style>
+80
View File
@@ -0,0 +1,80 @@
<template>
<div>
<v-chart
:forceFit="true"
:height="height"
:width="width"
:data="data"
:scale="scale"
:padding="0">
<v-tooltip/>
<v-interval
:shape="['liquid-fill-gauge']"
position="transfer*value"
color=""
:v-style="{
lineWidth: 8,
opacity: 0.75
}"
:tooltip="[
'transfer*value',
(transfer, value) => {
return {
name: transfer,
value,
};
},
]"
></v-interval>
<v-guide
v-for="(row, index) in data"
:key="index"
type="text"
:top="true"
:position="{
gender: row.transfer,
value: 45
}"
:content="row.value + '%'"
:v-style="{
fontSize: 100,
textAlign: 'center',
opacity: 0.75,
}"
/>
</v-chart>
</div>
</template>
<script>
const sourceDataConst = [
{ transfer: '一月', value: 813 },
{ transfer: '二月', value: 233 },
{ transfer: '三月', value: 561 }
]
export default {
name: 'Liquid',
props: {
height: {
type: Number,
default: 0
},
width: {
type: Number,
default: 0
}
},
data() {
return {
data: sourceDataConst,
scale: []
}
}
}
</script>
<style scoped>
</style>
+69
View File
@@ -0,0 +1,69 @@
<template>
<div class="antv-chart-mini">
<div class="chart-wrapper" :style="{ height: 46 }">
<v-chart :force-fit="true" :height="height" :data="data" :scale="scale" :padding="[36, 0, 18, 0]">
<v-tooltip/>
<v-smooth-area position="x*y"/>
</v-chart>
</div>
</div>
</template>
<script>
import moment from 'dayjs'
const sourceData = []
const beginDay = new Date().getTime()
for (let i = 0; i < 10; i++) {
sourceData.push({
x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'),
y: Math.round(Math.random() * 10)
})
}
export default {
name: 'MiniArea',
props: {
dataSource: {
type: Array,
default: () => []
},
// x 轴别名
x: {
type: String,
default: 'x'
},
// y 轴别名
y: {
type: String,
default: 'y'
}
},
data() {
return {
data: [],
height: 100
}
},
computed: {
scale() {
return [
{ dataKey: 'x', title: this.x, alias: this.x },
{ dataKey: 'y', title: this.y, alias: this.y }
]
}
},
created() {
if (this.dataSource.length === 0) {
this.data = sourceData
} else {
this.data = this.dataSource
}
}
}
</script>
<style lang="less" scoped>
@import "chart";
</style>
+76
View File
@@ -0,0 +1,76 @@
<template>
<div :style="{'width':width==null?'auto':width+'px'}">
<v-chart :forceFit="width==null" :height="height" :data="data" padding="0">
<v-tooltip/>
<v-bar position="x*y"/>
</v-chart>
</div>
</template>
<script>
import moment from 'dayjs'
const sourceData = []
const beginDay = new Date().getTime()
for (let i = 0; i < 10; i++) {
sourceData.push({
x: moment(new Date(beginDay + 1000 * 60 * 60 * 24 * i)).format('YYYY-MM-DD'),
y: Math.round(Math.random() * 10)
})
}
const tooltip = [
'x*y',
(x, y) => ({
name: x,
value: y
})
]
const scale = [{
dataKey: 'x',
min: 2
}, {
dataKey: 'y',
title: '时间',
min: 1,
max: 30
}]
export default {
name: 'MiniBar',
props: {
dataSource: {
type: Array,
default: () => []
},
width: {
type: Number,
default: null
},
height: {
type: Number,
default: 200
}
},
created() {
if (this.dataSource.length === 0) {
this.data = sourceData
} else {
this.data = this.dataSource
}
},
data() {
return {
tooltip,
data: [],
scale
}
}
}
</script>
<style lang="less" scoped>
@import "chart";
</style>
+75
View File
@@ -0,0 +1,75 @@
<template>
<div class="chart-mini-progress">
<div class="target" :style="{ left: target + '%'}">
<span :style="{ backgroundColor: color }"/>
<span :style="{ backgroundColor: color }"/>
</div>
<div class="progress-wrapper">
<div class="progress" :style="{ backgroundColor: color, width: percentage + '%', height: height+'px' }"></div>
</div>
</div>
</template>
<script>
export default {
name: 'MiniProgress',
props: {
target: {
type: Number,
default: 0
},
height: {
type: Number,
default: 10
},
color: {
type: String,
default: '#13C2C2'
},
percentage: {
type: Number,
default: 0
}
}
}
</script>
<style lang="less" scoped>
.chart-mini-progress {
padding: 5px 0;
position: relative;
width: 100%;
.target {
position: absolute;
top: 0;
bottom: 0;
span {
border-radius: 100px;
position: absolute;
top: 0;
left: 0;
height: 4px;
width: 2px;
&:last-child {
top: auto;
bottom: 0;
}
}
}
.progress-wrapper {
background-color: #f5f5f5;
position: relative;
.progress {
transition: all .4s cubic-bezier(.08, .82, .17, 1) 0s;
border-radius: 1px 0 0 1px;
background-color: #1890ff;
width: 0;
height: 100%;
}
}
}
</style>
+70
View File
@@ -0,0 +1,70 @@
<template>
<v-chart :forceFit="true" :height="height" :data="data" :scale="scale" :onClick="handleClick">
<v-tooltip :showTitle="false" dataKey="item*percent"/>
<v-axis/>
<v-legend dataKey="item"/>
<v-pie position="percent" color="item" :v-style="pieStyle" :label="labelConfig"/>
<v-coord type="theta"/>
</v-chart>
</template>
<script>
const DataSet = require('@antv/data-set')
import { ChartEventMixins } from './mixins/ChartMixins'
export default {
name: 'Pie',
mixins: [ChartEventMixins],
props: {
title: {
type: String,
default: ''
},
height: {
type: Number,
default: 254
},
dataSource: {
type: Array,
default: () => [
{ item: '示例一', count: 40 },
{ item: '示例二', count: 21 },
{ item: '示例三', count: 17 },
{ item: '示例四', count: 13 },
{ item: '示例五', count: 9 }
]
}
},
data() {
return {
scale: [{
dataKey: 'percent',
min: 0,
formatter: '.0%'
}],
pieStyle: {
stroke: '#fff',
lineWidth: 1
},
labelConfig: ['percent', {
formatter: (val, item) => {
return item.point.item + ': ' + val
}
}]
}
},
computed: {
data() {
let dv = new DataSet.View().source(this.dataSource)
// 计算数据百分比
dv.transform({
type: 'percent',
field: 'count',
dimension: 'item',
as: 'percent'
})
return dv.rows
}
}
}
</script>
+90
View File
@@ -0,0 +1,90 @@
<template>
<v-chart :forceFit="true" :height="height" :data="data" :padding="[20, 20, 95, 20]" :scale="scale">
<v-tooltip></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 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
}
}
}
const scale = [
{
dataKey: 'score',
min: 0,
max: 100
}, {
dataKey: 'user',
alias: '类型'
}
]
const sourceData = [
{ item: '示例一', score: 40 },
{ item: '示例二', score: 20 },
{ item: '示例三', score: 67 },
{ item: '示例四', score: 43 },
{ item: '示例五', score: 90 }
]
export default {
name: 'Radar',
props: {
height: {
type: Number,
default: 254
},
dataSource: {
type: Array,
default: () => []
}
},
data() {
return {
axis1Opts,
axis2Opts,
scale,
data: sourceData
}
},
watch: {
dataSource(newVal) {
if (newVal.length === 0) {
this.data = sourceData
} else {
this.data = newVal
}
}
}
}
</script>
<style scoped>
</style>
+81
View File
@@ -0,0 +1,81 @@
<template>
<div class="rank">
<h4 class="title">{{ title }}</h4>
<ul class="list" :style="{height:height?`${height}px`:'auto',overflow:'auto'}">
<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: "RankList",
// ['title', 'list']
props: {
title: {
type: String,
default: ''
},
list: {
type: Array,
default: null
},
height: {
type: Number,
default: null
}
}
}
</script>
<style lang="less" scoped>
.rank {
padding: 0 32px 32px 72px;
.list {
margin: 25px 0 0;
padding: 0;
list-style: none;
li {
margin-top: 16px;
span {
color: rgba(0, 0, 0, .65);
font-size: 14px;
line-height: 22px;
&:first-child {
background-color: #f5f5f5;
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;
}
&.active {
background-color: #314659;
color: #fff;
}
&:last-child {
float: right;
}
}
}
}
}
.mobile .rank {
padding: 0 32px 32px 32px;
}
</style>
+54
View File
@@ -0,0 +1,54 @@
<template>
<div>
<v-chart :forceFit="true" :height="height" :data="data">
<v-coord type="rect" direction="LB" />
<v-tooltip />
<v-legend />
<v-axis dataKey="State" :label="label" />
<v-stack-bar position="State*流程数量" color="流程状态" />
</v-chart>
</div>
</template>
<script>
const DataSet = require('@antv/data-set');
export default {
name: 'StackBar',
props: {
dataSource: {
type: Array,
required: true,
default: () => [
{ 'State': '请假', '流转中': 25, '已归档': 18 },
{ 'State': '出差', '流转中': 30, '已归档': 20 },
{ 'State': '加班', '流转中': 38, '已归档': 42},
{ 'State': '用车', '流转中': 51, '已归档': 67}
]
},
height: {
type: Number,
default: 254
}
},
data() {
return {
label: { offset: 12 }
}
},
computed: {
data() {
const dv = new DataSet.View().source(this.dataSource);
dv.transform({
type: 'fold',
fields: ['流转中', '已归档'],
key: '流程状态',
value: '流程数量',
retains: ['State'],
});
return dv.rows;
}
}
}
</script>
+66
View File
@@ -0,0 +1,66 @@
<template>
<div :style="{ padding: '0 0 32px 32px' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart
:height="height"
:data="data"
:scale="scale"
:forceFit="true"
:padding="['auto', 'auto', '40', '50']">
<v-tooltip/>
<v-axis/>
<v-bar position="x*y"/>
</v-chart>
</div>
</template>
<script>
export default {
name: 'Bar',
props: {
title: {
type: String,
default: ''
},
x: {
type: String,
default: 'x'
},
y: {
type: String,
default: 'y'
},
data: {
type: Array,
default: () => []
},
height: {
type: Number,
default: 254
}
},
data() {
return {}
},
computed: {
scale() {
return [
{ dataKey: 'x', title: this.x, alias: this.x },
{ dataKey: 'y', title: this.y, alias: this.y }
]
}
},
created() {
// this.getMonthBar()
},
methods: {
// getMonthBar() {
// this.$http.get('/analysis/month-bar')
// .then(res => {
// this.data = res.result
// })
// }
}
}
</script>
+84
View File
@@ -0,0 +1,84 @@
<template>
<div class="chart-trend">
{{ term }}
<span>{{ rate }}%</span>
<span :class="['trend-icon', trend]"><a-icon :type="'caret-' + trend"/></span>
</div>
</template>
<script>
export default {
name: "Trend",
props: {
// 同title
term: {
type: String,
default: '',
required: true
},
// 百分比
percentage: {
type: Number,
default: null
},
type: {
type: Boolean,
default: null
},
target: {
type: Number,
default: 0
},
value: {
type: Number,
default: 0
},
fixed: {
type: Number,
default: 2
}
},
data () {
return {
trend: this.type && 'up' || 'down',
rate: this.percentage
}
},
created () {
let type = this.type === null ? this.value >= this.target : this.type
this.trend = type ? 'up' : 'down';
this.rate = (this.percentage === null ? Math.abs(this.value - this.target) * 100 / this.target : this.percentage).toFixed(this.fixed)
}
}
</script>
<style lang="less" scoped>
.chart-trend {
display: inline-block;
font-size: 14px;
line-height: 22px;
.trend-icon {
font-size: 12px;
&.up, &.down {
margin-left: 4px;
position: relative;
top: 1px;
i {
font-size: 12px;
transform: scale(.83);
}
}
&.up {
color: #f5222d;
}
&.down {
color: #52c41a;
top: -1px;
}
}
}
</style>
+13
View File
@@ -0,0 +1,13 @@
.antv-chart-mini {
position: relative;
width: 100%;
.chart-wrapper {
position: absolute;
bottom: -28px;
width: 100%;
/* margin: 0 -5px;
overflow: hidden;*/
}
}
+13
View File
@@ -0,0 +1,13 @@
.antv-chart-mini {
position: relative;
width: 100%;
.chart-wrapper {
position: absolute;
bottom: -28px;
width: 100%;
/* margin: 0 -5px;
overflow: hidden;*/
}
}
@@ -0,0 +1,10 @@
export const ChartEventMixins = {
methods: {
handleClick(event, chart) {
this.handleEvent('click', event, chart)
},
handleEvent(eventName, event, chart) {
this.$emit(eventName, event, chart)
},
}
}
+4
View File
@@ -0,0 +1,4 @@
@import "~ant-design-vue/lib/style/index";
// The prefix to use on all css classes from ant-pro.
@ant-pro-prefix : ant-pro;
+155
View File
@@ -0,0 +1,155 @@
<template>
<div v-if="!reloading" class="j-area-linkage">
<area-cascader
v-if="_type === enums.type[0]"
:value="innerValue"
:data="pcaa"
:level="1"
:style="{width}"
v-bind="$attrs"
v-on="_listeners"
@change="handleChange"
/>
<area-select
v-else-if="_type === enums.type[1]"
:value="innerValue"
:data="pcaa"
:level="2"
v-bind="$attrs"
v-on="_listeners"
@change="handleChange"
/>
<div v-else>
<span style="color:red;"> Bad type value: {{_type}}</span>
</div>
</div>
</template>
<script>
import { pcaa } from 'area-data'
export default {
name: 'JAreaLinkage',
props: {
value: {
type: String,
required:false
},
// 组件的类型,可选值:
// select 下拉样式
// cascader 级联样式(默认)
type: {
type: String,
default: 'cascader'
},
width: {
type: String,
default: '100%'
}
},
data() {
return {
pcaa,
innerValue: [],
usedListeners: ['change'],
enums: {
type: ['cascader', 'select']
},
reloading: false,
areaData:''
}
},
computed: {
_listeners() {
let listeners = { ...this.$listeners }
// 去掉已使用的事件,防止冲突
this.usedListeners.forEach(key => {
delete listeners[key]
})
return listeners
},
_type() {
if (this.enums.type.includes(this.type)) {
return this.type
} else {
console.error(`JAreaLinkage的type属性只能接收指定的值(${this.enums.type.join('|')}`)
return this.enums.type[0]
}
},
},
watch: {
value: {
immediate: true,
handler() {
this.loadDataByValue(this.value)
}
},
},
created() {
this.initAreaData();
},
methods: {
/** 通过 value 反推 options */
loadDataByValue(value) {
if(!value || value.length==0){
this.innerValue = []
this.reloading = true;
setTimeout(()=>{
this.reloading = false
},100)
}else{
this.initAreaData();
let arr = this.areaData.getRealCode(value);
this.innerValue = arr
}
},
/** 通过地区code获取子级 */
loadDataByCode(value) {
let options = []
let data = pcaa[value]
if (data) {
for (let key in data) {
if (data.hasOwnProperty(key)) {
options.push({ value: key, label: data[key], })
}
}
return options
} else {
return []
}
},
/** 判断是否有子节点 */
hasChildren(options) {
options.forEach(option => {
let data = this.loadDataByCode(option.value)
option.isLeaf = data.length === 0
})
},
handleChange(values) {
let value = values[values.length - 1]
this.$emit('change', value)
},
initAreaData(){
if(!this.areaData){
this.areaData = new Area();
}
},
},
model: { prop: 'value', event: 'change' },
}
</script>
<style lang="less" scoped>
.j-area-linkage {
height:40px;
/deep/ .area-cascader-wrap .area-select {
width: 100%;
}
/deep/ .area-select .area-selected-trigger {
line-height: 1.15;
}
}
</style>
+238
View File
@@ -0,0 +1,238 @@
<template>
<a-tree-select
allowClear
labelInValue
style="width: 100%"
:disabled="disabled"
:dropdownStyle="{ maxHeight: '400px', overflow: 'auto' }"
:placeholder="placeholder"
:loadData="asyncLoadTreeData"
:value="treeValue"
:treeData="treeData"
:multiple="multiple"
@change="onChange">
</a-tree-select>
</template>
<script>
import { getAction } from '@/api/manage'
export default {
name: 'JCategorySelect',
props: {
value:{
type: String,
required: false
},
placeholder:{
type: String,
default: '请选择',
required: false
},
disabled:{
type:Boolean,
default:false,
required:false
},
condition:{
type:String,
default:'',
required:false
},
// 是否支持多选
multiple: {
type: Boolean,
default: false,
},
loadTriggleChange:{
type: Boolean,
default: false,
required:false
},
pid:{
type:String,
default:'',
required:false
},
pcode:{
type:String,
default:'',
required:false
},
back:{
type:String,
default:'',
required:false
}
},
data () {
return {
treeValue:"",
treeData:[],
url:"/sys/category/loadTreeData",
view:'/sys/category/loadDictItem/',
tableName:"",
text:"",
code:"",
}
},
watch: {
value () {
this.loadItemByCode()
},
pcode(){
this.loadRoot();
}
},
created(){
this.validateProp().then(()=>{
this.loadRoot()
this.loadItemByCode()
})
},
methods: {
/**加载一级节点 */
loadRoot(){
let param = {
pid:this.pid,
pcode:!this.pcode?'0':this.pcode,
condition:this.condition
}
getAction(this.url,param).then(res=>{
if(res.success && res.result){
for(let i of res.result){
i.value = i.key
if(i.leaf==false){
i.isLeaf=false
}else if(i.leaf==true){
i.isLeaf=true
}
}
this.treeData = [...res.result]
}else{
console.log("树一级节点查询结果-else",res)
}
})
},
/** 数据回显*/
loadItemByCode(){
if(!this.value || this.value=="0"){
this.treeValue = []
}else{
getAction(this.view,{ids:this.value}).then(res=>{
if(res.success){
let values = this.value.split(',')
this.treeValue = res.result.map((item, index) => ({
key: values[index],
value: values[index],
label: item
}))
this.onLoadTriggleChange(res.result[0]);
}
})
}
},
onLoadTriggleChange(text){
//只有单选才会触发
if(!this.multiple && this.loadTriggleChange){
this.backValue(this.value,text)
}
},
backValue(value,label){
let obj = {}
if(this.back){
obj[this.back] = label
}
this.$emit('change', value, obj)
},
asyncLoadTreeData (treeNode) {
return new Promise((resolve) => {
if (treeNode.$vnode.children) {
resolve()
return
}
let pid = treeNode.$vnode.key
let param = {
pid:pid,
condition:this.condition
}
getAction(this.url,param).then(res=>{
if(res.success){
for(let i of res.result){
i.value = i.key
if(i.leaf==false){
i.isLeaf=false
}else if(i.leaf==true){
i.isLeaf=true
}
}
this.addChildren(pid,res.result,this.treeData)
this.treeData = [...this.treeData]
}
resolve()
})
})
},
addChildren(pid,children,treeArray){
if(treeArray && treeArray.length>0){
for(let item of treeArray){
if(item.key == pid){
if(!children || children.length==0){
item.isLeaf=true
}else{
item.children = children
}
break
}else{
this.addChildren(pid,children,item.children)
}
}
}
},
onChange(value){
if(!value){
this.$emit('change', '');
this.treeValue = ''
} else if (value instanceof Array) {
//this.$emit('change', value.map(item => item.value).join(','))
//this.treeValue = value
} else {
this.backValue(value.value,value.label)
this.treeValue = value
}
},
getCurrTreeData(){
return this.treeData
},
validateProp(){
let mycondition = this.condition
return new Promise((resolve,reject)=>{
if(!mycondition){
resolve();
}else{
try {
let test=JSON.parse(mycondition);
if(typeof test == 'object' && test){
resolve()
}else{
this.$message.error("组件JTreeSelect-condition传值有误,需要一个json字符串!")
reject()
}
} catch(e) {
this.$message.error("组件JTreeSelect-condition传值有误,需要一个json字符串!")
reject()
}
}
})
}
},
//2.2新增 在组件内定义 指定父组件调用时候的传值属性和事件类型 这个牛逼
model: {
prop: 'value',
event: 'change'
}
}
</script>
+43
View File
@@ -0,0 +1,43 @@
<template>
<a-checkbox-group :options="options" :value="checkboxArray" v-bind="$attrs" @change="onChange" />
</template>
<script>
export default {
name: 'JCheckbox',
props: {
value:{
type: String,
required: false
},
/*label value*/
options:{
type: Array,
required: true
}
},
data(){
return {
checkboxArray:!this.value?[]:this.value.split(",")
}
},
watch:{
value (val) {
if(!val){
this.checkboxArray = []
}else{
this.checkboxArray = this.value.split(",")
}
}
},
methods:{
onChange (checkedValues) {
this.$emit('change', checkedValues.join(","));
},
},
model: {
prop: 'value',
event: 'change'
}
}
</script>
+429
View File
@@ -0,0 +1,429 @@
<template>
<div v-bind="fullScreenParentProps">
<a-icon v-if="fullScreen" class="full-screen-icon" :type="iconType" @click="()=>fullCoder=!fullCoder"/>
<div class="code-editor-cust full-screen-child">
<textarea ref="textarea"></textarea>
<span @click="nullTipClick" class="null-tip" :class="{'null-tip-hidden':hasCode}" :style="nullTipStyle">{{ placeholderShow }}</span>
<template v-if="languageChange">
<a-select v-model="mode" size="small" class="code-mode-select" @change="changeMode" placeholder="请选择主题">
<a-select-option
v-for="mode in modes"
:key="mode.value"
:value="mode.value">
{{ mode.label }}
</a-select-option>
</a-select>
</template>
</div>
</div>
</template>
<script type="text/ecmascript-6">
// 引入全局实例
import _CodeMirror from 'codemirror'
// 核心样式
import 'codemirror/lib/codemirror.css'
// 引入主题后还需要在 options 中指定主题才会生效 darcula gruvbox-dark hopscotch monokai
import 'codemirror/theme/panda-syntax.css'
//提示css
import "codemirror/addon/hint/show-hint.css";
// 需要引入具体的语法高亮库才会有对应的语法高亮效果
// codemirror 官方其实支持通过 /addon/mode/loadmode.js 和 /mode/meta.js 来实现动态加载对应语法高亮库
// 但 vue 貌似没有无法在实例初始化后再动态加载对应 JS ,所以此处才把对应的 JS 提前引入
import 'codemirror/mode/javascript/javascript.js'
import 'codemirror/mode/css/css.js'
import 'codemirror/mode/xml/xml.js'
import 'codemirror/mode/clike/clike.js'
import 'codemirror/mode/markdown/markdown.js'
import 'codemirror/mode/python/python.js'
import 'codemirror/mode/r/r.js'
import 'codemirror/mode/shell/shell.js'
import 'codemirror/mode/sql/sql.js'
import 'codemirror/mode/swift/swift.js'
import 'codemirror/mode/vue/vue.js'
// 尝试获取全局实例
const CodeMirror = window.CodeMirror || _CodeMirror
export default {
name: 'JCodeEditor',
props: {
// 外部传入的内容,用于实现双向绑定
value: {
type: String,
default: ''
},
// 外部传入的语法类型
language: {
type: String,
default: null
},
languageChange:{
type: Boolean,
default:false,
required:false
},
placeholder: {
type: String,
default: null
},
// 显示行号
lineNumbers: {
type: Boolean,
default: true
},
// 是否显示全屏按钮
fullScreen: {
type: Boolean,
default: false
},
// 全屏以后的z-index
zIndex: {
type: [Number, String],
default: 999
}
},
data () {
return {
// 内部真实的内容
code: '',
iconType: 'fullscreen',
hasCode:false,
// 默认的语法类型
mode: 'javascript',
// 编辑器实例
coder: null,
// 默认配置
options: {
// 缩进格式
tabSize: 2,
// 主题,对应主题库 JS 需要提前引入
theme: 'panda-syntax',
line: true,
// extraKeys: {'Ctrl': 'autocomplete'},//自定义快捷键
hintOptions: {
tables: {
users: ['name', 'score', 'birthDate'],
countries: ['name', 'population', 'size']
}
},
},
// 支持切换的语法高亮类型,对应 JS 已经提前引入
// 使用的是 MIME-TYPE ,不过作为前缀的 text/ 在后面指定时写死了
modes: [{
value: 'css',
label: 'CSS'
}, {
value: 'javascript',
label: 'Javascript'
}, {
value: 'html',
label: 'XML/HTML'
}, {
value: 'x-java',
label: 'Java'
}, {
value: 'x-objectivec',
label: 'Objective-C'
}, {
value: 'x-python',
label: 'Python'
}, {
value: 'x-rsrc',
label: 'R'
}, {
value: 'x-sh',
label: 'Shell'
}, {
value: 'x-sql',
label: 'SQL'
}, {
value: 'x-swift',
label: 'Swift'
}, {
value: 'x-vue',
label: 'Vue'
}, {
value: 'markdown',
label: 'Markdown'
}],
// code 编辑器 是否全屏
fullCoder: false
}
},
watch: {
fullCoder:{
handler(value) {
if(value){
this.iconType="fullscreen-exit"
}else{
this.iconType="fullscreen"
}
}
},
// value: {
// immediate: false,
// handler(value) {
// this._getCoder().then(() => {
// this.coder.setValue(value)
// })
// }
// },
language: {
immediate: true,
handler(language) {
this._getCoder().then(() => {
// 尝试从父容器获取语法类型
if (language) {
// 获取具体的语法类型对象
let modeObj = this._getLanguage(language)
// 判断父容器传入的语法是否被支持
if (modeObj) {
this.mode = modeObj.label
this.coder.setOption('mode', `text/${modeObj.value}`)
}
}
})
}
}
},
computed: {
placeholderShow() {
if (this.placeholder == null) {
return `请在此输入${this.language}代码`
} else {
return this.placeholder
}
},
nullTipStyle(){
if (this.lineNumbers) {
return { left: '36px' }
} else {
return { left: '12px' }
}
},
// coder 配置
coderOptions() {
return {
tabSize: this.options.tabSize,
theme: this.options.theme,
lineNumbers: this.lineNumbers,
line: true,
hintOptions: this.options.hintOptions
}
},
fullScreenParentProps(){
let props = {
class: ['full-screen-parent', this.fullCoder ? 'full-screen' : ''],
style: {}
}
if (this.fullCoder) {
props.style['z-index'] = this.zIndex
}
return props
}
},
mounted () {
// 初始化
this._initialize()
},
methods: {
// 初始化
_initialize () {
// 初始化编辑器实例,传入需要被实例化的文本域对象和默认配置
this.coder = CodeMirror.fromTextArea(this.$refs.textarea, this.coderOptions)
// 编辑器赋值
if(this.value||this.code){
this.hasCode=true
this.coder.setValue(this.value || this.code)
}else{
this.coder.setValue('')
this.hasCode=false
}
// 支持双向绑定
this.coder.on('change', (coder) => {
this.code = coder.getValue()
if(this.code){
this.hasCode=true
}else{
this.hasCode=false
}
if (this.$emit) {
this.$emit('input', this.code)
}
})
this.coder.on('focus', () => {
this.hasCode=true
})
this.coder.on('blur', () => {
if(this.code){
this.hasCode=true
}else{
this.hasCode=false
}
})
/* this.coder.on('cursorActivity',()=>{
this.coder.showHint()
})*/
},
getCodeContent(){
return this.code
},
setCodeContent(val){
setTimeout(()=>{
if(!val){
this.coder.setValue('')
}else{
this.coder.setValue(val)
}
},300)
},
// 获取当前语法类型
_getLanguage (language) {
// 在支持的语法类型列表中寻找传入的语法类型
return this.modes.find((mode) => {
// 所有的值都忽略大小写,方便比较
let currentLanguage = language.toLowerCase()
let currentLabel = mode.label.toLowerCase()
let currentValue = mode.value.toLowerCase()
// 由于真实值可能不规范,例如 java 的真实值是 x-java ,所以讲 value 和 label 同时和传入语法进行比较
return currentLabel === currentLanguage || currentValue === currentLanguage
})
},
_getCoder() {
let _this = this
return new Promise((resolve) => {
(function get() {
if (_this.coder) {
resolve(_this.coder)
} else {
setTimeout(get, 10)
}
})()
})
},
// 更改模式
changeMode (val) {
// 修改编辑器的语法配置
this.coder.setOption('mode', `text/${val}`)
// 获取修改后的语法
let label = this._getLanguage(val).label.toLowerCase()
// 允许父容器通过以下函数监听当前的语法值
this.$emit('language-change', label)
},
nullTipClick(){
this.coder.focus()
}
}
}
</script>
<style lang="less">
.code-editor-cust{
flex-grow:1;
display:flex;
position:relative;
height:100%;
.CodeMirror{
flex-grow:1;
z-index:1;
.CodeMirror-code{
line-height:19px;
}
}
.code-mode-select{
position:absolute;
z-index:2;
right:10px;
top:10px;
max-width:130px;
}
.CodeMirror{
height: auto;
min-height:100%;
}
.null-tip{
position: absolute;
top: 4px;
left: 36px;
z-index: 10;
color: #ffffffc9;
line-height: initial;
}
.null-tip-hidden{
display: none;
}
}
/* 全屏样式 */
.full-screen-parent {
position: relative;
.full-screen-icon {
opacity: 0;
color: black;
width: 20px;
height: 20px;
line-height: 24px;
background-color: white;
position: absolute;
top: 2px;
right: 2px;
z-index: 9;
cursor: pointer;
transition: opacity 0.3s;
}
&:hover {
.full-screen-icon {
opacity: 1;
&:hover {
background-color: rgba(255, 255, 255, 0.88);
}
}
}
&.full-screen {
position: fixed;
top: 10px;
left: 10px;
width: calc(100% - 20px);
height: calc(100% - 20px);
padding: 10px;
background-color: #f5f5f5;
.full-screen-icon {
top: 12px;
right: 12px;
}
.full-screen-child {
height: 100%;
max-height: 100%;
min-height: 100%;
}
}
.full-screen-child {
min-height: 120px;
max-height: 320px;
overflow:hidden;
}
}
.CodeMirror-cursor{
height:18.4px !important;
}
</style>
+65
View File
@@ -0,0 +1,65 @@
<template>
<div class="components-input-demo-presuffix">
<a-input @click="openModal" placeholder="corn表达式" v-model="cron" @change="handleOK">
<a-icon slot="prefix" type="schedule" title="corn控件"/>
<a-icon v-if="cron" slot="suffix" type="close-circle" @click="handleEmpty" title="清空"/>
</a-input>
<JCronModal ref="innerVueCron" :data="cron" @ok="handleOK"></JCronModal>
</div>
</template>
<script>
import JCronModal from "./modal/JCronModal";
export default {
name: 'JCron',
components: {
JCronModal
},
props: {
value: {
required: false,
type: String,
}
},
data(){
return {
cron: this.value,
}
},
watch:{
value(val){
this.cron = val
}
},
methods:{
openModal(){
this.$refs.innerVueCron.show();
},
handleOK(val){
this.cron = val;
this.$emit("change", this.cron);
//this.$emit("change", Object.assign({}, this.cron));
},
handleEmpty(){
this.handleOK('')
}
},
model: {
prop: 'value',
event: 'change'
}
}
</script>
<style scoped>
.components-input-demo-presuffix .anticon-close-circle {
cursor: pointer;
color: #ccc;
transition: color 0.3s;
font-size: 12px;
}
.components-input-demo-presuffix .anticon-close-circle:hover {
color: #f5222d;
}
.components-input-demo-presuffix .anticon-close-circle:active {
color: #666;
}
</style>

Some files were not shown because too many files have changed in this diff Show More