master
xy 2 years ago
commit 52154b241c

@ -0,0 +1,14 @@
# http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

@ -0,0 +1,8 @@
NODE_ENV = production
# just a flag
ENV = 'staging'
# base api
VUE_APP_BASE_API = '/stage-api'

@ -0,0 +1,4 @@
build/*.js
src/assets
public
dist

@ -0,0 +1,198 @@
module.exports = {
root: true,
parserOptions: {
parser: 'babel-eslint',
sourceType: 'module'
},
env: {
browser: true,
node: true,
es6: true,
},
extends: ['plugin:vue/recommended', 'eslint:recommended'],
// add your custom rules here
//it is base on https://github.com/vuejs/eslint-config-vue
rules: {
"vue/max-attributes-per-line": [2, {
"singleline": 10,
"multiline": {
"max": 1,
"allowFirstLine": false
}
}],
"vue/singleline-html-element-content-newline": "off",
"vue/multiline-html-element-content-newline":"off",
"vue/name-property-casing": ["error", "PascalCase"],
"vue/no-v-html": "off",
'accessor-pairs': 2,
'arrow-spacing': [2, {
'before': true,
'after': true
}],
'block-spacing': [2, 'always'],
'brace-style': [2, '1tbs', {
'allowSingleLine': true
}],
'camelcase': [0, {
'properties': 'always'
}],
'comma-dangle': [2, 'never'],
'comma-spacing': [2, {
'before': false,
'after': true
}],
'comma-style': [2, 'last'],
'constructor-super': 2,
'curly': [2, 'multi-line'],
'dot-location': [2, 'property'],
'eol-last': 2,
'eqeqeq': ["error", "always", {"null": "ignore"}],
'generator-star-spacing': [2, {
'before': true,
'after': true
}],
'handle-callback-err': [2, '^(err|error)$'],
'indent': [2, 2, {
'SwitchCase': 1
}],
'jsx-quotes': [2, 'prefer-single'],
'key-spacing': [2, {
'beforeColon': false,
'afterColon': true
}],
'keyword-spacing': [2, {
'before': true,
'after': true
}],
'new-cap': [2, {
'newIsCap': true,
'capIsNew': false
}],
'new-parens': 2,
'no-array-constructor': 2,
'no-caller': 2,
'no-console': 'off',
'no-class-assign': 2,
'no-cond-assign': 2,
'no-const-assign': 2,
'no-control-regex': 0,
'no-delete-var': 2,
'no-dupe-args': 2,
'no-dupe-class-members': 2,
'no-dupe-keys': 2,
'no-duplicate-case': 2,
'no-empty-character-class': 2,
'no-empty-pattern': 2,
'no-eval': 2,
'no-ex-assign': 2,
'no-extend-native': 2,
'no-extra-bind': 2,
'no-extra-boolean-cast': 2,
'no-extra-parens': [2, 'functions'],
'no-fallthrough': 2,
'no-floating-decimal': 2,
'no-func-assign': 2,
'no-implied-eval': 2,
'no-inner-declarations': [2, 'functions'],
'no-invalid-regexp': 2,
'no-irregular-whitespace': 2,
'no-iterator': 2,
'no-label-var': 2,
'no-labels': [2, {
'allowLoop': false,
'allowSwitch': false
}],
'no-lone-blocks': 2,
'no-mixed-spaces-and-tabs': 2,
'no-multi-spaces': 2,
'no-multi-str': 2,
'no-multiple-empty-lines': [2, {
'max': 1
}],
'no-native-reassign': 2,
'no-negated-in-lhs': 2,
'no-new-object': 2,
'no-new-require': 2,
'no-new-symbol': 2,
'no-new-wrappers': 2,
'no-obj-calls': 2,
'no-octal': 2,
'no-octal-escape': 2,
'no-path-concat': 2,
'no-proto': 2,
'no-redeclare': 2,
'no-regex-spaces': 2,
'no-return-assign': [2, 'except-parens'],
'no-self-assign': 2,
'no-self-compare': 2,
'no-sequences': 2,
'no-shadow-restricted-names': 2,
'no-spaced-func': 2,
'no-sparse-arrays': 2,
'no-this-before-super': 2,
'no-throw-literal': 2,
'no-trailing-spaces': 2,
'no-undef': 2,
'no-undef-init': 2,
'no-unexpected-multiline': 2,
'no-unmodified-loop-condition': 2,
'no-unneeded-ternary': [2, {
'defaultAssignment': false
}],
'no-unreachable': 2,
'no-unsafe-finally': 2,
'no-unused-vars': [2, {
'vars': 'all',
'args': 'none'
}],
'no-useless-call': 2,
'no-useless-computed-key': 2,
'no-useless-constructor': 2,
'no-useless-escape': 0,
'no-whitespace-before-property': 2,
'no-with': 2,
'one-var': [2, {
'initialized': 'never'
}],
'operator-linebreak': [2, 'after', {
'overrides': {
'?': 'before',
':': 'before'
}
}],
'padded-blocks': [2, 'never'],
'quotes': [2, 'single', {
'avoidEscape': true,
'allowTemplateLiterals': true
}],
'semi': [2, 'never'],
'semi-spacing': [2, {
'before': false,
'after': true
}],
'space-before-blocks': [2, 'always'],
'space-before-function-paren': [2, 'never'],
'space-in-parens': [2, 'never'],
'space-infix-ops': 2,
'space-unary-ops': [2, {
'words': true,
'nonwords': false
}],
'spaced-comment': [2, 'always', {
'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
}],
'template-curly-spacing': [2, 'never'],
'use-isnan': 2,
'valid-typeof': 2,
'wrap-iife': [2, 'any'],
'yield-star-spacing': [2, 'both'],
'yoda': [2, 'never'],
'prefer-const': 2,
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'object-curly-spacing': [2, 'always', {
objectsInObjects: false
}],
'array-bracket-spacing': [2, 'never']
}
}

19
.gitignore vendored

@ -0,0 +1,19 @@
.DS_Store
node_modules/
dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
package-lock.json
tests/**/coverage/
.env.development
.env.production
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017-present PanJiaChen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,102 @@
# vue-admin-template
> 这是一个极简的 vue admin 管理后台。它只包含了 Element UI & axios & iconfont & permission control & lint这些搭建后台必要的东西。
[线上地址](http://panjiachen.github.io/vue-admin-template)
[国内访问](https://panjiachen.gitee.io/vue-admin-template)
目前版本为 `v4.0+` 基于 `vue-cli` 进行构建,若你想使用旧版本,可以切换分支到[tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0),它不依赖 `vue-cli`
## Extra
如果你想要根据用户角色来动态生成侧边栏和 router你可以使用该分支[permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control)
## 相关项目
- [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
- [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
- [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template)
- [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312)
写了一个系列的教程配套文章,如何从零构建后一个完整的后台项目:
- [手摸手,带你用 vue 撸后台 系列一(基础篇)](https://juejin.im/post/59097cd7a22b9d0065fb61d2)
- [手摸手,带你用 vue 撸后台 系列二(登录权限篇)](https://juejin.im/post/591aa14f570c35006961acac)
- [手摸手,带你用 vue 撸后台 系列三 (实战篇)](https://juejin.im/post/593121aa0ce4630057f70d35)
- [手摸手,带你用 vue 撸后台 系列四(vueAdmin 一个极简的后台基础模板,专门针对本项目的文章,算作是一篇文档)](https://juejin.im/post/595b4d776fb9a06bbe7dba56)
- [手摸手,带你封装一个 vue component](https://segmentfault.com/a/1190000009090836)
## Build Setup
```bash
# 克隆项目
git clone https://github.com/PanJiaChen/vue-admin-template.git
# 进入项目目录
cd vue-admin-template
# 安装依赖
npm install
# 建议不要直接使用 cnpm 安装以来,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
npm install --registry=https://registry.npm.taobao.org
# 启动服务
npm run dev
```
浏览器访问 [http://localhost:9528](http://localhost:9528)
## 发布
```bash
# 构建测试环境
npm run build:stage
# 构建生产环境
npm run build:prod
```
## 其它
```bash
# 预览发布环境效果
npm run preview
# 预览发布环境效果 + 静态资源分析
npm run preview -- --report
# 代码格式检查
npm run lint
# 代码格式检查并自动修复
npm run lint -- --fix
```
更多信息请参考 [使用文档](https://panjiachen.github.io/vue-element-admin-site/zh/)
## 购买贴纸
你也可以通过 购买[官方授权的贴纸](https://smallsticker.com/product/vue-element-admin) 的方式来支持 vue-element-admin - 每售出一张贴纸,我们将获得 2 元的捐赠。
## Demo
![demo](https://github.com/PanJiaChen/PanJiaChen.github.io/blob/master/images/demo.gif)
## Browsers support
Modern browsers and Internet Explorer 10+.
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
| --------- | --------- | --------- | --------- |
| IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions
## License
[MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license.
Copyright (c) 2017-present PanJiaChen

@ -0,0 +1,91 @@
# vue-admin-template
English | [简体中文](./README-zh.md)
> A minimal vue admin template with Element UI & axios & iconfont & permission control & lint
**Live demo:** http://panjiachen.github.io/vue-admin-template
**The current version is `v4.0+` build on `vue-cli`. If you want to use the old version , you can switch branch to [tag/3.11.0](https://github.com/PanJiaChen/vue-admin-template/tree/tag/3.11.0), it does not rely on `vue-cli`**
## Build Setup
```bash
# clone the project
git clone https://github.com/PanJiaChen/vue-admin-template.git
# enter the project directory
cd vue-admin-template
# install dependency
npm install
# develop
npm run dev
```
This will automatically open http://localhost:9528
## Build
```bash
# build for test environment
npm run build:stage
# build for production environment
npm run build:prod
```
## Advanced
```bash
# preview the release environment effect
npm run preview
# preview the release environment effect + static resource analysis
npm run preview -- --report
# code format check
npm run lint
# code format check and auto fix
npm run lint -- --fix
```
Refer to [Documentation](https://panjiachen.github.io/vue-element-admin-site/guide/essentials/deploy.html) for more information
## Demo
![demo](https://github.com/PanJiaChen/PanJiaChen.github.io/blob/master/images/demo.gif)
## Extra
If you want router permission && generate menu by user roles , you can use this branch [permission-control](https://github.com/PanJiaChen/vue-admin-template/tree/permission-control)
For `typescript` version, you can use [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template) (Credits: [@Armour](https://github.com/Armour))
## Related Project
- [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)
- [electron-vue-admin](https://github.com/PanJiaChen/electron-vue-admin)
- [vue-typescript-admin-template](https://github.com/Armour/vue-typescript-admin-template)
- [awesome-project](https://github.com/PanJiaChen/vue-element-admin/issues/2312)
## Browsers support
Modern browsers and Internet Explorer 10+.
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
| --------- | --------- | --------- | --------- |
| IE10, IE11, Edge| last 2 versions| last 2 versions| last 2 versions
## License
[MIT](https://github.com/PanJiaChen/vue-admin-template/blob/master/LICENSE) license.
Copyright (c) 2017-present PanJiaChen

@ -0,0 +1,14 @@
module.exports = {
presets: [
// https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app
'@vue/cli-plugin-babel/preset'
],
'env': {
'development': {
// babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().
// This plugin can significantly increase the speed of hot updates, when you have a large number of pages.
// https://panjiachen.github.io/vue-element-admin-site/guide/advanced/lazy-loading.html
'plugins': ['dynamic-import-node']
}
}
}

@ -0,0 +1,35 @@
const { run } = require('runjs')
const chalk = require('chalk')
const config = require('../vue.config.js')
const rawArgv = process.argv.slice(2)
const args = rawArgv.join(' ')
if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
const report = rawArgv.includes('--report')
run(`vue-cli-service build ${args}`)
const port = 9526
const publicPath = config.publicPath
var connect = require('connect')
var serveStatic = require('serve-static')
const app = connect()
app.use(
publicPath,
serveStatic('./dist', {
index: ['index.html', '/']
})
)
app.listen(port, function () {
console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`))
if (report) {
console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`))
}
})
} else {
run(`vue-cli-service build ${args}`)
}

@ -0,0 +1,24 @@
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)'
],
collectCoverageFrom: ['src/utils/**/*.{js,vue}', '!src/utils/auth.js', '!src/utils/request.js', 'src/components/**/*.{js,vue}'],
coverageDirectory: '<rootDir>/tests/unit/coverage',
// 'collectCoverage': true,
'coverageReporters': [
'lcov',
'text-summary'
],
testURL: 'http://localhost/'
}

@ -0,0 +1,9 @@
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

@ -0,0 +1,57 @@
const Mock = require('mockjs')
const { param2Obj } = require('./utils')
const user = require('./user')
const table = require('./table')
const mocks = [
...user,
...table
]
// for front mock
// please use it cautiously, it will redefine XMLHttpRequest,
// which will cause many of your third-party libraries to be invalidated(like progress event).
function mockXHR() {
// mock patch
// https://github.com/nuysoft/Mock/issues/300
Mock.XHR.prototype.proxy_send = Mock.XHR.prototype.send
Mock.XHR.prototype.send = function() {
if (this.custom.xhr) {
this.custom.xhr.withCredentials = this.withCredentials || false
if (this.responseType) {
this.custom.xhr.responseType = this.responseType
}
}
this.proxy_send(...arguments)
}
function XHR2ExpressReqWrap(respond) {
return function(options) {
let result = null
if (respond instanceof Function) {
const { body, type, url } = options
// https://expressjs.com/en/4x/api.html#req
result = respond({
method: type,
body: JSON.parse(body),
query: param2Obj(url)
})
} else {
result = respond
}
return Mock.mock(result)
}
}
for (const i of mocks) {
Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response))
}
}
module.exports = {
mocks,
mockXHR
}

@ -0,0 +1,81 @@
const chokidar = require('chokidar')
const bodyParser = require('body-parser')
const chalk = require('chalk')
const path = require('path')
const Mock = require('mockjs')
const mockDir = path.join(process.cwd(), 'mock')
function registerRoutes(app) {
let mockLastIndex
const { mocks } = require('./index.js')
const mocksForServer = mocks.map(route => {
return responseFake(route.url, route.type, route.response)
})
for (const mock of mocksForServer) {
app[mock.type](mock.url, mock.response)
mockLastIndex = app._router.stack.length
}
const mockRoutesLength = Object.keys(mocksForServer).length
return {
mockRoutesLength: mockRoutesLength,
mockStartIndex: mockLastIndex - mockRoutesLength
}
}
function unregisterRoutes() {
Object.keys(require.cache).forEach(i => {
if (i.includes(mockDir)) {
delete require.cache[require.resolve(i)]
}
})
}
// for mock server
const responseFake = (url, type, respond) => {
return {
url: new RegExp(`${process.env.VUE_APP_BASE_API}${url}`),
type: type || 'get',
response(req, res) {
console.log('request invoke:' + req.path)
res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
}
}
}
module.exports = app => {
// parse app.body
// https://expressjs.com/en/4x/api.html#req.body
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({
extended: true
}))
const mockRoutes = registerRoutes(app)
var mockRoutesLength = mockRoutes.mockRoutesLength
var mockStartIndex = mockRoutes.mockStartIndex
// watch files, hot reload mock server
chokidar.watch(mockDir, {
ignored: /mock-server/,
ignoreInitial: true
}).on('all', (event, path) => {
if (event === 'change' || event === 'add') {
try {
// remove mock routes stack
app._router.stack.splice(mockStartIndex, mockRoutesLength)
// clear routes cache
unregisterRoutes()
const mockRoutes = registerRoutes(app)
mockRoutesLength = mockRoutes.mockRoutesLength
mockStartIndex = mockRoutes.mockStartIndex
console.log(chalk.magentaBright(`\n > Mock Server hot reload success! changed ${path}`))
} catch (error) {
console.log(chalk.redBright(error))
}
}
})
}

@ -0,0 +1,29 @@
const Mock = require('mockjs')
const data = Mock.mock({
'items|30': [{
id: '@id',
title: '@sentence(10, 20)',
'status|1': ['published', 'draft', 'deleted'],
author: 'name',
display_time: '@datetime',
pageviews: '@integer(300, 5000)'
}]
})
module.exports = [
{
url: '/vue-admin-template/table/list',
type: 'get',
response: config => {
const items = data.items
return {
code: 20000,
data: {
total: items.length,
items: items
}
}
}
}
]

@ -0,0 +1,84 @@
const tokens = {
admin: {
token: 'admin-token'
},
editor: {
token: 'editor-token'
}
}
const users = {
'admin-token': {
roles: ['admin'],
introduction: 'I am a super administrator',
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
name: 'Super Admin'
},
'editor-token': {
roles: ['editor'],
introduction: 'I am an editor',
avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
name: 'Normal Editor'
}
}
module.exports = [
// user login
{
url: '/vue-admin-template/user/login',
type: 'post',
response: config => {
const { username } = config.body
const token = tokens[username]
// mock error
if (!token) {
return {
code: 60204,
message: 'Account and password are incorrect.'
}
}
return {
code: 20000,
data: token
}
}
},
// get user info
{
url: '/vue-admin-template/user/info\.*',
type: 'get',
response: config => {
const { token } = config.query
const info = users[token]
// mock error
if (!info) {
return {
code: 50008,
message: 'Login failed, unable to get user details.'
}
}
return {
code: 20000,
data: info
}
}
},
// user logout
{
url: '/vue-admin-template/user/logout',
type: 'post',
response: _ => {
return {
code: 20000,
data: 'success'
}
}
}
]

@ -0,0 +1,25 @@
/**
* @param {string} url
* @returns {Object}
*/
function param2Obj(url) {
const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ')
if (!search) {
return {}
}
const obj = {}
const searchArr = search.split('&')
searchArr.forEach(v => {
const index = v.indexOf('=')
if (index !== -1) {
const name = v.substring(0, index)
const val = v.substring(index + 1, v.length)
obj[name] = val
}
})
return obj
}
module.exports = {
param2Obj
}

@ -0,0 +1,73 @@
{
"name": "vue-admin-template",
"version": "4.4.0",
"description": "A vue admin template with Element UI & axios & iconfont & permission control & lint",
"author": "Pan <panfree23@gmail.com>",
"scripts": {
"dev": "vue-cli-service serve",
"build:prod": "vue-cli-service build",
"build:stage": "vue-cli-service build --mode staging",
"preview": "node build/index.js --preview",
"svgo": "svgo -f src/icons/svg --config=src/icons/svgo.yml",
"lint": "eslint --ext .js,.vue src",
"test:unit": "jest --clearCache && vue-cli-service test:unit",
"test:ci": "npm run lint && npm run test:unit"
},
"dependencies": {
"axios": "0.18.1",
"core-js": "3.6.5",
"echarts": "^4.9.0",
"element-resize-detector": "^1.2.4",
"element-ui": "2.13.2",
"html2canvas": "^1.4.1",
"js-cookie": "2.2.0",
"less": "^3.13.1",
"moment": "^2.29.4",
"normalize.css": "7.0.0",
"nprogress": "0.2.0",
"path-to-regexp": "2.4.0",
"print-js": "^1.6.0",
"view-design": "^4.7.0",
"vue": "2.6.10",
"vue-grid-layout": "^2.4.0",
"vue-router": "3.0.6",
"vuex": "3.1.0",
"vuex-persistedstate": "^4.1.0",
"wangeditor": "^4.7.12"
},
"devDependencies": {
"@vue/cli-plugin-babel": "4.4.4",
"@vue/cli-plugin-eslint": "4.4.4",
"@vue/cli-plugin-unit-jest": "4.4.4",
"@vue/cli-service": "4.4.4",
"@vue/test-utils": "1.0.0-beta.29",
"autoprefixer": "9.5.1",
"babel-eslint": "^8.2.2",
"babel-jest": "23.6.0",
"babel-plugin-dynamic-import-node": "2.3.3",
"chalk": "2.4.2",
"connect": "3.6.6",
"eslint": "6.7.2",
"eslint-plugin-vue": "6.2.2",
"html-webpack-plugin": "3.2.0",
"less-loader": "^5.0.0",
"mockjs": "1.0.1-beta3",
"runjs": "4.3.2",
"sass": "1.26.8",
"sass-loader": "8.0.2",
"script-ext-html-webpack-plugin": "2.1.3",
"serve-static": "1.13.2",
"svg-sprite-loader": "4.1.3",
"svgo": "1.2.2",
"vue-template-compiler": "2.6.10"
},
"browserslist": [
"> 1%",
"last 2 versions"
],
"engines": {
"node": ">=8.9",
"npm": ">= 3.0.0"
},
"license": "MIT"
}

@ -0,0 +1,8 @@
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
'plugins': {
// to edit target browsers: use "browserslist" field in package.json
'autoprefixer': {}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!--清除浏览器中的缓存 -->
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= webpackConfig.name %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= webpackConfig.name %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

@ -0,0 +1,13 @@
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
import "./assets/css/common.less";
export default {
name: 'App'
}
</script>

@ -0,0 +1,33 @@
import request from '@/utils/request'
export function index(params) {
return request({
method: 'get',
url: '/api/admin/plan_evaluate/index',
params
})
}
export function show(params) {
return request({
method: 'get',
url: '/api/admin/plan_evaluate/show',
params
})
}
export function save(data) {
return request({
method: 'post',
url: '/api/admin/plan_evaluate/save',
data
})
}
export function destroy(params) {
return request({
method: 'get',
url: '/api/admin/plan_evaluate/destroy',
params
})
}

@ -0,0 +1,33 @@
import request from '@/utils/request'
export function index(params) {
return request({
method: "get",
url: "/api/admin/target/index",
params
})
}
export function show(params) {
return request({
method: "get",
url: "/api/admin/target/show",
params
})
}
export function save(data) {
return request({
method: "post",
url: "/api/admin/target/save",
data
})
}
export function destroy(params) {
return request({
method: "get",
url: "/api/admin/target/destroy",
params
})
}

@ -0,0 +1,34 @@
import request from '@/utils/request'
import * as url from 'url'
export function index(params) {
return request({
method: "get",
url: "/api/admin/target_type/index",
params
})
}
export function show(params) {
return request({
method: "get",
url: "/api/admin/target_type/show",
params
})
}
export function save(data) {
return request({
method: "post",
url: "/api/admin/target_type/save",
data
})
}
export function destroy(params) {
return request({
method: "get",
url: "/api/admin/target_type/destroy",
params
})
}

@ -0,0 +1,49 @@
import request from "@/utils/request";
export function getBudget(params){
return request({
method:'get',
url:'/api/admin/plan/index',
params
})
}
export function addBudget(data){
return request({
method:'post',
url:'/api/admin/plan/store',
data
})
}
export function delBudget(params){
return request({
method:'get',
url:'/api/admin/plan/destroy',
params
})
}
export function editorBudget(data){
return request({
method:'post',
url:'/api/admin/plan/save',
data
})
}
export function detailBudget(params){
return request({
method:'get',
url:"/api/admin/plan/show",
params
})
}
export function getProgress(params){
return request({
method:'get',
url:'/api/admin/plan/progress',
params
})
}

@ -0,0 +1,33 @@
import request from '@/utils/request'
export function index(params) {
return request({
method: "get",
url: "/api/admin/plan_target/index",
params
})
}
export function show(params) {
return request({
method: "get",
url: "/api/admin/plan_target/show",
params
})
}
export function save(data) {
return request({
method: "post",
url: "/api/admin/plan_target/save",
data
})
}
export function destroy(params) {
return request({
method: "get",
url: "/api/admin/plan_target/destroy",
params
})
}

@ -0,0 +1,33 @@
import request from '@/utils/request'
export function index(params) {
return request({
method: "get",
url: "/api/admin/pre_plan/index",
params
})
}
export function show(params) {
return request({
method: "get",
url: "/api/admin/pre_plan/show",
params
})
}
export function save(data) {
return request({
method: "post",
url: "/api/admin/pre_plan/save",
data
})
}
export function destroy(params) {
return request({
method: "get",
url: "/api/admin/pre_plan/destroy",
params
})
}

@ -0,0 +1,22 @@
import request from '@/utils/request'
export function listCommondepartment(params) {
return request({
url: '/api/admin/other/admin-department-list',
method: 'get',
params:params
})
}
export function listCommonuser(params) {
return request({
url: '/api/admin/other/admin-user-list',
method: 'get',
params:params
})
}

@ -0,0 +1,59 @@
import request from "@/utils/request";
export function getContract(params,noloading = false){
return request({
method:'get',
url:'/api/admin/contract/index',
params,
noloading
})
}
export function addContrant(data){
return request({
method:"post",
url:'/api/admin/contract/store',
data
})
}
export function detailContract(params){
return request({
method:'get',
url:'/api/admin/contract/show',
params
})
}
export function delContract(params){
return request({
method:'get',
url:'/api/admin/contract/destroy',
params
})
}
export function editorContract(data){
return request({
method:'post',
url:'/api/admin/contract/save',
data
})
}
export function checkContractName(params){
return request({
method:'get',
url:'/api/admin/contract/check-name',
params
})
}
export function updateStatus(params,noloading = true){
return request({
method:'get',
url:'api/admin/oa/update-contract',
params,
noloading
})
}

@ -0,0 +1,41 @@
import request from "@/utils/request";
export function addContractSign(data){
return request({
method:'post',
url:'/api/admin/sign_plan/store',
data
})
}
export function getContractSign(params){
return request({
method:'get',
url:'/api/admin/sign_plan/index',
params
})
}
export function delContractSign(params){
return request({
method:'get',
url:"/api/admin/sign_plan/destroy",
params
})
}
export function detailContractSign(params){
return request({
method:'get',
url:'/api/admin/sign_plan/show',
params
})
}
export function editorContractSign(data){
return request({
method:'post',
url:"/api/admin/sign_plan/save",
data
})
}

@ -0,0 +1,36 @@
import request from "@/utils/request";
export function getNotice(params,noLoading = false){
return request({
method:'get',
url:'/api/admin/notice/index',
params,
noLoading
})
}
export function getNotice2(params,noLoading = false){
return request({
method:'get',
url:'/api/admin/notice/index-v2',
params,
noLoading
})
}
export function readNotice(params){
return request({
method:'get',
url:'/api/admin/notice/has-read',
params
})
}
export function statistic(params,noLoading = false){
return request({
method:'get',
url:'/api/admin/notice/statistic',
params,
noLoading
})
}

@ -0,0 +1,33 @@
import request from '@/utils/request'
export function index (params) {
return request({
method: 'get',
url: '/api/admin/income/index',
params
})
}
export function show (params) {
return request({
method: 'get',
url: '/api/admin/income/show',
params
})
}
export function save (data) {
return request({
method: 'post',
url: '/api/admin/income/save',
data
})
}
export function destroy (params) {
return request({
method: 'get',
url: '/api/admin/income/destroy',
params
})
}

@ -0,0 +1,9 @@
import request from "@/utils/request";
export function getOatoken(params){
return request({
method:'get',
url:'/api/admin/oa/get-oa-token',
params
})
}

@ -0,0 +1,32 @@
import request from "@/utils/request"
export function getOutDetail({ tbname,out_caigou_id,out_contract_id,out_pay_id,out_zhaobiao_id }) {
return request({
method: 'get',
url: '/index.php',
requestBase: '/old',
params: {
s: "/Api/flowDetail",
tbname,
out_caigou_id,
out_contract_id,
out_pay_id,
out_zhaobiao_id
}
})
}
export function httpCurl(params,noloading = true,s= '/Api/flowDetail',method='GET',url) {
return request({
method: 'post',
url: '/api/admin/other/http-curl',
data: {
url: url || process.env.VUE_APP_OUT_OLD,
method,
params: {
s,
...params
}
},
noloading
})
}

@ -0,0 +1,41 @@
import request from "@/utils/request";
export function getFundLog(params){
return request({
method:'get',
url:'/api/admin/fund_log/index',
params
})
}
export function addFundLog(data){
return request({
method:'post',
url:"/api/admin/fund_log/store",
data
})
}
export function delFundLog(params){
return request({
method:'get',
url:'/api/admin/fund_log/destroy',
params
})
}
export function detailFundLog(params){
return request({
method:'get',
url:'/api/admin/fund_log/show',
params
})
}
export function editorFundLog(data){
return request({
method:"post",
url:"/api/admin/fund_log/save",
data
})
}

@ -0,0 +1,40 @@
import request from '@/utils/request'
export function save(data) {
return request({
url: '/api/admin/department/save',
method: 'post',
data
})
}
export function listdept() {
return request({
url: '/api/admin/department',
method: 'get'
})
}
export function listdeptNoAuth(params = {show_tree:1}){
return request({
url:'/api/admin/other/admin-department-list',
method:'get',
params
})
}
export function del(data) {
return request({
url: '/api/admin/department/delete',
method: 'post',
data
})
}
export function adminDepartmentList(data) {
return request({
url: '/api/admin/other/admin-department-list',
method: 'get',
params: data
})
}

@ -0,0 +1,50 @@
import request from '@/utils/request'
export function save(data) {
return request({
url: '/api/admin/parameter/save',
method: 'post',
data
})
}
export function store(data) {
return request({
url: '/api/admin/parameter/store',
method: 'post',
data
})
}
export function getparameter(param) {
return request({
url: '/api/admin/parameter/show',
method: 'get',
params: param
})
}
export function listparameter(param) {
return request({
url: '/api/admin/parameter/index',
method: 'get',
params: param
})
}
export function del(id) {
return request({
url: '/api/admin/parameter/delete',
method: 'get',
params: {
id
}
})
}
export function delDetail(id) {
return request({
url: '/api/admin/parameter/detail-delete',
method: 'get',
params: {
id
}
})
}

@ -0,0 +1,33 @@
import request from '@/utils/request';
export function index(params) {
return request({
method: 'get',
url: '/api/admin/admin_expand/index',
params
})
}
export function save(data) {
return request({
method: 'post',
url: '/api/admin/admin_expand/save',
data
})
}
export function show(params) {
return request({
method: 'get',
url: '/api/admin/admin_expand/show',
params
})
}
export function destroy(params) {
return request({
method: 'get',
url: '/api/admin/admin_expand/destroy',
params
})
}

@ -0,0 +1,9 @@
import request from "@/utils/request";
export function index(data){
return request({
method:'post',
url:'/api/admin/operate-log/index',
data
})
}

@ -0,0 +1,23 @@
import request from '@/utils/request'
export function save(data) {
return request({
url: '/api/admin/menu/save',
method: 'post',
data
})
}
export function listmenu() {
return request({
url: '/api/admin/menu',
method: 'get'
})
}
export function del(data) {
return request({
url: '/api/admin/menu/delete',
method: 'post',
data
})
}

@ -0,0 +1,13 @@
import request from "@/utils/request";
import store from '@/store';
export function ossLogin(){
return request({
isLoading:false,
method:'post',
url:'/api/admin/auth/oss-login',
data:{
id:store.state.user.userId,
username:store.state.user.username
}
})
}

@ -0,0 +1,9 @@
import request from '@/utils/request'
export function setPermissions(data) {
return request({
url: '/api/admin/role/set-permissions',
method: 'post',
data
})
}

@ -0,0 +1,32 @@
import request from '@/utils/request'
export function save(data) {
return request({
url: '/api/admin/role/save',
method: 'post',
data
})
}
export function list() {
return request({
url: '/api/admin/role',
method: 'get'
})
}
export function del(data) {
return request({
url: '/api/admin/role/delete',
method: 'post',
data
})
}
export function set(data) {
return request({
url: '/api/admin/admin/set-roles-many',
method: 'post',
data
})
}

@ -0,0 +1,33 @@
import request from '@/utils/request'
export function save(data) {
return request({
url: '/api/admin/admin/save',
method: 'post',
data
})
}
export function listuser(params) {
return request({
url: '/api/admin/admin',
method: 'get',
params:params
})
}
export function del(data) {
return request({
url: '/api/admin/admin/delete',
method: 'post',
data
})
}
export function setRoles(data) {
return request({
url: '/api/admin/admin/set-roles',
method: 'post',
data
})
}

@ -0,0 +1,43 @@
import request from '@/utils/request'
export function login(data) {
return request({
url: '/api/admin/auth/login',
method: 'post',
data
})
}
export function loginOss(data) {
return request({
url: '/api/admin/auth/oss-login',
method: 'post',
data,
noloading:true
})
}
export function getInfo(token) {
return request({
url: '/api/admin/auth/me',
method: 'post',
params: { token }
})
}
export function logout() {
return request({
url: '/api/admin/auth/logout',
method: 'post'
})
}
export function getAuthMenu(token) {
return request({
url: '/api/admin/auth/permissions',
method: 'get',
params: { token }
})
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

@ -0,0 +1,83 @@
.layout-container {
// border-top: 1px solid #eee;
background: #eee;
// padding: 15px;
/* margin-bottom: 40px; */
}
// .animated {
// -webkit-animation-duration: 1s;
// animation-duration: 1s;
// -webkit-animation-fill-mode: both;
// animation-fill-mode: both;
// }
// .animated.infinite {
// -webkit-animation-iteration-count: infinite;
// animation-iteration-count: infinite;
// }
// .animated.hinge {
// -webkit-animation-duration: 2s;
// animation-duration: 2s;
// }
// .animated.bounceIn,
// .animated.bounceOut,
// .animated.flipOutX,
// .animated.flipOutY {
// -webkit-animation-duration: 0.75s;
// animation-duration: 0.75s;
// }
// @-webkit-keyframes fadeInDown {
// 0% {
// opacity: 0;
// -webkit-transform: translate3d(0, -100%, 0);
// transform: translate3d(0, -100%, 0);
// }
// to {
// opacity: 1;
// -webkit-transform: none;
// transform: none;
// }
// }
// @keyframes fadeInDown {
// 0% {
// opacity: 0;
// -webkit-transform: translate3d(0, -100%, 0);
// transform: translate3d(0, -100%, 0);
// }
// to {
// opacity: 1;
// -webkit-transform: none;
// transform: none;
// }
// }
// .fadeInDown {
// -webkit-animation-name: fadeInDown;
// animation-name: fadeInDown;
// }
// @-webkit-keyframes slideInDown {
// 0% {
// -webkit-transform: translate3d(0, -100%, 0);
// transform: translate3d(0, -100%, 0);
// visibility: visible;
// }
// to {
// -webkit-transform: translateZ(0);
// transform: translateZ(0);
// }
// }
// @keyframes slideInDown {
// 0% {
// -webkit-transform: translate3d(0, -100%, 0);
// transform: translate3d(0, -100%, 0);
// visibility: visible;
// }
// to {
// -webkit-transform: translateZ(0);
// transform: translateZ(0);
// }
// }
// .slideInDown {
// -webkit-animation-name: slideInDown;
// animation-name: slideInDown;
// }

@ -0,0 +1,226 @@
.view-container{
// padding: 15px;
margin: 0px 20px;
padding-bottom: 15px;
background: white;
.grid-search{
padding-top:15px;
//padding: 15px 15px 0 15px;
}
.grid-container,.grid-body{
padding: 0 15px;
}
.view-header{
padding-left: 15px;
padding-right: 15px;
}
.fs-line{
height: 10px;
background: #eee;
margin-top: -10px;
margin-bottom: 11px;
border-top: 1px solid #dadada;
border-bottom: 1px solid #dadada;
}
}
.view-header {
height: 45px;
position: relative;
padding-bottom: 11px;
display: flex;
.search-line {
width: 180px;
}
.search-line > div {
margin-left: 5px;
margin-right: 10px;
}
.search-line > div > div{
width: 200px;
text-align: left;
}
.search-line > div:first-child{
flex: 1;
}
.search-line > div .ivu-select-dropdown{
max-height: 300px;
}
// .btn-group > button {
// text-align: right;
// }
.btn-group{
white-space: nowrap;
button {
margin-left: 10px;
// padding: 5px 16px;
}
.dropdown{
height: 31px;
padding-right: 9px;
padding-left: 11px;
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
}
.r-dropdown{
height: 31px;
margin-left: 0px;
padding-left: 5px;
padding-right: 5px;
border-bottom-left-radius: 0px;
border-left: 1px solid #eee;
border-top-left-radius: 0;
}
}
.btn-group .ivu-dropdown-item {
text-align: left !important;
}
.btn-group .ivu-dropdown-item:not(:last-child) {
border-bottom: 1px dotted #eee;
}
.desc-text {
margin-top: 5px;
font-weight: bold;
margin-bottom: 3px;
font-size: 14px;
color: #313131;
white-space: nowrap;
border-bottom: 2px solid #646565;
}
.desc-text .ivu-icon {
font-size: 20px;
bottom: 2px;
position: relative;
}
.search-box {
background: #fefefe;
margin-top: 45px;
border: 1px solid #ececec;
position: absolute;
z-index: 999;
left: 0;
right: 0;
// width: 100%;
padding: 25px 40px;
padding-bottom: 0;
box-shadow: 0px 7px 18px -12px #bdc0bb;
}
.notice {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
position: relative;
top: 12px;
flex: 1;
left: 10px;
margin-right: 20px;
}
}
.table-info-cell-title {
background-color: #f5f5f5 !important;
}
.iview-com {
background: #f3f3f3;
> div.item{
// margin-bottom: 10px;
margin-bottom: 12px;
background: white;
}
> div.form-item{
padding: 8px 16px 7px 16px;
//box-shadow: 0 1px 7px rgb(199, 199, 199);
}
> div.table-item{
padding: 0 10px;
border-top: 1px solid #eee;
}
.v-text{
line-height: 27px;
}
.form-text{
position: relative;
/* height: 38px; */
/* line-height: 38px; */
/* padding: 0 15px; */
// border-bottom: 1px solid #e4e4e4;
border-bottom: 1px solid #eee;
/* border-radius: 2px 2px 0 0; */
font-size: 14px;
margin-bottom: 14px;
.title{
border-bottom: 2px solid #00BCD4;
color: #009688;
font-weight: bold;
letter-spacing: 1px;
// border-bottom-right-radius: 5px;
// border-top-left-radius: 5px;
padding: 6px 0;
// background: #009688;
// color: white;
}
.icon{
color: #00BCD4;
font-size: 19px;
position: relative;
top: -1px;
}
}
}
.form-closex {
text-align: right;
padding-bottom: 24px;
}
.form-closex button {
margin-left: 10px;
padding: 4px 13px;
}
// .grid-detail{
// }
.toolbar{
padding: 3px 0px;
width: 100%;
display: flex;
.title{
line-height: 29px;
border-bottom: none;
font-size: 13px;
font-weight: bolder;
margin-bottom: 0;
color: #5d5c5c;
.icon{
color: #009688;
font-size: 18px;
}
i{
line-height: 29px;
border-bottom: none;
font-weight: bolder;
margin-bottom: 0;
color: #5d5c5c;
position: relative;
margin-top: -4px;
font-size: 14px;
}
}
.btns{
line-height: 28px;
flex: 1;
text-align: right;
margin-right: 12px;
button{
border: none;
margin-left:15px;
border: 0px;
color: #009688;
}
button:hover{
color: #FF9800;
border-color: #FF9800;
border: none;
}
}
}

@ -0,0 +1,75 @@
*{
box-sizing:border-box;
-moz-box-sizing:border-box; /* Firefox */
-webkit-box-sizing:border-box; /* Safari */
}
.el-pager li{
font-weight: 100;
margin-right: 9px;
border: 1px solid #eee;
border-radius: 3px;
min-width: 28px;
}
.el-pager li.active,.el-pager li:hover{
background: #ed4014;
color: white;
}
.el-pagination__editor.el-input .el-input__inner{
height: 23px;
}
.animated {
-webkit-animation-duration: 0.5s;
animation-duration: 0.5s;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}
@media (print), (prefers-reduced-motion) {
.animated {
-webkit-animation: unset !important;
animation: unset !important;
-webkit-transition: none !important;
transition: none !important;
}
}
@-webkit-keyframes fadeInDown {
from {
opacity: 1;
-webkit-transform: translate3d(0, -100%, 0);
transform: translate3d(0, -100%, 0);
}
to {
opacity: 1;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
}
@keyframes fadeInDown {
from {
opacity: 0;
-webkit-transform: translate3d(0, -100%, 0);
transform: translate3d(0, -100%, 0);
}
to {
opacity: 1;
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
}
.fadeInDown {
-webkit-animation-name: fadeInDown;
animation-name: fadeInDown;
}
.ivu-message{
z-index: 999999999 !important;
}
.ivu-form-item-content{
text-align: left;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

@ -0,0 +1,303 @@
<template>
<div class="app-container">
<div ref="lxHeader">
<LxHeader icon="md-apps" :text="typeLabel[type]" style="margin-bottom: 10px; border: 0px; margin-top: 15px">
<div slot="content" />
<slot>
<div>
<Input v-model="listQuery.keyword" style="width: 200px; margin-right: 10px" placeholder="关键字搜索" />
<Button type="primary" @click="handleFilter"></Button>
</div>
</slot>
</LxHeader>
</div>
<div class="table-tree">
<el-table :data="tableData" border default-expand-all class="v-table" style="width: 100%;margin-bottom: 20px;">
<el-table-column type="index" label="序号" width="50" />
<el-table-column label="项目名称" prop="name" width="400" />
<el-table-column label="文档数量" prop="item_accepts_count" align="center" />
<el-table-column label="编辑人" align="center">
<template slot-scope="{row}"><span>{{ row.admin ? row.admin.name: '' }}</span></template>
</el-table-column>
<el-table-column label="最后编辑时间" width="160" prop="updated_at" align="center" />
<el-table-column fixed="right" width="250" label="操作" align="center">
<template slot-scope="{row}">
<Button type="primary" size="small" style="margin-left: 10px;" ghost @click="editList(row)"></Button>
<Button type="primary" size="small" style="margin-left: 10px;" ghost @click="showList(row)"></Button>
<!-- <Button type="error" size="small" style="margin-left: 10px;" ghost @click="dialogFormVisible = true">删除</Button> -->
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.page_size" @pagination="getList" />
</div>
<el-dialog :title="'管理文件'" :visible.sync="dialogFormVisible" width="80%" @close="dialogClose">
<div class="rightCol">
<Button type="primary" @click="addRow" size="small" ghost>新增文件</Button>
</div>
<el-form ref="dataForm" :model="form" :rules="rules">
<el-table border default-expand-all class="v-table" :data="form.list">
<el-table-column label="文件名称" prop="name" width="400">
<template slot-scope="scope">
<el-form-item :prop="'list.' + scope.$index + '.name'" style="margin-top: 15px;margin-bottom: 17px;">
<el-input v-model="scope.row.name" placeholder="文件名称" style="width:100%" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="获取时间" prop="date">
<template slot-scope="scope">
<el-form-item :prop="'list.' + scope.$index + '.date'" style="margin-top: 15px;margin-bottom: 17px;">
<el-date-picker v-model="scope.row.date" type="date" placeholder="选择日期" value-format="yyyy-MM-dd" style="width: 100%;" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="附件名称" prop="upload_id">
<template slot-scope="scope">
<el-form-item :prop="'list.' + scope.$index + '.upload_id'" style="margin-top: 15px;margin-bottom: 17px;">
<UploadFile v-if="dialogFormVisible" v-model="scope.row.upload_id" :file_name="scope.row.upload ? scope.row.upload.original_name : ''" />
</el-form-item>
</template>
</el-table-column>
<el-table-column width="150" label="操作" align="center">
<template slot-scope="scope">
<Button @click="save(scope.$index)" type="primary" size="small" ghost>保存</Button>
<Button type="error" size="small" ghost @click="handleDelete(scope.row, scope.$index)">删除</Button>
</template>
</el-table-column>
</el-table>
</el-form>
<div slot="footer" class="dialog-footer"></div>
</el-dialog>
<el-dialog :title="'查看文件'" :visible.sync="dialogTableVisible" width="80%">
<el-table border default-expand-all class="v-table" :data="form.list">
<el-table-column label="文件名称" prop="name" width="400">
<template slot-scope="scope">
{{scope.row.name}}
</template>
</el-table-column>
<el-table-column label="获取时间" prop="date">
<template slot-scope="scope">
{{scope.row.date}}
</template>
</el-table-column>
<el-table-column label="附件名称" prop="upload_id">
<template slot-scope="scope">
{{scope.row.upload ? scope.row.upload.original_name : ''}}
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<a v-if="scope.row.upload" :href="scope.row.upload.url" target="_blank"></a>
</template>
</el-table-column>
</el-table>
<div slot="footer" class="dialog-footer"></div>
</el-dialog>
</div>
</template>
<script>
import LxHeader from '@/components/LxHeader/index.vue'
import UploadFile from '@/components/Upload/file.vue'
import Pagination from '@/components/Pagination'
import { contractIndex, index, show, destroy, store, save } from '../../api/accept.js'
export default {
name: 'Accept',
components: {
LxHeader, UploadFile, Pagination
},
props: {
type: {
type: Number,
default: 1
}
},
data() {
return {
typeLabel: {1: '结算审计资料', 2: '结算审计报告', 3: '归档管理', 4: '决算报告'},
listQuery: {
page: 1,
page_size: 10
},
total: 0,
tableData: [],
rules: {
list:[]
},
dialogFormVisible:false,
dialogTableVisible: false,
form: {
id: undefined,
contract_id: '',
list: []
},
bcglXiangXiList: [],
checkedDetail: [],
formLabelWidth: '120px',
search:''
}
},
created() {
this.getList()
},
methods: {
getList() {
this.listQuery.type = this.type
contractIndex(this.listQuery).then(response => {
this.tableData = response.data
this.total = response.total
}).catch(error => {})
},
handleFilter() {
this.listQuery.page = 1
this.getList()
},
createRules(idx) {
let ruleList = []
for (let i = 0; i < this.form.list.length; i++) {
let required = i == idx ? true : false
ruleList.push({
name: { required: required, message: '请输入文件名称', trigger: 'change' },
date: { required: required, message: '请选择获取时间', trigger: 'change' },
upload_id: { required: required, message: '请上传文件', trigger: 'change' },
})
}
this.rules = {list: ruleList}
},
editList(row) {
this.form.list = []
index({contract_id: row.id, type: this.type, page_size: 9999, sort_name: 'id', sort_type: 'asc'}).then(r => {
this.form.list = r.data
this.form.contract_id = row.id
if (r.data.length == 0) {
this.addRow()
}
this.dialogFormVisible = true
}).catch(error => {})
},
showList(row) {
this.form.list = []
index({contract_id: row.id, type: this.type, page_size: 9999, sort_name: 'id', sort_type: 'asc'}).then(r => {
this.form.list = r.data
this.dialogTableVisible = true
}).catch(error => {})
},
addRow() {
this.form.list.push({ name: '', date: '', upload_id: null, upload: {original_name: ''} })
},
save(idx) {
this.createRules(idx)
this.$refs['dataForm'].clearValidate()
this.$refs['dataForm'].validate()
let tempData = this.form.list[idx]
tempData.contract_id = this.form.contract_id
tempData.type = this.type
if (tempData.name && tempData.date && tempData.upload_id) {
if (tempData.id) {
save(tempData).then(r => { this.saved() }).catch(error => {})
} else {
store(tempData).then(r => { this.saved() }).catch(error => {})
}
}
},
saved() {
this.$message.success('保存成功')
},
handleDelete(row, index) {
this.$confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
if (!row.id) {
this.form.list.splice(index, 1)
return
}
destroy({ id: row.id }).then(response => {
this.$message.success('删除成功')
this.form.list.splice(index, 1)
}).catch(error => {})
}).catch(() => {})
},
dialogClose() {
this.getList()
},
}
}
</script>
<style scoped>
/deep/ .el-dialog__body{
padding:8px 20px;
}
/deep/ .el-col-5{
width:20%;
}
/deep/ .el-upload-list{
float:left;
line-height: 28px;
margin-right:15px;
}
/deep/ .el-upload-list__item:first-child{
margin-top:0;
}
</style>
<style lang="scss" scoped>
.ivu-btn{
margin-right:10px;
}
.table-tree{
margin-top:10px;
}
.rightCol{
display: flex;
justify-content: flex-end;
margin-left: 20px;
margin-bottom: 20px;
}
.v-text{
padding:8px 0;
border-bottom:1px solid #eee;
margin-bottom:20px;
.title{
color: #338de3;
}
}
.dataGroup{
padding:10px 0;
}
.grid-content{
border-radius:5px;
text-align:center;
background: #ffffff;
padding:15px 0;
p{
line-height: 1.8;
}
p:first-child{
font-weight:700;
font-size:16px;
}
}
.bg-org{
border:2px solid #EF6C24;
color:#EF6C24;
}
.bg-blue{
border:2px solid #0800FF;
color:#0800FF;
}
.bg-green{
border:2px solid #0F9700;
color:#0F9700;
}
.bg-black{
border:2px solid #333;
color:#333;
}
.bg-pink{
border:2px solid #FF0000;
color:#FF0000;
}
</style>

@ -0,0 +1,78 @@
<template>
<el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
<span v-if="item.redirect==='noRedirect'||index==levelList.length-1" class="no-redirect">{{ item.meta.title }}</span>
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
</el-breadcrumb-item>
</transition-group>
</el-breadcrumb>
</template>
<script>
import pathToRegexp from 'path-to-regexp'
export default {
data() {
return {
levelList: null
}
},
watch: {
$route() {
this.getBreadcrumb()
}
},
created() {
this.getBreadcrumb()
},
methods: {
getBreadcrumb() {
// only show routes with meta.title
let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
const first = matched[0]
if (!this.isDashboard(first)) {
matched = [{ path: '/dashboard', meta: { title: '系统首页' }}].concat(matched)
}
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
},
isDashboard(route) {
const name = route && route.name
if (!name) {
return false
}
return name.trim().toLocaleLowerCase() === '系统首页'.toLocaleLowerCase()
},
pathCompile(path) {
// To solve this problem https://github.com/PanJiaChen/vue-element-admin/issues/561
const { params } = this.$route
var toPath = pathToRegexp.compile(path)
return toPath(params)
},
handleLink(item) {
const { redirect, path } = item
if (redirect) {
this.$router.push(redirect)
return
}
this.$router.push(this.pathCompile(path))
}
}
}
</script>
<style lang="scss" scoped>
.app-breadcrumb.el-breadcrumb {
display: inline-block;
font-size: 14px;
line-height: 40px;
margin-left: 8px;
.no-redirect {
color: #97a8be;
cursor: text;
}
}
</style>

@ -0,0 +1,303 @@
<template>
<div class="app-container">
<div ref="lxHeader">
<LxHeader icon="md-apps" :text="typeLabel[type]" style="margin-bottom: 10px; border: 0px; margin-top: 15px">
<div slot="content" />
<slot>
<div>
<Input v-model="listQuery.keyword" style="width: 200px; margin-right: 10px" placeholder="关键字搜索" />
<Button type="primary" @click="handleFilter"></Button>
</div>
</slot>
</LxHeader>
</div>
<div class="table-tree">
<el-table :data="tableData" border default-expand-all class="v-table" style="width: 100%;margin-bottom: 20px;">
<el-table-column type="index" label="序号" width="50" />
<el-table-column label="项目名称" prop="name" width="400" />
<el-table-column label="文档数量" prop="item_builds_count" align="center" />
<el-table-column label="编辑人" align="center">
<template slot-scope="{row}"><span>{{ row.admin ? row.admin.name: '' }}</span></template>
</el-table-column>
<el-table-column label="最后编辑时间" prop="updated_at" align="center" />
<el-table-column fixed="right" width="250" label="操作" align="center">
<template slot-scope="{row}">
<Button type="primary" size="small" style="margin-left: 10px;" ghost @click="editList(row)"></Button>
<Button type="primary" size="small" style="margin-left: 10px;" ghost @click="showList(row)"></Button>
<!-- <Button type="error" size="small" style="margin-left: 10px;" ghost @click="dialogFormVisible = true">删除</Button> -->
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.page_size" @pagination="getList" />
</div>
<el-dialog :title="'管理文件'" :visible.sync="dialogFormVisible" width="80%" @close="dialogClose">
<div class="rightCol">
<Button type="primary" @click="addRow" size="small" ghost>新增文件</Button>
</div>
<el-form ref="dataForm" :model="form" :rules="rules">
<el-table border default-expand-all class="v-table" :data="form.list">
<el-table-column label="文件名称" prop="name" width="400">
<template slot-scope="scope">
<el-form-item :prop="'list.' + scope.$index + '.name'" style="margin-top: 15px;margin-bottom: 17px;">
<el-input v-model="scope.row.name" placeholder="文件名称" style="width:100%" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="获取时间" prop="date">
<template slot-scope="scope">
<el-form-item :prop="'list.' + scope.$index + '.date'" style="margin-top: 15px;margin-bottom: 17px;">
<el-date-picker v-model="scope.row.date" type="date" placeholder="选择日期" value-format="yyyy-MM-dd" style="width: 100%;" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="附件名称" prop="upload_id">
<template slot-scope="scope">
<el-form-item :prop="'list.' + scope.$index + '.upload_id'" style="margin-top: 15px;margin-bottom: 17px;">
<UploadFile v-if="dialogFormVisible" v-model="scope.row.upload_id" :file_name="scope.row.upload ? scope.row.upload.original_name : ''" />
</el-form-item>
</template>
</el-table-column>
<el-table-column width="150" label="操作" align="center">
<template slot-scope="scope">
<Button @click="save(scope.$index)" type="primary" size="small" ghost>保存</Button>
<Button type="error" size="small" ghost @click="handleDelete(scope.row, scope.$index)">删除</Button>
</template>
</el-table-column>
</el-table>
</el-form>
<div slot="footer" class="dialog-footer"></div>
</el-dialog>
<el-dialog :title="'查看文件'" :visible.sync="dialogTableVisible" width="80%">
<el-table border default-expand-all class="v-table" :data="form.list">
<el-table-column label="文件名称" prop="name" width="400">
<template slot-scope="scope">
{{scope.row.name}}
</template>
</el-table-column>
<el-table-column label="获取时间" prop="date">
<template slot-scope="scope">
{{scope.row.date}}
</template>
</el-table-column>
<el-table-column label="附件名称" prop="upload_id">
<template slot-scope="scope">
{{scope.row.upload ? scope.row.upload.original_name : ''}}
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<a v-if="scope.row.upload" :href="scope.row.upload.url" target="_blank"></a>
</template>
</el-table-column>
</el-table>
<div slot="footer" class="dialog-footer"></div>
</el-dialog>
</div>
</template>
<script>
import LxHeader from '@/components/LxHeader/index.vue'
import UploadFile from '@/components/Upload/file.vue'
import Pagination from '@/components/Pagination'
import { contractIndex, index, show, destroy, store, save } from '../../api/build.js'
export default {
name: 'BuildPage',
components: {
LxHeader, UploadFile, Pagination
},
props: {
type: {
type: Number,
default: 1
}
},
data() {
return {
typeLabel: {1: '合同文件管理', 2: '合同登记', 3: '履约验收'},
listQuery: {
page: 1,
page_size: 10
},
total: 0,
tableData: [],
rules: {
list:[]
},
dialogFormVisible:false,
dialogTableVisible: false,
form: {
id: undefined,
contract_id: '',
list: []
},
bcglXiangXiList: [],
checkedDetail: [],
formLabelWidth: '120px',
search:''
}
},
created() {
this.getList()
},
methods: {
getList() {
this.listQuery.type = this.type
contractIndex(this.listQuery).then(response => {
this.tableData = response.data
this.total = response.total
}).catch(error => {})
},
handleFilter() {
this.listQuery.page = 1
this.getList()
},
createRules(idx) {
let ruleList = []
for (let i = 0; i < this.form.list.length; i++) {
let required = i == idx ? true : false
ruleList.push({
name: { required: required, message: '请输入文件名称', trigger: 'change' },
date: { required: required, message: '请选择获取时间', trigger: 'change' },
upload_id: { required: required, message: '请上传文件', trigger: 'change' },
})
}
this.rules = {list: ruleList}
},
editList(row) {
this.form.list = []
index({contract_id: row.id, type: this.type, page_size: 9999, sort_name: 'id', sort_type: 'asc'}).then(r => {
this.form.list = r.data
this.form.contract_id = row.id
if (r.data.length == 0) {
this.addRow()
}
this.dialogFormVisible = true
}).catch(error => {})
},
showList(row) {
this.form.list = []
index({contract_id: row.id, type: this.type, page_size: 9999, sort_name: 'id', sort_type: 'asc'}).then(r => {
this.form.list = r.data
this.dialogTableVisible = true
}).catch(error => {})
},
addRow() {
this.form.list.push({ name: '', date: '', upload_id: null, upload: {original_name: ''} })
},
save(idx) {
this.createRules(idx)
this.$refs['dataForm'].clearValidate()
this.$refs['dataForm'].validate()
let tempData = this.form.list[idx]
tempData.contract_id = this.form.contract_id
tempData.type = this.type
if (tempData.name && tempData.date && tempData.upload_id) {
if (tempData.id) {
save(tempData).then(r => { this.saved() }).catch(error => {})
} else {
store(tempData).then(r => { this.saved() }).catch(error => {})
}
}
},
saved() {
this.$message.success('保存成功')
},
handleDelete(row, index) {
this.$confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
if (!row.id) {
this.form.list.splice(index, 1)
return
}
destroy({ id: row.id }).then(response => {
this.$message.success('删除成功')
this.form.list.splice(index, 1)
}).catch(error => {})
}).catch(() => {})
},
dialogClose() {
this.getList()
},
}
}
</script>
<style scoped>
/deep/ .el-dialog__body{
padding:8px 20px;
}
/deep/ .el-col-5{
width:20%;
}
/deep/ .el-upload-list{
float:left;
line-height: 28px;
margin-right:15px;
}
/deep/ .el-upload-list__item:first-child{
margin-top:0;
}
</style>
<style lang="scss" scoped>
.ivu-btn{
margin-right:10px;
}
.table-tree{
margin-top:10px;
}
.rightCol{
display: flex;
justify-content: flex-end;
margin-left: 20px;
margin-bottom: 20px;
}
.v-text{
padding:8px 0;
border-bottom:1px solid #eee;
margin-bottom:20px;
.title{
color: #338de3;
}
}
.dataGroup{
padding:10px 0;
}
.grid-content{
border-radius:5px;
text-align:center;
background: #ffffff;
padding:15px 0;
p{
line-height: 1.8;
}
p:first-child{
font-weight:700;
font-size:16px;
}
}
.bg-org{
border:2px solid #EF6C24;
color:#EF6C24;
}
.bg-blue{
border:2px solid #0800FF;
color:#0800FF;
}
.bg-green{
border:2px solid #0F9700;
color:#0F9700;
}
.bg-black{
border:2px solid #333;
color:#333;
}
.bg-pink{
border:2px solid #FF0000;
color:#FF0000;
}
</style>

@ -0,0 +1,239 @@
<template>
<div class="app-container">
<div ref="lxHeader">
<LxHeader icon="md-apps" :text="typeLabel[type]" style="margin-bottom: 10px; border: 0px; margin-top: 15px">
<div slot="content" />
<slot>
<div>
<!-- <Select v-model="listQuery.item_type" style="width:200px; margin-right: 10px" placeholder="项目属性">
<Option v-for="(i, idx) in []" :key="idx" :value="i.id">{{ i.name }}</Option>
</Select> -->
<Input v-model="listQuery.keyword" style="width: 200px; margin-right: 10px" placeholder="关键字搜索" />
<Button type="primary" @click="handleFilter"></Button>
<Button type="primary" @click="handleUpdate(null)"></Button>
</div>
</slot>
</LxHeader>
</div>
<div class="table-tree">
<el-table :data="list" border default-expand-all class="v-table" style="width: 100%;margin-bottom: 20px;">
<el-table-column type="index" label="序号" width="50" />
<el-table-column label="项目名称" align="center">
<template slot-scope="{row}"><span>{{ row.item ? row.item.name: '' }}</span></template>
</el-table-column>
<el-table-column label="项目属性" align="center">
<template slot-scope="{row}"><span>{{ row.item ? row.item.name: '' }}</span></template>
</el-table-column>
<el-table-column label="文件名称" prop="name" align="center" />
<el-table-column label="验收日期" prop="check_date" align="center" />
<el-table-column label="附件" align="center">
<template slot-scope="{row}"><a v-if="row.upload" :href="row.upload.url" target="_blank">{{ row.upload.original_name }}</a></template>
</el-table-column>
<el-table-column fixed="right" width="250" label="操作" align="center">
<template slot-scope="{row, $index}">
<Button type="error" size="small" style="margin-left: 10px;" ghost @click="handleDelete(row, $index)">删除</Button>
<Button type="primary" size="small" style="margin-left: 10px;" ghost @click="handleUpdate(row)"></Button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.page_size" @pagination="getList" />
</div>
<el-dialog :title="form.id ? '编辑项目' : '新增项目'" :visible.sync="dialogFormVisible" width="45%">
<el-form ref="dataForm" :model="form" label-position="right" :label-width="formLabelWidth">
<el-form-item label="项目名称" prop="name" :rules="{ required: true, message: '请输入项目名称', trigger: 'change' }">
<el-select v-model="form.item_id" placeholder="请选择" style="width:100%">
<el-option v-for="(i, idx) in itemList" :key="idx" :label="i.name" :value="i.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="文件名称" prop="name" :rules="{ required: true, message: '请输入文件名称', trigger: 'change' }">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="验收日期" prop="check_date" :rules="{ required: true, message: '请选择验收日期', trigger: 'change' }">
<el-date-picker v-model="form.check_date" type="date" placeholder="选择日期" value-format="yyyy-MM-dd" style="width: 100%;" />
</el-form-item>
<el-form-item label="附件" prop="upload_id" :rules="{ required: true, message: '请上传附件', trigger: 'change' }">
<UploadFile v-if="dialogFormVisible" v-model="form.upload_id" :file_name="form.upload ? form.upload.original_name : ''" />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false"> </el-button>
<el-button type="primary" @click="save"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import LxHeader from '@/components/LxHeader/index.vue'
import UploadFile from '@/components/Upload/file.vue'
import Pagination from '@/components/Pagination'
import { getList } from '../../api/item.js'
import { itemIndex, index, show, destroy, store, save } from '../../api/end-check.js'
export default {
name: 'EndCheck',
components: {
LxHeader, UploadFile, Pagination
},
props: {
type: {
type: Number,
default: 1
}
},
data() {
return {
itemList: [],
typeLabel: {1: '竣工验收', 2: '项目结算', 3: '项目决算'},
list: [],
total: 0,
listQuery: {
page: 1,
page_size: 10
},
form: {
id: undefined,
upload_id: null,
},
dialogFormVisible:false,
formLabelWidth: '120px',
search:''
}
},
created() {
this.getList()
getList({}).then(r => { this.itemList = r.list }).catch(error => {})
},
methods: {
getList() {
this.listQuery.type = this.type
index(this.listQuery).then(response => {
this.list = response.data
this.total = response.total
}).catch(error => {})
},
handleFilter() {
this.listQuery.page = 1
this.getList()
},
handleUpdate(row) {
if (row) {
show({id: row.id}).then(r => {
this.form = r
this.showDialog()
}).catch(error => {})
} else {
this.form = { id: '' }
this.showDialog()
}
},
showDialog() {
this.dialogFormVisible = true
this.$nextTick(() => { this.$refs['dataForm'].clearValidate() })
},
save() {
this.$refs['dataForm'].validate((valid) => {
if (valid) {
const tempData = Object.assign({}, this.form)
tempData.stage = this.type
if (tempData.id) {
save(tempData).then(r => { this.saved() }).catch(error => {})
} else {
store(tempData).then(r => { this.saved() }).catch(error => {})
}
}
})
},
saved() {
this.handleFilter()
this.dialogFormVisible = false
this.$message.success('保存成功')
},
handleDelete(row, index) {
this.$confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
destroy({id: row.id}).then(response => {
this.$message.success('删除成功')
this.getList()
}).catch(error => {})
}).catch(() => {});
},
}
}
</script>
<style scoped>
/deep/ .el-dialog__body{
padding:8px 20px;
}
/deep/ .el-col-5{
width:20%;
}
/deep/ .el-upload-list{
float:left;
line-height: 28px;
margin-right:15px;
}
/deep/ .el-upload-list__item:first-child{
margin-top:0;
}
</style>
<style lang="scss" scoped>
.ivu-btn{
margin-right:10px;
}
.table-tree{
margin-top:10px;
}
.rightCol{
display: flex;
justify-content: flex-end;
margin-left: 20px;
margin-bottom: 20px;
}
.v-text{
padding:8px 0;
border-bottom:1px solid #eee;
margin-bottom:20px;
.title{
color: #338de3;
}
}
.dataGroup{
padding:10px 0;
}
.grid-content{
border-radius:5px;
text-align:center;
background: #ffffff;
padding:15px 0;
p{
line-height: 1.8;
}
p:first-child{
font-weight:700;
font-size:16px;
}
}
.bg-org{
border:2px solid #EF6C24;
color:#EF6C24;
}
.bg-blue{
border:2px solid #0800FF;
color:#0800FF;
}
.bg-green{
border:2px solid #0F9700;
color:#0F9700;
}
.bg-black{
border:2px solid #333;
color:#333;
}
.bg-pink{
border:2px solid #FF0000;
color:#FF0000;
}
</style>

@ -0,0 +1,65 @@
<template>
<div style="padding: 0 15px;" @click="toggleClick">
<svg :class="{'is-active-hamburger':isActive}" class="hamburger" viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg" width="64" height="64">
<path
d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
</svg>
<transition name="fade">
<span class="hamburgertxt" v-if="isActive"></span>
</transition>
</div>
</template>
<script>
export default {
name: 'Hamburger',
props: {
isActive: {
type: Boolean,
default: false
}
},
methods: {
toggleClick() {
this.$emit('toggleClick')
}
}
}
</script>
<style scoped>
.hamburger {
display: inline-block;
vertical-align: middle;
width: 20px;
height: 20px;
}
.hamburgertxt {
color: #606266;
font-size: 16px;
margin-left: 0.625rem;
}
.hamburger.is-active-hamburger {
transform: rotate(180deg);
background-color: none !important;
}
.fade-enter {
opacity: 0;
}
.fade-enter-active {
transition: opacity .1s;
}
.fade-leave-to {
opacity: 0;
}
.fade-leave-active {
transition: opacity .1s;
}
</style>

@ -0,0 +1,53 @@
<script>
export default {
name: 'MenuItem',
functional: true,
props: {
icon: {
type: String,
default: ''
},
title: {
type: String,
default: ''
}
},
render(h, context) {
const { icon, title } = context.props
const vnodes = []
if (icon) {
if (icon.includes('el-icon')) {
vnodes.push(<i class={[icon, 'sub-el-icon']} />)
}
else if(icon.includes("viewicon")){
let _icon=icon.split("/")[1];
vnodes.push(<Icon type={_icon} class='sub-el-icon' size="18" />)
}
else if(icon.includes("iconfont")){
let _icon=icon.split("/")[1]+" iconfont";
vnodes.push(<Icon custom={_icon} class='sub-el-icon' size="18" />)
}
else {
vnodes.push(<svg-icon icon-class={icon}/>)
}
}else {
var _icon="el-icon-menu";
vnodes.push(<i class={[_icon, 'sub-el-icon']} />)
}
if (title) {
vnodes.push(<span slot='title'>{(title)}</span>)
}
return vnodes
}
}
</script>
<style scoped>
.sub-el-icon {
color: currentColor;
width: 1em;
height: 1em;
}
</style>

@ -0,0 +1,93 @@
<template>
<div class="v-header">
<div class="v-left-text">
<item :icon="iconImg" />
<span style="margin-left: 3px;">{{title}}</span>
</div>
<div class="content">
<slot name="content"></slot>
</div>
<!-- <div class="v-right-content">
<slot></slot>
</div> -->
<div class="selerchcontent">
<slot></slot>
</div>
</div>
</template>
<script>
import Item from './Item'
export default {
components: {
Item
},
props: {
icon: {
type: String,
default: ""
},
text: {
type: String,
default: "未定义名称"
},
custom:{
type: Boolean,
default: false
},
},
computed: {
title() {
if(this.custom){
return this.text;
}else
return this.$route.meta.title;
},
iconImg() {
if(this.custom){
return this.icon;
}else
return this.$route.meta.icon;
}
}
};
</script>
<style lang="less" scoped>
.v-header {
display: flex;
border-bottom: 1px solid #dcdee2;
flex-direction: column;
.v-left-text {
margin-top: 3px;
padding-bottom: 6px;
// padding-top: 10px;
font-weight: bold;
font-size: 15px;
color: #338de3;
white-space: nowrap;
border-bottom: 2px solid #338de3;
>span {
position: relative;
// top: 2px;
}
}
.content {
line-height: 25px;
padding-left: 10px;
padding: 6px 0 0 10px;
}
.v-right-content {
flex: 1;
}
.selerchcontent {
flex: 1;
}
}
</style>

@ -0,0 +1,101 @@
<template>
<div :class="{'hidden':hidden}" class="pagination-container">
<el-pagination
:background="background"
:current-page.sync="currentPage"
:page-size.sync="pageSize"
:layout="layout"
:page-sizes="pageSizes"
:total="total"
v-bind="$attrs"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</template>
<script>
import { scrollTo } from '@/utils/scroll-to'
export default {
name: 'Pagination',
props: {
total: {
required: true,
type: Number
},
page: {
type: Number,
default: 1
},
limit: {
type: Number,
default: 20
},
pageSizes: {
type: Array,
default() {
return [10, 20, 30, 50]
}
},
layout: {
type: String,
default: 'total, prev, pager, next, jumper'
},
background: {
type: Boolean,
default: true
},
autoScroll: {
type: Boolean,
default: true
},
hidden: {
type: Boolean,
default: false
}
},
computed: {
currentPage: {
get() {
return this.page
},
set(val) {
this.$emit('update:page', val)
}
},
pageSize: {
get() {
return this.limit
},
set(val) {
this.$emit('update:limit', val)
}
}
},
methods: {
handleSizeChange(val) {
this.$emit('pagination', { page: this.currentPage, limit: val })
if (this.autoScroll) {
scrollTo(0, 800)
}
},
handleCurrentChange(val) {
this.$emit('pagination', { page: val, limit: this.pageSize })
if (this.autoScroll) {
scrollTo(0, 800)
}
}
}
}
</script>
<style scoped>
.pagination-container {
text-align:right;
padding: 32px 16px;
}
.pagination-container.hidden {
display: none;
}
</style>

@ -0,0 +1,303 @@
<template>
<div class="app-container">
<div ref="lxHeader">
<LxHeader icon="md-apps" :text="typeLabel[type]" style="margin-bottom: 10px; border: 0px; margin-top: 15px">
<div slot="content" />
<slot>
<div>
<Input v-model="listQuery.keyword" style="width: 200px; margin-right: 10px" placeholder="关键字搜索" />
<Button type="primary" @click="handleFilter"></Button>
</div>
</slot>
</LxHeader>
</div>
<div class="table-tree">
<el-table :data="tableData" border default-expand-all class="v-table" style="width: 100%;margin-bottom: 20px;">
<el-table-column type="index" label="序号" width="50" />
<el-table-column label="项目名称" prop="name" width="400" />
<el-table-column label="文档数量" prop="prepares_count" align="center" />
<el-table-column label="编辑人" align="center">
<template slot-scope="{row}"><span>{{ row.admin ? row.admin.name: '' }}</span></template>
</el-table-column>
<el-table-column label="最后编辑时间" width="160" prop="updated_at" align="center" />
<el-table-column fixed="right" width="250" label="操作" align="center">
<template slot-scope="{row}">
<Button type="primary" size="small" style="margin-left: 10px;" ghost @click="editList(row)"></Button>
<Button type="primary" size="small" style="margin-left: 10px;" ghost @click="showList(row)"></Button>
<!-- <Button type="error" size="small" style="margin-left: 10px;" ghost @click="dialogFormVisible = true">删除</Button> -->
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="listQuery.page" :limit.sync="listQuery.page_size" @pagination="getList" />
</div>
<el-dialog :title="'管理文件'" :visible.sync="dialogFormVisible" width="80%" @close="dialogClose">
<div class="rightCol">
<Button type="primary" @click="addRow" size="small" ghost>新增文件</Button>
</div>
<el-form ref="dataForm" :model="form" :rules="rules">
<el-table border default-expand-all class="v-table" :data="form.list">
<el-table-column label="文件名称" prop="name" width="400">
<template slot-scope="scope">
<el-form-item :prop="'list.' + scope.$index + '.name'" style="margin-top: 15px;margin-bottom: 17px;">
<el-input v-model="scope.row.name" placeholder="文件名称" style="width:100%" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="获取时间" prop="date">
<template slot-scope="scope">
<el-form-item :prop="'list.' + scope.$index + '.date'" style="margin-top: 15px;margin-bottom: 17px;">
<el-date-picker v-model="scope.row.date" type="date" placeholder="选择日期" value-format="yyyy-MM-dd" style="width: 100%;" />
</el-form-item>
</template>
</el-table-column>
<el-table-column label="附件名称" prop="upload_id">
<template slot-scope="scope">
<el-form-item :prop="'list.' + scope.$index + '.upload_id'" style="margin-top: 15px;margin-bottom: 17px;">
<UploadFile v-if="dialogFormVisible" v-model="scope.row.upload_id" :file_name="scope.row.upload ? scope.row.upload.original_name : ''" />
</el-form-item>
</template>
</el-table-column>
<el-table-column width="150" label="操作" align="center">
<template slot-scope="scope">
<Button @click="save(scope.$index)" type="primary" size="small" ghost>保存</Button>
<Button type="error" size="small" ghost @click="handleDelete(scope.row, scope.$index)">删除</Button>
</template>
</el-table-column>
</el-table>
</el-form>
<div slot="footer" class="dialog-footer"></div>
</el-dialog>
<el-dialog :title="'查看文件'" :visible.sync="dialogTableVisible" width="80%">
<el-table border default-expand-all class="v-table" :data="form.list">
<el-table-column label="文件名称" prop="name" width="400">
<template slot-scope="scope">
{{scope.row.name}}
</template>
</el-table-column>
<el-table-column label="获取时间" prop="date">
<template slot-scope="scope">
{{scope.row.date}}
</template>
</el-table-column>
<el-table-column label="附件名称" prop="upload_id">
<template slot-scope="scope">
{{scope.row.upload ? scope.row.upload.original_name : ''}}
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<a v-if="scope.row.upload" :href="scope.row.upload.url" target="_blank"></a>
</template>
</el-table-column>
</el-table>
<div slot="footer" class="dialog-footer"></div>
</el-dialog>
</div>
</template>
<script>
import LxHeader from '@/components/LxHeader/index.vue'
import UploadFile from '@/components/Upload/file.vue'
import Pagination from '@/components/Pagination'
import { itemIndex, index, show, destroy, store, save } from '../../api/prepares.js'
export default {
name: 'Prepare',
components: {
LxHeader, UploadFile, Pagination
},
props: {
type: {
type: Number,
default: 1
}
},
data() {
return {
typeLabel: {1: '立项阶段', 2: '可研阶段', 3: '初设阶段', 4: '概算', 5: '预算', 6: '其他'},
listQuery: {
page: 1,
page_size: 10
},
total: 0,
tableData: [],
rules: {
list:[]
},
dialogFormVisible:false,
dialogTableVisible: false,
form: {
id: undefined,
item_id: '',
list: []
},
bcglXiangXiList: [],
checkedDetail: [],
formLabelWidth: '120px',
search:''
}
},
created() {
this.getList()
},
methods: {
getList() {
this.listQuery.type = this.type
itemIndex(this.listQuery).then(response => {
this.tableData = response.data
this.total = response.total
}).catch(error => {})
},
handleFilter() {
this.listQuery.page = 1
this.getList()
},
createRules(idx) {
let ruleList = []
for (let i = 0; i < this.form.list.length; i++) {
let required = i == idx ? true : false
ruleList.push({
name: { required: required, message: '请输入文件名称', trigger: 'change' },
date: { required: required, message: '请选择获取时间', trigger: 'change' },
upload_id: { required: required, message: '请上传文件', trigger: 'change' },
})
}
this.rules = {list: ruleList}
},
editList(row) {
this.form.list = []
index({item_id: row.id, type: this.type, page_size: 9999, sort_name: 'id', sort_type: 'asc'}).then(r => {
this.form.list = r.data
this.form.item_id = row.id
if (r.data.length == 0) {
this.addRow()
}
this.dialogFormVisible = true
}).catch(error => {})
},
showList(row) {
this.form.list = []
index({item_id: row.id, type: this.type, page_size: 9999, sort_name: 'id', sort_type: 'asc'}).then(r => {
this.form.list = r.data
this.dialogTableVisible = true
}).catch(error => {})
},
addRow() {
this.form.list.push({ name: '', date: '', upload_id: null, upload: {original_name: ''} })
},
save(idx) {
this.createRules(idx)
this.$refs['dataForm'].clearValidate()
this.$refs['dataForm'].validate()
let tempData = this.form.list[idx]
tempData.item_id = this.form.item_id
tempData.type = this.type
if (tempData.name && tempData.date && tempData.upload_id) {
if (tempData.id) {
save(tempData).then(r => { this.saved() }).catch(error => {})
} else {
store(tempData).then(r => { this.saved() }).catch(error => {})
}
}
},
saved() {
this.$message.success('保存成功')
},
handleDelete(row, index) {
this.$confirm('确定要删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
if (!row.id) {
this.form.list.splice(index, 1)
return
}
destroy({ id: row.id }).then(response => {
this.$message.success('删除成功')
this.form.list.splice(index, 1)
}).catch(error => {})
}).catch(() => {})
},
dialogClose() {
this.getList()
},
}
}
</script>
<style scoped>
/deep/ .el-dialog__body{
padding:8px 20px;
}
/deep/ .el-col-5{
width:20%;
}
/deep/ .el-upload-list{
float:left;
line-height: 28px;
margin-right:15px;
}
/deep/ .el-upload-list__item:first-child{
margin-top:0;
}
</style>
<style lang="scss" scoped>
.ivu-btn{
margin-right:10px;
}
.table-tree{
margin-top:10px;
}
.rightCol{
display: flex;
justify-content: flex-end;
margin-left: 20px;
margin-bottom: 20px;
}
.v-text{
padding:8px 0;
border-bottom:1px solid #eee;
margin-bottom:20px;
.title{
color: #338de3;
}
}
.dataGroup{
padding:10px 0;
}
.grid-content{
border-radius:5px;
text-align:center;
background: #ffffff;
padding:15px 0;
p{
line-height: 1.8;
}
p:first-child{
font-weight:700;
font-size:16px;
}
}
.bg-org{
border:2px solid #EF6C24;
color:#EF6C24;
}
.bg-blue{
border:2px solid #0800FF;
color:#0800FF;
}
.bg-green{
border:2px solid #0F9700;
color:#0F9700;
}
.bg-black{
border:2px solid #333;
color:#333;
}
.bg-pink{
border:2px solid #FF0000;
color:#FF0000;
}
</style>

@ -0,0 +1,62 @@
<template>
<div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
<svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
<use :xlink:href="iconName" />
</svg>
</template>
<script>
// doc: https://panjiachen.github.io/vue-element-admin-site/feature/component/svg-icon.html#usage
import { isExternal } from '@/utils/validate'
export default {
name: 'SvgIcon',
props: {
iconClass: {
type: String,
required: true
},
className: {
type: String,
default: ''
}
},
computed: {
isExternal() {
return isExternal(this.iconClass)
},
iconName() {
return `#icon-${this.iconClass}`
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className
} else {
return 'svg-icon'
}
},
styleExternalIcon() {
return {
mask: `url(${this.iconClass}) no-repeat 50% 50%`,
'-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
}
}
}
}
</script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
.svg-external-icon {
background-color: currentColor;
mask-size: cover!important;
display: inline-block;
}
</style>

@ -0,0 +1,68 @@
<template>
<el-upload
class="upload-demo"
:action="uploadUrl"
:before-upload="beforeFileUpload"
:on-remove="picDel"
:on-success="picSuccess"
:headers="headers"
:limit="1"
:file-list="file_list">
<el-button size="small" type="primary" v-if="!value">{{loading ? '...' : ''}}</el-button>
</el-upload>
</template>
<script>
import { getToken } from '@/utils/auth'
export default {
name: 'UploadFile',
props: {
value: {
type: Number,
default: null
},
file_name: {
type: String,
default: ''
}
},
data() {
return {
headers: {},
uploadUrl: process.env.VUE_APP_BASE_API + '/api/admin/upload-file',
loading: false,
picUrl: '',
picDialog: false,
file_list: [],
}
},
created() {
this.headers = {Authorization: "Bearer " + getToken()}
this.file_list = this.value ? [{name: this.file_name, url: this.value.toString()}] : []
},
methods: {
beforeFileUpload(file) {
this.loading = true
return true
},
picSuccess: function (res, file, fileList) {
this.loading = false
if (res.errcode) {
alert(res.errmsg)
return
}
fileList[fileList.length - 1].url = res.id.toString();
this.$emit('input', res.id)
},
picDel: function(file, fileList) {
this.$emit('input', null)
},
}
}
</script>
<style lang="scss" scoped>
.upload-demo {
display: flex;
}
</style>

@ -0,0 +1,312 @@
<script>
export default {
props:{
type:{
type:String,
default:"normal"
//"normal" "form"
},
width:{
type:Number,
default:55
},
isShow:{
type:Boolean,
default:false
},
title:{
type:String,
default: ''
},
form:{
type:Object,
default:()=>{
return {}
}
},
rules:{
type:Object,
default:()=>{
return {}
}
},
okText:{
type:String
}
},
data() {
return {
}
},
methods: {
footerRender(){
if(this.type === 'form'){
return (
<div>
<Button ghost type="primary" on-click={this.reset}>重置</Button>
<Button type="primary" on-click={this.submit}>{this.okText || '确定'}</Button>
</div>
)
}
if(this.type === 'normal'){
return (
<div>
<Button ghost type="primary" on-click={()=>{this.$emit('update:isShow',false)}}>取消</Button>
<Button type="primary" on-click={()=>{this.$emit('on-ok')}}>{this.okText || '确定'}</Button>
</div>
)
}
},
showChange(e){
this.$emit('update:isShow',e)
},
validate(){
return new Promise((resolve,reject)=>{
this.$refs['elForm'].validate().then(res=>{
if(res){
resolve(res)
}else{
reject(res)
}
}).catch(err=>{
reject(err)
this.$Message.warning({
content:'请填写完整信息',
duration:1
})
})
})
},
reset(){
this.$emit('reset')
if(this.type === 'normal'){
return
}
this.$refs['elForm'].resetFields()
},
submit(){
if(this.type === 'normal'){
return
}
this.$refs['elForm'].validate().then(res=>{
if(res)this.$emit('submit')
}).catch(err=>{
this.$Message.warning({
content:'请填写完整信息',
duration:1
})
})
},
okClick(){
this.$emit('on-ok')
},
clearValidate(){
this.$refs['elForm'].clearValidate()
}
},
watch:{
isShow(val){
if(!val && this.type === 'form'){
this.reset()
}
}
},
render(h) {
/*
type='form' slot:
extraFormTop
extraFormBottom
{表单名}
type='normal' slot:
normalContent
footer slot:
footerContent
*/
const {okText,okClick,footerRender,width,type,$scopedSlots,rules,form,showChange,isShow,title} = this
return (
<Modal
ok-text={okText}
class-name={'vertical-center-modal'}
width={width}
title={title}
value={isShow}
on={{['on-visible-change']:showChange,['on-ok']:okClick}}
scopedSlots={{
default(){
if(type === "form"){
let formItems = []
for(let key in form){
if(form.hasOwnProperty(key) && $scopedSlots[key]){
formItems.push(
<el-form-item
prop={key}>
{eval(`{$scopedSlots.${key} ? $scopedSlots.${key}() : ''}`)}
</el-form-item>
)
}
}
return (
<el-form
style={title.length === 0 ? {'margin-top':'32px'} : {}}
ref="elForm"
props={{
model:form,
rules
}}>
{$scopedSlots.extraFormTop ? $scopedSlots.extraFormTop() : ''}
<div
style={
{
'display':'flex',
'align-items':'center',
'flex-wrap':'wrap',
'min-width':'380px'
}
}>
{
formItems.map(item=>{
return item
})
}
</div>
{$scopedSlots.extraFormBottom ? $scopedSlots.extraFormBottom() : ''}
</el-form>
)
}else{
return (
<div
style={title.length === 0 ? {'margin-top':'32px'} : {}}>
{$scopedSlots.default ? $scopedSlots.default() : ''}
</div>
)
}
},
header(){
if($scopedSlots.headerContent){
return $scopedSlots.headerContent()
}
},
footer(){
{
if(type === 'form' || type === 'normal') return ($scopedSlots.footerContent ? $scopedSlots.footerContent() : footerRender())
}
}
}}>
</Modal>
)
}
}
</script>
<style lang="scss">
.xy-table-item-label{
width: 140px;
text-align: right;
}
.xy-table-item-min{
position: relative;
&::after{
z-index: 1;
position: absolute;
right: 0;
top: 0;
content:'(分钟)'
}
::v-deep .el-input__clear{
position: relative;
right: 46px;
z-index: 2;
}
}
.xy-table-item-price{
position: relative;
&::after{
z-index: 1;
position: absolute;
right: 0;
top: 0;
content:'(元)'
}
::v-deep .el-input__clear{
padding: 0 6px;
position: relative;
right: 30px;
z-index: 2;
}
}
.xy-table-item-price-wan{
position: relative;
&::after{
padding: 0 6px;
position: absolute;
right: 0;
top: 0;
content:'(万元)'
}
::v-deep .el-input__clear{
position: relative;
right: 46px;
z-index: 2;
}
}
.xy-table-item-price-percent{
position: relative;
&::after{
padding: 0 6px;
position: absolute;
right: 0;
top: 0;
content:'%'
}
::v-deep .el-input__clear{
position: relative;
right: 46px;
z-index: 2;
}
}
.vertical-center-modal{
display: flex;
align-items: center;
justify-content: center;
.ivu-modal{
top: 0;
}
}
.ivu-modal-body{
max-height: 65vh !important;
min-height: 300px;
overflow: scroll;
}
.xy-table-item{
display: flex;
align-items: center;
padding-right: 80px;
&-label{
padding: 0 20px;
}
&-content{
}
}
.el-form-item{
flex-shrink: 0;
flex-basis: 50%;
}
.el-form-item__error{
white-space: nowrap;
word-break: keep-all !important;
top: 100% !important;
left: calc(100% - 80px) !important;
transform: translateX(-100%);
}
</style>

@ -0,0 +1,225 @@
<script>
export default {
props:{
},
data() {
return {
isShowSelector:false,
width:300,
left:0,
}
},
methods: {
initStyle(){
const header = document.querySelector('.v-header').getBoundingClientRect()
this.width = header.width
const select = document.querySelector('.xy-selectors').getBoundingClientRect()
this.left = select.left - header.left
this.$forceUpdate()
},
showSelector(){
this.isShowSelector =! this.isShowSelector
},
renderAdd(){
return (<Button
class="xy-selectors-btn__item"
type='primary'
on={{
['click']:()=>this.$emit('add')
}}>
新增
</Button>)
}
},
mounted() {
this.initStyle()
window.onresize = this.initStyle
},
destroyed() {
window.onresize = null
},
render(h) {
let {isShowSelector,showSelector,$scopedSlots} = this
return (
<div class="xy-selectors">
<div v-show={isShowSelector} style={{'width':'100vw','height':'100vh','position':'fixed','top':0,'left':0,'z-index':1}} on={{['click']:()=>this.isShowSelector = false}}></div>
<Button
icon='md-arrow-dropdown'
ghost={isShowSelector}
class={isShowSelector ? 'xy-selectors-btn__item xy-selectors-btn__select xy-selectors-btn-active__select' : 'xy-selectors-btn__item xy-selectors-btn__select"'}
type='primary'
on={{
['click']:() => showSelector()
}}>
高级搜索
</Button>
<transition
enter-active-class="scale-enter"
leave-to-class="scale-leave">
<div class="xy-selectors-card" style={{'width':`${this.width}px`,'left':`-${this.left}px`}} v-show={isShowSelector}>
<Icon
type="md-close"
size={24}
class="xy-selectors-card__close"
on={{['click']:()=> {
this.isShowSelector = false
}}}/>
<div class="xy-selectors-card-content">
{$scopedSlots?.selected ? $scopedSlots?.selected() : ''}
{$scopedSlots?.default ? $scopedSlots?.default() : ''}
</div>
<div class="xy-selectors-card-btn">
<Button
class="xy-selectors-card-btn__item"
type="primary"
ghost={true}
on={{['click']:()=>{this.$emit('reset')}}}>重置</Button>
<Button
class="xy-selectors-card-btn__item"
type="primary"
on={{['click']:()=>{
this.$emit('search')
this.isShowSelector = false
}}}>搜索</Button>
</div>
</div>
</transition>
</div>
)
}
}
</script>
<style lang="scss">
.xy-selectors{
&__item{
display: flex;
align-items: center;
padding: 8px 20px;
&--name{
width: 100px;
margin-right: 20px;
}
}
}
</style>
<style scoped lang="scss">
@import "../../styles/variables";
.xy-selectors{
position: relative;
&-btn{
//display: flex;
//justify-content: flex-start;
//align-items: center;
&__item{
//margin-right: 10px;
}
&__select{
}
&-active__select{
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
border-bottom: none;
box-shadow: 0 2px 10px 1px $primaryColor;
position: relative;
&::after{
content:'';
height: 12px;
background: linear-gradient(to top,rgba(235,238,244,0.98) 40%,#0000 80%,#0000);
z-index: 5;
position: absolute;
bottom: -4px;
left: 0;
right: 0;
}
}
}
&-card{
//width: 100%;
background: rgba(239,242,250,0.95);
border-radius: 0 4px 4px 4px;
border: $primaryColor solid 1px;
box-shadow: 0 4px 10px 1px $primaryColor;
z-index: 4;
position: absolute;
&__close{
cursor: pointer;
position: absolute;
top: 6px;
right: 14px;
}
&-content{
min-height: 50px;
max-height: 400px;
overflow: scroll;
padding: 36px 0 44px 8px;
&::-webkit-scrollbar-thumb{
background: $primaryColor;
}
}
&-btn{
width: 100%;
display: flex;
justify-content: space-evenly;
background: rgba(239,242,250,0.95);
position: absolute;
bottom: 0;
&__item{
width: 120px;
margin: 10px 0px;
}
}
}
}
.scale-enter{
transform-origin: 0 0%;
animation: scale-enter 0.5s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
}
@keyframes scale-enter {
from{
transform: scaleY(0);
opacity: 0;
}
to{
transform: scaleY(1);
opacity: 1;
}
}
.scale-leave{
transform-origin: 0 0%;
animation: scale-leave 0.5s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
}
@keyframes scale-leave {
from{
transform: scaleY(1);
opacity: 1;
}
to{
transform: scaleY(0);
opacity: 0;
}
}
</style>

@ -0,0 +1,350 @@
<script>
export default {
props:{
height:{
type:Number
},
list:{
type:Array,
default:()=> []
},
tableItem:{
type:Array,
default:()=> []
},
indent:{
type:Number,
default:18
},
expandRowKeys:{
type: Array,
default: ()=>[]
},
rowKey:{
type:[String,Function],
default:'id'
},
cellStyle:{
type:Function,
default:()=>{}
},
rowStyle:{
type:Function,
default:()=>{}
},
headerRowStyle:{
type:Function,
default:()=>{}
},
headerCellStyle:{
type:Function,
default:()=>{}
},
defaultExpandAll:{
type:Boolean,
default:true
},
showIndex:{
type:Boolean,
default:true
},
showHeader:{
type:Boolean,
default:true
},
showSummary:{
type:Boolean,
default:false
},
summaryMethod:{
type:Function
},
objectSpanMethod:{
type:Function
},
treeProps:{
type:Object,
default:()=> {
return { children: 'children', hasChildren: 'hasChildren' }
}
},
tableStyle:{
type:Object,
default:()=>{
return { width: '100%', marginBottom: "20px"}
}
},
btnWidth:{
type:Number,
default:190
}
},
data() {
return {
tableHeight: 0,
}
},
methods: {
initLoad() {
var that = this;
var clientHeight = document.documentElement.clientHeight
var lxHeader_height = 96.5; //
var paginationHeight = 37; //
var topHeight = 50; //
let tableHeight = clientHeight - lxHeader_height - topHeight - paginationHeight - 20;
that.tableHeight = tableHeight;
},
deleteClick(row){
this.$emit('delete',row)
},
editorClick(row){
this.$emit('editor',row)
},
selectClick(selection, row){
this.$emit('select',selection, row)
},
rowClick(selection, row){
this.$emit('rowClick',selection, row)
},
cellClick(row, column, cell){
this.$emit('cellClick',row, column, cell)
},
selectAllClick(selection){
this.$emit('select-all',selection)
},
selectChange(selection){
this.$emit('selection-change',selection)
},
createIndexRow(){
return <el-table-column type="index" align="center" fixed="left"></el-table-column>
},
toggleRowSelection(row){
this.$nextTick(()=>{
this.$refs.table.toggleRowSelection(row);
})
},
clearSelection(){
this.$refs.table.clearSelection();
},
toggleAllSelection(){
this.$refs.table.toggleAllSelection()
},
doLayout() {
this.$refs['table'].doLayout();
},
toggleRowExpansion(row,expanded){
this.$refs['table'].toggleRowExpansion(row,expanded)
},
getSelection(){
return this.$refs.table?.store?.states?.selection ?? []
},
},
created() {
this.initLoad()
},
mounted() {
},
render(h) {
let {rowKey,expandRowKeys,selectChange,selectAllClick,summaryMethod,cellClick,btnWidth,selectClick,height,createIndexRow,tableStyle,treeProps,showSummary,showHeader,$scopedSlots,showIndex,defaultExpandAll,headerCellStyle,headerRowStyle,rowStyle,cellStyle,indent,tableHeight,tableItem,list,deleteClick,editorClick,rowClick,objectSpanMethod} = this
return (
<div className="table-tree" style={{ 'position': 'relative' }}>
{tableItem && tableItem.length > 0 ?
(<el-table
ref="table"
size="small"
span-method={objectSpanMethod}
show-summary={showSummary}
show-header={showHeader}
summary-method={summaryMethod}
header-row-style={headerRowStyle}
header-cell-style={headerCellStyle}
row-style={rowStyle}
cell-style={cellStyle}
indent={indent}
data={list}
expand-row-keys={expandRowKeys}
height={height ?? tableHeight}
className="v-table"
style={tableStyle}
row-key={rowKey}
border={true}
default-expand-all={defaultExpandAll}
tree-props={treeProps}
fit={true}
on={{
['select']: selectClick,
['cell-click']: cellClick,
['select-all']: selectAllClick,
['selection-change']: selectChange,
['expand-change']: (row, expanded) => this.$emit('expand-change', { row, expanded })
}}
>
<el-table-column
fixed='left'
align='center'
width='60'
type='index'
>
</el-table-column>
{
tableItem.map((item, index) => {
//
if (item.customFn) {
return (
<el-table-column
fixed={item.fixed ?? false}
render-header={item.renderHeader ?? undefined}
align={item.align ?? 'center'}
sortable={item.sortable ?? false}
width={item.width ?? 'auto'}
min-width={item.minWidth ?? item.width}
header-align={item.headerAlign ?? 'center'}
label={item.label}
prop={item.prop}
scopedSlots={{
default(scope) {
return item.customFn(scope.row, scope)
}
}}
>
</el-table-column>
)
} else if (item.multiHd) {
//
return (
<el-table-column
render-header={item.renderHeader ?? undefined}
header-align={item.headerAlign ?? 'center'}
label={item.label}
>
{item.multiHd.map((item1, index1) => {
if (item1.customFn) {
return (
<el-table-column
render-header={item1.renderHeader ?? undefined}
align={item1.align ?? 'center'}
header-align={item1.headerAlign ?? 'center'}
label={item1.label}
width={item1.width ?? 'auto'}
min-width={item1.minWidth ?? item1.width}
sortable={item1.sortable ?? false}
prop={`${item.Fprop ? item.Fprop + '.' : ''}${item1.prop}`}
scopedSlots={{
default(scope) {
return item1.customFn(scope.row)
}
}}
>
</el-table-column>
)
} else {
return (
<el-table-column
render-header={item1.renderHeader ?? undefined}
fixed={item1.fixed ?? false}
align={item1.align ?? 'center'}
header-align={item1.headerAlign ?? 'center'}
prop={`${item.Fprop ? item.Fprop + '.' : ''}${item1.prop}`}
label={item1.label}
width={item1.width ?? 'auto'}
min-width={item1.minWidth ?? item1.width}
sortable={item1.sortable ?? false}
type={item1.type ?? ''}
formatter={item1.formatter}
>
</el-table-column>
)
}
})}
</el-table-column>
)
} else {
//
return (
<el-table-column
render-header={item.renderHeader ?? undefined}
fixed={item.fixed ?? false}
formatter={item.formatter}
width={item.width ?? 'auto'}
min-width={item.minWidth ?? item.width}
header-align={item.headerAlign ?? 'center'}
align={item.align ?? 'center'}
label={item.label}
prop={item.prop}
sortable={item.sortable ?? false}
type={item.type}
reserve-selection={item.reserveSelection}
selectable={item.selectable}
scopedSlots={item.type === 'expand' ? {
default(props) {
return item.expandFn(props)
}
} : ''}
>
</el-table-column>
)
}
})}
{
$scopedSlots.btns ? $scopedSlots.btns() :
(<el-table-column
fixed="right"
label="操作"
width={btnWidth}
header-align="center"
scopedSlots={{
default(scope) {
return (
<div>
<Poptip
transfer={true}
confirm
title="确认要删除吗"
on={{ ['on-ok']: () => deleteClick(scope.row) }}
>
<i-button
type="error"
size="small"
style="margin-left: 10px;"
ghost
>删除
</i-button>
</Poptip>
<i-button
type="primary"
size="small"
style="margin-left: 10px;"
ghost
onClick={() => editorClick(scope.row)}
>编辑
</i-button>
</div>
)
}
}}
>
</el-table-column>)
}
</el-table>)
:
<el-table height={height ?? tableHeight}/>}
<el-backtop
target=".el-table__body-wrapper"
visibility-height={120}
bottom={100}
right={36}
>
</el-backtop>
</div>
)
}
}
</script>
<style scoped lang="scss">
</style>

@ -0,0 +1,556 @@
<template>
<div class="kindeditor">
<div>
<VolBox
icon="ios-chatbubbles"
:model.sync="uploadModel"
title="图片上传"
:height="180"
:width="520"
:padding="15"
>
<VolUpload
style="text-align: center; border: 1px dotted #FF9800;padding: 20px;"
:autoUpload="false"
:multiple="true"
:url="UploadImgUrl"
:max-file="uploadCount"
:img="true"
:upload-after="uploadAfter"
:upload-before="uploadBefore"
></VolUpload>
</VolBox>
</div>
<!-- <Button type="info" @click="appendContent()"></Button> -->
<textarea :height="height" :width="width" :id="id" name="content" v-model="outContent"></textarea>
</div>
</template>
<script>
import "@/../static/kindeditor/themes/default/default.css";
import "@/../static/kindeditor/kindeditor-all-min.js";
// import "@/../static/kindeditor/kindeditor-all.js";
import "@/../static/kindeditor/lang/zh-CN.js";
let $this;
export default {
components: {
VolBox: () => import("@/components/basic/VolBox.vue"),
VolUpload: () => import("@/components/basic/VolUpload.vue")
},
name: "kindeditor",
data() {
return {
uploadModel: false,
id: "vue-kind" + ~~(Math.random() * 1000),
editor: null,
outContent: this.content
};
},
props: {
UploadImgUrl: {
//url
type: String,
default: ""
},
upload:{ //
type:Function,
default:(file)=>{
}
},
uploadCount: {
//()
type: Number,
default: 3
},
content: {
type: String,
default: ""
},
// id: {
// type: String,
// required: true
// },
width: {
type: String,
default: "100%"
},
height: {
type: String,
default: "500px"
},
minWidth: {
type: Number,
default: 650
},
minHeight: {
type: Number,
default: 100
},
items: {
type: Array,
default: function() {
return [
"source",
"|",
"undo",
"redo",
"|",
"preview",
"print",
"template",
"code",
"cut",
"copy",
"paste",
"plainpaste",
"wordpaste",
"|",
"justifyleft",
"justifycenter",
"justifyright",
"justifyfull",
"insertorderedlist",
"insertunorderedlist",
"indent",
"outdent",
"subscript",
"superscript",
"clearhtml",
"quickformat",
"selectall",
"|",
"fullscreen",
"/",
"formatblock",
"fontname",
"fontsize",
"|",
"forecolor",
"hilitecolor",
"bold",
"italic",
"underline",
"strikethrough",
"lineheight",
"removeformat",
"|",
// "image",
// "multiimage",
// "flash",
// "media",
// "insertfile",
"table",
"hr",
"emoticons",
"baidumap",
"pagebreak",
"anchor",
"link",
"unlink",
"|",
"vue-img"
// "about"
];
}
},
noDisableItems: {
type: Array,
default: function() {
return ["source", "fullscreen"];
}
},
filterMode: {
type: Boolean,
default: true
},
htmlTags: {
type: Object,
default: function() {
return {
font: ["color", "size", "face", ".background-color"],
span: ["style"],
div: ["class", "align", "style"],
table: [
"class",
"border",
"cellspacing",
"cellpadding",
"width",
"height",
"align",
"style"
],
"td,th": [
"class",
"align",
"valign",
"width",
"height",
"colspan",
"rowspan",
"bgcolor",
"style"
],
a: ["class", "href", "target", "name", "style"],
embed: [
"src",
"width",
"height",
"type",
"loop",
"autostart",
"quality",
"style",
"align",
"allowscriptaccess",
"/"
],
img: [
"src",
"width",
"height",
"border",
"alt",
"title",
"align",
"style",
"/"
],
hr: ["class", "/"],
br: ["/"],
"p,ol,ul,li,blockquote,h1,h2,h3,h4,h5,h6": ["align", "style"],
"tbody,tr,strong,b,sub,sup,em,i,u,strike": []
};
}
},
wellFormatMode: {
type: Boolean,
default: true
},
resizeType: {
type: Number,
default: 2
},
themeType: {
type: String,
default: "default"
},
langType: {
type: String,
default: "zh-CN"
},
designMode: {
type: Boolean,
default: true
},
fullscreenMode: {
type: Boolean,
default: false
},
basePath: {
type: String
},
themesPath: {
type: String
},
pluginsPath: {
type: String,
default: ""
},
langPath: {
type: String
},
minChangeSize: {
type: Number,
default: 5
},
loadStyleMode: {
type: Boolean,
default: true
},
urlType: {
type: String,
default: ""
},
newlineTag: {
type: String,
default: "p"
},
pasteType: {
type: Number,
default: 2
},
dialogAlignType: {
type: String,
default: "page"
},
shadowMode: {
type: Boolean,
default: true
},
zIndex: {
type: Number,
default: 811213
},
useContextmenu: {
type: Boolean,
default: true
},
syncType: {
type: String,
default: "form"
},
indentChar: {
type: String,
default: "\t"
},
cssPath: {
type: [String, Array]
},
cssData: {
type: String
},
bodyClass: {
type: String,
default: "ke-content"
},
colorTable: {
type: Array
},
afterCreate: {
type: Function
},
afterChange: {
type: Function
},
afterTab: {
type: Function
},
afterFocus: {
type: Function
},
afterBlur: {
type: Function
},
afterUpload: {
type: Function
},
uploadJson: {
type: String
},
fileManagerJson: {
type: Function
},
allowPreviewEmoticons: {
type: Boolean,
default: true
},
allowImageUpload: {
type: Boolean,
default: true
},
allowFlashUpload: {
type: Boolean,
default: true
},
allowMediaUpload: {
type: Boolean,
default: true
},
allowFileUpload: {
type: Boolean,
default: true
},
allowFileManager: {
type: Boolean,
default: false
},
fontSizeTable: {
type: Array,
default: function() {
return ["9px", "10px", "12px", "14px", "16px", "18px", "24px", "32px"];
}
},
imageTabIndex: {
type: Number,
default: 0
},
formatUploadUrl: {
type: Boolean,
default: true
},
fullscreenShortcut: {
type: Boolean,
default: false
},
extraFileUploadParams: {
type: Array,
default: function() {
return [];
}
},
filePostName: {
type: String,
default: "imgFile"
},
fillDescAfterUploadImage: {
type: Boolean,
default: false
},
afterSelectFile: {
type: Function
},
pagebreakHtml: {
type: String,
default: "<hr style=”page-break-after: always;” class=”ke-pagebreak” />"
},
allowImageRemote: {
type: Boolean,
default: true
},
autoHeightMode: {
type: Boolean,
default: false
},
fixToolBar: {
type: Boolean,
default: false
},
tabIndex: {
type: Number
}
},
watch: {
content(val) {
this.editor && val !== this.outContent && this.editor.html(val);
},
outContent(val) {
this.$emit("update:content", val);
this.$emit("on-content-change", val);
}
},
methods: {
uploadBefore(files) {
//,
return true;
},
uploadAfter(result, files) {
if (!result.status) {
return;
}
let urls = files.map(x => {
return (
`<img class="editor-img" src="` +
(this.http.ipAddress + result.data + x.name) +
`"/>`
);
});
this.editor.insertHtml(urls.join(","));
this.uploadModel = false;
},
getContent() {
return this.outContent;
},
setContent(content) {
//
this.outContent = content || "";
this.editor.html(this.outContent);
},
appendContent(htmlContent) {
//kindEditor
var startOffset = this.editor.cmd.range.startOffset;
this.editor.insertHtml(htmlContent);
}
},
mounted() {
$this = this;
KindEditor.plugin("vue-img", function(K) {
var editor = this,
name = "vue-img";
//
editor.clickToolbar(name, function() {
$this.uploadModel = true;
});
});
KindEditor.lang({
"vue-img": "上传图片"
});
var _this = this;
_this.editor = window.KindEditor.create("#" + this.id, {
width: _this.width,
height: _this.height,
minWidth: _this.minWidth,
minHeight: _this.minHeight,
items: _this.items,
noDisableItems: _this.noDisableItems,
filterMode: _this.filterMode,
htmlTags: _this.htmlTags,
wellFormatMode: _this.wellFormatMode,
resizeType: _this.resizeType,
themeType: _this.themeType,
langType: _this.langType,
designMode: _this.designMode,
fullscreenMode: _this.fullscreenMode,
basePath: _this.basePath,
themesPath: _this.cssPath,
pluginsPath: 'static/kindeditor/plugins/', // _this.pluginsPath,
langPath: _this.langPath,
minChangeSize: _this.minChangeSize,
loadStyleMode: _this.loadStyleMode,
urlType: _this.urlType,
newlineTag: _this.newlineTag,
pasteType: _this.pasteType,
dialogAlignType: _this.dialogAlignType,
shadowMode: _this.shadowMode,
zIndex: _this.zIndex,
useContextmenu: _this.useContextmenu,
syncType: _this.syncType,
indentChar: _this.indentChar,
cssPath: _this.cssPath,
cssData: _this.cssData,
bodyClass: _this.bodyClass,
colorTable: _this.colorTable,
afterCreate: _this.afterCreate,
afterChange: function() {
_this.afterChange;
_this.outContent = this.html();
},
afterTab: _this.afterTab,
afterFocus: _this.afterFocus,
afterBlur: _this.afterBlur,
afterUpload: _this.afterUpload,
uploadJson: _this.uploadJson,
fileManagerJson: _this.fileManagerJson,
allowPreviewEmoticons: _this.allowPreviewEmoticons,
allowImageUpload: _this.allowImageUpload,
allowFlashUpload: _this.allowFlashUpload,
allowMediaUpload: _this.allowMediaUpload,
allowFileUpload: _this.allowFileUpload,
allowFileManager: _this.allowFileManager,
fontSizeTable: _this.fontSizeTable,
imageTabIndex: _this.imageTabIndex,
formatUploadUrl: _this.formatUploadUrl,
fullscreenShortcut: _this.fullscreenShortcut,
extraFileUploadParams: _this.extraFileUploadParams,
filePostName: _this.filePostName,
fillDescAfterUploadImage: _this.fillDescAfterUploadImage,
afterSelectFile: _this.afterSelectFile,
pagebreakHtml: _this.pagebreakHtml,
allowImageRemote: _this.allowImageRemote,
autoHeightMode: _this.autoHeightMode,
fixToolBar: _this.fixToolBar,
tabIndex: _this.tabIndex
});
}
};
</script>
<style>
.ke-icon-vue-img {
background-image: url(/../static/kindeditor/themes/default/default.png);
background-position: 0px -496px;
width: 16px;
height: 16px;
}
</style>

@ -0,0 +1,9 @@
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'// svg component
// register globally
Vue.component('svg-icon', SvgIcon)
const req = require.context('./svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys().map(requireContext)
requireAll(req)

@ -0,0 +1 @@
<svg width="128" height="100" xmlns="http://www.w3.org/2000/svg"><path d="M27.429 63.638c0-2.508-.893-4.65-2.679-6.424-1.786-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.465 2.662-1.785 1.774-2.678 3.916-2.678 6.424 0 2.508.893 4.65 2.678 6.424 1.786 1.775 3.94 2.662 6.465 2.662 2.524 0 4.678-.887 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm13.714-31.801c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM71.714 65.98l7.215-27.116c.285-1.23.107-2.378-.536-3.443-.643-1.064-1.56-1.762-2.75-2.094-1.19-.33-2.333-.177-3.429.462-1.095.639-1.81 1.573-2.143 2.804l-7.214 27.116c-2.857.237-5.405 1.266-7.643 3.088-2.238 1.822-3.738 4.152-4.5 6.992-.952 3.644-.476 7.098 1.429 10.364 1.905 3.265 4.69 5.37 8.357 6.317 3.667.947 7.143.474 10.429-1.42 3.285-1.892 5.404-4.66 6.357-8.305.762-2.84.619-5.607-.429-8.305-1.047-2.697-2.762-4.85-5.143-6.46zm47.143-2.342c0-2.508-.893-4.65-2.678-6.424-1.786-1.775-3.94-2.662-6.465-2.662-2.524 0-4.678.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.786 1.775 3.94 2.662 6.464 2.662 2.524 0 4.679-.887 6.465-2.662 1.785-1.775 2.678-3.916 2.678-6.424zm-45.714-45.43c0-2.509-.893-4.65-2.679-6.425C68.68 10.01 66.524 9.122 64 9.122c-2.524 0-4.679.887-6.464 2.661-1.786 1.775-2.679 3.916-2.679 6.425 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zm32 13.629c0-2.508-.893-4.65-2.679-6.424-1.785-1.775-3.94-2.662-6.464-2.662-2.524 0-4.679.887-6.464 2.662-1.786 1.774-2.679 3.916-2.679 6.424 0 2.508.893 4.65 2.679 6.424 1.785 1.774 3.94 2.662 6.464 2.662 2.524 0 4.679-.888 6.464-2.662 1.786-1.775 2.679-3.916 2.679-6.424zM128 63.638c0 12.351-3.357 23.78-10.071 34.286-.905 1.372-2.19 2.058-3.858 2.058H13.93c-1.667 0-2.953-.686-3.858-2.058C3.357 87.465 0 76.037 0 63.638c0-8.613 1.69-16.847 5.071-24.703C8.452 31.08 13 24.312 18.714 18.634c5.715-5.68 12.524-10.199 20.429-13.559C47.048 1.715 55.333.035 64 .035c8.667 0 16.952 1.68 24.857 5.04 7.905 3.36 14.714 7.88 20.429 13.559 5.714 5.678 10.262 12.446 13.643 20.301 3.38 7.856 5.071 16.09 5.071 24.703z"/></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M96.258 57.462h31.421C124.794 27.323 100.426 2.956 70.287.07v31.422a32.856 32.856 0 0 1 25.971 25.97zm-38.796-25.97V.07C27.323 2.956 2.956 27.323.07 57.462h31.422a32.856 32.856 0 0 1 25.97-25.97zm12.825 64.766v31.421c30.46-2.885 54.507-27.253 57.713-57.712H96.579c-2.886 13.466-13.146 23.726-26.292 26.291zM31.492 70.287H.07c2.886 30.46 27.253 54.507 57.713 57.713V96.579c-13.466-2.886-23.726-13.146-26.291-26.292z"/></svg>

After

Width:  |  Height:  |  Size: 497 B

@ -0,0 +1 @@
<svg class="icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" width="128" height="128"><defs><style/></defs><path d="M512 128q69.675 0 135.51 21.163t115.498 54.997 93.483 74.837 73.685 82.006 51.67 74.837 32.17 54.827L1024 512q-2.347 4.992-6.315 13.483T998.87 560.17t-31.658 51.669-44.331 59.99-56.832 64.34-69.504 60.16-82.347 51.5-94.848 34.687T512 896q-69.675 0-135.51-21.163t-115.498-54.826-93.483-74.326-73.685-81.493-51.67-74.496-32.17-54.997L0 513.707q2.347-4.992 6.315-13.483t18.816-34.816 31.658-51.84 44.331-60.33 56.832-64.683 69.504-60.331 82.347-51.84 94.848-34.816T512 128.085zm0 85.333q-46.677 0-91.648 12.331t-81.152 31.83-70.656 47.146-59.648 54.485-48.853 57.686-37.675 52.821-26.325 43.99q12.33 21.674 26.325 43.52t37.675 52.351 48.853 57.003 59.648 53.845T339.2 767.02t81.152 31.488T512 810.667t91.648-12.331 81.152-31.659 70.656-46.848 59.648-54.186 48.853-57.344 37.675-52.651T927.957 512q-12.33-21.675-26.325-43.648t-37.675-52.65-48.853-57.345-59.648-54.186-70.656-46.848-81.152-31.659T512 213.334zm0 128q70.656 0 120.661 50.006T682.667 512 632.66 632.661 512 682.667 391.339 632.66 341.333 512t50.006-120.661T512 341.333zm0 85.334q-35.328 0-60.33 25.002T426.666 512t25.002 60.33T512 597.334t60.33-25.002T597.334 512t-25.002-60.33T512 426.666z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@ -0,0 +1 @@
<svg width="128" height="64" xmlns="http://www.w3.org/2000/svg"><path d="M127.072 7.994c1.37-2.208.914-5.152-.914-6.87-2.056-1.717-4.797-1.226-6.396.982-.229.245-25.586 32.382-55.74 32.382-29.24 0-55.74-32.382-55.968-32.627-1.6-1.963-4.57-2.208-6.397-.49C-.17 3.086-.399 6.275 1.2 8.238c.457.736 5.94 7.36 14.62 14.72L4.17 35.96c-1.828 1.963-1.6 5.152.228 6.87.457.98 1.6 1.471 2.742 1.471s2.284-.49 3.198-1.472l12.564-13.983c5.94 4.416 13.021 8.587 20.788 11.53l-4.797 17.418c-.685 2.699.686 5.397 3.198 6.133h1.37c2.057 0 3.884-1.472 4.341-3.68L52.6 42.83c3.655.736 7.538 1.227 11.422 1.227 3.883 0 7.767-.49 11.422-1.227l4.797 17.173c.457 2.208 2.513 3.68 4.34 3.68.457 0 .914 0 1.143-.246 2.513-.736 3.883-3.434 3.198-6.133l-4.797-17.172c7.767-2.944 14.848-7.114 20.788-11.53l12.336 13.738c.913.981 2.056 1.472 3.198 1.472s2.284-.49 3.198-1.472c1.828-1.963 1.828-4.906.228-6.87l-11.65-13.001c9.366-7.36 14.849-14.474 14.849-14.474z"/></svg>

After

Width:  |  Height:  |  Size: 944 B

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M84.068 23.784c-1.02 0-1.877-.32-2.572-.96a8.588 8.588 0 0 1-1.738-2.237 11.524 11.524 0 0 1-1.042-2.621c-.232-.895-.348-1.641-.348-2.238V0h.278c.834 0 1.622.085 2.363.256.742.17 1.645.575 2.711 1.214 1.066.64 2.363 1.535 3.892 2.686 1.53 1.15 3.453 2.664 5.77 4.54 2.502 2.045 4.494 3.771 5.977 5.178 1.483 1.406 2.618 2.6 3.406 3.58.787.98 1.274 1.812 1.46 2.494.185.682.277 1.278.277 1.79v2.046H84.068zM127.3 84.01c.278.682.464 1.535.556 2.558.093 1.023-.37 2.003-1.39 2.94-.463.427-.88.832-1.25 1.215-.372.384-.696.704-.974.96a6.69 6.69 0 0 1-.973.767l-11.816-10.741a44.331 44.331 0 0 0 1.877-1.535 31.028 31.028 0 0 1 1.737-1.406c1.112-.938 2.317-1.343 3.615-1.215 1.297.128 2.363.405 3.197.83.927.427 1.923 1.173 2.989 2.239 1.065 1.065 1.876 2.195 2.432 3.388zM78.23 95.902c2.038 0 3.752-.511 5.143-1.534l-26.969 25.83H18.037c-1.761 0-3.684-.47-5.77-1.407a24.549 24.549 0 0 1-5.838-3.709 21.373 21.373 0 0 1-4.518-5.306c-1.204-2.003-1.807-4.07-1.807-6.202V16.495c0-1.79.44-3.665 1.32-5.626A18.41 18.41 0 0 1 5.04 5.562a21.798 21.798 0 0 1 5.213-3.964C12.198.533 14.237 0 16.37 0h53.24v15.984c0 1.62.278 3.367.834 5.242a16.704 16.704 0 0 0 2.572 5.179c1.159 1.577 2.665 2.898 4.518 3.964 1.853 1.066 4.078 1.598 6.673 1.598h20.295v42.325L85.458 92.45c1.02-1.364 1.529-2.856 1.529-4.476 0-2.216-.857-4.113-2.572-5.69-1.714-1.577-3.776-2.366-6.186-2.366H26.1c-2.409 0-4.448.789-6.116 2.366-1.668 1.577-2.502 3.474-2.502 5.69 0 2.217.834 4.092 2.502 5.626 1.668 1.535 3.707 2.302 6.117 2.302h52.13zM26.1 47.951c-2.41 0-4.449.789-6.117 2.366-1.668 1.577-2.502 3.473-2.502 5.69 0 2.216.834 4.092 2.502 5.626 1.668 1.534 3.707 2.302 6.117 2.302h52.13c2.409 0 4.47-.768 6.185-2.302 1.715-1.534 2.572-3.41 2.572-5.626 0-2.217-.857-4.113-2.572-5.69-1.714-1.577-3.776-2.366-6.186-2.366H26.1zm52.407 64.063l1.807-1.663 3.476-3.196a479.75 479.75 0 0 0 4.587-4.284 500.757 500.757 0 0 1 5.004-4.667c3.985-3.666 8.48-7.758 13.485-12.276l11.677 10.741-13.485 12.404-5.004 4.603-4.587 4.22a179.46 179.46 0 0 0-3.267 3.068c-.88.853-1.367 1.322-1.46 1.407-.463.341-.973.703-1.529 1.087-.556.383-1.112.703-1.668.959-.556.256-1.413.575-2.572.959a83.5 83.5 0 0 1-3.545 1.087 72.2 72.2 0 0 1-3.475.895c-1.112.256-1.946.426-2.502.511-1.112.17-1.854.043-2.224-.383-.371-.426-.464-1.151-.278-2.174.092-.511.278-1.279.556-2.302.278-1.023.602-2.067.973-3.132l1.042-3.005c.325-.938.58-1.577.765-1.918a10.157 10.157 0 0 1 2.224-2.941z"/></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M115.625 127.937H.063V12.375h57.781v12.374H12.438v90.813h90.813V70.156h12.374z"/><path d="M116.426 2.821l8.753 8.753-56.734 56.734-8.753-8.745z"/><path d="M127.893 37.982h-12.375V12.375H88.706V0h39.187z"/></svg>

After

Width:  |  Height:  |  Size: 285 B

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.002 9.2c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-5.043-3.58-9.132-7.997-9.132S.002 4.157.002 9.2zM31.997.066h95.981V18.33H31.997V.066zm0 45.669c0 5.044 3.58 9.132 7.998 9.132 4.417 0 7.997-4.088 7.997-9.132 0-3.263-1.524-6.278-3.998-7.91-2.475-1.63-5.524-1.63-7.998 0-2.475 1.632-4 4.647-4 7.91zM63.992 36.6h63.986v18.265H63.992V36.6zm-31.995 82.2c0 5.043 3.58 9.132 7.998 9.132 4.417 0 7.997-4.089 7.997-9.132 0-5.044-3.58-9.133-7.997-9.133s-7.998 4.089-7.998 9.133zm31.995-9.131h63.986v18.265H63.992V109.67zm0-27.404c0 5.044 3.58 9.133 7.998 9.133 4.417 0 7.997-4.089 7.997-9.133 0-3.263-1.524-6.277-3.998-7.909-2.475-1.631-5.524-1.631-7.998 0-2.475 1.632-4 4.646-4 7.91zm31.995-9.13h31.991V91.4H95.987V73.135z"/></svg>

After

Width:  |  Height:  |  Size: 821 B

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M108.8 44.322H89.6v-5.36c0-9.04-3.308-24.163-25.6-24.163-23.145 0-25.6 16.881-25.6 24.162v5.361H19.2v-5.36C19.2 15.281 36.798 0 64 0c27.202 0 44.8 15.281 44.8 38.961v5.361zm-32 39.356c0-5.44-5.763-9.832-12.8-9.832-7.037 0-12.8 4.392-12.8 9.832 0 3.682 2.567 6.808 6.407 8.477v11.205c0 2.718 2.875 4.962 6.4 4.962 3.524 0 6.4-2.244 6.4-4.962V92.155c3.833-1.669 6.393-4.795 6.393-8.477zM128 64v49.201c0 8.158-8.645 14.799-19.2 14.799H19.2C8.651 128 0 121.359 0 113.201V64c0-8.153 8.645-14.799 19.2-14.799h89.6c10.555 0 19.2 6.646 19.2 14.799z"/></svg>

After

Width:  |  Height:  |  Size: 623 B

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/><path d="M.006.064h127.988v31.104H.006V.064zm0 38.016h38.396v41.472H.006V38.08zm0 48.384h38.396v41.472H.006V86.464zM44.802 38.08h38.396v41.472H44.802V38.08zm0 48.384h38.396v41.472H44.802V86.464zM89.598 38.08h38.396v41.472H89.598zm0 48.384h38.396v41.472H89.598z"/></svg>

After

Width:  |  Height:  |  Size: 597 B

@ -0,0 +1 @@
<svg width="128" height="128" xmlns="http://www.w3.org/2000/svg"><path d="M126.713 90.023c.858.985 1.287 2.134 1.287 3.447v29.553c0 1.423-.429 2.6-1.287 3.53-.858.93-1.907 1.395-3.146 1.395H97.824c-1.145 0-2.146-.465-3.004-1.395-.858-.93-1.287-2.107-1.287-3.53V93.47c0-.875.19-1.696.572-2.462.382-.766.906-1.368 1.573-1.806a3.84 3.84 0 0 1 2.146-.657h9.725V69.007a3.84 3.84 0 0 0-.43-1.806 3.569 3.569 0 0 0-1.143-1.313 2.714 2.714 0 0 0-1.573-.492h-36.47v23.149h9.725c1.144 0 2.145.492 3.004 1.478.858.985 1.287 2.134 1.287 3.447v29.553c0 .876-.191 1.696-.573 2.463-.38.766-.905 1.368-1.573 1.806a3.84 3.84 0 0 1-2.145.656H51.915a3.84 3.84 0 0 1-2.145-.656c-.668-.438-1.216-1.04-1.645-1.806a4.96 4.96 0 0 1-.644-2.463V93.47c0-1.313.43-2.462 1.288-3.447.858-.986 1.907-1.478 3.146-1.478h9.582v-23.15h-37.9c-.953 0-1.74.356-2.359 1.068-.62.711-.93 1.56-.93 2.544v19.538h9.726c1.239 0 2.264.492 3.074 1.478.81.985 1.216 2.134 1.216 3.447v29.553c0 1.423-.405 2.6-1.216 3.53-.81.93-1.835 1.395-3.074 1.395H4.29c-.476 0-.93-.082-1.358-.246a4.1 4.1 0 0 1-1.144-.657 4.658 4.658 0 0 1-.93-1.067 5.186 5.186 0 0 1-.643-1.395 5.566 5.566 0 0 1-.215-1.56V93.47c0-.437.048-.875.143-1.313a3.95 3.95 0 0 1 .429-1.15c.19-.328.429-.656.715-.984.286-.329.572-.602.858-.821.286-.22.62-.383 1.001-.493.382-.11.763-.164 1.144-.164h9.726V61.619c0-.985.31-1.833.93-2.544.619-.712 1.358-1.068 2.216-1.068h44.335V39.62h-9.582c-1.24 0-2.288-.492-3.146-1.477a5.09 5.09 0 0 1-1.287-3.448V5.14c0-1.423.429-2.627 1.287-3.612.858-.985 1.907-1.477 3.146-1.477h25.743c.763 0 1.478.246 2.145.739a5.17 5.17 0 0 1 1.573 1.888c.382.766.573 1.587.573 2.462v29.553c0 1.313-.43 2.463-1.287 3.448-.859.985-1.86 1.477-3.004 1.477h-9.725v18.389h42.762c.954 0 1.74.355 2.36 1.067.62.711.93 1.56.93 2.545v26.925h9.582c1.239 0 2.288.492 3.146 1.478z"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@ -0,0 +1 @@
<svg width="130" height="130" xmlns="http://www.w3.org/2000/svg"><path d="M63.444 64.996c20.633 0 37.359-14.308 37.359-31.953 0-17.649-16.726-31.952-37.359-31.952-20.631 0-37.36 14.303-37.358 31.952 0 17.645 16.727 31.953 37.359 31.953zM80.57 75.65H49.434c-26.652 0-48.26 18.477-48.26 41.27v2.664c0 9.316 21.608 9.325 48.26 9.325H80.57c26.649 0 48.256-.344 48.256-9.325v-2.663c0-22.794-21.605-41.271-48.256-41.271z" stroke="#979797"/></svg>

After

Width:  |  Height:  |  Size: 440 B

@ -0,0 +1,22 @@
# replace default config
# multipass: true
# full: true
plugins:
# - name
#
# or:
# - name: false
# - name: true
#
# or:
# - name:
# param1: 1
# param2: 2
- removeAttrs:
attrs:
- 'fill'
- 'fill-rule'

@ -0,0 +1,49 @@
<template>
<section class="app-main">
<breadcrumb class="breadcrumb-container" />
<transition name="fade-transform" mode="out-in">
<router-view :key="key" />
</transition>
</section>
</template>
<script>
import Breadcrumb from '@/components/Breadcrumb'
export default {
components: {
Breadcrumb
},
name: 'AppMain',
computed: {
key() {
return this.$route.path
}
}
}
</script>
<style scoped>
.app-main {
/*50 = navbar */
min-height: calc(100vh - 30px);
width: 100%;
position: relative;
overflow: hidden;
}
.fixed-header+.app-main {
padding-top: 50px;
}
</style>
<style lang="scss">
// fix css style bug in open el-dialog
.el-popup-parent--hidden {
.fixed-header {
padding-right: 15px;
}
}
.breadcrumb-container {
margin-left: 20px !important;
}
</style>

@ -0,0 +1,118 @@
<template>
<div class="navbar">
<a href="javascript:;" class="btn-quit">退出</a>
</div>
</template>
<script>
import {
mapGetters
} from 'vuex'
import Breadcrumb from '@/components/Breadcrumb'
import Hamburger from '@/components/Hamburger'
export default {
components: {
Breadcrumb,
Hamburger
},
computed: {
...mapGetters([
'sidebar',
'avatar'
])
},
methods: {
toggleSideBar() {
this.$store.dispatch('app/toggleSideBar')
},
async logout() {
await this.$store.dispatch('user/logout')
this.$router.push(`/login?redirect=${this.$route.fullPath}`)
}
}
}
</script>
<style lang="scss" scoped>
.navbar {
height: 30px;
overflow: hidden;
position: relative;
//background: #fff;
background: #338de3;
box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
color: #FFFFFF;
line-height: 30px;
.hamburger-container {
line-height: 46px;
height: 100%;
float: left;
cursor: pointer;
transition: background .3s;
-webkit-tap-highlight-color: transparent;
&:hover {
background: rgba(0, 0, 0, .025)
}
}
.breadcrumb-container {
float: left;
}
.right-menu {
float: right;
height: 100%;
line-height: 50px;
&:focus {
outline: none;
}
.right-menu-item {
display: inline-block;
padding: 0 8px;
height: 100%;
font-size: 18px;
color: #5a5e66;
vertical-align: text-bottom;
&.hover-effect {
cursor: pointer;
transition: background .3s;
&:hover {
background: rgba(0, 0, 0, .025)
}
}
}
.avatar-container {
margin-right: 30px;
.avatar-wrapper {
margin-top: 5px;
position: relative;
.user-avatar {
cursor: pointer;
width: 40px;
height: 40px;
border-radius: 10px;
}
.el-icon-caret-bottom {
cursor: pointer;
position: absolute;
right: -20px;
top: 25px;
font-size: 12px;
}
}
}
}
}
</style>

@ -0,0 +1,26 @@
export default {
computed: {
device() {
return this.$store.state.app.device
}
},
mounted() {
// In order to fix the click on menu on the ios device will trigger the mouseleave bug
// https://github.com/PanJiaChen/vue-element-admin/issues/1135
this.fixBugIniOS()
},
methods: {
fixBugIniOS() {
const $subMenu = this.$refs.subMenu
if ($subMenu) {
const handleMouseleave = $subMenu.handleMouseleave
$subMenu.handleMouseleave = (e) => {
if (this.device === 'mobile') {
return
}
handleMouseleave(e)
}
}
}
}
}

@ -0,0 +1,53 @@
<script>
export default {
name: 'MenuItem',
functional: true,
props: {
icon: {
type: String,
default: ''
},
title: {
type: String,
default: ''
}
},
render(h, context) {
const { icon, title } = context.props
const vnodes = []
if (icon) {
if (icon.includes('el-icon')) {
vnodes.push(<i class={[icon, 'sub-el-icon']} />)
}
else if(icon.includes("viewicon")){
let _icon=icon.split("/")[1];
vnodes.push(<Icon type={_icon} class='sub-el-icon' size="18" />)
}
else if(icon.includes("iconfont")){
let _icon=icon.split("/")[1]+" iconfont";
vnodes.push(<Icon custom={_icon} class='sub-el-icon' size="18" />)
}
else {
vnodes.push(<svg-icon icon-class={icon}/>)
}
}else {
var _icon="el-icon-menu";
vnodes.push(<i class={[_icon, 'sub-el-icon']} />)
}
if (title) {
vnodes.push(<span slot='title'>{(title)}</span>)
}
return vnodes
}
}
</script>
<style scoped>
.sub-el-icon {
color: currentColor;
width: 1em;
height: 1em;
}
</style>

@ -0,0 +1,43 @@
<template>
<component :is="type" v-bind="linkProps(to)">
<slot />
</component>
</template>
<script>
import { isExternal } from '@/utils/validate'
export default {
props: {
to: {
type: String,
required: true
}
},
computed: {
isExternal() {
return isExternal(this.to)
},
type() {
if (this.isExternal) {
return 'a'
}
return 'router-link'
}
},
methods: {
linkProps(to) {
if (this.isExternal) {
return {
href: to,
target: '_blank',
rel: 'noopener'
}
}
return {
to: to
}
}
}
}
</script>

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

Loading…
Cancel
Save