This commit is contained in:
2023-07-10 11:28:24 +08:00
parent 823dd6d79b
commit 4094e7cc4b
272 changed files with 44008 additions and 7 deletions

BIN
.DS_Store vendored

Binary file not shown.

39
.editorconfig Normal file
View File

@@ -0,0 +1,39 @@
[*]
charset=utf-8
end_of_line=lf
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
.env Normal file
View File

@@ -0,0 +1,7 @@
NODE_ENV=production
VUE_APP_PREVIEW=false
# <!-- //水感应正式环境-->
# VUE_APP_API_BASE_URL=https://douhuo.douhuofalv.com/agent
# <!-- // 水感应测试-->
VUE_APP_API_BASE_URL=https://api.douhuotest.douhuofalv.com/agent

15
.env.development Normal file
View File

@@ -0,0 +1,15 @@
###
# @Author: Aimee~
# @Date: 2023-05-11 11:17:32
# @LastEditTime: 2023-07-05 11:08:59
# @LastEditors: Aimee
# @FilePath: /douhuo-agent/.env.development
# @Description: 正式环境测试环境更改
###
NODE_ENV=development
VUE_APP_PREVIEW=true
# <!-- //水感应正式环境-->
# VUE_APP_API_BASE_URL=https://douhuo.douhuofalv.com/agent
# <!-- //水感应测试-->
VUE_APP_API_BASE_URL=https://api.douhuotest.douhuofalv.com/agent

15
.env.preview Normal file
View File

@@ -0,0 +1,15 @@
###
# @Author: Aimee~
# @Date: 2023-05-11 11:17:32
# @LastEditTime: 2023-07-05 11:08:51
# @LastEditors: Aimee
# @FilePath: /douhuo-agent/.env.preview
# @Description:
###
NODE_ENV=production
VUE_APP_PREVIEW=true
# <!-- //水感应正式环境-->
# VUE_APP_API_BASE_URL=https://douhuo.douhuofalv.com/agent
# <!-- //水感应测试-->
VUE_APP_API_BASE_URL=https://api.douhuotest.douhuofalv.com/agent

75
.eslintrc.js Normal file
View File

@@ -0,0 +1,75 @@
module.exports = {
root: true,
env: {
node: true
},
'extends': [
'plugin:vue/strongly-recommended',
'@vue/standard'
],
rules: {
'no-console': 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'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-tabs': 0,
'quotes': [
2,
'single',
{
'avoidEscape': true,
'allowTemplateLiterals': true
}
],
'semi': [
2,
'never',
{
'beforeStatementContinuationChars': 'never'
}
],
'no-delete-var': 2,
'prefer-const': [
2,
{
'ignoreReadBeforeAssign': false
}
],
'template-curly-spacing': 'off',
'indent': 'off'
},
parserOptions: {
parser: 'babel-eslint'
},
overrides: [
{
files: [
'**/__tests__/*.{j,t}s?(x)',
'**/tests/unit/**/*.spec.{j,t}s?(x)'
],
env: {
jest: true
}
}
]
}

5
.eslintrc.json Normal file
View File

@@ -0,0 +1,5 @@
{
"rules": {
"space-before-function-paren": 0
}
}

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
public/* linguist-vendored

23
.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
.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

6
.prettierrc Normal file
View File

@@ -0,0 +1,6 @@
{
"printWidth": 120,
"semi": false,
"singleQuote": true,
"prettier.spaceBeforeFunctionParen": true
}

View File

@@ -4,20 +4,20 @@
* @LastEditTime: 2023-06-27 14:25:57 * @LastEditTime: 2023-06-27 14:25:57
* @LastEditors: Aimee * @LastEditors: Aimee
* @FilePath: /douhuo-agent/README.md * @FilePath: /douhuo-agent/README.md
* @Description: * @Description: 备注信息
--> -->
# 抖火中台 # 水感应 分公司 中台(数据展示及分公司数据)
打包后文件件需要有一个 static 文件存放 打包后文件件需要有一个 static 文件存放
'/static/vue.min.js', '/static/vue.min.js'
'/static/vue-router.min.js', '/static/vue-router.min.js'
'/static/vuex.min.js', '/static/vuex.min.js'
'/static/axios.min.js' '/static/axios.min.js'
否则报错 否则报错
# node 版本 10.23.1 # node 版本 10.23.1
# 抖火测试-存放路径 # 水感应测试-存放路径
/home/wwwroot/Development/Douhuo/agent /home/wwwroot/Development/Douhuo/agent
# 抖火正式-存放路径 # 水感应正式-存放路径
oss://douhuo-agent/ oss://douhuo-agent/

28
babel.config.js Normal file
View File

@@ -0,0 +1,28 @@
const IS_PROD = ['production', 'prod'].includes(process.env.NODE_ENV)
const plugins = []
if (IS_PROD) {
plugins.push('transform-remove-console')
}
// lazy load ant-design-vue
// if your use import on Demand, Use this code
plugins.push(['import', {
'libraryName': 'ant-design-vue',
'libraryDirectory': 'es',
'style': true // `style: true` 会加载 less 文件
}])
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',
[
'@babel/preset-env',
{
'useBuiltIns': 'entry',
'corejs': 3
}
]
],
plugins
}

49
config/plugin.config.js Normal file
View File

@@ -0,0 +1,49 @@
const ThemeColorReplacer = require('webpack-theme-color-replacer')
const generate = require('@ant-design/colors/lib/generate').default
const getAntdSerials = (color) => {
// 淡化即less的tint
const lightens = new Array(9).fill().map((t, i) => {
return ThemeColorReplacer.varyColor.lighten(color, i / 10)
})
const colorPalettes = generate(color)
const rgb = ThemeColorReplacer.varyColor.toNum3(color.replace('#', '')).join(',')
return lightens.concat(colorPalettes).concat(rgb)
}
const themePluginOption = {
fileName: 'css/theme-colors-[contenthash:8].css',
matchColors: getAntdSerials('#1890ff'), // 主色系列
// 改变样式选择器,解决样式覆盖问题
changeSelector (selector) {
switch (selector) {
case '.ant-calendar-today .ant-calendar-date':
return ':not(.ant-calendar-selected-date):not(.ant-calendar-selected-day)' + selector
case '.ant-btn:focus,.ant-btn:hover':
return '.ant-btn:focus:not(.ant-btn-primary):not(.ant-btn-danger),.ant-btn:hover:not(.ant-btn-primary):not(.ant-btn-danger)'
case '.ant-btn.active,.ant-btn:active':
return '.ant-btn.active:not(.ant-btn-primary):not(.ant-btn-danger),.ant-btn:active:not(.ant-btn-primary):not(.ant-btn-danger)'
case '.ant-steps-item-process .ant-steps-item-icon > .ant-steps-icon':
case '.ant-steps-item-process .ant-steps-item-icon>.ant-steps-icon':
return ':not(.ant-steps-item-process)' + selector
// fixed https://github.com/vueComponent/ant-design-vue-pro/issues/876
case '.ant-steps-item-process .ant-steps-item-icon':
return ':not(.ant-steps-item-custom)' + selector
case '.ant-menu-horizontal>.ant-menu-item-active,.ant-menu-horizontal>.ant-menu-item-open,.ant-menu-horizontal>.ant-menu-item-selected,.ant-menu-horizontal>.ant-menu-item:hover,.ant-menu-horizontal>.ant-menu-submenu-active,.ant-menu-horizontal>.ant-menu-submenu-open,.ant-menu-horizontal>.ant-menu-submenu-selected,.ant-menu-horizontal>.ant-menu-submenu:hover':
case '.ant-menu-horizontal > .ant-menu-item-active,.ant-menu-horizontal > .ant-menu-item-open,.ant-menu-horizontal > .ant-menu-item-selected,.ant-menu-horizontal > .ant-menu-item:hover,.ant-menu-horizontal > .ant-menu-submenu-active,.ant-menu-horizontal > .ant-menu-submenu-open,.ant-menu-horizontal > .ant-menu-submenu-selected,.ant-menu-horizontal > .ant-menu-submenu:hover':
return '.ant-menu-horizontal > .ant-menu-item-active,.ant-menu-horizontal > .ant-menu-item-open,.ant-menu-horizontal > .ant-menu-item-selected,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-item:hover,.ant-menu-horizontal > .ant-menu-submenu-active,.ant-menu-horizontal > .ant-menu-submenu-open,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu-selected,.ant-menu-horizontal:not(.ant-menu-dark) > .ant-menu-submenu:hover'
case '.ant-menu-horizontal > .ant-menu-item-selected > a':
case '.ant-menu-horizontal>.ant-menu-item-selected>a':
return '.ant-menu-horizontal:not(ant-menu-light):not(.ant-menu-dark) > .ant-menu-item-selected > a'
case '.ant-menu-horizontal > .ant-menu-item > a:hover':
case '.ant-menu-horizontal>.ant-menu-item>a:hover':
return '.ant-menu-horizontal:not(ant-menu-light):not(.ant-menu-dark) > .ant-menu-item > a:hover'
default :
return selector
}
}
}
const createThemeColorReplacerPlugin = () => new ThemeColorReplacer(themePluginOption)
module.exports = createThemeColorReplacerPlugin

115
config/themePluginConfig.js Normal file
View File

@@ -0,0 +1,115 @@
export default {
theme: [
{
key: 'dark',
fileName: 'dark.css',
theme: 'dark'
},
{
key: '#F5222D',
fileName: '#F5222D.css',
modifyVars: {
'@primary-color': '#F5222D'
}
},
{
key: '#FA541C',
fileName: '#FA541C.css',
modifyVars: {
'@primary-color': '#FA541C'
}
},
{
key: '#FAAD14',
fileName: '#FAAD14.css',
modifyVars: {
'@primary-color': '#FAAD14'
}
},
{
key: '#13C2C2',
fileName: '#13C2C2.css',
modifyVars: {
'@primary-color': '#13C2C2'
}
},
{
key: '#52C41A',
fileName: '#52C41A.css',
modifyVars: {
'@primary-color': '#52C41A'
}
},
{
key: '#2F54EB',
fileName: '#2F54EB.css',
modifyVars: {
'@primary-color': '#2F54EB'
}
},
{
key: '#722ED1',
fileName: '#722ED1.css',
modifyVars: {
'@primary-color': '#722ED1'
}
},
{
key: '#F5222D',
theme: 'dark',
fileName: 'dark-#F5222D.css',
modifyVars: {
'@primary-color': '#F5222D'
}
},
{
key: '#FA541C',
theme: 'dark',
fileName: 'dark-#FA541C.css',
modifyVars: {
'@primary-color': '#FA541C'
}
},
{
key: '#FAAD14',
theme: 'dark',
fileName: 'dark-#FAAD14.css',
modifyVars: {
'@primary-color': '#FAAD14'
}
},
{
key: '#13C2C2',
theme: 'dark',
fileName: 'dark-#13C2C2.css',
modifyVars: {
'@primary-color': '#13C2C2'
}
},
{
key: '#52C41A',
theme: 'dark',
fileName: 'dark-#52C41A.css',
modifyVars: {
'@primary-color': '#52C41A'
}
},
{
key: '#2F54EB',
theme: 'dark',
fileName: 'dark-#2F54EB.css',
modifyVars: {
'@primary-color': '#2F54EB'
}
},
{
key: '#722ED1',
theme: 'dark',
fileName: 'dark-#722ED1.css',
modifyVars: {
'@primary-color': '#722ED1'
}
}
]
}

View File

@@ -0,0 +1,31 @@
为首屏增加 加载动画
====
## 需求
> 为了缓解用户第一次访问时,加载 JS 过大所导致用户等待白屏时间过长导致的用户体验不好,进行的一个优化动效。
## 实现方案
1. 将 动画加载 dom 元素放在 #appVue 生命周期开始时,会自动清掉 #app 下的所有元素。
2. 将 动画加载 dom 元素放在 body 下Vue 生命周期开始时 App.vue (created, mounted) 调用 `@/utils/utll` 下的 removeLoadingAnimate(#id, timeout) 则会移除加载动画
最后一步:
将样式插入到 `public/index.html` 文件的 `<head></head>` 最好写成内联 `<style>动画样式</style>`
----
目前提供有两个样式,均在 `public/loading` 文件夹内。且 pro 已经默认使用了一套 loading 动画方案,可以直接参考 `public/index.html`
## 写在最后
目前 pro 有页面 overflow 显示出浏览器滚动条时,页面会抖动一下的问题。
欢迎各位提供能解决的方案和实现 demo。如果在条件允许的情况下建议请直接使用 pro 进行改造,也欢迎直接 PR 到 pro 的仓库

View File

@@ -0,0 +1,40 @@
先增加依赖
```bash
// npm
$ npm install --save-dev webpack-bundle-analyzer
// or yarn
$ yarn add webpack-bundle-analyzer -D
```
配置文件 `vue.config.js` 增加 `configureWebpack.plugins` 参数
```
const path = require('path')
const webpack = require('webpack')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
function resolve (dir) {
return path.join(__dirname, dir)
}
// vue.config.js
module.exports = {
configureWebpack: {
plugins: [
// Ignore all locale files of moment.js
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
// 依赖大小分析工具
new BundleAnalyzerPlugin(),
]
},
...
}
```
启动 `cli``build` 命令进行项目编译,编译完成时,会自动运行一个 http://localhost:8888 的地址,完整显示了支持库依赖

23
jest.config.js Normal file
View File

@@ -0,0 +1,23 @@
module.exports = {
moduleFileExtensions: [
'js',
'jsx',
'json',
'vue'
],
transform: {
'^.+\\.vue$': 'vue-jest',
'.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
'^.+\\.jsx?$': 'babel-jest'
},
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
},
snapshotSerializers: [
'jest-serializer-vue'
],
testMatch: [
'**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
],
testURL: 'http://localhost/'
}

11
jsconfig.json Normal file
View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "es6",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"exclude": ["node_modules", "dist"],
"include": ["src/**/*", "static"]
}

67
package.json Normal file
View File

@@ -0,0 +1,67 @@
{
"name": "vue-antd-pro",
"version": "3.0.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit",
"lint": "vue-cli-service lint",
"build:preview": "vue-cli-service build --mode preview",
"lint:nofix": "vue-cli-service lint --no-fix"
},
"dependencies": {
"@ant-design-vue/pro-layout": "^1.0.7",
"@antv/data-set": "^0.10.2",
"ali-oss": "^6.13.2",
"ant-design-vue": "^1.7.2",
"axios": "^0.19.0",
"chain33-rpc-api": "^1.5.3",
"core-js": "^3.8.2",
"element-ui": "^2.15.9",
"enquire.js": "^2.1.6",
"lodash.clonedeep": "^4.5.0",
"lodash.get": "^4.4.2",
"lodash.pick": "^4.4.0",
"md5": "^2.3.0",
"moment": "^2.24.0",
"nprogress": "^0.2.0",
"store": "^2.0.12",
"viser-vue": "^2.4.6",
"vue": "^2.6.12",
"vue-clipboard2": "^0.2.1",
"vue-cropper": "0.4.9",
"vue-i18n": "^8.22.3",
"vue-quill-editor": "^3.0.6",
"vue-router": "^3.4.9",
"vue-sku-form": "^0.4.1",
"vue-svg-component-runtime": "^1.0.1",
"vuex": "^3.6.0",
"wangeditor": "^4.6.8"
},
"devDependencies": {
"@ant-design/colors": "^3.2.1",
"@vue/cli-plugin-babel": "^4.5.10",
"@vue/cli-plugin-eslint": "^4.5.10",
"@vue/cli-plugin-router": "^4.5.10",
"@vue/cli-plugin-unit-jest": "^4.5.10",
"@vue/cli-plugin-vuex": "^4.5.10",
"@vue/cli-service": "^4.5.10",
"@vue/eslint-config-standard": "^4.0.0",
"@vue/test-utils": "^1.1.2",
"babel-eslint": "^10.0.1",
"babel-plugin-import": "^1.13.3",
"babel-plugin-transform-remove-console": "^6.9.4",
"eslint": "^5.16.0",
"eslint-plugin-html": "^5.0.0",
"eslint-plugin-vue": "^5.2.3",
"git-revision-webpack-plugin": "^3.0.6",
"less": "^3.13.1",
"less-loader": "^5.0.0",
"opencollective": "^1.0.3",
"opencollective-postinstall": "^2.0.3",
"vue-svg-icon-loader": "^2.1.1",
"vue-template-compiler": "^2.6.12",
"webpack-theme-color-replacer": "^1.3.14"
}
}

5
postcss.config.js Normal file
View File

@@ -0,0 +1,5 @@
module.exports = {
plugins: {
autoprefixer: {}
}
}

271
public/Index.vue vendored Normal file
View File

@@ -0,0 +1,271 @@
<template>
<div>
<div class="a-row">
<!-- 基本信息 -->
<a-col :span="8">
<div class="block">
<div class="block-title">基本信息</div>
<div class="store">
<a-avatar :src="base_info.cover" class="store-avatar" :size="64" alt="LOGO" />
<div class="store-title nowrap">{{ base_info.name }} </div>
<div class="store-introduction ellipsis">{{ base_info.description }}</div>
</div>
</div>
</a-col>
<!-- 店铺数据 -->
<a-col :span="8">
<div class="block">
<div class="block-title">财务数据概况</div>
<div class="statistical">
<div class="statistical-item">
<h4>{{ shop.orders }}</h4>
<p>总销量()</p>
</div>
<div class="statistical-item">
<h4>{{ shop.business }}</h4>
<p>总营业额</p>
</div>
</div>
</div>
</a-col>
<!-- 订单状况 -->
<a-col :span="8">
<div class="block">
<div class="block-title">订单数据</div>
<div class="statistical">
<div class="statistical-item">
<h4>{{ orders.paid }}</h4>
<p>待发货</p>
</div>
<div class="statistical-item">
<h4>{{ orders.delivered }}</h4>
<p>待收货</p>
</div>
</div>
</div>
</a-col>
<!-- 今日统计 -->
<a-col :span="8">
<div class="block">
<div class="block-title">今日统计</div>
<div class="business">
<div class="business-item">
<a-avatar :size="40" style="backgroundColor:#e5f3fe; color: #0383fe" icon="pay-circle" />
<h4>{{ business.today }}</h4>
<p>营业额()</p>
</div>
<div class="business-item">
<a-avatar :size="40" style="backgroundColor:#f3f1fe; color: #876cfd" icon="unordered-list" />
<h4>{{ business.today_sold }}</h4>
<p>总销量()</p>
</div>
<div class="business-item">
<a-avatar :size="40" style="backgroundColor:#fff8e5; color: #ffbe17" icon="unordered-list" />
<h4>{{ business.people }}</h4>
<p>消费人数</p>
</div>
<div class="business-item">
</div>
</div>
</div>
</a-col>
<!-- 渠道订单情况 -->
<a-col :span="16">
<div class="block">
<div class="block-title">渠道订单情况</div>
<div class="business">
<div class="business-item">
<a-avatar :size="40" style="background-color: #e5f3fe; color: #0383fe" icon="pay-circle" />
<h4>{{ group.order.current_group_number }}</h4>
<p>购买本店铺商品单数()</p>
</div>
<div class="business-item">
<a-avatar :size="40" style="background-color: #f3f1fe; color: #876cfd" icon="unordered-list" />
<h4>{{ group.order.current_group_price }}</h4>
<p>购买本店铺商品总金额()</p>
</div>
<div class="business-item">
<a-avatar :size="40" style="background-color: #fff8e5; color: #ffbe17" icon="unordered-list" />
<h4>{{ group.order.other_group_number }}</h4>
<p>购买其他店铺商品单数()</p>
</div>
<div class="business-item">
<a-avatar :size="40" style="background-color: #ebf9ea; color: #41c941" icon="snippets" />
<h4>{{ group.order.other_group_price }}</h4>
<p>购买其他店铺商品总金额()</p>
</div>
</div>
</div>
</a-col>
<!-- 渠道货权信息 -->
<a-col :span="24">
<div class="block">
<div class="block-title">渠道货权信息</div>
<div class="statistical">
<div class="statistical-item statistical-item1">
<h4>{{ group.right.price }}</h4>
<p>当前货权价格</p>
</div>
<div class="statistical-item statistical-item1">
<h4>{{ group.right.wheel }}</h4>
<p>当前货权第几轮</p>
</div>
<div class="statistical-item statistical-item1">
<h4>{{ group.right.out_total }}</h4>
<p>货权出货总量</p>
</div>
<div class="statistical-item statistical-item1">
<h4>{{ group.right.out_price }}</h4>
<p>货权出货总金额</p>
</div>
<div class="statistical-item statistical-item1">
<h4>{{ group.right.buy_total }}</h4>
<p>认购货权总量</p>
</div>
<div class="statistical-item statistical-item1">
<h4>{{ group.right.buy_price }}</h4>
<p>认购货权总金额</p>
</div>
<div class="statistical-item statistical-item1">
<h4>{{ group.right.appointment_total }}</h4>
<p>带票货权总量</p>
</div>
<div class="statistical-item statistical-item1">
<h4>{{ group.right.appointment_price }}</h4>
<p>带票货权总金额</p>
</div>
</div>
</div>
</a-col>
<!-- 渠道用户信息 -->
<a-col :span="24">
<div class="block">
<div class="block-title">渠道用户信息</div>
<div class="statistical">
<div class="statistical-item statistical-item1">
<h4>{{ group.user.total }}</h4>
<p>总用户</p>
</div>
<div class="statistical-item statistical-item1">
<h4>{{ group.user.identity_1 }}</h4>
<p>普通用户</p>
</div>
<div class="statistical-item statistical-item1">
<h4>{{ group.user.identity_2 }}</h4>
<p> 水感应大数据分析审核系统会员</p>
</div>
<div class="statistical-item statistical-item1">
<h4>{{ group.user.identity_3 }}</h4>
<p>经纪人</p>
</div>
<div class="statistical-item statistical-item1">
<h4>{{ group.user.identity_4 }}</h4>
<p>经纪公司</p>
</div>
<div class="statistical-item statistical-item1">
<h4>{{ group.user.identity_5 }}</h4>
<p>代理商</p>
</div>
<div class="statistical-item statistical-item1">
<h4>{{ group.user.identity_6 }}</h4>
<p>分公司</p>
</div>
<!-- <div class="statistical-item statistical-item1">
<h4>{{ group.right.appointment_price }}</h4>
<p>带票货权总金额</p>
</div> -->
</div>
</div>
</a-col>
</div>
</div>
</template>
<script>
import { getStatistical } from '@/api/statistical'
import { timeFix } from '@/utils/util'
import EditShop from '@/views/home/modules/EditShop'
export default {
name: 'Home',
data () {
return {
timeFix: timeFix(),
base_info: {
cover: '',
description: '',
name: ''
},
business: {},
orders: {},
group: {},
ranking: [],
shop: {
today: '',
user: '',
total: '',
sold: '',
goods_up: ''
}
}
},
computed: {
currentUser () {
return {
name: this.$store.getters.nickname,
avatar: this.$store.getters.avatar
}
}
},
created () {
this.getInfo()
},
mounted () {
},
methods: {
getInfo () {
getStatistical().then(res => {
this.base_info = res.base_info
this.business = res.business
this.orders = res.orders
this.ranking = res.ranking
this.shop = res.shop
this.group = res.group
}).catch(err => {
this.$notification.error(err)
})
},
EditShop () {
this.$dialog(EditShop,
{
on: {
ok: () => {
this.getInfo()
}
}
},
{
title: '修改店铺信息',
width: 500,
maskClosable: true,
closable: true,
okText: '确认修改',
cancelText: '取消'
}
)
}
}
}
</script>
<style lang='less' scoped>
@import "Index.less";
</style>

BIN
public/favicon.ico vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

35
public/index.html vendored Normal file
View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<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 %>logo.png">
<title> 水感应经销商管理后台 WaterFeel.Vip</title>
<style>.first-loading-wrp{display:flex;justify-content:center;align-items:center;flex-direction:column;min-height:420px;height:100%}.first-loading-wrp>h1{font-size:128px}.first-loading-wrp .loading-wrp{padding:98px;display:flex;justify-content:center;align-items:center}.dot{animation:antRotate 1.2s infinite linear;transform:rotate(45deg);position:relative;display:inline-block;font-size:32px;width:32px;height:32px;box-sizing:border-box}.dot i{width:14px;height:14px;position:absolute;display:block;background-color:#1890ff;border-radius:100%;transform:scale(.75);transform-origin:50% 50%;opacity:.3;animation:antSpinMove 1s infinite linear alternate}.dot i:nth-child(1){top:0;left:0}.dot i:nth-child(2){top:0;right:0;-webkit-animation-delay:.4s;animation-delay:.4s}.dot i:nth-child(3){right:0;bottom:0;-webkit-animation-delay:.8s;animation-delay:.8s}.dot i:nth-child(4){bottom:0;left:0;-webkit-animation-delay:1.2s;animation-delay:1.2s}@keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@-webkit-keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@keyframes antSpinMove{to{opacity:1}}@-webkit-keyframes antSpinMove{to{opacity:1}}</style>
<!-- require cdn assets css -->
<% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
<link rel="stylesheet" href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" />
<% } %>
</head>
<body>
<noscript>
<strong>We're sorry but vue-antd-pro doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app">
<div class="first-loading-wrp">
<h1> 水感应经销商管理后台</h1>
<div class="loading-wrp">
<span class="dot dot-spin"><i></i><i></i><i></i><i></i></span>
</div>
<div style="display: flex; justify-content: center; align-items: center;"WaterFeel.manager Co.,Ltd.</div>
</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>

BIN
public/logo.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 KiB

14
src/.env Normal file
View File

@@ -0,0 +1,14 @@
NODE_ENV=production
VUE_APP_PREVIEW=false
; <!-- // 水感应大数据分析审核系统环境测试 -->
#VUE_APP_API_BASE_URL=https://admin.shuzijingji.org.cn/agent
; VUE_APP_API_BASE_URL=http://api.goodsright.shangkelian.cn/agent
; VUE_APP_CHAIN_RPC_URL=https://chain-api.shuzijingji.org.cn
; <!-- // 水感应大数据分析审核系统正式测试 -->
#VUE_APP_API_BASE_URL=https://admin.shuzijingji.org.cn/agent
VUE_APP_API_BASE_URL=https://gz-api.lianshang.vip/agent
VUE_APP_CHAIN_RPC_URL=https://chain-api.shuzijingji.org.cn

34
src/App.vue Normal file
View File

@@ -0,0 +1,34 @@
<template>
<a-config-provider :locale="locale">
<div id="app">
<router-view />
</div>
</a-config-provider>
</template>
<script>
import { domTitle, setDocumentTitle } from '@/utils/domUtil'
import { i18nRender } from '@/locales'
export default {
data () {
return {}
},
computed: {
locale () {
// 只是为了切换语言时,更新标题
const { title } = this.$route.meta
title && (setDocumentTitle(`${i18nRender(title)} - ${domTitle}`))
return this.$i18n.getLocaleMessage(this.$store.getters.lang).antLocale
}
}
}
</script>
<style lang="less">
.ml8 {
margin-left: 8px;
}
</style>

102
src/api/address.js Normal file
View File

@@ -0,0 +1,102 @@
import request from '@/utils/request'
const api = {
addressList: '/staff/addresses',
addressAllList: '/staff/addresses/all',
addressExpress: '/orders/{order}/express',
addressCreate: '/staff/addresses',
addressDetail: '/staff/addresses/{addressId}',
updateAddress: '/staff/addresses/{addressId}',
deleteAddress: '/staff/addresses/{addressId}',
defaultAddress: '/staff/addresses/{addressId}/default'
}
/**
* 获取地址列表
* @param parameter
* @returns {AxiosPromise}
*/
export function getAddrList (parameter) {
return request({
url: api.addressList,
method: 'get',
params: parameter
})
}
// 获取所有地址
export function getAllAddrList () {
return request({
url: api.addressAllList,
method: 'get'
})
}
// 指派邮寄地址
export function addressExpress (order, parameter) {
return request({
url: api.addressExpress.replace('{order}', order),
method: 'post',
params: parameter
})
}
/**
* 新增地址
* @param parameter
* @returns {AxiosPromise}
*/
export function createAddr (parameter) {
return request({
url: api.addressCreate,
method: 'post',
data: parameter
})
}
/**
* 获取地址详情
* @param addressId
* @returns {AxiosPromise}
*/
export function getAddr (addressId) {
return request({
url: api.addressDetail.replace('{addressId}', addressId),
method: 'get'
})
}
/**
* 编辑地址
* @param addressId
*/
export function updateAddr (addressId, parameter) {
return request({
url: api.updateAddress.replace('{addressId}', addressId),
method: 'put',
data: parameter
})
}
/**
* 删除地址
* @param addressId
*/
export function deleteAddr (addressId) {
return request({
url: api.deleteAddress.replace('{addressId}', addressId),
method: 'delete'
})
}
/**
* 默认地址
* @param parameter
* @returns {AxiosPromise}
*/
export function defaultAddress (addressId) {
return request({
url: api.defaultAddress.replace('{addressId}', addressId),
method: 'post'
})
}

112
src/api/express.js Normal file
View File

@@ -0,0 +1,112 @@
import request from '@/utils/request'
const api = {
expressCommonList: '/mall/orders/{orderId}/deliver',
expressList: '/store/expresses',
createExpress: '/store/expresses',
editExpress: '/store/expresses/{expressId}/edit',
updateExpress: '/store/expresses/{expressId}',
deleteExpress: '/store/expresses/{expressId}',
getExpressDrow: '/staff/expresses',
signInDrow: '/orders/{orderId}/outlineexpress'
}
/**
* 保存物流信息时候用的接口
* @returns {AxiosPromise}
*/
export function getExpressCommonList (orderId) {
return request({
url: api.expressCommonList.replace('{orderId}', orderId),
method: 'get'
})
}
/**
* 物流列表
* @param parameter
* @returns {AxiosPromise}
*/
export function getExpressList (parameter) {
return request({
url: api.expressList,
method: 'get',
params: parameter
})
}
/**
* 新增
* @param data
* @returns {AxiosPromise}
*/
export function storeExpress (data) {
return request({
url: api.createExpress,
method: 'post',
data: data
})
}
/**
* 编辑
* @param expressId
* @returns {AxiosPromise}
*/
export function editExpress (expressId) {
return request({
url: api.editExpress.replace('{expressId}', expressId),
method: 'get'
})
}
/**
* 更新
* @param expressId
* @param data
* @returns {AxiosPromise}
*/
export function updateExpress (expressId, data) {
return request({
url: api.updateExpress.replace('{expressId}', expressId),
method: 'put',
data: data
})
}
/**
* 删除物流
* @param expressId
* @returns {AxiosPromise}
*/
export function deleteExpress (expressId) {
return request({
url: api.deleteExpress.replace('{expressId}', expressId),
method: 'delete'
})
}
/**
* 线下签收信息前置
* @param parameter
* @returns {AxiosPromise}
*/
export function getExpressDrow (parameter) {
return request({
url: api.getExpressDrow,
method: 'get'
})
}
/**
* 线下签收
* @param parameter
* @returns {AxiosPromise}
*/
export function signInDrow (orderId, parameter) {
return request({
url: api.signInDrow.replace('{orderId}', orderId),
method: 'POST',
data: parameter
})
}

87
src/api/login.js Normal file
View File

@@ -0,0 +1,87 @@
import request from '@/utils/request'
const userApi = {
SendSms: '/staff/verify',
Login: '/auth/sms',
Logout: '/auth/logout',
CommonLogin: '/staff/login',
forgetPassword: '/staff/forget/password',
password: '/staff/password',
// get my info
UserPermission: '/staff',
getScanCode: '/staff/scan_code',
getVerifyScanCode: '/staff/verify_subscribe'
}
export function login (parameter) {
return request({
url: userApi.Login,
method: 'post',
data: parameter
})
}
/**
* 通用账号密码登录
* @param parameter
* @returns {*}
*/
export function commonLogin (parameter) {
parameter.redirectUrl = window.location.href
return request({
url: userApi.CommonLogin,
method: 'post',
data: parameter
})
}
export function forgetPassword (parameter) {
return request({
url: userApi.forgetPassword,
method: 'post',
data: parameter
})
}
export function password (parameter) {
return request({
url: userApi.password,
method: 'post',
data: parameter
})
}
export function getSmsCaptcha (parameter) {
return request({
url: userApi.SendSms,
method: 'post',
data: parameter
})
}
export function getPermission () {
return request({
url: userApi.UserPermission,
method: 'get'
})
}
export function logout () {
return request({
url: userApi.Logout,
method: 'post'
})
}
// 获取关注二维码
export function getScanCode () {
return request({
url: userApi.getScanCode
})
}
// 判断用户是否关注二维码
export function getVerifyScanCode () {
return request({
url: userApi.getVerifyScanCode
})
}

54
src/api/notice.js Normal file
View File

@@ -0,0 +1,54 @@
import request from '@/utils/request'
const api = {
unreadCount: '/notifies/unread_count',
notifies: '/notifies',
readNotice: '/notifies/mark_as_read/{noticeId}',
readAllNotices: '/notifies/mark_as_read_all',
sendMessage: '/notifies/send/{orderId}'
}
// 未读消息总数 6s 一轮回
export function getUnreadCount () {
return request({
url: api.unreadCount,
method: 'get'
})
}
// 消息列表
export function getNoticeList (parameter) {
return request({
url: api.notifies,
method: 'get',
params: parameter
})
}
// 消息已读
export function readNotice (noticeId) {
return request({
url: api.readNotice.replace('{noticeId}', noticeId),
method: 'POST'
})
}
// 消息全部已读
export function readAllNotice (noticeId) {
return request({
url: api.readAllNotices,
method: 'POST'
})
}
// 发送消息
export function sendMessage (orderId, type, itemId) {
return request({
url: api.sendMessage.replace('{orderId}', orderId),
method: 'POST',
params: {
type: type,
item_id: itemId
}
})
}

247
src/api/order.js Normal file
View File

@@ -0,0 +1,247 @@
import request from '@/utils/request'
const api = {
list: '/orders',
info: '/orders/{orderId}',
base: '/orders/{orderId}/base',
items: '/orders/{orderId}/items',
services: '/orders/{orderId}/services',
check: '/orders/{orderId}/check',
updateOrderInfo: '/orders/{orderId}/update',
pass: '/orders/{orderId}/check/pass',
repass: '/orders/{orderId}/repass',
diffp: '/orders/{orderId}/diffprice',
diffs: '/orders/{orderId}/diffs',
diffBack: '/orders/{orderId}/diffprice/{diff}',
firstpass: 'orders/{orderId}/firstpass',
teachermakesure: 'orders/{orderId}/teachermakesure',
getExpressesList: 'orders/{orderId}/expresses',
logistic: 'orders/{express}/logistic',
signInLogistic: 'orders/{orderId}/sign/{express}',
doLogs: 'orders/{orderId}/dologs',
doLogsList: 'orders/{orderId}/dologs',
delLog: 'orders/{orderId}/dologs/{logId}',
getSchemes: 'orders/{orderId}/schemes/{itemId}',
institutionEdit: 'institutions/{itemId}/institution', // 修改机构名称 主要做备注用换了机构
getOrdersCount: 'orders/count',
changeBankCount: 'orders/{orderId}/items/{itemId}/price',
getChangeBankCountLog: 'orders/{orderId}/items/{itemId}/price_log',
getAllBanks: 'orders/{orderId}/all_items' // 获得所有机构信息
}
// 订单列表
export function getOrderList (parameter) {
return request({
url: api.list,
method: 'get',
params: parameter
})
}
// 根据订单id获取订单详情
export function orderInfo (orderId) {
return request({
url: api.info.replace('{orderId}', orderId),
method: 'get'
})
}
// 根据订单id获取下单用户的真实信息
export function realName (orderId) {
return request({
url: api.base.replace('{orderId}', orderId),
method: 'get'
})
}
// 根据订单 id 获取下单申请机构列表
export function getBankItem (orderId, parameter) {
return request({
url: api.items.replace('{orderId}', orderId),
method: 'get',
params: parameter
})
}
// 根据id 展示服务包
export function getServices (orderId, parameter) {
return request({
url: api.services.replace('{orderId}', orderId),
method: 'get',
params: parameter
})
}
// 订单信息修改
export function updateOrderInfo (orderId, parameter) {
return request({
url: api.updateOrderInfo.replace('{orderId}', orderId),
method: 'post',
params: parameter
})
}
// 驳回用户基本信息和机构信息 type 1. 用户信息 2. 机构信息
export function checkInfo (orderId, parameter) {
return request({
url: api.check.replace('{orderId}', orderId),
method: 'post',
params: parameter
})
}
// 用户信息驳回通过/orders/1/check/pass
export function passInfo (orderId, parameter) {
return request({
url: api.pass.replace('{orderId}', orderId),
method: 'post',
params: parameter
})
}
// 用户服务包补差价
export function diffPrice (orderId, parameter) {
return request({
url: api.diffp.replace('{orderId}', orderId),
method: 'post',
params: parameter
})
}
// 用户服务包补差价记录
export function diffPayList (orderId) {
return request({
url: api.diffs.replace('{orderId}', orderId),
method: 'get'
})
}
// 撤回补差价
export function diffBack (orderId, diff) {
return request({
url: api.diffBack.replace('{orderId}', orderId).replace('{diff}', diff),
method: 'DELETE'
})
}
// 初审通过orders/{order}/firstpass
export function firstPass (orderId) {
return request({
url: api.firstpass.replace('{orderId}', orderId),
method: 'post'
})
}
// 复审
export function secondPass (orderId) {
return request({
url: api.repass.replace('{orderId}', orderId),
method: 'post'
})
}
// 待老师确认通过审核按钮
export function teachermakesure (orderId) {
return request({
url: api.teachermakesure.replace('{orderId}', orderId),
method: 'post'
})
}
// 查看邮寄材料列表
export function getExpressesList (orderId, parameter) {
return request({
url: api.getExpressesList.replace('{orderId}', orderId),
params: parameter
})
}
// 查看邮寄材料物流信息
export function getLogistic (express) {
return request({
url: api.logistic.replace('{express}', express)
})
}
// 确认签收
export function signInLogistic (orderId, express) {
return request({
url: api.signInLogistic.replace('{orderId}', orderId).replace('{express}', express),
method: 'post'
})
}
// 添加记录 itemid 有就是给机构添加的记录 没有就是给订单加的记录信息
export function doLogs (orderId, parameter) {
return request({
url: api.doLogs.replace('{orderId}', orderId),
method: 'post',
params: parameter
})
}
// 记录列表 itemid 代表查机构的记录列表 不带itemid 就是查全部的记录列表
export function doLogsList (orderId, parameter) {
return request({
url: api.doLogsList.replace('{orderId}', orderId),
method: 'get',
params: parameter
})
}
// 删除记录delLog
export function delLog (orderId, logId) {
return request({
url: api.delLog.replace('{orderId}', orderId).replace('{logId}', logId),
method: 'DELETE'
})
}
// 获取确认方案信息
export function getSchemes (orderId, itemId) {
return request({
url: api.getSchemes.replace('{orderId}', orderId).replace('{itemId}', itemId),
method: 'get'
})
}
// 修改机构名称(备注使用换了机构)
export function institutionEdit (itemId, parameter) {
return request({
url: api.institutionEdit.replace('{itemId}', itemId),
method: 'POST',
params: parameter
})
}
// 提交确认方案
export function addSchemes (orderId, itemId, parameter) {
return request({
url: api.getSchemes.replace('{orderId}', orderId).replace('{itemId}', itemId),
method: 'post',
params: parameter
})
}
// 修改机构金额
export function changeBankCount (orderId, itemId, parameter) {
return request({
url: api.changeBankCount.replace('{orderId}', orderId).replace('{itemId}', itemId),
method: 'PUT',
params: parameter
})
}
// 机构修改金额记录getChangeBankCount
export function getChangeBankCountLog (orderId, itemId) {
return request({
url: api.getChangeBankCountLog.replace('{orderId}', orderId).replace('{itemId}', itemId),
method: 'get'
})
}
// 获取订单数量
export function getOrdersCount (parameter) {
return request({
url: api.getOrdersCount,
method: 'get',
params: parameter
})
}
// 获得所有机构信息
export function getAllBanks (orderId) {
return request({
url: api.getAllBanks.replace('{orderId}', orderId),
method: 'get'
})
}

90
src/api/organization.js Normal file
View File

@@ -0,0 +1,90 @@
/*
* @Author: Aimee~
* @Date: 2023-05-11 12:02:45
* @LastEditTime: 2023-06-27 13:10:40
* @LastEditors: Aimee
* @FilePath: /douhuo-agent/src/api/organization.js
* @Description:
*/
import request from '@/utils/request'
const api = {
list: '/institutions', // 机构列表
init: '/institutions/init', // 筛选前置
info: '/institutions/{item}/show', // 机构详情接口
changeStatus: '/institutions/{item}/status', // 修改机构状态
service: 'institutions/{item}/service', // 修改服务包信息
remark: 'institutions/{item}/remark', // 修改机构备注信息
canExport: 'institutions/can_export', // 机构是否能导出
changeStatusTwo: 'institutions/{item}/status_two' // 更改机构状态2
}
// 机构列表
export function getList (parameter) {
return request({
url: api.list,
method: 'get',
params: parameter
})
}
// 筛选前置
export function init () {
return request({
url: api.init
})
}
// 获取机构详情接口
export function getOriganizationInfo (item) {
return request({
url: api.info.replace('{item}', item),
method: 'get'
})
}
// 修改结构状态
export function changeStatus (item, params) {
return request({
url: api.changeStatus.replace('{item}', item),
method: 'post',
params: params
})
}
// 修改服务包信息
export function editServiceInfo (item, params) {
console.log(item, params)
return request({
url: api.service.replace('{item}', item),
method: 'post',
params: params
})
}
// 修改机构备注消息
export function remark (item, params) {
console.log(item, params)
return request({
url: api.remark.replace('{item}', item),
method: 'post',
params: params
})
}
// 机构列表是否能导出
export function canExport (userid) {
return request({
url: api.canExport,
method: 'get',
params: {
user_id: userid
}
})
}
// 更改机构状态2
export function changeStatusTwo (item, params) {
return request({
url: api.changeStatusTwo.replace('{item}', item),
method: 'post',
params: params
})
}

113
src/api/promissions.js Normal file
View File

@@ -0,0 +1,113 @@
import request from '@/utils/request'
const api = {
staffCan: '/staff/can',
staffDeparts: '/staff/departs',
staffDepartsUser: '/staff/users',
addPreDepartUser: '/staff/users/create',
addDepartsUser: '/staff/users',
editDepartsUser: '/staff/users/{staff_id}',
delDepartsUser: '/staff/users/{staff_id}',
getDepartsUser: '/staff/users',
getDepartsOrder: '/staff/addresses',
getDefaultUserInfo: '/staff/users/{staff_id}/edit'
}
/**
* @returns 判断是否能查看订单与权限
*/
export function staffCan () {
return request({
url: api.staffCan
})
}
/**
* @returns 用户组织架构
*/
export function staffDeparts () {
return request({
url: api.staffDeparts
})
}
/**
* @returns 我的组织列表
*/
export function staffDepartsUser () {
return request({
url: api.staffDepartsUser
})
}
/**
* @returns 组织下用户列表
*/
export function getDepartsUser (parameter) {
return request({
url: api.getDepartsUser,
method: 'get',
params: parameter
})
}
/**
* @returns 组织下待指派订单列表
*/
export function getDepartsOrder (parameter) {
return request({
url: api.getDepartsOrder,
method: 'get',
params: parameter
})
}
/**
* @returns 我的用户新增前置获取下级身份
*/
export function addPreDepartUser (parameter) {
return request({
url: api.addPreDepartUser,
params: parameter
})
}
/**
* @returns 组织下增加用户操作
*/
export function addDepartsUser (parameter) {
return request({
url: api.addDepartsUser,
method: 'POST',
params: parameter
})
}
/**
* @returns 组织下修改用户操作
*/
export function editDepartsUser (staffId, parameter) {
return request({
url: api.editDepartsUser.replace('{staff_id}', staffId),
method: 'PUT',
params: parameter
})
}
/**
* @returns 组织下删除用户操作
*/
export function delDepartsUser (staffId) {
return request({
url: api.delDepartsUser.replace('{staff_id}', staffId),
method: 'DELETE'
})
}
/**
* @returns 组织下用户修改前置
*/
export function getDefaultUserInfo (staffId) {
return request({
url: api.getDefaultUserInfo.replace('{staff_id}', staffId)
})
}

15
src/api/statistical.js Normal file
View File

@@ -0,0 +1,15 @@
import request from '@/utils/request'
export function getStatistical () {
return request({
url: '/data/index',
method: 'get'
})
}
export function getHomeUserInfo () {
return request({
url: '/data/user',
method: 'get'
})
}

25
src/api/storage.js Normal file
View File

@@ -0,0 +1,25 @@
import request from '@/utils/request'
const api = {
stsAuth: '/storage/sts',
storageUpload: '/storage/upload',
storageDownload: '/orders/exports', // 订单导出
institutionsExports: '/institutions/exports' // 订单导出
}
export function getStsAssumeRole () {
return request({
url: api.stsAuth,
method: 'get'
})
}
export function getStorageUploadUrl () {
return process.env.VUE_APP_API_BASE_URL + api.storageUpload
}
export function getStorageDownUrl () {
return process.env.VUE_APP_API_BASE_URL + api.storageDownload
}
export function institutionsExports () {
return process.env.VUE_APP_API_BASE_URL + api.institutionsExports
}

69
src/assets/background.svg Normal file
View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="1361px" height="609px" viewBox="0 0 1361 609" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
<title>Group 21</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Ant-Design-Pro-3.0" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="账户密码登录-校验" transform="translate(-79.000000, -82.000000)">
<g id="Group-21" transform="translate(77.000000, 73.000000)">
<g id="Group-18" opacity="0.8" transform="translate(74.901416, 569.699158) rotate(-7.000000) translate(-74.901416, -569.699158) translate(4.901416, 525.199158)">
<ellipse id="Oval-11" fill="#CFDAE6" opacity="0.25" cx="63.5748792" cy="32.468367" rx="21.7830479" ry="21.766008"></ellipse>
<ellipse id="Oval-3" fill="#CFDAE6" opacity="0.599999964" cx="5.98746479" cy="13.8668601" rx="5.2173913" ry="5.21330997"></ellipse>
<path d="M38.1354514,88.3520215 C43.8984227,88.3520215 48.570234,83.6838647 48.570234,77.9254015 C48.570234,72.1669383 43.8984227,67.4987816 38.1354514,67.4987816 C32.3724801,67.4987816 27.7006688,72.1669383 27.7006688,77.9254015 C27.7006688,83.6838647 32.3724801,88.3520215 38.1354514,88.3520215 Z" id="Oval-3-Copy" fill="#CFDAE6" opacity="0.45"></path>
<path d="M64.2775582,33.1704963 L119.185836,16.5654915" id="Path-12" stroke="#CFDAE6" stroke-width="1.73913043" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M42.1431708,26.5002681 L7.71190162,14.5640702" id="Path-16" stroke="#E0B4B7" stroke-width="0.702678964" opacity="0.7" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"></path>
<path d="M63.9262187,33.521561 L43.6721326,69.3250951" id="Path-15" stroke="#BACAD9" stroke-width="0.702678964" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"></path>
<g id="Group-17" transform="translate(126.850922, 13.543654) rotate(30.000000) translate(-126.850922, -13.543654) translate(117.285705, 4.381889)" fill="#CFDAE6">
<ellipse id="Oval-4" opacity="0.45" cx="9.13482653" cy="9.12768076" rx="9.13482653" ry="9.12768076"></ellipse>
<path d="M18.2696531,18.2553615 C18.2696531,13.2142826 14.1798519,9.12768076 9.13482653,9.12768076 C4.08980114,9.12768076 0,13.2142826 0,18.2553615 L18.2696531,18.2553615 Z" id="Oval-4" transform="translate(9.134827, 13.691521) scale(-1, -1) translate(-9.134827, -13.691521) "></path>
</g>
</g>
<g id="Group-14" transform="translate(216.294700, 123.725600) rotate(-5.000000) translate(-216.294700, -123.725600) translate(106.294700, 35.225600)">
<ellipse id="Oval-2" fill="#CFDAE6" opacity="0.25" cx="29.1176471" cy="29.1402439" rx="29.1176471" ry="29.1402439"></ellipse>
<ellipse id="Oval-2" fill="#CFDAE6" opacity="0.3" cx="29.1176471" cy="29.1402439" rx="21.5686275" ry="21.5853659"></ellipse>
<ellipse id="Oval-2-Copy" stroke="#CFDAE6" opacity="0.4" cx="179.019608" cy="138.146341" rx="23.7254902" ry="23.7439024"></ellipse>
<ellipse id="Oval-2" fill="#BACAD9" opacity="0.5" cx="29.1176471" cy="29.1402439" rx="10.7843137" ry="10.7926829"></ellipse>
<path d="M29.1176471,39.9329268 L29.1176471,18.347561 C23.1616351,18.347561 18.3333333,23.1796097 18.3333333,29.1402439 C18.3333333,35.1008781 23.1616351,39.9329268 29.1176471,39.9329268 Z" id="Oval-2" fill="#BACAD9"></path>
<g id="Group-9" opacity="0.45" transform="translate(172.000000, 131.000000)" fill="#E6A1A6">
<ellipse id="Oval-2-Copy-2" cx="7.01960784" cy="7.14634146" rx="6.47058824" ry="6.47560976"></ellipse>
<path d="M0.549019608,13.6219512 C4.12262681,13.6219512 7.01960784,10.722722 7.01960784,7.14634146 C7.01960784,3.56996095 4.12262681,0.670731707 0.549019608,0.670731707 L0.549019608,13.6219512 Z" id="Oval-2-Copy-2" transform="translate(3.784314, 7.146341) scale(-1, 1) translate(-3.784314, -7.146341) "></path>
</g>
<ellipse id="Oval-10" fill="#CFDAE6" cx="218.382353" cy="138.685976" rx="1.61764706" ry="1.61890244"></ellipse>
<ellipse id="Oval-10-Copy-2" fill="#E0B4B7" opacity="0.35" cx="179.558824" cy="175.381098" rx="1.61764706" ry="1.61890244"></ellipse>
<ellipse id="Oval-10-Copy" fill="#E0B4B7" opacity="0.35" cx="180.098039" cy="102.530488" rx="2.15686275" ry="2.15853659"></ellipse>
<path d="M28.9985381,29.9671598 L171.151018,132.876024" id="Path-11" stroke="#CFDAE6" opacity="0.8"></path>
</g>
<g id="Group-10" opacity="0.799999952" transform="translate(1054.100635, 36.659317) rotate(-11.000000) translate(-1054.100635, -36.659317) translate(1026.600635, 4.659317)">
<ellipse id="Oval-7" stroke="#CFDAE6" stroke-width="0.941176471" cx="43.8135593" cy="32" rx="11.1864407" ry="11.2941176"></ellipse>
<g id="Group-12" transform="translate(34.596774, 23.111111)" fill="#BACAD9">
<ellipse id="Oval-7" opacity="0.45" cx="9.18534718" cy="8.88888889" rx="8.47457627" ry="8.55614973"></ellipse>
<path d="M9.18534718,17.4450386 C13.8657264,17.4450386 17.6599235,13.6143199 17.6599235,8.88888889 C17.6599235,4.16345787 13.8657264,0.332739156 9.18534718,0.332739156 L9.18534718,17.4450386 Z" id="Oval-7"></path>
</g>
<path d="M34.6597385,24.809694 L5.71666084,4.76878945" id="Path-2" stroke="#CFDAE6" stroke-width="0.941176471"></path>
<ellipse id="Oval" stroke="#CFDAE6" stroke-width="0.941176471" cx="3.26271186" cy="3.29411765" rx="3.26271186" ry="3.29411765"></ellipse>
<ellipse id="Oval-Copy" fill="#F7E1AD" cx="2.79661017" cy="61.1764706" rx="2.79661017" ry="2.82352941"></ellipse>
<path d="M34.6312443,39.2922712 L5.06366663,59.785082" id="Path-10" stroke="#CFDAE6" stroke-width="0.941176471"></path>
</g>
<g id="Group-19" opacity="0.33" transform="translate(1282.537219, 446.502867) rotate(-10.000000) translate(-1282.537219, -446.502867) translate(1142.537219, 327.502867)">
<g id="Group-17" transform="translate(141.333539, 104.502742) rotate(275.000000) translate(-141.333539, -104.502742) translate(129.333539, 92.502742)" fill="#BACAD9">
<circle id="Oval-4" opacity="0.45" cx="11.6666667" cy="11.6666667" r="11.6666667"></circle>
<path d="M23.3333333,23.3333333 C23.3333333,16.8900113 18.1099887,11.6666667 11.6666667,11.6666667 C5.22334459,11.6666667 0,16.8900113 0,23.3333333 L23.3333333,23.3333333 Z" id="Oval-4" transform="translate(11.666667, 17.500000) scale(-1, -1) translate(-11.666667, -17.500000) "></path>
</g>
<circle id="Oval-5-Copy-6" fill="#CFDAE6" cx="201.833333" cy="87.5" r="5.83333333"></circle>
<path d="M143.5,88.8126685 L155.070501,17.6038544" id="Path-17" stroke="#BACAD9" stroke-width="1.16666667"></path>
<path d="M17.5,37.3333333 L127.466252,97.6449735" id="Path-18" stroke="#BACAD9" stroke-width="1.16666667"></path>
<polyline id="Path-19" stroke="#CFDAE6" stroke-width="1.16666667" points="143.902597 120.302281 174.935455 231.571342 38.5 147.510847 126.366941 110.833333"></polyline>
<path d="M159.833333,99.7453842 L195.416667,89.25" id="Path-20" stroke="#E0B4B7" stroke-width="1.16666667" opacity="0.6"></path>
<path d="M205.333333,82.1372105 L238.719406,36.1666667" id="Path-24" stroke="#BACAD9" stroke-width="1.16666667"></path>
<path d="M266.723424,132.231988 L207.083333,90.4166667" id="Path-25" stroke="#CFDAE6" stroke-width="1.16666667"></path>
<circle id="Oval-5" fill="#C1D1E0" cx="156.916667" cy="8.75" r="8.75"></circle>
<circle id="Oval-5-Copy-3" fill="#C1D1E0" cx="39.0833333" cy="148.75" r="5.25"></circle>
<circle id="Oval-5-Copy-2" fill-opacity="0.6" fill="#D1DEED" cx="8.75" cy="33.25" r="8.75"></circle>
<circle id="Oval-5-Copy-4" fill-opacity="0.6" fill="#D1DEED" cx="243.833333" cy="30.3333333" r="5.83333333"></circle>
<circle id="Oval-5-Copy-5" fill="#E0B4B7" cx="175.583333" cy="232.75" r="5.25"></circle>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1551058675966" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7872" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M85.333333 512h85.333334a340.736 340.736 0 0 1 99.712-241.621333 337.493333 337.493333 0 0 1 108.458666-72.96 346.453333 346.453333 0 0 1 261.546667-1.749334A106.154667 106.154667 0 0 0 746.666667 298.666667C805.802667 298.666667 853.333333 251.136 853.333333 192S805.802667 85.333333 746.666667 85.333333c-29.397333 0-55.978667 11.776-75.221334 30.933334-103.722667-41.514667-222.848-40.874667-325.76 2.517333a423.594667 423.594667 0 0 0-135.68 91.264 423.253333 423.253333 0 0 0-91.306666 135.637333A426.88 426.88 0 0 0 85.333333 512z m741.248 133.205333c-17.109333 40.618667-41.685333 77.141333-72.96 108.416s-67.797333 55.850667-108.458666 72.96a346.453333 346.453333 0 0 1-261.546667 1.749334A106.154667 106.154667 0 0 0 277.333333 725.333333C218.197333 725.333333 170.666667 772.864 170.666667 832S218.197333 938.666667 277.333333 938.666667c29.397333 0 55.978667-11.776 75.221334-30.933334A425.173333 425.173333 0 0 0 512 938.666667a425.941333 425.941333 0 0 0 393.258667-260.352A426.325333 426.325333 0 0 0 938.666667 512h-85.333334a341.034667 341.034667 0 0 1-26.752 133.205333z" p-id="7873"></path><path d="M512 318.378667c-106.752 0-193.621333 86.869333-193.621333 193.621333S405.248 705.621333 512 705.621333s193.621333-86.869333 193.621333-193.621333S618.752 318.378667 512 318.378667z m0 301.909333c-59.690667 0-108.288-48.597333-108.288-108.288S452.309333 403.712 512 403.712s108.288 48.597333 108.288 108.288-48.597333 108.288-108.288 108.288z" p-id="7874"></path></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
src/assets/login_top.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
src/assets/logo_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
src/assets/logo_bg_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

BIN
src/assets/scan_top_bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
src/assets/平安.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,57 @@
<template>
<a-cascader
:fieldNames="{ label: 'name', value: 'code', children: 'children' }"
:options="areaData"
:placeholder="placeholder"
v-model="selectedValues"
@change="onChange"
/>
</template>
<script>
import areaData from './areadata'
export default {
name: 'AreaSelect',
props: {
placeholder: {
type: String,
default: '请选择省市区'
},
defaultValue: {
type: Array,
default () {
return []
}
}
},
data () {
return {
// 地区数据
areaData,
// 选择的数据
selectedValues: []
}
},
created () {
if (this.defaultValue.length) {
this.selectedValues = [...this.defaultValue]
}
},
watch: {
defaultValue (newValue) {
if (newValue.length) {
this.selectedValues = newValue
} else {
this.selectedValues = []
}
}
},
methods: {
// 选择好之后的回调
onChange (value) {
this.$emit('change', value)
}
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,82 @@
<template>
<div class="antd-pro-components-article-list-content-index-listContent">
<div class="description">
<slot>
{{ description }}
</slot>
</div>
<div class="extra">
<a-avatar :src="avatar" size="small" />
{{ owner }} 发布于
<em>{{ updateAt | moment }}</em>
</div>
</div>
</template>
<script>
export default {
name: 'ArticleListContent',
props: {
prefixCls: {
type: String,
default: 'antd-pro-components-article-list-content-index-listContent'
},
description: {
type: String,
default: ''
},
owner: {
type: String,
required: true
},
avatar: {
type: String,
required: true
},
updateAt: {
type: String,
required: true
}
}
}
</script>
<style lang="less" scoped>
@import '../index.less';
.antd-pro-components-article-list-content-index-listContent {
.description {
max-width: 720px;
line-height: 22px;
}
.extra {
margin-top: 16px;
color: @text-color-secondary;
line-height: 22px;
& /deep/ .ant-avatar {
position: relative;
top: 1px;
width: 20px;
height: 20px;
margin-right: 8px;
vertical-align: top;
}
& > em {
font-style: normal;
}
}
}
@media screen and (max-width: @screen-xs) {
.antd-pro-components-article-list-content-index-listContent {
.extra {
& > em {
display: block;
margin-top: 8px;
}
}
}
}
</style>

View File

@@ -0,0 +1,3 @@
import ArticleListContent from './ArticleListContent'
export default ArticleListContent

View File

@@ -0,0 +1,24 @@
import PropTypes from 'ant-design-vue/es/_util/vue-types'
import { Tooltip, Avatar } from 'ant-design-vue'
import { getSlotOptions } from 'ant-design-vue/lib/_util/props-util'
import { warning } from 'ant-design-vue/lib/vc-util/warning'
export const AvatarListItemProps = {
tips: PropTypes.string.def(null),
src: PropTypes.string.def('')
}
const Item = {
__ANT_AVATAR_CHILDREN: true,
name: 'AvatarListItem',
props: AvatarListItemProps,
created () {
warning(getSlotOptions(this.$parent).__ANT_AVATAR_LIST, 'AvatarListItem must be a subcomponent of AvatarList')
},
render () {
const AvatarDom = <Avatar size={this.$parent.size} src={this.src} />
return this.tips && <Tooltip title={this.tips}>{AvatarDom}</Tooltip> || <AvatarDom />
}
}
export default Item

View File

@@ -0,0 +1,72 @@
import './index.less'
import PropTypes from 'ant-design-vue/es/_util/vue-types'
import Avatar from 'ant-design-vue/es/avatar'
import Item from './Item.jsx'
import { filterEmpty } from '@/components/_util/util'
/**
* size: `number`、 `large`、`small`、`default` 默认值: default
* maxLength: number
* excessItemsStyle: CSSProperties
*/
const AvatarListProps = {
prefixCls: PropTypes.string.def('ant-pro-avatar-list'),
size: {
validator: val => {
return typeof val === 'number' || ['small', 'large', 'default'].includes(val)
},
default: 'default'
},
maxLength: PropTypes.number.def(0),
excessItemsStyle: PropTypes.object.def({
color: '#f56a00',
backgroundColor: '#fde3cf'
})
}
const AvatarList = {
__ANT_AVATAR_LIST: true,
Item,
name: 'AvatarList',
props: AvatarListProps,
render (h) {
const { prefixCls, size } = this.$props
const className = {
[`${prefixCls}`]: true,
[`${size}`]: true
}
const items = filterEmpty(this.$slots.default)
const itemsDom = items && items.length ? <ul class={`${prefixCls}-items`}>{this.getItems(items)}</ul> : null
return (
<div class={className}>
{itemsDom}
</div>
)
},
methods: {
getItems (items) {
const className = {
[`${this.prefixCls}-item`]: true,
[`${this.size}`]: true
}
const totalSize = items.length
if (this.maxLength > 0) {
items = items.slice(0, this.maxLength)
items.push((<Avatar size={this.size} style={this.excessItemsStyle}>{`+${totalSize - this.maxLength}`}</Avatar>))
}
return items.map((item) => (
<li class={className}>{item}</li>
))
}
}
}
AvatarList.install = function (Vue) {
Vue.component(AvatarList.name, AvatarList)
Vue.component(AvatarList.Item.name, AvatarList.Item)
}
export default AvatarList

View File

@@ -0,0 +1,9 @@
import AvatarList from './List'
import Item from './Item'
export {
AvatarList,
Item as AvatarListItem
}
export default AvatarList

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;
}
}
}
}
}

View File

@@ -0,0 +1,64 @@
# AvatarList 用户头像列表
一组用户头像,常用在项目/团队成员列表。可通过设置 `size` 属性来指定头像大小。
引用方式:
```javascript
import AvatarList from '@/components/AvatarList'
const AvatarListItem = AvatarList.Item
export default {
components: {
AvatarList,
AvatarListItem
}
}
```
## 代码演示 [demo](https://pro.loacg.com/test/home)
```html
<avatar-list size="mini">
<avatar-list-item tips="Jake" src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png" />
<avatar-list-item tips="Andy" src="https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png" />
<avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
</avatar-list>
```
```html
<avatar-list :max-length="3">
<avatar-list-item tips="Jake" src="https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png" />
<avatar-list-item tips="Andy" src="https://gw.alipayobjects.com/zos/rmsportal/sfjbOqnsXXJgNCjCzDBL.png" />
<avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
<avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
<avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
<avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
<avatar-list-item tips="Niko" src="https://gw.alipayobjects.com/zos/rmsportal/kZzEzemZyKLKFsojXItE.png" />
</avatar-list>
```
## API
### AvatarList
| 参数 | 说明 | 类型 | 默认值 |
| ---------------- | -------- | ---------------------------------- | --------- |
| size | 头像大小 | `large``small``mini`, `default` | `default` |
| maxLength | 要显示的最大项目 | number | - |
| excessItemsStyle | 多余的项目风格 | CSSProperties | - |
### AvatarList.Item
| 参数 | 说明 | 类型 | 默认值 |
| ---- | ------ | --------- | --- |
| tips | 头像展示文案 | string | - |
| src | 头像图片连接 | string | - |

View File

@@ -0,0 +1,62 @@
<template>
<div :style="{ padding: '0 0 32px 32px' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart
height="254"
:data="data"
: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: ''
},
data: {
type: Array,
default: () => {
return []
}
},
scale: {
type: Array,
default: () => {
return [{
dataKey: 'x',
min: 2
}, {
dataKey: 'y',
title: '时间',
min: 1,
max: 22
}]
}
},
tooltip: {
type: Array,
default: () => {
return [
'x*y',
(x, y) => ({
name: x,
value: y
})
]
}
}
},
data () {
return {
}
}
}
</script>

View File

@@ -0,0 +1,120 @@
<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">
<slot name="title">
{{ title }}
</slot>
</span>
<span class="chart-card-action">
<slot name="action"></slot>
</span>
</div>
<div class="total">
<slot name="total">
<span>{{ typeof total === 'function' && total() || total }}</span>
</slot>
</div>
</div>
<div class="chart-card-content">
<div class="content-fix">
<slot></slot>
</div>
</div>
<div class="chart-card-footer">
<div class="field">
<slot name="footer"></slot>
</div>
</div>
</a-card>
</template>
<script>
export default {
name: 'ChartCard',
props: {
title: {
type: String,
default: ''
},
total: {
type: [Function, Number, String],
required: false,
default: null
},
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>

View File

@@ -0,0 +1,67 @@
<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: 10,
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>
export default {
name: 'Liquid',
props: {
height: {
type: Number,
default: 0
},
width: {
type: Number,
default: 0
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,64 @@
<template>
<div class="antv-chart-mini">
<div class="chart-wrapper" :style="{ height: 46 }">
<v-chart :force-fit="true" :height="height" :data="data" :padding="[36, 0, 18, 0]">
<v-tooltip />
<v-smooth-area position="x*y" />
</v-chart>
</div>
</div>
</template>
<script>
// import moment from 'moment'
// const data = []
// const beginDay = new Date().getTime()
// for (let i = 0; i < 10; i++) {
// data.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: 22
}]
export default {
name: 'MiniArea',
props: {
data: {
type: Array,
default: () => {
return []
}
}
},
data () {
return {
// data,
tooltip,
scale,
height: 100
}
}
}
</script>
<style lang="less" scoped>
@import "chart";
</style>

View File

@@ -0,0 +1,64 @@
<template>
<div class="antv-chart-mini">
<div class="chart-wrapper" :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 moment from 'moment'
// const data = []
// const beginDay = new Date().getTime()
// for (let i = 0; i < 10; i++) {
// data.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: {
data: {
type: Array,
default: () => {
return []
}
}
},
data () {
return {
tooltip,
scale,
height: 100
}
}
}
</script>
<style lang="less" scoped>
@import "chart";
</style>

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 }"></div>
</div>
</div>
</template>
<script>
export default {
name: 'MiniProgress',
props: {
target: {
type: Number,
default: 0
},
height: {
type: String,
default: '10px'
},
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>

View File

@@ -0,0 +1,40 @@
<template>
<div :class="prefixCls">
<div class="chart-wrapper" :style="{ height: 46 }">
<v-chart :force-fit="true" :height="100" :data="dataSource" :scale="scale" :padding="[36, 0, 18, 0]">
<v-tooltip />
<v-smooth-line position="x*y" :size="2" />
<v-smooth-area position="x*y" />
</v-chart>
</div>
</div>
</template>
<script>
export default {
name: 'MiniSmoothArea',
props: {
prefixCls: {
type: String,
default: 'ant-pro-smooth-area'
},
scale: {
type: [Object, Array],
required: true
},
dataSource: {
type: Array,
required: true
}
},
data () {
return {
height: 100
}
}
}
</script>
<style lang="less" scoped>
@import "smooth.area.less";
</style>

View File

@@ -0,0 +1,68 @@
<template>
<v-chart :forceFit="true" height="400" :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: 80
}, {
dataKey: 'user',
alias: '类型'
}
]
export default {
name: 'Radar',
props: {
data: {
type: Array,
default: null
}
},
data () {
return {
axis1Opts,
axis2Opts,
scale
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,77 @@
<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: 'RankList',
// ['title', 'list']
props: {
title: {
type: String,
default: ''
},
list: {
type: Array,
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>

View File

@@ -0,0 +1,113 @@
<template>
<v-chart :width="width" :height="height" :padding="[0]" :data="data" :scale="scale">
<v-tooltip :show-title="false" />
<v-coord type="rect" direction="TL" />
<v-point position="x*y" color="category" shape="cloud" tooltip="value*category" />
</v-chart>
</template>
<script>
import { registerShape } from 'viser-vue'
const DataSet = require('@antv/data-set')
const imgUrl = 'https://gw.alipayobjects.com/zos/rmsportal/gWyeGLCdFFRavBGIDzWk.png'
const scale = [
{ dataKey: 'x', nice: false },
{ dataKey: 'y', nice: false }
]
registerShape('point', 'cloud', {
draw (cfg, container) {
return container.addShape('text', {
attrs: {
fillOpacity: cfg.opacity,
fontSize: cfg.origin._origin.size,
rotate: cfg.origin._origin.rotate,
text: cfg.origin._origin.text,
textAlign: 'center',
fontFamily: cfg.origin._origin.font,
fill: cfg.color,
textBaseline: 'Alphabetic',
...cfg.style,
x: cfg.x,
y: cfg.y
}
})
}
})
export default {
name: 'TagCloud',
props: {
tagList: {
type: Array,
required: true
},
height: {
type: Number,
default: 400
},
width: {
type: Number,
default: 640
}
},
data () {
return {
data: [],
scale
}
},
watch: {
tagList: function (val) {
if (val.length > 0) {
this.initTagCloud(val)
}
}
},
mounted () {
if (this.tagList.length > 0) {
this.initTagCloud(this.tagList)
}
},
methods: {
initTagCloud (dataSource) {
const { height, width } = this
const dv = new DataSet.View().source(dataSource)
const range = dv.range('value')
const min = range[0]
const max = range[1]
const imageMask = new Image()
imageMask.crossOrigin = ''
imageMask.src = imgUrl
imageMask.onload = () => {
dv.transform({
type: 'tag-cloud',
fields: ['name', 'value'],
size: [width, height],
imageMask,
font: 'Verdana',
padding: 0,
timeInterval: 5000, // max execute time
rotate () {
let random = ~~(Math.random() * 4) % 4
if (random === 2) {
random = 0
}
return random * 90 // 0, 90, 270
},
fontSize (d) {
if (d.value) {
return ((d.value - min) / (max - min)) * (32 - 8) + 8
}
return 0
}
})
this.data = dv.rows
}
}
}
}
</script>

View File

@@ -0,0 +1,64 @@
<template>
<div :style="{ padding: '0 0 32px 32px' }">
<h4 :style="{ marginBottom: '20px' }">{{ title }}</h4>
<v-chart
height="254"
: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>
const tooltip = [
'x*y',
(x, y) => ({
name: x,
value: y
})
]
const scale = [{
dataKey: 'x',
title: '日期(天)',
alias: '日期(天)',
min: 2
}, {
dataKey: 'y',
title: '流量(Gb)',
alias: '流量(Gb)',
min: 1
}]
export default {
name: 'Bar',
props: {
title: {
type: String,
default: ''
}
},
data () {
return {
data: [],
scale,
tooltip
}
},
created () {
this.getMonthBar()
},
methods: {
getMonthBar () {
this.$http.get('/analysis/month-bar')
.then(res => {
this.data = res.result
})
}
}
}
</script>

View File

@@ -0,0 +1,82 @@
<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: {
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 () {
const 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>

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; */
}
}

View File

@@ -0,0 +1,14 @@
@import '../index';
@smoothArea-prefix-cls: ~"@{ant-pro-prefix}-smooth-area";
.@{smoothArea-prefix-cls} {
position: relative;
width: 100%;
.chart-wrapper {
position: absolute;
bottom: -28px;
width: 100%;
}
}

113
src/components/Dialog.js Normal file
View File

@@ -0,0 +1,113 @@
import Modal from 'ant-design-vue/es/modal'
export default (Vue) => {
function dialog (component, componentProps, modalProps) {
const _vm = this
modalProps = modalProps || {}
if (!_vm || !_vm._isVue) {
return
}
let dialogDiv = document.querySelector('body>div[type=dialog]')
if (!dialogDiv) {
dialogDiv = document.createElement('div')
dialogDiv.setAttribute('type', 'dialog')
document.body.appendChild(dialogDiv)
}
const handle = function (checkFunction, afterHandel) {
if (checkFunction instanceof Function) {
const res = checkFunction()
if (res instanceof Promise) {
res.then(c => {
c && afterHandel()
})
} else {
res && afterHandel()
}
} else {
// checkFunction && afterHandel()
checkFunction || afterHandel()
}
}
const dialogInstance = new Vue({
data () {
return {
visible: true
}
},
router: _vm.$router,
store: _vm.$store,
mounted () {
this.$on('close', (v) => {
this.handleClose()
})
},
methods: {
handleClose () {
handle(this.$refs._component.onCancel, () => {
this.visible = false
this.$refs._component.$emit('close')
this.$refs._component.$emit('cancel')
dialogInstance.$destroy()
})
},
handleOk () {
handle(this.$refs._component.onOK || this.$refs._component.onOk, () => {
this.visible = false
this.$refs._component.$emit('close')
this.$refs._component.$emit('ok')
dialogInstance.$destroy()
})
}
},
render: function (h) {
const that = this
const modalModel = modalProps && modalProps.model
if (modalModel) {
delete modalProps.model
}
const ModalProps = Object.assign({}, modalModel && { model: modalModel } || {}, {
attrs: Object.assign({}, {
...(modalProps.attrs || modalProps)
}, {
visible: this.visible
}),
on: Object.assign({}, {
...(modalProps.on || modalProps)
}, {
ok: () => {
that.handleOk()
},
cancel: () => {
that.handleClose()
}
})
})
const componentModel = componentProps && componentProps.model
if (componentModel) {
delete componentProps.model
}
const ComponentProps = Object.assign({}, componentModel && { model: componentModel } || {}, {
ref: '_component',
attrs: Object.assign({}, {
...((componentProps && componentProps.attrs) || componentProps)
}),
on: Object.assign({}, {
...((componentProps && componentProps.on) || componentProps)
})
})
return h(Modal, ModalProps, [h(component, ComponentProps)])
}
}).$mount(dialogDiv)
}
Object.defineProperty(Vue.prototype, '$dialog', {
get: () => {
return function () {
dialog.apply(this, arguments)
}
}
})
}

View File

@@ -0,0 +1,334 @@
<template>
<div
ref="dragVerify"
:style="dragVerifyStyle"
class="drag_verify"
@mouseleave="dragFinish"
@mousemove="dragMoving"
@mouseup="dragFinish"
@touchend="dragFinish"
@touchmove="dragMoving"
>
<div
ref="progressBar"
:class="{goFirst2:isOk}"
:style="progressBarStyle"
class="dv_progress_bar"
>
</div>
<div
ref="message"
:style="textStyle"
class="dv_text"
>
<slot
v-if="$slots.textBefore"
name="textBefore"
></slot>
{{ message }}
<slot
v-if="$slots.textAfter"
name="textAfter"
></slot>
</div>
<div
ref="handler"
:class="{goFirst:isOk}"
:style="handlerStyle"
class="dv_handler dv_handler_bg"
@mousedown="dragStart"
@touchstart="dragStart"
>
<a-icon type="double-right" />
</div>
</div>
</template>
<script>
export default {
name: 'DragVerify',
props: {
isPassing: {
type: Boolean,
default: false
},
width: {
type: Number,
default: 250
},
height: {
type: Number,
default: 40
},
text: {
type: String,
default: 'swiping to the right side'
},
successText: {
type: String,
default: 'success'
},
background: {
type: String,
default: '#eee'
},
progressBarBg: {
type: String,
default: '#76c61d'
},
completedBg: {
type: String,
default: '#76c61d'
},
circle: {
type: Boolean,
default: false
},
radius: {
type: String,
default: '4px'
},
handlerIcon: {
type: String,
default: ''
},
successIcon: {
type: String,
default: ''
},
handlerBg: {
type: String,
default: '#fff'
},
textSize: {
type: String,
default: '14px'
},
textColor: {
type: String,
default: '#333'
}
},
mounted: function () {
const dragEl = this.$refs.dragVerify
dragEl.style.setProperty('--textColor', this.textColor)
dragEl.style.setProperty('--width', Math.floor(this.width / 2) + 'px')
dragEl.style.setProperty('--pwidth', -Math.floor(this.width / 2) + 'px')
},
computed: {
handlerStyle: function () {
return {
left: '0px',
width: this.height + 'px',
height: this.height + 'px',
background: this.handlerBg
}
},
message: function () {
return this.isPassing ? this.successText : this.text
},
dragVerifyStyle: function () {
return {
width: this.width + 'px',
height: this.height + 'px',
lineHeight: this.height + 'px',
background: this.background,
borderRadius: this.circle ? this.height / 2 + 'px' : this.radius
}
},
progressBarStyle: function () {
return {
background: this.progressBarBg,
height: this.height + 'px',
borderRadius: this.circle
? this.height / 2 + 'px 0 0 ' + this.height / 2 + 'px'
: this.radius
}
},
textStyle: function () {
return {
height: this.height + 'px',
width: this.width + 'px',
fontSize: this.textSize
}
}
},
data () {
return {
isMoving: false,
x: 0,
isOk: false
}
},
methods: {
dragStart: function (e) {
if (!this.isPassing) {
this.isMoving = true
var handler = this.$refs.handler
this.x =
(e.pageX || e.touches[0].pageX) -
parseInt(handler.style.left.replace('px', ''), 10)
}
this.$emit('handlerMove')
},
dragMoving: function (e) {
if (this.isMoving && !this.isPassing) {
var _x = (e.pageX || e.touches[0].pageX) - this.x
var handler = this.$refs.handler
if (_x > 0 && _x <= this.width - this.height) {
handler.style.left = _x + 'px'
this.$refs.progressBar.style.width = _x + this.height / 2 + 'px'
} else if (_x > this.width - this.height) {
handler.style.left = this.width - this.height + 'px'
this.$refs.progressBar.style.width =
this.width - this.height / 2 + 'px'
this.passVerify()
}
}
},
dragFinish: function (e) {
if (this.isMoving && !this.isPassing) {
var _x = (e.pageX || e.changedTouches[0].pageX) - this.x
if (_x < this.width - this.height) {
this.isOk = true
var that = this
setTimeout(function () {
that.$refs.handler.style.left = '0'
that.$refs.progressBar.style.width = '0'
that.isOk = false
}, 500)
this.$emit('passfail')
} else {
var handler = this.$refs.handler
handler.style.left = this.width - this.height + 'px'
this.$refs.progressBar.style.width =
this.width - this.height / 2 + 'px'
this.passVerify()
}
this.isMoving = false
}
},
passVerify: function () {
this.$emit('update:isPassing', true)
this.isMoving = false
var handler = this.$refs.handler
handler.children[0].className = this.successIcon
this.$refs.progressBar.style.background = this.completedBg
this.$refs.message.style['-webkit-text-fill-color'] = 'unset'
this.$refs.message.style.animation = 'slidetounlock2 3s infinite'
this.$refs.message.style.color = '#fff'
this.$emit('passCallback')
},
reset: function () {
const oriData = this.$options.data()
for (const key in oriData) {
if (Object.prototype.hasOwnProperty.call(oriData, key)) {
this.$set(this, key, oriData[key])
}
}
var handler = this.$refs.handler
var message = this.$refs.message
handler.style.left = '0'
this.$refs.progressBar.style.width = '0'
handler.children[0].className = this.handlerIcon
message.style['-webkit-text-fill-color'] = 'transparent'
message.style.animation = 'slidetounlock 3s infinite'
message.style.color = this.background
}
}
}
</script>
<style scoped>
.drag_verify {
position: relative;
background-color: #e8e8e8;
text-align: center;
overflow: hidden;
}
.drag_verify .dv_handler {
position: absolute;
top: 0px;
left: 0px;
cursor: move;
}
.drag_verify .dv_handler i {
color: #666;
padding-left: 0;
font-size: 16px;
}
.drag_verify .dv_handler .el-icon-circle-check {
color: #6c6;
margin-top: 9px;
}
.drag_verify .dv_progress_bar {
position: absolute;
height: 34px;
width: 0px;
}
.drag_verify .dv_text {
position: absolute;
top: 0px;
color: transparent;
-moz-user-select: none;
-webkit-user-select: none;
user-select: none;
-o-user-select: none;
-ms-user-select: none;
background: -webkit-gradient(
linear,
left top,
right top,
color-stop(0, var(--textColor)),
color-stop(0.4, var(--textColor)),
color-stop(0.5, #fff),
color-stop(0.6, var(--textColor)),
color-stop(1, var(--textColor))
);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
-webkit-text-size-adjust: none;
animation: slidetounlock 3s infinite;
}
.drag_verify .dv_text * {
-webkit-text-fill-color: var(--textColor);
}
.goFirst {
left: 0px !important;
transition: left 0.5s;
}
.goFirst2 {
width: 0px !important;
transition: width 0.5s;
}
@-webkit-keyframes slidetounlock {
0% {
background-position: var(--pwidth) 0;
}
100% {
background-position: var(--width) 0;
}
}
@-webkit-keyframes slidetounlock2 {
0% {
background-position: var(--pwidth) 0;
}
100% {
background-position: var(--pwidth) 0;
}
}
</style>

View File

@@ -0,0 +1,83 @@
<template>
<div :class="prefixCls">
<quill-editor
v-model="content"
ref="myQuillEditor"
:options="editorOption"
@blur="onEditorBlur($event)"
@focus="onEditorFocus($event)"
@ready="onEditorReady($event)"
@change="onEditorChange($event)">
</quill-editor>
</div>
</template>
<script>
import 'quill/dist/quill.core.css'
import 'quill/dist/quill.snow.css'
import 'quill/dist/quill.bubble.css'
import { quillEditor } from 'vue-quill-editor'
export default {
name: 'QuillEditor',
components: {
quillEditor
},
props: {
prefixCls: {
type: String,
default: 'ant-editor-quill'
},
// 表单校验用字段
// eslint-disable-next-line
value: {
type: String
}
},
data () {
return {
content: null,
editorOption: {
// some quill options
}
}
},
methods: {
onEditorBlur (quill) {
// console.log('editor blur!', quill)
},
onEditorFocus (quill) {
// console.log('editor focus!', quill)
},
onEditorReady (quill) {
// console.log('editor ready!', quill)
},
onEditorChange ({ quill, html, text }) {
// console.log('editor change!', quill, html, text)
this.$emit('change', html)
}
},
watch: {
value (val) {
this.content = val
}
}
}
</script>
<style lang="less" scoped>
@import url('../index.less');
/* 覆盖 quill 默认边框圆角为 ant 默认圆角,用于统一 ant 组件风格 */
.ant-editor-quill {
line-height: initial;
/deep/ .ql-toolbar.ql-snow {
border-radius: @border-radius-base @border-radius-base 0 0;
}
/deep/ .ql-container.ql-snow {
border-radius: 0 0 @border-radius-base @border-radius-base;
}
}
</style>

View File

@@ -0,0 +1,148 @@
<template>
<div :class="prefixCls">
<a-spin :spinning="spinning">
<div ref="editor" class="editor-wrapper"></div>
<a-progress
v-if="spinning && percent > 0"
:stroke-color="{
'0%': '#108ee9',
'100%': '#87d068',
}"
:percent="percent"
/>
</a-spin>
</div>
</template>
<script>
import WEditor from 'wangeditor'
import { getStsAssumeRole } from '@/api/storage'
import moment from 'moment'
const OSS = require('ali-oss')
const md5 = require('md5')
export default {
name: 'WangEditor',
props: {
prefixCls: {
type: String,
default: 'ant-editor-wang'
},
// eslint-disable-next-line
value: {
type: String
}
},
data () {
return {
editor: null,
editorContent: null,
store: null,
spinning: false,
percent: 0
}
},
watch: {
value (val) {
this.editorContent = val
this.editor.txt.html(val)
}
},
mounted () {
this.initEditor()
getStsAssumeRole().then(res => {
this.store = new OSS(res)
}).catch(() => {
this.$message.error('OSS 初始化失败,图片无法上传')
})
},
methods: {
computeFileMd5 (file) {
return new Promise((resolve, reject) => {
const fileReader = new FileReader()
fileReader.readAsArrayBuffer(file)
fileReader.onload = (e) => {
resolve(md5(new Uint8Array(e.target.result)))
}
fileReader.onerror = (e) => {
reject(e)
}
})
},
initEditor () {
this.editor = new WEditor(this.$refs.editor)
// this.editor.onchangeTimeout = 200
this.editor.config.onchange = (html) => {
this.editorContent = html
this.$emit('change', this.editorContent)
}
this.editor.config.zIndex = 500
this.editor.config.pasteIgnoreImg = true
this.editor.config.pasteFilterStyle = true
this.editor.config.customUploadImg = (files, insertImgFn) => {
this.spinning = true
files.map(async file => {
const fileMd5 = await this.computeFileMd5(file)
const fileName = 'front' + moment().format('/YYYY/MM/DD/') + fileMd5 + '.' + file.type.replace('image/', '')
this.store.put(fileName, file).then(res => {
insertImgFn(res.url)
}).catch(err => {
this.$message.error(err.message)
}).finally(() => {
this.spinning = false
})
})
}
this.editor.config.customUploadVideo = (files, insertVideoFn) => {
this.spinning = true
files.map(async file => {
const fileMd5 = await this.computeFileMd5(file)
const fileName = 'front' + moment().format('/YYYY/MM/DD/') + fileMd5 + '.' + file.type.replace('image/', '')
this.store.multipartUpload(fileName, file, {
progress: (e) => {
this.percent = Math.round(e * 100)
},
partSize: 200 * 1024
}).then(res => {
let fileUrl = ''
const result = res.res.requestUrls
if (result[0].indexOf('?') !== -1) {
fileUrl = result[0].slice(0, result[0].indexOf('?'))
} else {
fileUrl = result[0]
}
insertVideoFn(fileUrl)
}).catch(err => {
this.$message.error(err.message)
}).finally(() => {
this.percent = 0
this.spinning = false
})
})
}
this.editor.create()
},
getContent: function () {
return this.editorContent
}
}
}
</script>
<style lang="less" scoped>
.ant-editor-wang {
.editor-wrapper {
text-align: left;
}
}
</style>

View File

@@ -0,0 +1,64 @@
<script>
import Tooltip from 'ant-design-vue/es/tooltip'
import { cutStrByFullLength, getStrFullLength } from '@/components/_util/util'
/*
const isSupportLineClamp = document.body.style.webkitLineClamp !== undefined;
const TooltipOverlayStyle = {
overflowWrap: 'break-word',
wordWrap: 'break-word',
};
*/
export default {
name: 'Ellipsis',
components: {
Tooltip
},
props: {
prefixCls: {
type: String,
default: 'ant-pro-ellipsis'
},
tooltip: {
type: Boolean
},
length: {
type: Number,
required: true
},
lines: {
type: Number,
default: 1
},
fullWidthRecognition: {
type: Boolean,
default: false
}
},
methods: {
getStrDom (str, fullLength) {
return (
<span>{ cutStrByFullLength(str, this.length) + (fullLength > this.length ? '...' : '') }</span>
)
},
getTooltip (fullStr, fullLength) {
return (
<Tooltip>
<template slot="title">{ fullStr }</template>
{ this.getStrDom(fullStr, fullLength) }
</Tooltip>
)
}
},
render () {
const { tooltip, length } = this.$props
const str = this.$slots.default.map(vNode => vNode.text).join('')
const fullLength = getStrFullLength(str)
const strDom = tooltip && fullLength > length ? this.getTooltip(str, fullLength) : this.getStrDom(str, fullLength)
return (
strDom
)
}
}
</script>

View File

@@ -0,0 +1,3 @@
import Ellipsis from './Ellipsis'
export default Ellipsis

View File

@@ -0,0 +1,38 @@
# Ellipsis 文本自动省略号
文本过长自动处理省略号,支持按照文本长度和最大行数两种方式截取。
引用方式:
```javascript
import Ellipsis from '@/components/Ellipsis'
export default {
components: {
Ellipsis
}
}
```
## 代码演示 [demo](https://pro.loacg.com/test/home)
```html
<ellipsis :length="100" tooltip>
There were injuries alleged in three cases in 2015, and a
fourth incident in September, according to the safety recall report. After meeting with US regulators in October, the firm decided to issue a voluntary recall.
</ellipsis>
```
## API
参数 | 说明 | 类型 | 默认值
----|------|-----|------
tooltip | 移动到文本展示完整内容的提示 | boolean | -
length | 在按照长度截取下的文本最大字符数,超过则截取省略 | number | -

View File

@@ -0,0 +1,49 @@
<template>
<div :class="prefixCls" :style="{ width: barWidth, transition: '0.3s all' }">
<div style="float: left">
<slot name="extra">{{ extra }}</slot>
</div>
<div style="float: right">
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'FooterToolBar',
props: {
prefixCls: {
type: String,
default: 'ant-pro-footer-toolbar'
},
collapsed: {
type: Boolean,
default: false
},
isMobile: {
type: Boolean,
default: false
},
siderWidth: {
type: Number,
default: undefined
},
extra: {
type: [String, Object],
default: ''
}
},
computed: {
barWidth () {
return this.isMobile ? undefined : `calc(100% - ${this.collapsed ? 80 : this.siderWidth || 256}px)`
}
}
}
</script>
<style lang="less" scoped>
.ant-pro-footer-toolbar {
z-index: 1001;
}
</style>

View File

@@ -0,0 +1,4 @@
import FooterToolBar from './FooterToolBar'
import './index.less'
export default FooterToolBar

View File

@@ -0,0 +1,23 @@
@import "../index";
@footer-toolbar-prefix-cls: ~"@{ant-pro-prefix}-footer-toolbar";
.@{footer-toolbar-prefix-cls} {
position: fixed;
width: 100%;
bottom: 0;
right: 0;
height: 56px;
line-height: 56px;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.03);
background: #fff;
border-top: 1px solid #e8e8e8;
padding: 0 24px;
z-index: 9;
&:after {
content: "";
display: block;
clear: both;
}
}

View File

@@ -0,0 +1,48 @@
# FooterToolbar 底部工具栏
固定在底部的工具栏。
## 何时使用
固定在内容区域的底部,不随滚动条移动,常用于长页面的数据搜集和提交工作。
引用方式:
```javascript
import FooterToolBar from '@/components/FooterToolBar'
export default {
components: {
FooterToolBar
}
}
```
## 代码演示
```html
<footer-tool-bar>
<a-button type="primary" @click="validate" :loading="loading">提交</a-button>
</footer-tool-bar>
```
```html
<footer-tool-bar extra="扩展信息提示">
<a-button type="primary" @click="validate" :loading="loading">提交</a-button>
</footer-tool-bar>
```
## API
参数 | 说明 | 类型 | 默认值
----|------|-----|------
children (slot) | 工具栏内容,向右对齐 | - | -
extra | 额外信息,向左对齐 | String, Object | -

View File

@@ -0,0 +1,22 @@
<template>
<global-footer class="footer custom-render">
<template v-slot:links>
<a href="https://www.github.com/vueComponent/pro-layout" target="_blank">Pro Layout</a>
<a href="https://www.github.com/vueComponent/ant-design-vue-pro" target="_blank">Github</a>
</template>
<template v-slot:copyright>
<a href="https://www.cnskl.com" target="_blank">Uz.Tech &copy; 2021</a>
</template>
</global-footer>
</template>
<script>
import { GlobalFooter } from '@ant-design-vue/pro-layout'
export default {
name: 'ProGlobalFooter',
components: {
GlobalFooter
}
}
</script>

View File

@@ -0,0 +1,165 @@
<template>
<div>
<a-dropdown v-if="currentUser && currentUser.name" placement="bottomRight">
<span class="ant-pro-account-avatar">
<a-avatar v-if="currentUser.avatar" :src="currentUser.avatar" class="antd-pro-global-header-index-avatar" size="small" />
<a-avatar v-else src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" class="antd-pro-global-header-index-avatar" size="small" />
<!-- <img class="avatar" src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" /> -->
<!-- <a-avatar v-else style="color: #f56a00; backgroundColor: #fde3cf" class="antd-pro-global-header-index-avatar" size="small">U</a-avatar> -->
<span>{{ currentUser.name }} ({{ currentUser.type }})</span>
</span>
<template v-slot:overlay>
<a-menu :selected-keys="[]" class="ant-pro-drop-down menu">
<a-menu-item key="changePwd" @click="changePassword">
<a-icon type="lock" />
修改密码
</a-menu-item>
<!-- <a-menu-item key="permission" @click="goPermission">
<a-icon type="user" />
组织结构权限管理
</a-menu-item> -->
<a-menu-item key="logout" @click="handleLogout">
<a-icon type="logout" />
退出登录
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<span v-else>
<a-spin :style="{ marginLeft: 8, marginRight: 8 }" size="small" />
</span>
<a-modal v-model="visible" title="修改密码" on-ok="handleOk">
<a-form-item>
<a-input
placeholder="请输入密码"
size="large"
type="password"
oninput="value=value.replace(/[, ]/g,'')"
onkeyup="value=value.replace(/[, ]/g,'')"
onblur="value=value.replace(/\s+/g,'')"
v-model="postData.passWord">
<template v-slot:prefix>
<a-icon :style="{ color: 'rgba(0,0,0,.25)' }" type="lock" />
</template>
</a-input>
</a-form-item>
<a-form-item>
<a-input
placeholder="请再次输入密码"
size="large"
type="password"
oninput="value=value.replace(/[, ]/g,'')"
onkeyup="value=value.replace(/[, ]/g,'')"
onblur="value=value.replace(/\s+/g,'')"
v-model="postData.passWordAgain">
<template v-slot:prefix>
<a-icon :style="{ color: 'rgba(0,0,0,.25)' }" type="lock" />
</template>
</a-input>
</a-form-item>
<template slot="footer">
<a-button key="back" @click="visible = false">
取消
</a-button>
<a-button key="submit" type="primary" :loading="loading" @click="handleOk">
确认
</a-button>
</template>
</a-modal>
</div>
</template>
<script>
import { Modal } from 'ant-design-vue'
import { password } from '@/api/login'
export default {
name: 'AvatarDropdown',
props: {
currentUser: {
type: Object,
default: () => null
},
menu: {
type: Boolean,
default: true
}
},
data () {
return {
loading: false,
visible: false,
postData: {
passWord: '',
passWordAgain: ''
}
}
},
methods: {
goPermission () {
if (this.$route.name !== 'PermissionsIndex') {
this.$router.push({ name: 'PermissionsIndex' })
}
},
handleToCenter () {
if (this.$route.name !== 'UserCenter') {
this.$router.push({ name: 'UserCenter' })
}
},
handleToSettings () {
if (this.$route.name !== 'SecuritySettings') {
this.$router.push({ name: 'SecuritySettings' })
}
},
handleLogout (e) {
Modal.confirm({
title: this.$t('layouts.usermenu.dialog.title'),
content: this.$t('layouts.usermenu.dialog.content'),
onOk: () => {
return this.$store.dispatch('Logout').then(() => {
this.$router.replace({ name: 'login' })
})
},
onCancel () {
}
})
},
changePassword () {
this.visible = true
},
handleOk () {
this.loading = true
password({
password: this.postData.passWord,
password_confirmation: this.postData.passWordAgain
}).then(res => {
this.loading = false
this.visible = false
this.postData.passWord = ''
this.postData.passWordAgain = ''
this.$notification.success({
message: res
})
}).catch(err => {
this.loading = false
this.visible = true
this.$notification.error({
message: err.message
})
})
}
}
}
</script>
<style lang="less" scoped>
.ant-pro-drop-down {
/deep/ .action {
margin-right: 8px;
}
/deep/ .ant-dropdown-menu-item {
min-width: 160px;
}
}
</style>

View File

@@ -0,0 +1,60 @@
<template>
<div :class="wrpCls">
<notice-icon />
<avatar-dropdown :class="prefixCls" :current-user="currentUser" :menu="showMenu" />
<!-- <select-lang :class="prefixCls" /> -->
</div>
</template>
<script>
import AvatarDropdown from './AvatarDropdown'
import SelectLang from '@/components/SelectLang'
import NoticeIcon from '../NoticeIcon'
export default {
name: 'RightContent',
components: {
AvatarDropdown,
SelectLang,
NoticeIcon
},
props: {
prefixCls: {
type: String,
default: 'ant-pro-global-header-index-action'
},
isMobile: {
type: Boolean,
default: () => false
},
topMenu: {
type: Boolean,
required: true
},
theme: {
type: String,
required: true
}
},
data () {
return {
showMenu: true
}
},
computed: {
wrpCls () {
return {
'ant-pro-global-header-index-right': true,
[`ant-pro-global-header-index-${(this.isMobile || !this.topMenu) ? 'light' : this.theme}`]: true
}
},
currentUser () {
return {
name: this.$store.getters.nickname,
avatar: this.$store.getters.avatar,
type: this.$store.getters.userInfo.type
}
}
}
}
</script>

View File

@@ -0,0 +1,86 @@
<template>
<div :class="prefixCls">
<a-tabs v-model="currentTab" @change="handleTabChange">
<a-tab-pane v-for="v in icons" :tab="v.title" :key="v.key">
<ul>
<li v-for="(icon, key) in v.icons" :key="`${v.key}-${key}`" :class="{ 'active': selectedIcon==icon }" @click="handleSelectedIcon(icon)" >
<a-icon :type="icon" :style="{ fontSize: '36px' }" />
</li>
</ul>
</a-tab-pane>
</a-tabs>
</div>
</template>
<script>
import icons from './icons'
export default {
name: 'IconSelect',
props: {
prefixCls: {
type: String,
default: 'ant-pro-icon-selector'
},
// eslint-disable-next-line
value: {
type: String
}
},
data () {
return {
selectedIcon: this.value || '',
currentTab: 'directional',
icons
}
},
watch: {
value (val) {
this.selectedIcon = val
this.autoSwitchTab()
}
},
created () {
if (this.value) {
this.autoSwitchTab()
}
},
methods: {
handleSelectedIcon (icon) {
this.selectedIcon = icon
this.$emit('change', icon)
},
handleTabChange (activeKey) {
this.currentTab = activeKey
},
autoSwitchTab () {
icons.some(item => item.icons.some(icon => icon === this.value) && (this.currentTab = item.key))
}
}
}
</script>
<style lang="less" scoped>
@import "../index.less";
ul{
list-style: none;
padding: 0;
overflow-y: scroll;
height: 250px;
li{
display: inline-block;
padding: @padding-sm;
margin: 3px 0;
border-radius: @border-radius-base;
&:hover, &.active{
// box-shadow: 0px 0px 5px 2px @primary-color;
cursor: pointer;
color: @white;
background-color: @primary-color;
}
}
}
</style>

View File

@@ -0,0 +1,48 @@
IconSelector
====
> 图标选择组件,常用于为某一个数据设定一个图标时使用
> eg: 设定菜单列表时,为每个菜单设定一个图标
该组件由 [@Saraka](https://github.com/saraka-tsukai) 封装
### 使用方式
```vue
<template>
<div>
<icon-selector @change="handleIconChange"/>
</div>
</template>
<script>
import IconSelector from '@/components/IconSelector'
export default {
name: 'YourView',
components: {
IconSelector
},
data () {
return {
}
},
methods: {
handleIconChange (icon) {
// console.log('change Icon', icon)
}
}
}
</script>
```
### 事件
| 名称 | 说明 | 类型 | 默认值 |
| ------ | -------------------------- | ------ | ------ |
| change | 当改变了 `icon` 选中项触发 | String | - |

View File

@@ -0,0 +1,36 @@
/**
* 增加新的图标时,请遵循以下数据结构
* Adding new icon please follow the data structure below
*/
export default [
{
key: 'directional',
title: '方向性图标',
icons: ['step-backward', 'step-forward', 'fast-backward', 'fast-forward', 'shrink', 'arrows-alt', 'down', 'up', 'left', 'right', 'caret-up', 'caret-down', 'caret-left', 'caret-right', 'up-circle', 'down-circle', 'left-circle', 'right-circle', 'double-right', 'double-left', 'vertical-left', 'vertical-right', 'forward', 'backward', 'rollback', 'enter', 'retweet', 'swap', 'swap-left', 'swap-right', 'arrow-up', 'arrow-down', 'arrow-left', 'arrow-right', 'play-circle', 'up-square', 'down-square', 'left-square', 'right-square', 'login', 'logout', 'menu-fold', 'menu-unfold', 'border-bottom', 'border-horizontal', 'border-inner', 'border-left', 'border-right', 'border-top', 'border-verticle', 'pic-center', 'pic-left', 'pic-right', 'radius-bottomleft', 'radius-bottomright', 'radius-upleft', 'fullscreen', 'fullscreen-exit']
},
{
key: 'suggested',
title: '提示建议性图标',
icons: ['question', 'question-circle', 'plus', 'plus-circle', 'pause', 'pause-circle', 'minus', 'minus-circle', 'plus-square', 'minus-square', 'info', 'info-circle', 'exclamation', 'exclamation-circle', 'close', 'close-circle', 'close-square', 'check', 'check-circle', 'check-square', 'clock-circle', 'warning', 'issues-close', 'stop']
},
{
key: 'editor',
title: '编辑类图标',
icons: ['edit', 'form', 'copy', 'scissor', 'delete', 'snippets', 'diff', 'highlight', 'align-center', 'align-left', 'align-right', 'bg-colors', 'bold', 'italic', 'underline', 'strikethrough', 'redo', 'undo', 'zoom-in', 'zoom-out', 'font-colors', 'font-size', 'line-height', 'colum-height', 'dash', 'small-dash', 'sort-ascending', 'sort-descending', 'drag', 'ordered-list', 'radius-setting']
},
{
key: 'data',
title: '数据类图标',
icons: ['area-chart', 'pie-chart', 'bar-chart', 'dot-chart', 'line-chart', 'radar-chart', 'heat-map', 'fall', 'rise', 'stock', 'box-plot', 'fund', 'sliders']
},
{
key: 'brand_logo',
title: '网站通用图标',
icons: ['lock', 'unlock', 'bars', 'book', 'calendar', 'cloud', 'cloud-download', 'code', 'copy', 'credit-card', 'delete', 'desktop', 'download', 'ellipsis', 'file', 'file-text', 'file-unknown', 'file-pdf', 'file-word', 'file-excel', 'file-jpg', 'file-ppt', 'file-markdown', 'file-add', 'folder', 'folder-open', 'folder-add', 'hdd', 'frown', 'meh', 'smile', 'inbox', 'laptop', 'appstore', 'link', 'mail', 'mobile', 'notification', 'paper-clip', 'picture', 'poweroff', 'reload', 'search', 'setting', 'share-alt', 'shopping-cart', 'tablet', 'tag', 'tags', 'to-top', 'upload', 'user', 'video-camera', 'home', 'loading', 'loading-3-quarters', 'cloud-upload', 'star', 'heart', 'environment', 'eye', 'camera', 'save', 'team', 'solution', 'phone', 'filter', 'exception', 'export', 'customer-service', 'qrcode', 'scan', 'like', 'dislike', 'message', 'pay-circle', 'calculator', 'pushpin', 'bulb', 'select', 'switcher', 'rocket', 'bell', 'disconnect', 'database', 'compass', 'barcode', 'hourglass', 'key', 'flag', 'layout', 'printer', 'sound', 'usb', 'skin', 'tool', 'sync', 'wifi', 'car', 'schedule', 'auth-add', 'auth-delete', 'usergroup-add', 'usergroup-delete', 'man', 'woman', 'shop', 'gift', 'idcard', 'medicine-box', 'red-envelope', 'coffee', 'copyright', 'trademark', 'safety', 'wallet', 'bank', 'trophy', 'contacts', 'global', 'shake', 'api', 'fork', 'home', 'table', 'profile', 'alert', 'audit', 'branches', 'build', 'border', 'crown', 'experiment', 'fire', 'money-collect', 'property-safety', 'read', 'reconciliation', 'rest', 'security-scan', 'insurance', 'interation', 'safety-certificate', 'project', 'thunderbolt', 'block', 'cluster', 'deployment-unit', 'dollar', 'euro', 'pound', 'file-done', 'file-exclamation', 'file-protect', 'file-search', 'file-sync', 'gateway', 'gold', 'robot', 'shopping']
},
{
key: 'application',
title: '品牌和标识',
icons: ['android', 'apple', 'windows', 'ie', 'chrome', 'github', 'aliwangwang', 'dingding', 'weibo-square', 'weibo-circle', 'taobao-circle', 'html5', 'weibo', 'twitter', 'wechat', 'youtube', 'alipay-circle', 'taobao', 'skype', 'qq', 'medium-workmark', 'gitlab', 'medium', 'linkedin', 'google-plus', 'dropbox', 'facebook', 'codepen', 'code-sandbox', 'amazon', 'google', 'codepen-circle', 'alipay', 'ant-design', 'aliyun', 'zhihu', 'slack', 'slack-square', 'behance', 'behance-square', 'dribbble', 'dribbble-square', 'instagram', 'yuque', 'alibaba', 'yahoo']
}
]

View File

@@ -0,0 +1,2 @@
import IconSelector from './IconSelector'
export default IconSelector

View File

@@ -0,0 +1,29 @@
<template>
<div>
<img
:style="{ backgroundImage: 'url(' + src + ')',paddingTop: '100%' }"
class="jason-image"
/>
</div>
</template>
<script>
export default {
name: 'Image',
props: {
src: {
type: String,
default: ''
}
}
}
</script>
<style lang="less" scoped>
.jason-image {
width: 100%;
height: 0;
background-position: center;
background-size: cover;
}
</style>

View File

@@ -0,0 +1,2 @@
import Image from './Image'
export default Image

View File

@@ -0,0 +1,76 @@
@import url('../index.less');
/* Make clicks pass-through */
#nprogress {
pointer-events: none;
}
#nprogress .bar {
background: @primary-color;
position: fixed;
z-index: 1031;
top: 0;
left: 0;
width: 100%;
height: 2px;
}
/* Fancy blur effect */
#nprogress .peg {
display: block;
position: absolute;
right: 0px;
width: 100px;
height: 100%;
box-shadow: 0 0 10px @primary-color, 0 0 5px @primary-color;
opacity: 1.0;
-webkit-transform: rotate(3deg) translate(0px, -4px);
-ms-transform: rotate(3deg) translate(0px, -4px);
transform: rotate(3deg) translate(0px, -4px);
}
/* Remove these to get rid of the spinner */
#nprogress .spinner {
display: block;
position: fixed;
z-index: 1031;
top: 15px;
right: 15px;
}
#nprogress .spinner-icon {
width: 18px;
height: 18px;
box-sizing: border-box;
border: solid 2px transparent;
border-top-color: @primary-color;
border-left-color: @primary-color;
border-radius: 50%;
-webkit-animation: nprogress-spinner 400ms linear infinite;
animation: nprogress-spinner 400ms linear infinite;
}
.nprogress-custom-parent {
overflow: hidden;
position: relative;
}
.nprogress-custom-parent #nprogress .spinner,
.nprogress-custom-parent #nprogress .bar {
position: absolute;
}
@-webkit-keyframes nprogress-spinner {
0% { -webkit-transform: rotate(0deg); }
100% { -webkit-transform: rotate(360deg); }
}
@keyframes nprogress-spinner {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

View File

@@ -0,0 +1,128 @@
<template>
<a-popover
v-model="visible"
:arrowPointAtCenter="true"
:autoAdjustOverflow="true"
:getPopupContainer="() => $refs.noticeRef.parentElement"
placement="bottomRight"
trigger="click"
>
<template slot="content">
<a-spin :spinning="loading">
<a-tabs>
<a-tab-pane key="1" tab="消息通知">
<a-list>
<a-list-item @click="goNewList">
<a-list-item-meta :title="noticeCount>0?'您有'+ noticeCount +'份未读消息通知':'您的消息通知列表'">
<a-avatar
slot="avatar"
src="https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png"
style="background-color: white" />
</a-list-item-meta>
</a-list-item>
</a-list>
</a-tab-pane>
</a-tabs>
</a-spin>
</template>
<span ref="noticeRef" class="header-notice" @click="fetchNotice">
<a-badge :count="noticeCount">
<a-icon type="bell" />
</a-badge>
<div style="margin: 10px;display: none;">
<audio controls="controls" style="width: 100%;" id="yx_player">
<source src="./notice.mp3">
</audio>
</div>
</span>
</a-popover>
</template>
<script>
// import SettingItemVue from '../SettingDrawer/SettingItem.vue'
import storage from 'store'
import { ACCESS_TOKEN } from '@/store/mutation-types'
import { getUnreadCount } from '@/api/notice'
export default {
name: 'HeaderNotice',
data () {
return {
loading: false,
visible: false,
timer: '',
noticeCount: 0
}
},
destroyed () {
clearInterval(this.timer)
},
created () {
// console.log('created..noticeIcon...notifies/unread_count')
const token = storage.get(ACCESS_TOKEN)
this.getNoticeCount()
if (token) {
this.timer = setInterval(() => {
this.getNoticeCount()
}, 6000)
}
},
methods: {
getNoticeCount () {
getUnreadCount().then(res => {
if (res.count > this.noticeCount && this.noticeCount !== 0) {
const plr = document.getElementById('yx_player')
plr.play()
setTimeout(() => {
plr.play()
}, 1500)
}
this.noticeCount = res.count
}).catch(err => {
console.log(err)
})
},
goNewList () {
if (this.$route.path === '/noticeList') {
this.getNoticeCount()
} else {
this.$router.push({ name: 'NoticeList', params: {} })
}
this.visible = !this.visible
},
fetchNotice () {
if (!this.visible) {
this.loading = true
setTimeout(() => {
this.loading = false
}, 1000)
} else {
this.loading = false
}
this.visible = !this.visible
}
}
}
</script>
<style lang="less" scoped>
@import "~ant-design-vue/lib/style/index";
.header-notice {
display: inline-block;
transition: all 0.3s;
cursor: pointer;
padding: 0 20px;
&:hover {
background: rgba(0, 0, 0, 0.025);
}
.anticon {
font-size: 18px;
}
span {
vertical-align: initial;
}
}
</style>

View File

@@ -0,0 +1,110 @@
<template>
<a-card title="消息列表">
<div class="extra" slot="extra" href="#" @click="readAll">全部已读</div>
<a-list class="demo-loadmore-list" :loading="loading" item-layout="horizontal" :data-source="data">
<div v-if="showLoadingMore" slot="loadMore" :style="{ textAlign: 'center', marginTop: '12px', height: '32px', lineHeight: '32px' }">
<a-spin v-if="loadingMore" />
<a-button v-else @click="onLoadMore"> 获取更多 </a-button>
</div>
<a-list-item slot="renderItem" slot-scope="item,index">
<a slot="actions" :class="['title', item.read_at !== '' ? 'active' : '']" @click="readed(item,index)">设置已读</a>
<a-list-item-meta :description="item.content">
<a slot="title" :class="['title', item.read_at !== '' ? 'active' : '']" >{{ item.title }} <span style="font-size:90%;padding-left: 10px;">{{ item.created_at }}</span></a>
<a-avatar v-if="item.read_at === ''" icon="sound" slot="avatar" style="color: #FFF; backgroundColor: #1890FF"></a-avatar>
<a-avatar v-else slot="avatar" icon="sound"></a-avatar>
</a-list-item-meta>
</a-list-item>
</a-list>
</a-card>
</template>
<script>
import {
getNoticeList,
readNotice,
readAllNotice
} from '@/api/notice'
export default {
name: 'NoticeList',
data () {
return {
loading: false, //
loadingMore: false,
showLoadingMore: true,
data: [],
page: 1
}
},
created () {
this.getList()
},
watch: {
$route (to, from) {
if (to.name === 'NoticeList') {
this.data = []
this.page = 1
this.showLoadingMore = true
this.loadingMore = false
this.getList()
}
}
},
methods: {
// 获取消息列表
getList () {
getNoticeList({ page: this.page }).then(res => {
this.data = this.data.concat(res.data)
this.loadingMore = false
this.showLoadingMore = res.page.has_more
this.$nextTick(() => {
window.dispatchEvent(new Event('resize'))
})
}).catch(err => {
this.$$notification.error(err)
})
},
// 获取更多
onLoadMore () {
if (this.showLoadingMore) {
this.page = this.page + 1
this.loadingMore = true
this.getList()
}
},
// 消息已读
readed (item, index) {
readNotice(item.notification_id).then(res => {
this.$notification.success({ message: '已读消息' })
this.data[index].read_at = '----'
}).catch(err => {
this.$notification.error(err)
})
},
// 消息全部已读
readAll () {
readAllNotice().then(res => {
this.$notification.success({ message: '已读全部消息' })
this.page = 1
this.data = []
this.showLoadingMore = true
this.getList()
}).catch(err => {
this.$notification.error(err)
})
}
}
}
</script>
<style lang="less" scoped>
@import '~ant-design-vue/lib/style/index';
.demo-loadmore-list {
min-height: 500px;
}
.active {
color: rgba(6, 6, 6, 0.45) !important;
}
.extra{
cursor: pointer;
}
</style>

View File

@@ -0,0 +1,2 @@
import NoticeIcon from './NoticeIcon'
export default NoticeIcon

Binary file not shown.

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>

View File

@@ -0,0 +1,3 @@
import NumberInfo from './NumberInfo'
export default NumberInfo

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;
}
}
}
}
}

View File

@@ -0,0 +1,43 @@
# NumberInfo 数据文本
常用在数据卡片中,用于突出展示某个业务数据。
引用方式:
```javascript
import NumberInfo from '@/components/NumberInfo'
export default {
components: {
NumberInfo
}
}
```
## 代码演示 [demo](https://pro.loacg.com/test/home)
```html
<number-info
:sub-title="() => { return 'Visits this week' }"
:total="12321"
status="up"
:sub-total="17.1"></number-info>
```
## API
参数 | 说明 | 类型 | 默认值
----|------|-----|------
title | 标题 | ReactNode\|string | -
subTitle | 子标题 | ReactNode\|string | -
total | 总量 | ReactNode\|string | -
subTotal | 子总量 | ReactNode\|string | -
status | 增加状态 | 'up \| down' | -
theme | 状态样式 | string | 'light'
gap | 设置数字和描述之间的间距(像素)| number | 8

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