Commit a2b4d1c5 by 蒋勇

d

parent 7e6ce580
{
"presets": [
"@vue/app"
]
}
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
module.exports = {
root: true,
'extends': [
'plugin:vue/essential',
//'@vue/standard'
],
rules: {
// allow async-await
'generator-star-spacing': 'off',
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'vue/no-parsing-error': [2, {
'x-invalid-end-tag': false
}],
'no-undef': 'off',
'camelcase': 'off'
},
parserOptions: {
parser: 'babel-eslint'
}
}
.DS_Store
node_modules
/dist
package-lock.json
/tests/e2e/videos/
/tests/e2e/screenshots/
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*
build/env.js
module.exports = {
plugins: {
autoprefixer: {}
}
}
language: node_js
node_js: stable
script: npm run lint
notifications:
email: false
MIT License
Copyright (c) 2017 TalkingData
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.
<p align="center">
<a href="https://www.iviewui.com">
<img width="200" src="https://file.iviewui.com/logo-new.svg">
</a>
</p>
<h1>
iView Admin
<h3>Vue.js 2.0 admin management system template based on iView.</h3>
</h1>
[![](https://img.shields.io/github/release/iview/iview-admin.svg)](https://github.com/iview/iview-admin/releases)
[![](https://img.shields.io/travis/iview/iview-admin.svg?style=flat-square)](https://travis-ci.org/iview/iview-admin)
[![vue](https://img.shields.io/badge/vue-2.5.17-brightgreen.svg?style=flat-square)](https://github.com/vuejs/vue)
[![iview ui](https://img.shields.io/badge/iview-3.2.2-brightgreen.svg?style=flat-square)](https://github.com/iview/iview)
[![npm](https://img.shields.io/npm/l/express.svg)]()
## Introduction
iView Admin is a front-end management background integration solution. It based on [Vue.js](https://github.com/vuejs/vue) and use the UI Toolkit [iView](https://github.com/iview/iview).
- [Document](https://lison16.github.io/iview-admin-doc/)
- [Preview](https://admin.iviewui.com/)
- [Base template recommends using](https://github.com/iview/iview-admin/tree/template)
![image](https://file.iviewui.com/admin-dist/admin-preview.png)
## Features
- Login / Logout
- Permission Authentication
- A list of filters
- Permission to switch
- i18n
- Components
- Rich Text Editor
- Markdown Editor
- City Cascader
- Photos preview and edit
- Draggable list
- File upload
- Digital gradient
- split-pane
- Form
- The article published
- Workflow
- Table
- Drag-and-drop sort
- Searchable form
- Table export data
- Export to Csv file
- Export to Xls file
- Table to picture
- Error Page
- 403
- 404
- 500
- Router
- Dynamic routing
- With reference page
- Theme
- Shrink the sidebar
- Tag navigation
- Breadcrumb navigation
- Full screen / exit full screen
- Lock screen
- The message center
- Personal center
## Getting started
```bush
# clone the project
git clone https://github.com/iview/iview-admin.git
// install dependencies
npm install
// develop
npm run dev
```
## Build
```bush
npm run build
```
## License
[MIT](http://opensource.org/licenses/MIT)
Copyright (c) 2016-present, TalkingData
{
"pluginsFile": "tests/e2e/plugins/index.js"
}
{
"name": "iview-admin",
"version": "2.0.0",
"author": "Lison<lison16new@163.com>",
"private": false,
"scripts": {
"dev": "vue-cli-service serve --open",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"test:unit": "vue-cli-service test:unit",
"test:e2e": "vue-cli-service test:e2e"
},
"dependencies": {
"axios": "^0.18.0",
"clipboard": "^2.0.0",
"codemirror": "^5.38.0",
"countup": "^1.8.2",
"cropperjs": "^1.2.2",
"dayjs": "^1.7.7",
"echarts": "^4.0.4",
"html2canvas": "^1.0.0-alpha.12",
"iview": "^3.2.2",
"iview-area": "^1.5.17",
"js-cookie": "^2.2.0",
"simplemde": "^1.11.2",
"sortablejs": "^1.7.0",
"tree-table-vue": "^1.1.0",
"v-org-tree": "^1.0.6",
"vue": "^2.5.10",
"vue-i18n": "^7.8.0",
"vue-router": "^3.0.1",
"vuedraggable": "^2.16.0",
"vuex": "^3.0.1",
"wangeditor": "^3.1.1",
"xlsx": "^0.13.3"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.0.1",
"@vue/cli-plugin-eslint": "^3.0.1",
"@vue/cli-plugin-unit-mocha": "^3.0.1",
"@vue/cli-service": "^3.0.1",
"@vue/eslint-config-standard": "^3.0.0-beta.10",
"@vue/test-utils": "^1.0.0-beta.10",
"chai": "^4.1.2",
"eslint-plugin-cypress": "^2.0.1",
"less": "^2.7.3",
"less-loader": "^4.0.5",
"lint-staged": "^6.0.0",
"mockjs": "^1.0.1-beta3",
"vue-template-compiler": "^2.5.13"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
],
"gitHooks": {
"pre-commit": "lint-staged"
},
"lint-staged": {
"*.js": [
"vue-cli-service lint",
"git add"
],
"*.vue": [
"vue-cli-service lint",
"git add"
]
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title></title>
</head>
<body>
<noscript>
<strong>We're sorry but iview-admin 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>
<template>
<div id="app">
<router-view/>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
<style lang="less">
.size{
width: 100%;
height: 100%;
}
html,body{
.size;
overflow: hidden;
margin: 0;
padding: 0;
}
#app {
.size;
}
</style>
import axios from '@/libs/api.request'
export const getTableData = (searchinfo,url) => {
console.log("searchinfo...................")
console.log(searchinfo)
return axios.request({
url: url,
data:searchinfo,
method: 'post'
})
}
export const create = (url,entity) => {
return axios.request({
url: url,
data:entity,
method: 'post'
})
}
export const update = (url,entity) => {
return axios.request({
url: url,
data:entity,
method: 'post'
})
}
export const deleteByIds = (url,parray) => {
return axios.request({
url: url,
data:parray,
method: 'post'
})
}
export const getDragList = () => {
return axios.request({
url: 'get_drag_list',
method: 'get'
})
}
export const errorReq = () => {
return axios.request({
url: 'error_url',
method: 'post'
})
}
export const saveErrorLogger = info => {
return axios.request({
url: 'save_error_logger',
data: info,
method: 'post'
})
}
export const uploadImg = formData => {
return axios.request({
url: 'image/upload',
data: formData
})
}
export const getOrgData = () => {
return axios.request({
url: 'get_org_data',
method: 'get'
})
}
export const getTreeSelectData = () => {
return axios.request({
url: 'get_tree_select_data',
method: 'get'
})
}
import axios from '@/libs/api.request'
export const refQuery = (dicKey,model) => {
console.log("...............................................api.");
let url="web/metaCtl/getDicConfig"
if(model){
url="web/"+model+"Ctl/refQuery"
}
return axios.request({
url: url,
data:{dicKey:dicKey},
method: 'post'
})
}
export const formInfoQuery = (bizCode) => {
console.log("...............................................api.");
let url="web/metaCtl/getUIConfig"
return axios.request({
url: url,
data:{biz:bizCode},
method: 'post'
})
}
export const tableInfoQuery = (bizCode) => {
console.log("...............................................api.");
let url="web/metaCtl/getUIConfig"
return axios.request({
url: url,
data:{biz:bizCode},
method: 'post'
})
}
\ No newline at end of file
import axios from '@/libs/api.request'
export const getRouterReq = (access) => {
return axios.request({
url: 'get_router',
params: {
access
},
method: 'get'
})
}
import axios from '@/libs/api.request'
export const login = ({ userName, password }) => {
const data = {
userName,
password
}
return axios.request({
//url: 'web/auth/userCtl/login',
//{u:data}
url: 'login',
data:data,
method: 'post'
})
}
export const getUserInfo = (token) => {
return axios.request({
url: 'get_info',
params: {
token
},
method: 'get'
})
}
export const logout = (token) => {
return axios.request({
url: 'logout',
method: 'post'
})
}
export const getUnreadCount = () => {
return axios.request({
url: 'message/count',
method: 'get'
})
}
export const getMessage = () => {
return axios.request({
url: 'message/init',
method: 'get'
})
}
export const getContentByMsgId = msg_id => {
return axios.request({
url: 'message/content',
method: 'get',
params: {
msg_id
}
})
}
export const hasRead = msg_id => {
return axios.request({
url: 'message/has_read',
method: 'post',
data: {
msg_id
}
})
}
export const removeReaded = msg_id => {
return axios.request({
url: 'message/remove_readed',
method: 'post',
data: {
msg_id
}
})
}
export const restoreTrash = msg_id => {
return axios.request({
url: 'message/restore',
method: 'post',
data: {
msg_id
}
})
}
@font-face {font-family: "iconfont";
src: url('iconfont.eot?t=1541579316141'); /* IE9*/
src: url('iconfont.eot?t=1541579316141#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAiEAAsAAAAADmgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFY8eUnXY21hcAAAAYAAAACjAAACLi+YJuBnbHlmAAACJAAABAgAAAcg4dRWHmhlYWQAAAYsAAAAMQAAADYTL8piaGhlYQAABmAAAAAgAAAAJAfdA4xobXR4AAAGgAAAABQAAAAsLAD//2xvY2EAAAaUAAAAGAAAABgImgpGbWF4cAAABqwAAAAfAAAAIAEcAG5uYW1lAAAGzAAAAUUAAAJtPlT+fXBvc3QAAAgUAAAAbgAAAI54roygeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2BkYWCcwMDKwMHUyXSGgYGhH0IzvmYwYuRgYGBiYGVmwAoC0lxTGByeMTx/ytzwv4EhhrmBoRkozAiSAwDuUwzMeJzlkUEKwkAMRd/YabXFhQvxFF6qPYPrUujGY7jyIr1JoZNjtMnEhag3MOEN5MMk8D9QAoVyVSKEJwGrh6oh6wVN1iM3nc+cVImJVKdOehlklElmWdYVstp+ql8VdIv15a1NLW0zFXsO7Kjz3erH/3+rY37vr6kxnx1LKNWOJZlaxxJNnWOpSu+ot8jgqMvI6KjfyOSo88jsaAbI4tBsig89rQB4nLVUTWwbRRSeNzO767i2g7N/FP9s7MRrE5ON4/V6rSZyU0PiINSSNImES4IUoapWz6hEiqiMBDQqEojkAkiFStyKRC+9VSoFCeUEyqESVUAqEkcu3OAQb3hrJxAXwSGI3X0/szPz5vvm2x0i7O/vf8IJe5VkSJnUyUtklRBQJE1VIjRtUafkmk6pSu2ipleh4+xikkKxSksWTUeo8m8NoagpYtoslTmxrLl37z64e33esuJjU8P5Wd262LxoPVnPZ06Pxfe+C0YjkhSJygPhQCA8ABPOykwuN7NyuRvgUnAgLEnhATkaCQQiUe/7XKUyV6nQz+t2o7l66+rs7NVbq82GXTdrdjxjRGU5amTids2bUDMFtzCsqsMYMqr3IDY6OT05GjsI8Exv/6CSkOWEQigh+y3clxY5QVTcEZFIGtHLxDUJs6WsHR1y9SFKdr1HggCp3V1ICYL36OOpVmvKN9bC1u6R3vZ0qwWtVovgJfqOfUvfIYxIWL+fyETHNVJqSkIT1JTjW8ZWh3yDJDz0ctvsyt51etvrg9/QHhqGlzMM+vbmizPnDWPLMNbW19e7tffvsBzL99aWEfBRY46t+tbe3PypXv/IMDYN43WsQBe9HL2NC33RuxABrPsG+xH3o4bVRE2KgCRqulbWNf8W/UYVHM129aKra24VshZkq+CWD/Oy6Xt8cGYEthgHVlVliCfynAlqjo6oysTKlYUAD4docMI5/1ZioN+GwZNBcTwWUmTdBUqhTwX29QebXzF4An4JJMzwfMl+WQ01+IlQZVR4yhie53ycA16pOI/ODiYNGK4MChdCgXNnX5gIJXPCSYnf2OF850aQ+zJIyOs+u8+mMO8jQdwtg1TIWVRjKAnFcslMi8KfGUPoSUCergUyUk77dMyS69Ms6tijKZKYwUGKbpfdzu+iYeZYAHMFiOVi+MD7h9mb99qC0L7X8c+XatMfTj97KZ5IxJt/pd43tYYQKEjAnXMOB6kQEBrwg+LPjindAPOHNdC3q3ait0I3/ZIunZEARLNYNEUA6czSP3N/7j9wz6ZESdX0VNl1zGNS/szbQaQSIGk4DtVPcZf8AgXpf9A2OyTit5s2syZmand46bhEe2WtodLHkvaoqtTXuXN2/c42WADP9HGfbUcUW7JgqHss4xHtlMys679FqUomdP9VJBQBdnlPABBubpuNwqnmQj6/0HwNQzKxDUJFgKiXurBG6dqFjmeBzsvtRPJgGIZThYa5fdOvsReOticPh6JHHXxsv7ItJpOniYPYsmZ/x0QD/o5P105DeQwF6MH33ogoLi+KQp7zpY3HQV5bFMURzheXeds7gpP+jKNXljjHuYvXHke7cdCxLLZf6YX7B63UcCV4nGNgZGBgAOKAN2ZR8fw2Xxm4WRhA4AbHYRMY/f///1oWBuYGIJeDgQkkCgAvWgs2AAAAeJxjYGRgYG7438AQw8Lw/z8DAwsDA1AEBXADAHXiBHJ4nGNhYGBgYfj/nwVM48cATwECKwAAAAAAjAC6AOgBFAGAAf4CbgLqAzgDkHicY2BkYGDgZkhiYGcAASYg5gJCBob/YD4DABOmAYsAeJxlj01OwzAQhV/6B6QSqqhgh+QFYgEo/RGrblhUavdddN+mTpsqiSPHrdQDcB6OwAk4AtyAO/BIJ5s2lsffvHljTwDc4Acejt8t95E9XDI7cg0XuBeuU38QbpBfhJto41W4Rf1N2MczpsJtdGF5g9e4YvaEd2EPHXwI13CNT+E69S/hBvlbuIk7/Aq30PHqwj7mXle4jUcv9sdWL5xeqeVBxaHJIpM5v4KZXu+Sha3S6pxrW8QmU4OgX0lTnWlb3VPs10PnIhVZk6oJqzpJjMqt2erQBRvn8lGvF4kehCblWGP+tsYCjnEFhSUOjDFCGGSIyujoO1Vm9K+xQ8Jee1Y9zed0WxTU/3OFAQL0z1xTurLSeTpPgT1fG1J1dCtuy56UNJFezUkSskJe1rZUQuoBNmVXjhF6XNGJPyhnSP8ACVpuyAAAAHicbYhdDoIwEAb3a6k/YIIX8VArWewmdJFWJOnpJTG+OQ+TzJCjLy39p4ODR4OAA4444YwWHS7U3IVzn6Voldtb8ksHnvohrlqjjmw1rmzXsvdT7fEbblnCmOfNfJIYStJJfGIL27yb6AOCGR89AAA=') format('woff'),
url('iconfont.ttf?t=1541579316141') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
url('iconfont.svg?t=1541579316141#iconfont') format('svg'); /* iOS 4.1- */
}
.iconfont {
font-family:"iconfont" !important;
font-size:16px;
font-style:normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-bear:before { content: "\e600"; }
.icon-resize-vertical:before { content: "\e7c3"; }
.icon-chuizhifanzhuan:before { content: "\e661"; }
.icon-shuipingfanzhuan:before { content: "\e662"; }
.icon-qq:before { content: "\e609"; }
.icon-frown:before { content: "\e77e"; }
.icon-meh:before { content: "\e780"; }
.icon-smile:before { content: "\e783"; }
.icon-man:before { content: "\e7e2"; }
.icon-woman:before { content: "\e7e5"; }
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<!--
2013-9-30: Created.
-->
<svg>
<metadata>
Created by iconfont
</metadata>
<defs>
<font id="iconfont" horiz-adv-x="1024" >
<font-face
font-family="iconfont"
font-weight="500"
font-stretch="normal"
units-per-em="1024"
ascent="896"
descent="-128"
/>
<missing-glyph />
<glyph glyph-name="bear" unicode="&#58880;" d="M1024 683.008q0-70.656-46.08-121.856 46.08-89.088 46.08-193.536 0-96.256-39.936-181.248t-109.568-147.968-162.816-99.328-199.68-36.352-199.68 36.352-162.304 99.328-109.568 147.968-40.448 181.248q0 104.448 46.08 193.536-46.08 51.2-46.08 121.856 0 37.888 13.824 71.168t37.376 58.368 55.808 39.424 68.096 14.336q43.008 0 78.848-18.432t59.392-50.176q46.08 17.408 96.256 26.624t102.4 9.216 102.4-9.216 96.256-26.624q24.576 31.744 59.904 50.176t78.336 18.432q36.864 0 68.608-14.336t55.296-39.424 37.376-58.368 13.824-71.168zM205.824 268.288q10.24 0 18.944 10.24t15.36 28.672 10.24 42.496 3.584 51.712-3.584 51.712-10.24 41.984-15.36 28.16-18.944 10.24q-9.216 0-17.92-10.24t-15.36-28.16-10.752-41.984-4.096-51.712 4.096-51.712 10.752-42.496 15.36-28.672 17.92-10.24zM512-31.744000000000028q53.248 0 99.84 13.312t81.408 35.84 54.784 52.736 19.968 65.024q0 33.792-19.968 64t-54.784 52.736-81.408 35.84-99.84 13.312-99.84-13.312-81.408-35.84-54.784-52.736-19.968-64q0-34.816 19.968-65.024t54.784-52.736 81.408-35.84 99.84-13.312zM818.176 268.288q10.24 0 18.944 10.24t15.36 28.672 10.24 42.496 3.584 51.712-3.584 51.712-10.24 41.984-15.36 28.16-18.944 10.24q-9.216 0-17.92-10.24t-15.36-28.16-10.752-41.984-4.096-51.712 4.096-51.712 10.752-42.496 15.36-28.672 17.92-10.24zM512 235.51999999999998q39.936 0 68.096-9.728t28.16-24.064-28.16-24.064-68.096-9.728-68.096 9.728-28.16 24.064 28.16 24.064 68.096 9.728z" horiz-adv-x="1024" />
<glyph glyph-name="resize-vertical" unicode="&#59331;" d="M512 896C229.248 896 0 666.752 0 384s229.248-512 512-512 512 229.248 512 512S794.752 896 512 896zM576 192l64 0-128-128-128 128 64 0L448 576l-64 0 128 128 128-128-64 0L576 192z" horiz-adv-x="1024" />
<glyph glyph-name="chuizhifanzhuan" unicode="&#58977;" d="M286.01856 645.08416l472.4224 0 0-146.2784-472.4224 0 0 146.2784ZM87.19872 420.37248l885.80096 0 0-70.87104-885.80096 0 0 70.87104ZM773.55008 268.05248l0-31.0016L270.6688 237.05088l0 31.0016L773.55008 268.05248zM773.55008 121.4208l0-31.0016L270.6688 90.4192l0 31.0016L773.55008 121.4208zM742.54848 240.75776l31.0016 0 0-123.04896-31.0016 0L742.54848 240.75776zM270.70464 240.57856l31.0016 0 0-123.04896-31.0016 0L270.70464 240.57856z" horiz-adv-x="1024" />
<glyph glyph-name="shuipingfanzhuan" unicode="&#58978;" d="M252.76928 596.096l146.2784 0 0-472.42752-146.2784 0 0 472.42752ZM477.48096 810.65472l70.87104 0 0-885.80608-70.87104 0 0 885.80608ZM629.80096 611.2l31.0016 0 0-502.88128-31.0016 0L629.80096 611.2zM776.42752 611.2l31.0016 0 0-502.88128-31.0016 0L776.42752 611.2zM657.09056 580.1984l0 31.0016 123.04896 0 0-31.0016L657.09056 580.1984zM657.27488 108.35456l0 31.0016 123.04896 0 0-31.0016L657.27488 108.35456z" horiz-adv-x="1024" />
<glyph glyph-name="qq" unicode="&#58889;" d="M147.372058 491.394284c-5.28997-13.909921 2.431986-22.698872 0-75.732573-0.682996-14.25092-62.165649-78.762555-86.569511-145.791177-24.192863-66.517625-27.519845-135.978232 9.811944-163.285078 37.419789-27.305846 72.191593 90.879487 76.757567 73.685584 1.961989-7.509958 4.436975-15.317914 7.423958-23.338868a331.945126 331.945126 0 0 1 61.140655-101.162429c5.929967-6.783962-36.009797-19.199892-61.140655-61.99365-25.173858-42.751759 7.209959-120.49032 132.223254-120.49032 161.27909 0 197.288886 56.70368 200.574868 56.447681 12.031932-0.895995 12.841928 0 25.599855 0 15.572912 0 9.129948-1.279993 23.593867 0 7.807956 0.682996 86.186514-67.839617 194.686901-56.447681 184.873956 19.45589 156.586116 81.40754 142.079198 120.48932-15.103915 40.83277-68.692612 59.946662-66.303626 62.549647 44.28775 48.938724 51.285711 79.018554 66.346626 123.9463 6.143965 18.473896 49.066723-101.674426 82.089537-73.685584 13.781922 11.690934 41.301767 60.24566 13.781922 163.285078-27.519845 102.996419-80.767544 126.505286-79.615551 145.791177 2.389987 40.191773 1.023994 68.436614-1.023994 75.732573-9.812945 35.4128-30.378829 27.604844-30.378829 35.4128C858.450044 730.752933 705.10691 896 515.966978 896s-342.398067-165.289067-342.398068-369.192916c0-16.169909-14.378919-4.223976-26.154852-35.4128z" horiz-adv-x="1024" />
<glyph glyph-name="frown" unicode="&#59262;" d="M336 475m-48 0a48 48 0 1 1 96 0 48 48 0 1 1-96 0ZM688 475m-48 0a48 48 0 1 1 96 0 48 48 0 1 1-96 0ZM512 832C264.6 832 64 631.4 64 384s200.6-448 448-448 448 200.6 448 448S759.4 832 512 832z m263-711c-34.2-34.2-74-61-118.3-79.8C611 21.8 562.3 12 512 12c-50.3 0-99 9.8-144.8 29.2-44.3 18.7-84.1 45.6-118.3 79.8-34.2 34.2-61 74-79.8 118.3C149.8 285 140 333.7 140 384s9.8 99 29.2 144.8c18.7 44.3 45.6 84.1 79.8 118.3 34.2 34.2 74 61 118.3 79.8C413 746.2 461.7 756 512 756c50.3 0 99-9.8 144.8-29.2 44.3-18.7 84.1-45.6 118.3-79.8 34.2-34.2 61-74 79.8-118.3C874.2 483 884 434.3 884 384s-9.8-99-29.2-144.8c-18.7-44.3-45.6-84.1-79.8-118.2zM512 363c-85.5 0-155.6-67.3-160-151.6-0.2-4.6 3.4-8.4 8-8.4h48.1c4.2 0 7.8 3.2 8.1 7.4C420 259.9 461.5 299 512 299s92.1-39.1 95.8-88.6c0.3-4.2 3.9-7.4 8.1-7.4H664c4.6 0 8.2 3.8 8 8.4-4.4 84.3-74.5 151.6-160 151.6z" horiz-adv-x="1024" />
<glyph glyph-name="meh" unicode="&#59264;" d="M336 475m-48 0a48 48 0 1 1 96 0 48 48 0 1 1-96 0ZM688 475m-48 0a48 48 0 1 1 96 0 48 48 0 1 1-96 0ZM512 832C264.6 832 64 631.4 64 384s200.6-448 448-448 448 200.6 448 448S759.4 832 512 832z m263-711c-34.2-34.2-74-61-118.3-79.8C611 21.8 562.3 12 512 12c-50.3 0-99 9.8-144.8 29.2-44.3 18.7-84.1 45.6-118.3 79.8-34.2 34.2-61 74-79.8 118.3C149.8 285 140 333.7 140 384s9.8 99 29.2 144.8c18.7 44.3 45.6 84.1 79.8 118.3 34.2 34.2 74 61 118.3 79.8C413 746.2 461.7 756 512 756c50.3 0 99-9.8 144.8-29.2 44.3-18.7 84.1-45.6 118.3-79.8 34.2-34.2 61-74 79.8-118.3C874.2 483 884 434.3 884 384s-9.8-99-29.2-144.8c-18.7-44.3-45.6-84.1-79.8-118.2zM664 331H360c-4.4 0-8-3.6-8-8v-48c0-4.4 3.6-8 8-8h304c4.4 0 8 3.6 8 8v48c0 4.4-3.6 8-8 8z" horiz-adv-x="1024" />
<glyph glyph-name="smile" unicode="&#59267;" d="M336 475m-48 0a48 48 0 1 1 96 0 48 48 0 1 1-96 0ZM688 475m-48 0a48 48 0 1 1 96 0 48 48 0 1 1-96 0ZM512 832C264.6 832 64 631.4 64 384s200.6-448 448-448 448 200.6 448 448S759.4 832 512 832z m263-711c-34.2-34.2-74-61-118.3-79.8C611 21.8 562.3 12 512 12c-50.3 0-99 9.8-144.8 29.2-44.3 18.7-84.1 45.6-118.3 79.8-34.2 34.2-61 74-79.8 118.3C149.8 285 140 333.7 140 384s9.8 99 29.2 144.8c18.7 44.3 45.6 84.1 79.8 118.3 34.2 34.2 74 61 118.3 79.8C413 746.2 461.7 756 512 756c50.3 0 99-9.8 144.8-29.2 44.3-18.7 84.1-45.6 118.3-79.8 34.2-34.2 61-74 79.8-118.3C874.2 483 884 434.3 884 384s-9.8-99-29.2-144.8c-18.7-44.3-45.6-84.1-79.8-118.2zM664 363h-48.1c-4.2 0-7.8-3.2-8.1-7.4C604 306.1 562.5 267 512 267s-92.1 39.1-95.8 88.6c-0.3 4.2-3.9 7.4-8.1 7.4H360c-4.6 0-8.2-3.8-8-8.4 4.4-84.3 74.5-151.6 160-151.6s155.6 67.3 160 151.6c0.2 4.6-3.4 8.4-8 8.4z" horiz-adv-x="1024" />
<glyph glyph-name="man" unicode="&#59362;" d="M874 776H622c-3.3 0-6-2.7-6-6v-56c0-3.3 2.7-6 6-6h160.4L583.1 508.7c-50 38.5-111 59.3-175.1 59.3-76.9 0-149.3-30-203.6-84.4S120 356.9 120 280s30-149.3 84.4-203.6C258.7 22 331.1-8 408-8s149.3 30 203.6 84.4C666 130.7 696 203.1 696 280c0 64.1-20.8 124.9-59.2 174.9L836 654.1V494c0-3.3 2.7-6 6-6h56c3.3 0 6 2.7 6 6V746c0 16.5-13.5 30-30 30zM408 68c-116.9 0-212 95.1-212 212s95.1 212 212 212 212-95.1 212-212-95.1-212-212-212z" horiz-adv-x="1024" />
<glyph glyph-name="woman" unicode="&#59365;" d="M909.7 739.4l-42.2 42.2c-3.1 3.1-8.2 3.1-11.3 0L764 689.4l-84.2 84.2c-3.1 3.1-8.2 3.1-11.3 0l-42.1-42.1c-3.1-3.1-3.1-8.1 0-11.3l84.2-84.2-135.5-135.3c-50 38.5-111 59.3-175.1 59.3-76.9 0-149.3-30-203.6-84.4S112 348.9 112 272s30-149.3 84.4-203.6C250.7 14 323.1-16 400-16s149.3 30 203.6 84.4C658 122.7 688 195.1 688 272c0 64.2-20.9 125.1-59.3 175.1l135.4 135.4 84.2-84.2c3.1-3.1 8.2-3.1 11.3 0l42.1 42.1c3.1 3.1 3.1 8.1 0 11.3l-84.2 84.2 92.2 92.2c3.1 3.1 3.1 8.2 0 11.3zM400 60c-116.9 0-212 95.1-212 212s95.1 212 212 212 212-95.1 212-212-95.1-212-212-212z" horiz-adv-x="1024" />
</font>
</defs></svg>
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1547543867554" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16276" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400"><defs><style type="text/css"></style></defs><path d="M253.44 577.536c2.56 18.432 5.12 36.352 7.68 55.296 15.36-11.776 30.208-23.04 45.056-34.304-1.536-2.56-43.008-18.944-52.736-20.992zM610.304 577.536c2.56 18.432 5.12 36.352 7.68 55.296 15.36-11.776 30.208-23.04 45.056-34.304-2.048-2.56-43.008-18.944-52.736-20.992z" fill="#DD6DA6" p-id="16277"></path><path d="M512 0C229.376 0 0 229.376 0 512s229.376 512 512 512 512-229.376 512-512S794.624 0 512 0z m-29.184 444.928h16.896c3.072 0 4.096 1.024 4.096 4.608v53.76c-6.656-0.512-13.824-0.512-20.992-1.024V444.928z m-115.712-6.656c2.048 0 3.072 1.024 3.584 3.584 1.536 17.92 3.584 35.328 5.12 53.76-6.656 1.024-13.312 1.024-20.48 1.536-2.56-19.456-5.12-38.912-7.168-58.368 6.144-0.512 12.8-0.512 18.944-0.512z m-27.648 3.072c2.56-0.512 2.048 2.048 2.56 3.584 0.512 6.656 1.536 12.8 2.048 19.456 1.024 10.24 2.56 19.968 3.584 30.208v4.096l-15.872 2.56c-3.584-18.944-6.656-37.376-10.24-56.832 5.632-1.024 11.776-2.56 17.92-3.072z m-3.072 184.32c-2.56 8.192-8.192 14.848-15.36 20.48-16.896 13.312-34.304 25.088-54.784 31.744-14.336 5.12-29.184 8.192-44.032 10.752-19.456 3.072-39.424 4.096-59.392 6.144-4.096 0.512-8.192 0-12.288 0-2.048 0-2.56-1.024-2.56-3.072 0-8.704-1.024-17.92-1.536-27.136-1.024-11.776-2.048-23.04-3.584-34.816-2.048-16.384-4.096-32.256-5.632-48.64-2.048-16.896-4.096-34.304-6.144-51.2-2.048-16.896-3.584-33.28-5.632-49.664-2.56-18.944-5.12-37.376-8.192-56.832-3.072-22.016-7.168-44.544-12.8-66.56-0.512-1.024 0.512-3.072 1.024-3.584 19.456-7.168 38.4-14.848 57.856-23.04 6.144-2.56 6.656-3.584 6.656 4.608 0 34.304 0 68.608 0.512 103.424 0.512 17.92 1.024 35.84 2.56 53.76 1.536 21.504 4.096 43.008 5.632 65.024 0 1.024 0.512 1.536 0.512 2.56 7.168-0.512 14.848-1.024 22.016-1.024 30.72-1.024 60.416 3.584 89.088 14.848 13.824 5.632 27.648 12.288 39.936 20.992 9.216 7.68 11.776 17.408 6.144 31.232z m26.112 44.544c-10.24-50.688-19.968-100.352-29.696-151.04 8.192-1.024 15.872-2.56 23.552-3.584 5.632-1.024 11.264-1.024 16.896-1.536 3.072-0.512 5.12 1.024 5.632 4.096 1.024 7.68 2.56 15.36 3.584 23.552 1.536 13.312 3.072 26.624 4.096 39.936 1.536 11.776 2.56 23.04 4.096 34.816 1.536 12.288 3.072 24.576 4.608 37.376 0.512 3.584 1.024 7.168 1.536 11.264-11.776 2.048-22.528 3.584-34.304 5.12zM445.44 655.36c-10.24-1.024-19.968-1.536-30.72-2.56-9.216-92.672-16.896-185.856-34.304-279.04 5.632-0.512 11.264-1.536 17.408-2.048 6.656-0.512 13.312-1.024 20.48-1.536 5.632-0.512 7.68 1.024 8.192 6.656l3.072 44.032c1.536 19.968 3.072 39.936 4.096 59.392 1.024 14.336 1.536 28.672 3.072 43.008 1.536 15.872 3.072 31.232 4.096 47.104 1.536 11.264 2.56 23.04 3.584 34.304 1.536 12.288 2.56 24.576 3.584 37.376l1.024 10.752c-0.512 2.56-1.536 3.072-3.584 2.56z m27.136-210.944c2.56 0 3.584 0.512 3.584 3.584-0.512 7.68 0 15.872 0 23.552v29.696c-5.632 0.512-11.264 1.024-16.384 1.536-1.536-18.944-3.072-37.888-4.608-58.368h17.408z m2.56 228.864c-1.024 0-2.56-1.536-2.56-2.56l-4.608-50.688c-1.536-15.36-3.072-31.744-4.096-47.104-1.536-15.872-3.072-32.256-4.096-48.64 0-1.024 0-2.048-0.512-3.584 4.096-1.024 7.168-1.536 11.264-1.536 10.24 0 20.48 0 30.72 0.512 2.56 0 4.096 2.048 4.096 4.096 0 5.12 0.512 10.24 0.512 15.36 0 23.552-0.512 48.128 0 71.68 0.512 19.456 1.024 37.888 1.536 57.344 0 1.024 0 2.56 0.512 4.608-11.264 0.512-22.016 1.024-32.768 0.512zM896 444.928h16.896c3.072 0 4.096 1.024 4.096 4.608v53.76c-6.656-0.512-13.824-0.512-20.992-1.024V444.928z m-116.224-6.656c2.048 0 3.072 1.024 3.584 3.584 1.536 17.92 3.584 35.328 5.12 53.76-6.656 1.024-13.312 1.024-20.48 1.536-2.56-19.456-5.12-38.912-7.168-58.368 6.656-0.512 12.8-0.512 18.944-0.512z m-27.648 3.072c2.56-0.512 2.048 2.048 2.56 3.584 0.512 6.656 1.536 12.8 2.048 19.456 1.024 10.24 2.56 19.968 3.584 30.208v4.096l-15.872 2.56c-3.584-18.944-6.656-37.376-10.24-56.832 5.632-1.024 11.776-2.56 17.92-3.072z m-3.072 183.808c-2.56 8.192-8.192 14.848-15.36 20.48-16.896 13.312-34.304 25.088-54.784 31.744-14.336 5.12-29.184 8.192-44.032 10.752-19.456 3.072-39.424 4.096-59.392 6.144-4.096 0.512-8.192 0-12.288 0-2.048 0-2.56-1.024-2.56-3.072 0-8.704-1.024-17.92-1.536-27.136-1.024-11.776-2.048-23.04-3.584-34.816-2.048-16.384-4.096-32.256-5.632-48.64-2.048-16.896-4.096-34.304-6.144-51.2-2.048-16.896-3.584-33.28-5.632-49.664-2.56-18.944-5.12-37.376-8.192-56.832-3.072-22.016-7.168-44.544-12.8-66.56 0-1.024 1.024-3.072 1.536-3.584 19.456-7.168 38.4-14.848 57.856-23.04 6.144-2.56 6.656-3.584 6.656 4.608 0 34.304 0 68.608 0.512 103.424 0.512 17.92 1.024 35.84 2.56 53.76 1.536 21.504 4.096 43.008 5.632 65.024 0 1.024 0.512 1.536 0.512 2.56 7.168-0.512 14.848-1.024 22.016-1.024 30.72-1.024 60.416 3.584 89.088 14.848 13.824 5.632 27.648 12.288 39.936 20.992 9.216 8.192 11.264 17.92 5.632 31.232z m26.112 45.056c-10.24-50.688-19.968-100.352-29.696-151.04 8.192-1.024 15.872-2.56 23.552-3.584 5.632-1.024 11.264-1.024 16.896-1.536 3.072-0.512 5.12 1.024 5.632 4.096 1.024 7.68 2.56 15.36 3.584 23.552 1.536 13.312 3.072 26.624 4.096 39.936 1.536 11.776 2.56 23.04 4.096 34.816 1.536 12.288 3.072 24.576 4.608 37.376 0.512 3.584 1.024 7.168 1.536 11.264-11.264 2.048-22.528 3.584-34.304 5.12z m83.456-14.848c-10.24-1.024-19.968-1.536-30.72-2.56-9.216-92.672-16.896-185.856-34.304-279.04 5.632-0.512 11.264-1.536 17.408-2.048 6.656-0.512 13.312-1.024 20.48-1.536 5.632-0.512 7.68 1.024 8.192 6.656l3.072 44.032c1.536 19.968 3.072 39.936 4.096 59.392 1.024 14.336 1.536 28.672 3.072 43.008 1.536 15.872 3.072 31.232 4.096 47.104 1.536 11.264 2.56 23.04 3.584 34.304 1.536 12.288 2.56 24.576 3.584 37.376l1.024 10.752c-1.024 2.56-1.536 3.072-3.584 2.56z m26.624-210.944c2.56 0 3.584 0.512 3.584 3.584-0.512 7.68 0 15.872 0 23.552v29.696c-5.632 0.512-11.264 1.024-16.384 1.536-1.536-18.944-3.072-37.888-4.608-58.368h17.408z m3.072 228.864c-1.024 0-2.56-1.536-2.56-2.56l-4.608-50.688c-1.536-15.36-3.072-31.744-4.096-47.104-1.536-15.872-3.072-32.256-4.096-48.64 0-1.024 0-2.048-0.512-3.584 4.096-1.024 7.168-1.536 11.264-1.536 10.24 0 20.48 0 30.72 0.512 2.56 0 4.096 2.048 4.096 4.096 0 5.12 0.512 10.24 0.512 15.36 0 23.552-0.512 48.128 0 71.68 0.512 19.456 1.024 37.888 1.536 57.344 0 1.024 0 2.56 0.512 4.608-11.776 0.512-22.528 1.024-32.768 0.512z" fill="#DD6DA6" p-id="16278"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1547543883733" class="icon" style="" viewBox="0 0 1272 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16831" xmlns:xlink="http://www.w3.org/1999/xlink" width="496.875" height="400"><defs><style type="text/css"></style></defs><path d="M729.64116345 165.27693991L634.32650881 90.125l-99.5625 78.52693991-5.17887981 4.16056009 104.74137981 83.50215546 105.09051682-83.50215546-9.77586218-7.53556009z m361.21228445 291.47198236l-456.78879245 360.19396555-456.49784537-359.99030128L110.125 511.12715547l523.93965546 413.11745671 524.23060335-413.35021555-67.44181091-54.14547436z m-456.78879245 29.21120673L385.4784479 290.00646554 318.06573237 344.12284454l315.96982771 249.16810336 316.28987101-249.40086136-67.41271555-54.14547436-248.84806008 196.21551682z" fill="#006cff" p-id="16832"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1547543874130" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16498" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400"><defs><style type="text/css"></style></defs><path d="M511.8 0.6C229.2 0.6 0.1 229.7 0.1 512.3S229.2 1024 511.8 1024s511.7-229.1 511.7-511.7S794.4 0.6 511.8 0.6z m264.9 375.3c-0.1 0.1-0.1 0.2-0.2 0.3h0.2c-9 14.3-21.2 28.8-34.2 39.2-5.2 4.2-10.5 8.3-15.7 12.5v0.5c0.3 22.9-0.3 44.9-4.7 64.2-25.2 113.1-91.9 189.9-197.4 222.8-37.9 11.8-99.1 16.7-142.6 5.9-21.5-5.4-41-11.4-59.3-19.3-10.2-4.5-19.6-9.3-28.5-14.7l-8.8-5.3h0.5l-0.5-0.3c9.8 0.2 21.3 2.9 32.2 1.2 9.8-1.6 19.6-1.2 28.7-3.2 22.7-5 43-11.6 60.4-21.8 8.3-4.8 20.9-10.6 27-17.6-11.2 0.2-21.4-2.4-29.7-5.4-32.6-11.5-51.6-32.7-63.9-64.4h0.1c0-0.1-0.1-0.2-0.1-0.3 9.7 1.1 37.3 3.5 44.6-1.7-12.3-0.8-24.1-7.9-32.5-13.2-26.2-16.4-47.6-43.9-47.4-86.2v-0.3c3.5 1.7 6.9 3.3 10.4 4.9 6.6 2.8 13.3 4.3 21.1 5.9 3.1 0.7 9.3 2.4 13.2 1.4-5.1-5.8-13.2-9.7-18.4-16-14.7-18.3-28.7-45.3-25.2-77.4 0.5-4.7 1.3-9.4 2.6-14.3 2.5-9.7 6.5-18.3 10.8-26.2 0.2 0.1 0.4 0.2 0.5 0.3 2 4.2 6.4 7.2 9.1 10.6 8.6 10.7 19.2 20.3 30 28.7 36.7 28.7 69.9 46.4 123.1 59.5 13.5 3.3 29 5.9 45.1 5.9-1.9-5.8-2.7-13-2.7-20.5 0-9.7 1.3-19.6 3.3-26.8 9-32.1 28.5-55.2 57-67.6 6.9-3 14.4-5.2 22.4-6.9 4.1-0.6 8.2-1.1 12.3-1.6 39-0.7 59.8 13.5 79.6 31.6 16.8-1.4 38.7-10.8 51.6-17.4l12.6-6.9c0 0.1-0.1 0.3-0.1 0.4l0.1-0.1c-7.3 19.9-17.4 35.5-32.7 47.3-3.1 2.4-6.3 5.6-10 7.4 21.5-0.4 39.3-10 56.1-15.4v0.3z" fill="#2EB1EB" p-id="16499"></path><path d="M719.7 391.1s0.1 0 0.1-0.1c0 0-0.1 0-0.1 0.1zM726.8 428.4v-0.5 0.5zM336.4 479.9c3.3 0.7 9.9 2.7 13.8 1.2h-0.5l-0.1-0.1c-3.9 1-10.1-0.7-13.2-1.4-7.8-1.6-14.5-3.1-21.1-5.9-3.5-1.6-6.9-3.2-10.4-4.9v0.3c3.4 1.6 6.9 3.2 10.4 4.9 6.6 2.8 13.3 4.3 21.1 5.9zM719.6 391.4v0.2c21.9-0.2 39.8-10.1 56.9-15.4 0.1-0.1 0.1-0.2 0.2-0.3v-0.3c-16.9 5.3-34.6 14.9-56.1 15.4-0.4 0.1-0.7 0.3-1 0.4zM584.8 337.5c6.9-3 14.4-5.1 22.4-6.9 4.1-0.6 8.2-1 12.3-1.6 39-0.7 59.8 13.5 79.6 31.6 16.8-1.4 38.7-10.8 51.6-17.4 4.2-2.3 8.3-4.5 12.5-6.8 0-0.1 0.1-0.3 0.1-0.4l-12.6 6.9c-12.9 6.6-34.8 16-51.6 17.4-19.8-18.1-40.6-32.3-79.6-31.6-4.1 0.5-8.2 1-12.3 1.6-8 1.7-15.5 3.9-22.4 6.9-28.5 12.4-48 35.5-57 67.6-2 7.2-3.4 17.2-3.3 26.8 0-9.6 1.3-19.4 3.3-26.5 9-32.1 28.5-55.2 57-67.6zM385.2 568.5h-0.4c-7.3 5.2-34.9 2.8-44.6 1.7 0 0.1 0.1 0.2 0.1 0.3 10 1.1 38.2 3.6 44.9-2zM319.4 347.4c0.1 0.1 0.3 0.1 0.5 0.3 2 4.1 6.3 7.1 9.1 10.6 8.6 10.6 19.2 20.2 30 28.7 36.8 28.7 69.9 46.4 123.1 59.5 13.5 3.3 29.1 5.9 45.2 5.9 0-0.1-0.1-0.2-0.1-0.3-16.1 0-31.6-2.6-45.1-5.9-53.2-13.1-86.4-30.8-123.1-59.5-10.8-8.4-21.4-18-30-28.7-2.7-3.4-7.1-6.4-9.1-10.6-0.1-0.1-0.3-0.2-0.5-0.3-4.3 7.9-8.3 16.5-10.8 26.2-1.3 4.9-2.1 9.6-2.6 14.3 0.5-4.6 1.3-9.2 2.6-14 2.5-9.7 6.5-18.3 10.8-26.2zM317.7 683.2c9.9-1.6 19.6-1.2 28.7-3.2 22.8-5 43-11.6 60.4-21.8 8.4-4.9 21.3-10.8 27.3-17.9h-0.3c-6.1 7-18.7 12.8-27 17.6-17.4 10.2-37.7 16.8-60.4 21.8-9.1 2-18.9 1.6-28.7 3.2-10.9 1.7-22.4-1-32.2-1.2l0.5 0.3c9.7 0.4 21 2.9 31.7 1.2z" fill="#FFFFFF" p-id="16500"></path></svg>
\ No newline at end of file
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1547543863835" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16165" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400"><defs><style type="text/css"></style></defs><path d="M512 1024C229.236364 1024 0 794.763636 0 512S229.236364 0 512 0s512 229.236364 512 512-229.236364 512-512 512z m-129.861818-756.48s-36.212364 2.094545-48.989091 24.482909c-12.8 22.365091-54.318545 137.378909-54.318546 137.378909s13.847273 6.376727 37.28291-10.658909c23.435636-17.035636 30.882909-46.848 30.882909-46.848l42.589091-2.117818 1.070545 121.390545s-73.495273-1.070545-88.413091 0c-14.894545 1.047273-23.412364 40.448-23.412364 40.448h111.825455s-9.588364 67.095273-38.353455 116.084364c-28.741818 48.989091-83.060364 87.319273-83.060363 87.319273s39.424 15.965091 77.730909-6.4c38.353455-22.341818 66.629818-120.692364 66.629818-120.692364l89.925818 110.056727s8.192-52.386909-1.466182-67.188363c-9.658182-14.778182-62.208-74.286545-62.208-74.286546l-22.946909 20.247273 16.337455-65.117091h97.954909s0-38.353455-19.153455-40.494545c-19.176727-2.094545-78.801455 0-78.801454 0V371.898182h88.389818s-1.070545-39.400727-18.106182-39.400727h-143.755636l22.341818-64.954182z m169.984 61.184v358.562909h36.002909l13.102545 45.009455 63.348364-45.009455h89.064727V328.704h-201.518545z" fill="#0f84fd" p-id="16166"></path><path d="M594.781091 368.64h117.899636v277.876364h-41.890909l-53.364363 40.261818-11.636364-40.261818h-11.008V368.64z" fill="#0f84fd" p-id="16167"></path></svg>
\ No newline at end of file
<template>
<div ref="dom" class="charts chart-bar"></div>
</template>
<script>
import echarts from 'echarts'
import tdTheme from './theme.json'
import { on, off } from '@/libs/tools'
echarts.registerTheme('tdTheme', tdTheme)
export default {
name: 'ChartBar',
props: {
value: Object,
text: String,
subtext: String
},
data () {
return {
dom: null
}
},
methods: {
resize () {
this.dom.resize()
}
},
mounted () {
this.$nextTick(() => {
let xAxisData = Object.keys(this.value)
let seriesData = Object.values(this.value)
let option = {
title: {
text: this.text,
subtext: this.subtext,
x: 'center'
},
xAxis: {
type: 'category',
data: xAxisData
},
yAxis: {
type: 'value'
},
series: [{
data: seriesData,
type: 'bar'
}]
}
this.dom = echarts.init(this.$refs.dom, 'tdTheme')
this.dom.setOption(option)
on(window, 'resize', this.resize)
})
},
beforeDestroy () {
off(window, 'resize', this.resize)
}
}
</script>
import ChartPie from './pie.vue'
import ChartBar from './bar.vue'
export { ChartPie, ChartBar }
<template>
<div ref="dom" class="charts chart-pie"></div>
</template>
<script>
import echarts from 'echarts'
import tdTheme from './theme.json'
import { on, off } from '@/libs/tools'
echarts.registerTheme('tdTheme', tdTheme)
export default {
name: 'ChartPie',
props: {
value: Array,
text: String,
subtext: String
},
data () {
return {
dom: null
}
},
methods: {
resize () {
this.dom.resize()
}
},
mounted () {
this.$nextTick(() => {
let legend = this.value.map(_ => _.name)
let option = {
title: {
text: this.text,
subtext: this.subtext,
x: 'center'
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left',
data: legend
},
series: [
{
type: 'pie',
radius: '55%',
center: ['50%', '60%'],
data: this.value,
itemStyle: {
emphasis: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
}
this.dom = echarts.init(this.$refs.dom, 'tdTheme')
this.dom.setOption(option)
on(window, 'resize', this.resize)
})
},
beforeDestroy () {
off(window, 'resize', this.resize)
}
}
</script>
<template>
<component :is="iconType" :type="iconName" :color="iconColor" :size="iconSize"/>
</template>
<script>
import Icons from '_c/icons'
export default {
name: 'CommonIcon',
components: { Icons },
props: {
type: {
type: String,
required: true
},
color: String,
size: Number
},
computed: {
iconType () {
return this.type.indexOf('_') === 0 ? 'Icons' : 'Icon'
},
iconName () {
return this.iconType === 'Icons' ? this.getCustomIconName(this.type) : this.type
},
iconSize () {
return this.size || (this.iconType === 'Icons' ? 12 : undefined)
},
iconColor () {
return this.color || ''
}
},
methods: {
getCustomIconName (iconName) {
return iconName.slice(1)
}
}
}
</script>
<style>
</style>
import CommonIcon from './common-icon.vue'
export default CommonIcon
.no-select{
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
export const showTitle = (item, vm) => {
return vm.$config.useI18n ? vm.$t(item.name) : ((item.meta && item.meta.title) || item.name)
}
<template>
<div class="count-to-wrapper">
<slot name="left"/>
<p class="content-outer"><span :class="['count-to-count-text', countClass]" :id="counterId">{{ init }}</span><i :class="['count-to-unit-text', unitClass]">{{ unitText }}</i></p>
<slot name="right"/>
</div>
</template>
<script>
import CountUp from 'countup'
import './index.less'
export default {
name: 'CountTo',
props: {
init: {
type: Number,
default: 0
},
/**
* @description 起始值,即动画开始前显示的数值
*/
startVal: {
type: Number,
default: 0
},
/**
* @description 结束值,即动画结束后显示的数值
*/
end: {
type: Number,
required: true
},
/**
* @description 保留几位小数
*/
decimals: {
type: Number,
default: 0
},
/**
* @description 分隔整数和小数的符号,默认是小数点
*/
decimal: {
type: String,
default: '.'
},
/**
* @description 动画持续的时间,单位是秒
*/
duration: {
type: Number,
default: 2
},
/**
* @description 动画延迟开始的时间,单位是秒
*/
delay: {
type: Number,
default: 0
},
/**
* @description 是否禁用easing动画效果
*/
uneasing: {
type: Boolean,
default: false
},
/**
* @description 是否使用分组,分组后每三位会用一个符号分隔
*/
usegroup: {
type: Boolean,
default: false
},
/**
* @description 用于分组(usegroup)的符号
*/
separator: {
type: String,
default: ','
},
/**
* @description 是否简化显示,设为true后会使用unit单位来做相关省略
*/
simplify: {
type: Boolean,
default: false
},
/**
* @description 自定义单位,如[3, 'K+'], [6, 'M+']即大于3位数小于6位数的用k+来做省略
* 1000即显示为1K+
*/
unit: {
type: Array,
default () {
return [[3, 'K+'], [6, 'M+'], [9, 'B+']]
}
},
countClass: {
type: String,
default: ''
},
unitClass: {
type: String,
default: ''
}
},
data () {
return {
counter: null,
unitText: ''
}
},
computed: {
counterId () {
return `count_to_${this._uid}`
}
},
methods: {
getHandleVal (val, len) {
return {
endVal: parseInt(val / Math.pow(10, this.unit[len - 1][0])),
unitText: this.unit[len - 1][1]
}
},
transformValue (val) {
let len = this.unit.length
let res = {
endVal: 0,
unitText: ''
}
if (val < Math.pow(10, this.unit[0][0])) res.endVal = val
else {
for (let i = 1; i < len; i++) {
if (val >= Math.pow(10, this.unit[i - 1][0]) && val < Math.pow(10, this.unit[i][0])) res = this.getHandleVal(val, i)
}
}
if (val > Math.pow(10, this.unit[len - 1][0])) res = this.getHandleVal(val, len)
return res
},
getValue (val) {
let res = 0
if (this.simplify) {
let { endVal, unitText } = this.transformValue(val)
this.unitText = unitText
res = endVal
} else {
res = val
}
return res
}
},
mounted () {
this.$nextTick(() => {
let endVal = this.getValue(this.end)
this.counter = new CountUp(this.counterId, this.startVal, endVal, this.decimals, this.duration, {
useEasing: !this.uneasing,
useGrouping: this.useGroup,
separator: this.separator,
decimal: this.decimal
})
setTimeout(() => {
if (!this.counter.error) this.counter.start()
}, this.delay)
})
},
watch: {
end (newVal) {
let endVal = this.getValue(newVal)
this.counter.update(endVal)
}
}
}
</script>
import countTo from './count-to.vue'
export default countTo
@prefix: ~"count-to";
.@{prefix}-wrapper{
.content-outer{
display: inline-block;
.@{prefix}-unit-text{
font-style: normal;
}
}
}
import Cropper from './index.vue'
export default Cropper
.bg{
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC")
}
.cropper-wrapper{
width: 600px;
height: 340px;
.img-box{
height: 340px;
width: 430px;
border: 1px solid #ebebeb;
display: inline-block;
.bg;
img{
max-width: 100%;
display: block;
}
}
.right-con{
display: inline-block;
width: 170px;
vertical-align: top;
box-sizing: border-box;
padding: 0 10px;
.preview-box{
height: 150px !important;
width: 100% !important;
overflow: hidden;
border: 1px solid #ebebeb;
.bg;
}
.button-box{
padding: 10px 0 0;
}
}
}
<template>
<div class="cropper-wrapper">
<div class="img-box">
<img class="cropper-image" :id="imgId" alt="">
</div>
<div class="right-con">
<div v-if="preview" class="preview-box" :id="previewId"></div>
<div class="button-box">
<slot>
<Upload action="image/upload" :before-upload="beforeUpload">
<Button style="width: 150px;" type="primary">上传图片</Button>
</Upload>
</slot>
<div v-show="insideSrc">
<Button type="primary" @click="rotate">
<Icon type="md-refresh" :size="18"/>
</Button>
<Button type="primary" @click="shrink">
<Icon type="md-remove" :size="18"/>
</Button>
<Button type="primary" @click="magnify">
<Icon type="md-add" :size="18"/>
</Button>
<Button type="primary" @click="scale('X')">
<Icon custom="iconfont icon-shuipingfanzhuan" :size="18"/>
</Button>
<Button type="primary" @click="scale('Y')">
<Icon custom="iconfont icon-chuizhifanzhuan" :size="18"/>
</Button>
<Button type="primary" @click="move(0, -moveStep)">
<Icon type="md-arrow-round-up" :size="18"/>
</Button>
<Button type="primary" @click="move(-moveStep, 0)">
<Icon type="md-arrow-round-back" :size="18"/>
</Button>
<Button type="primary" @click="move(0, moveStep)">
<Icon type="md-arrow-round-down" :size="18"/>
</Button>
<Button type="primary" @click="move(moveStep, 0)">
<Icon type="md-arrow-round-forward" :size="18"/>
</Button>
<Button style="width: 150px;margin-top: 10px;" type="primary" @click="crop">{{ cropButtonText }}</Button>
</div>
</div>
</div>
</div>
</template>
<script>
import Cropper from 'cropperjs'
import './index.less'
import 'cropperjs/dist/cropper.min.css'
export default {
name: 'Cropper',
props: {
src: {
type: String,
default: ''
},
preview: {
type: Boolean,
default: true
},
moveStep: {
type: Number,
default: 4
},
cropButtonText: {
type: String,
default: '裁剪'
}
},
data () {
return {
cropper: null,
insideSrc: ''
}
},
computed: {
imgId () {
return `cropper${this._uid}`
},
previewId () {
return `cropper_preview${this._uid}`
}
},
watch: {
src (src) {
this.replace(src)
},
insideSrc (src) {
this.replace(src)
}
},
methods: {
beforeUpload (file) {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = (event) => {
this.insideSrc = event.srcElement.result
}
return false
},
replace (src) {
this.cropper.replace(src)
this.insideSrc = src
},
rotate () {
this.cropper.rotate(90)
},
shrink () {
this.cropper.zoom(-0.1)
},
magnify () {
this.cropper.zoom(0.1)
},
scale (d) {
this.cropper[`scale${d}`](-this.cropper.getData()[`scale${d}`])
},
move (...argu) {
this.cropper.move(...argu)
},
crop () {
this.cropper.getCroppedCanvas().toBlob(blob => {
this.$emit('on-crop', blob)
})
}
},
mounted () {
this.$nextTick(() => {
let dom = document.getElementById(this.imgId)
this.cropper = new Cropper(dom, {
preview: `#${this.previewId}`,
checkCrossOrigin: true
})
})
}
}
</script>
<template>
<div :class="`${prefix}-move-trigger`">
<div :class="`${prefix}-move-trigger-point`">
<i></i><i></i><i></i><i></i><i></i>
</div>
</div>
</template>
<script>
import Mixin from './mixin'
export default {
name: 'DragDrawerTrigger',
mixins: [Mixin]
}
</script>
<style>
</style>
<template>
<Drawer ref="drawerWrapper"
:value="value"
@input="handleInput"
:width="width"
:class-name="outerClasses"
v-bind="$attrs"
v-on="$listeners">
<!-- 所有插槽内容显示在这里 ↓ -->
<template v-for="(slots, slotsName) in $slots">
<template v-if="slotsName !== 'default'">
<render-dom v-for="(render, index) in slots"
:key="`b_drawer_${slotsName}_${index}`"
:render="() => render"
:slot="slotsName">
</render-dom>
</template>
<template v-else>
<div :class="`${prefix}-body-wrapper`"
:key="`b_drawer_${slotsName}`">
<render-dom v-for="(render, index) in slots"
:key="`b_drawer_${slotsName}_${index}`"
:render="() => render"
:slot="slotsName">
</render-dom>
</div>
</template>
</template>
<!-- 所有插槽内容显示在这里 ↑ -->
<div v-if="draggable"
:style="triggerStyle"
:class="`${prefix}-trigger-wrapper`"
@mousedown="handleTriggerMousedown">
<slot name="trigger">
<drag-drawer-trigger></drag-drawer-trigger>
</slot>
</div>
<div v-if="$slots.footer"
:class="`${prefix}-footer`">
<slot name="footer"></slot>
</div>
</Drawer>
</template>
<script>
import RenderDom from '@/libs/render-dom'
import DragDrawerTrigger from './drag-drawer-trigger.vue'
import Mixin from './mixin'
import { on, off } from '@/libs/tools'
import './index.less'
export default {
name: 'BDrawer',
components: {
RenderDom,
DragDrawerTrigger
},
mixins: [Mixin],
props: {
value: {
type: Boolean,
default: false
},
width: {
type: [String, Number],
default: 256
},
// 是否可拖动修改宽度
draggable: {
type: Boolean,
default: false
},
// 最小拖动宽度
minWidth: {
type: [String, Number],
default: 256
}
},
data () {
return {
canMove: false,
wrapperWidth: 0,
wrapperLeft: 0
}
},
computed: {
outerClasses () {
const classesArray = [
`${this.prefix}-wrapper`,
this.canMove ? 'no-select pointer-events-none' : ''
]
return classesArray.join(' ')
},
placement () {
return this.$attrs.placement
},
innerWidth () {
const width = this.width
return width <= 100 ? (this.wrapperWidth * width) / 100 : width
},
triggerStyle () {
return {
[this.placement]: `${this.innerWidth}px`,
position: this.$attrs.inner ? 'absolute' : 'fixed'
}
}
},
methods: {
handleInput (status) {
this.$emit('input', status)
},
handleTriggerMousedown (event) {
this.canMove = true
this.$emit('on-resize-start')
// 防止鼠标选中抽屉中文字,造成拖动trigger触发浏览器原生拖动行为
window.getSelection().removeAllRanges()
},
handleMousemove (event) {
if (!this.canMove) return
// 更新容器宽度和距离左侧页面距离,如果是window则距左侧距离为0
this.setWrapperWidth()
const left = event.pageX - this.wrapperLeft
// 如果抽屉方向为右边,宽度计算需用容器宽度减去left
let width = this.placement === 'right' ? this.wrapperWidth - left : left
// 限定做小宽度
width = Math.max(width, parseFloat(this.minWidth))
event.atMin = width === parseFloat(this.minWidth)
// 如果当前width不大于100,视为百分比
if (width <= 100) width = (width / this.wrapperWidth) * 100
this.$emit('update:width', parseInt(width))
this.$emit('on-resize', event)
},
handleMouseup (event) {
this.canMove = false
this.$emit('on-resize-end')
},
setWrapperWidth () {
const {
width,
left
} = this.$refs.drawerWrapper.$el.getBoundingClientRect()
this.wrapperWidth = width
this.wrapperLeft = left
}
},
mounted () {
on(document, 'mousemove', this.handleMousemove)
on(document, 'mouseup', this.handleMouseup)
this.setWrapperWidth()
},
beforeDestroy () {
off(document, 'mousemove', this.handleMousemove)
off(document, 'mouseup', this.handleMouseup)
}
}
</script>
import DragDrawer from './drag-drawer.vue'
export default DragDrawer
@prefix: ~"drag-drawer";
@drag-drawer-trigger-height: 100px;
@drag-drawer-trigger-width: 8px;
.@{prefix}-wrapper{
&.no-select{
user-select: none;
}
&.pointer-events-none{
pointer-events: none;
& .@{prefix}-trigger-wrapper{
pointer-events: all;
}
}
.ivu-drawer{
&-header{
overflow: hidden !important;
box-sizing: border-box;
}
&-body{
padding: 0;
overflow: visible;
position: static;
display: flex;
flex-direction: column;
}
}
.@{prefix}-body-wrapper{
width: 100%;
height: 100%;
padding: 16px;
overflow: auto;
}
.@{prefix}-trigger-wrapper{
top: 0;
height: 100%;
width: 0;
.@{prefix}-move-trigger{
position: absolute;
top: 50%;
height: @drag-drawer-trigger-height;
width: @drag-drawer-trigger-width;
background: rgb(243, 243, 243);
transform: translate(-50%, -50%);
border-radius: ~"4px / 6px";
box-shadow: 0 0 1px 1px rgba(0, 0, 0, .2);
line-height: @drag-drawer-trigger-height;
cursor: col-resize;
&-point{
display: inline-block;
width: 50%;
transform: translateX(50%);
i{
display: block;
border-bottom: 1px solid rgb(192, 192, 192);
padding-bottom: 2px;
}
}
}
}
.@{prefix}-footer{
flex-grow: 1;
width: 100%;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
background: #fff;
}
}
export default {
data () {
return {
prefix: 'drag-drawer'
}
}
}
<template>
<div class="drag-list-wrapper">
<div class="drag-list-con con1">
<slot name="left-title"></slot>
<draggable class="drop-box1" :class="dropConClass.left" :options="options" :value="list1" @input="handleListChange($event, 'left')" @end="handleEnd($event, 'left')">
<div class="drag-list-item" v-for="(itemLeft, index) in list1" :key="`drag_li1_${index}`">
<slot name="left" :itemLeft="itemLeft">{{ itemLeft }}</slot>
</div>
</draggable>
</div>
<div class="drag-list-con con2">
<slot name="right-title"></slot>
<draggable class="drop-box2" :class="dropConClass.right" :options="options" :value="list2" @input="handleListChange($event, 'right')" @end="handleEnd($event, 'right')">
<div class="drag-list-item" v-for="(itemRight, index) in list2" :key="`drag_li2_${index}`">
<slot name="right" :itemRight="itemRight">{{ itemRight }}</slot>
</div>
</draggable>
</div>
</div>
</template>
<script>
import draggable from 'vuedraggable'
export default {
name: 'DragList',
components: {
draggable
},
props: {
list1: {
type: Array,
required: true
},
list2: {
type: Array,
default: () => []
},
dropConClass: {
type: Object,
default: () => ({})
}
},
data () {
return {
options: { group: 'drag_list' }
}
},
methods: {
handleListChange (value, type) {
if (type === 'left') this.$emit('update:list1', value)
else this.$emit('update:list2', value)
},
handleEnd (event, type) {
const srcClassName = (event.srcElement || event.target).classList[0]
const targetClassName = event.to.classList[0]
let src = ''
let target = ''
if (srcClassName === targetClassName) {
if (type === 'left') {
src = 'left'
target = 'left'
} else {
src = 'right'
target = 'right'
}
} else {
if (type === 'left') {
src = 'left'
target = 'right'
} else {
src = 'right'
target = 'left'
}
}
this.$emit('on-change', {
src: src,
target: target,
oldIndex: event.oldIndex,
newIndex: event.newIndex
})
}
}
}
</script>
<style lang="less">
.drag-list-wrapper{
height: 100%;
.drag-list-con{
width: 50%;
float: left;
}
}
</style>
import DragList from './drag-list.vue'
export default DragList
<template>
<div class="editor-wrapper">
<div :id="editorId"></div>
</div>
</template>
<script>
import Editor from 'wangeditor'
import 'wangeditor/release/wangEditor.min.css'
import { oneOf } from '@/libs/tools'
export default {
name: 'Editor',
props: {
value: {
type: String,
default: ''
},
/**
* 绑定的值的类型, enum: ['html', 'text']
*/
valueType: {
type: String,
default: 'html',
validator: (val) => {
return oneOf(val, ['html', 'text'])
}
},
/**
* @description 设置change事件触发时间间隔
*/
changeInterval: {
type: Number,
default: 200
},
/**
* @description 是否开启本地存储
*/
cache: {
type: Boolean,
default: true
}
},
computed: {
editorId () {
return `editor${this._uid}`
}
},
methods: {
setHtml (val) {
this.editor.txt.html(val)
}
},
mounted () {
this.editor = new Editor(`#${this.editorId}`)
this.editor.customConfig.onchange = (html) => {
let text = this.editor.txt.text()
if (this.cache) localStorage.editorCache = html
this.$emit('input', this.valueType === 'html' ? html : text)
this.$emit('on-change', html, text)
}
this.editor.customConfig.onchangeTimeout = this.changeInterval
// create这个方法一定要在所有配置项之后调用
this.editor.create()
// 如果本地有存储加载本地存储内容
let html = this.value || localStorage.editorCache
if (html) this.editor.txt.html(html)
}
}
</script>
<style lang="less">
.editor-wrapper *{
z-index: 100 !important;
}
</style>
import Editor from './editor.vue'
export default Editor
<template>
<i :class="`iconfont icon-${type}`" :style="styles"></i>
</template>
<script>
export default {
name: 'Icons',
props: {
type: {
type: String,
required: true
},
color: {
type: String,
default: '#5c6b77'
},
size: {
type: Number,
default: 16
}
},
computed: {
styles () {
return {
fontSize: `${this.size}px`,
color: this.color
}
}
}
}
</script>
<style>
</style>
import Icons from './icons.vue'
export default Icons
import InforCard from './infor-card.vue'
export default InforCard
<template>
<Card :shadow="shadow" class="info-card-wrapper" :padding="0">
<div class="content-con">
<div class="left-area" :style="{background: color, width: leftWidth}">
<common-icon class="icon" :type="icon" :size="iconSize" color="#fff"/>
</div>
<div class="right-area" :style="{width: rightWidth}">
<div>
<slot></slot>
</div>
</div>
</div>
</Card>
</template>
<script>
import CommonIcon from '_c/common-icon'
export default {
name: 'InforCard',
components: {
CommonIcon
},
props: {
left: {
type: Number,
default: 36
},
color: {
type: String,
default: '#2d8cf0'
},
icon: {
type: String,
default: ''
},
iconSize: {
type: Number,
default: 20
},
shadow: {
type: Boolean,
default: false
}
},
computed: {
leftWidth () {
return `${this.left}%`
},
rightWidth () {
return `${100 - this.left}%`
}
}
}
</script>
<style lang="less">
.common{
float: left;
height: 100%;
display: table;
text-align: center;
}
.size{
width: 100%;
height: 100%;
}
.middle-center{
display: table-cell;
vertical-align: middle;
}
.info-card-wrapper{
.size;
overflow: hidden;
.ivu-card-body{
.size;
}
.content-con{
.size;
position: relative;
.left-area{
.common;
& > .icon{
.middle-center;
}
}
.right-area{
.common;
& > div{
.middle-center;
}
}
}
}
</style>
import LoginForm from './login-form.vue'
export default LoginForm
<template>
<Form ref="loginForm" :model="form" :rules="rules" @keydown.enter.native="handleSubmit">
<FormItem prop="userName">
<Input v-model="form.userName" placeholder="请输入用户名">
<span slot="prepend">
<Icon :size="16" type="ios-person"></Icon>
</span>
</Input>
</FormItem>
<FormItem prop="password">
<Input type="password" v-model="form.password" placeholder="请输入密码">
<span slot="prepend">
<Icon :size="14" type="md-lock"></Icon>
</span>
</Input>
</FormItem>
<FormItem>
<Button @click="handleSubmit" type="primary" long>登录</Button>
</FormItem>
</Form>
</template>
<script>
export default {
name: 'LoginForm',
props: {
userNameRules: {
type: Array,
default: () => {
return [
{ required: true, message: '账号不能为空', trigger: 'blur' }
]
}
},
passwordRules: {
type: Array,
default: () => {
return [
{ required: true, message: '密码不能为空', trigger: 'blur' }
]
}
}
},
data () {
return {
form: {
userName: 'super_admin',
password: ''
}
}
},
computed: {
rules () {
return {
userName: this.userNameRules,
password: this.passwordRules
}
}
},
methods: {
handleSubmit () {
this.$refs.loginForm.validate((valid) => {
if (valid) {
this.$emit('on-success-valid', {
userName: this.form.userName,
password: this.form.password
})
}
})
}
}
}
</script>
import ABackTop from './index.vue'
export default ABackTop
<template>
<div :class="classes" :style="styles" @click="back">
<slot>
<div :class="innerClasses">
<i class="ivu-icon ivu-icon-ios-arrow-up"></i>
</div>
</slot>
</div>
</template>
<script>
import { scrollTop } from '@/libs/util'
import { on, off } from '@/libs/tools'
const prefixCls = 'ivu-back-top'
export default {
name: 'ABackTop',
props: {
height: {
type: Number,
default: 400
},
bottom: {
type: Number,
default: 30
},
right: {
type: Number,
default: 30
},
duration: {
type: Number,
default: 1000
},
container: {
type: null,
default: window
}
},
data () {
return {
backTop: false
}
},
mounted () {
// window.addEventListener('scroll', this.handleScroll, false)
// window.addEventListener('resize', this.handleScroll, false)
on(this.containerEle, 'scroll', this.handleScroll)
on(this.containerEle, 'resize', this.handleScroll)
},
beforeDestroy () {
// window.removeEventListener('scroll', this.handleScroll, false)
// window.removeEventListener('resize', this.handleScroll, false)
off(this.containerEle, 'scroll', this.handleScroll)
off(this.containerEle, 'resize', this.handleScroll)
},
computed: {
classes () {
return [
`${prefixCls}`,
{
[`${prefixCls}-show`]: this.backTop
}
]
},
styles () {
return {
bottom: `${this.bottom}px`,
right: `${this.right}px`
}
},
innerClasses () {
return `${prefixCls}-inner`
},
containerEle () {
return this.container === window ? window : document.querySelector(this.container)
}
},
methods: {
handleScroll () {
this.backTop = this.containerEle.scrollTop >= this.height
},
back () {
let target = typeof this.container === 'string' ? this.containerEle : (document.documentElement || document.body)
const sTop = target.scrollTop
scrollTop(this.containerEle, sTop, 0, this.duration)
this.$emit('on-click')
}
}
}
</script>
<template>
<div class="error-store">
<Badge dot :count="countComputed">
<Button type="text" @click="openErrorLoggerPage">
<Icon :size="20" type="ios-bug"/>
</Button>
</Badge>
</div>
</template>
<script>
export default {
name: 'ErrorStore',
props: {
count: {
type: Number,
default: 0
},
hasRead: {
type: Boolean,
default: false
}
},
computed: {
countComputed () {
return this.hasRead ? 0 : this.count
}
},
methods: {
openErrorLoggerPage () {
this.$router.push({
name: 'error_logger_page'
})
}
}
}
</script>
<style lang="less">
.error-store{
margin-right: 12px;
.ivu-badge-dot{
top: 20px;
}
.ivu-btn.ivu-btn-text{
padding: 5px 1px 6px;
}
}
</style>
import ErrorStore from './error-store.vue'
export default ErrorStore
<template>
<div v-if="showFullScreenBtn" class="full-screen-btn-con">
<Tooltip :content="value ? '退出全屏' : '全屏'" placement="bottom">
<Icon @click.native="handleChange" :type="value ? 'md-contract' : 'md-expand'" :size="23"></Icon>
</Tooltip>
</div>
</template>
<script>
export default {
name: 'Fullscreen',
computed: {
showFullScreenBtn () {
return window.navigator.userAgent.indexOf('MSIE') < 0
}
},
props: {
value: {
type: Boolean,
default: false
}
},
methods: {
handleFullscreen () {
let main = document.body
if (this.value) {
if (document.exitFullscreen) {
document.exitFullscreen()
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen()
} else if (document.webkitCancelFullScreen) {
document.webkitCancelFullScreen()
} else if (document.msExitFullscreen) {
document.msExitFullscreen()
}
} else {
if (main.requestFullscreen) {
main.requestFullscreen()
} else if (main.mozRequestFullScreen) {
main.mozRequestFullScreen()
} else if (main.webkitRequestFullScreen) {
main.webkitRequestFullScreen()
} else if (main.msRequestFullscreen) {
main.msRequestFullscreen()
}
}
},
handleChange () {
this.handleFullscreen()
}
},
mounted () {
let isFullscreen = document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen
isFullscreen = !!isFullscreen
document.addEventListener('fullscreenchange', () => {
this.$emit('input', !this.value)
this.$emit('on-change', !this.value)
})
document.addEventListener('mozfullscreenchange', () => {
this.$emit('input', !this.value)
this.$emit('on-change', !this.value)
})
document.addEventListener('webkitfullscreenchange', () => {
this.$emit('input', !this.value)
this.$emit('on-change', !this.value)
})
document.addEventListener('msfullscreenchange', () => {
this.$emit('input', !this.value)
this.$emit('on-change', !this.value)
})
this.$emit('input', isFullscreen)
}
}
</script>
<style lang="less">
.full-screen-btn-con .ivu-tooltip-rel{
height: 64px;
line-height: 56px;
i{
cursor: pointer;
}
}
</style>
import Fullscreen from './fullscreen.vue'
export default Fullscreen
.custom-bread-crumb{
display: inline-block;
vertical-align: top;
}
<template>
<div class="custom-bread-crumb">
<Breadcrumb :style="{fontSize: `${fontSize}px`}">
<BreadcrumbItem v-for="item in list" :to="item.to" :key="`bread-crumb-${item.name}`">
<common-icon style="margin-right: 4px;" :type="item.icon || ''"/>
{{ showTitle(item) }}
</BreadcrumbItem>
</Breadcrumb>
</div>
</template>
<script>
import { showTitle } from '@/libs/util'
import CommonIcon from '_c/common-icon'
import './custom-bread-crumb.less'
export default {
name: 'customBreadCrumb',
components: {
CommonIcon
},
props: {
list: {
type: Array,
default: () => []
},
fontSize: {
type: Number,
default: 14
},
showIcon: {
type: Boolean,
default: false
}
},
methods: {
showTitle (item) {
return showTitle(item, this)
},
isCustomIcon (iconName) {
return iconName.indexOf('_') === 0
},
getCustomIconName (iconName) {
return iconName.slice(1)
}
}
}
</script>
import customBreadCrumb from './custom-bread-crumb.vue'
export default customBreadCrumb
.header-bar{
width: 100%;
height: 100%;
position: relative;
.custom-content-con{
float: right;
height: auto;
padding-right: 20px;
line-height: 64px;
& > *{
float: right;
}
}
}
<template>
<div class="header-bar">
<sider-trigger :collapsed="collapsed" icon="md-menu" @on-change="handleCollpasedChange"></sider-trigger>
<custom-bread-crumb show-icon style="margin-left: 30px;" :list="breadCrumbList"></custom-bread-crumb>
<div class="custom-content-con">
<slot></slot>
</div>
</div>
</template>
<script>
import siderTrigger from './sider-trigger'
import customBreadCrumb from './custom-bread-crumb'
import './header-bar.less'
export default {
name: 'HeaderBar',
components: {
siderTrigger,
customBreadCrumb
},
props: {
collapsed: Boolean
},
computed: {
breadCrumbList () {
return this.$store.state.app.breadCrumbList
}
},
methods: {
handleCollpasedChange (state) {
this.$emit('on-coll-change', state)
}
}
}
</script>
import HeaderBar from './header-bar'
export default HeaderBar
import siderTrigger from './sider-trigger.vue'
export default siderTrigger
.trans{
transition: transform .2s ease;
}
@size: 40px;
.sider-trigger-a{
padding: 6px;
width: @size;
height: @size;
display: inline-block;
text-align: center;
color: #5c6b77;
margin-top: 12px;
i{
.trans;
vertical-align: top;
}
&.collapsed i{
transform: rotateZ(90deg);
.trans;
}
}
<template>
<a @click="handleChange" type="text" :class="['sider-trigger-a', collapsed ? 'collapsed' : '']"><Icon :type="icon" :size="size" /></a>
</template>
<script>
export default {
name: 'siderTrigger',
props: {
collapsed: Boolean,
icon: {
type: String,
default: 'navicon-round'
},
size: {
type: Number,
default: 26
}
},
methods: {
handleChange () {
this.$emit('on-change', !this.collapsed)
}
}
}
</script>
<style lang="less">
@import './sider-trigger.less';
</style>
import Language from './language.vue'
export default Language
<template>
<div>
<Dropdown trigger="click" @on-click="selectLang">
<a href="javascript:void(0)">
{{ title }}
<Icon :size="18" type="md-arrow-dropdown" />
</a>
<DropdownMenu slot="list">
<DropdownItem v-for="(value, key) in localList" :name="key" :key="`lang-${key}`">{{ value }}</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
</template>
<script>
export default {
name: 'Language',
props: {
lang: String
},
data () {
return {
langList: {
'zh-CN': '语言',
'zh-TW': '語言',
'en-US': 'Lang'
},
localList: {
'zh-CN': '中文简体',
'zh-TW': '中文繁体',
'en-US': 'English'
}
}
},
watch: {
lang (lang) {
this.$i18n.locale = lang
}
},
computed: {
title () {
return this.langList[this.lang]
}
},
methods: {
selectLang (name) {
this.$emit('on-lang-change', name)
}
}
}
</script>
<template>
<Dropdown ref="dropdown" @on-click="handleClick" :class="hideTitle ? '' : 'collased-menu-dropdown'" :transfer="hideTitle" :placement="placement">
<a class="drop-menu-a" type="text" @mouseover="handleMousemove($event, children)" :style="{textAlign: !hideTitle ? 'left' : ''}"><common-icon :size="rootIconSize" :color="textColor" :type="parentItem.icon"/><span class="menu-title" v-if="!hideTitle">{{ showTitle(parentItem) }}</span><Icon style="float: right;" v-if="!hideTitle" type="ios-arrow-forward" :size="16"/></a>
<DropdownMenu ref="dropdown" slot="list">
<template v-for="child in children">
<collapsed-menu v-if="showChildren(child)" :icon-size="iconSize" :parent-item="child" :key="`drop-${child.name}`"></collapsed-menu>
<DropdownItem v-else :key="`drop-${child.name}`" :name="child.name"><common-icon :size="iconSize" :type="child.icon"/><span class="menu-title">{{ showTitle(child) }}</span></DropdownItem>
</template>
</DropdownMenu>
</Dropdown>
</template>
<script>
import mixin from './mixin'
import itemMixin from './item-mixin'
import { findNodeUpperByClasses } from '@/libs/util'
export default {
name: 'CollapsedMenu',
mixins: [ mixin, itemMixin ],
props: {
hideTitle: {
type: Boolean,
default: false
},
rootIconSize: {
type: Number,
default: 16
}
},
data () {
return {
placement: 'right-end'
}
},
methods: {
handleClick (name) {
this.$emit('on-click', name)
},
handleMousemove (event, children) {
const { pageY } = event
const height = children.length * 38
const isOverflow = pageY + height < window.innerHeight
this.placement = isOverflow ? 'right-start' : 'right-end'
}
},
mounted () {
let dropdown = findNodeUpperByClasses(this.$refs.dropdown.$el, ['ivu-select-dropdown', 'ivu-dropdown-transfer'])
if (dropdown) dropdown.style.overflow = 'visible'
}
}
</script>
import SideMenu from './side-menu.vue'
export default SideMenu
export default {
props: {
parentItem: {
type: Object,
default: () => {}
},
theme: String,
iconSize: Number
},
computed: {
parentName () {
return this.parentItem.name
},
children () {
return this.parentItem.children
},
textColor () {
return this.theme === 'dark' ? '#fff' : '#495060'
}
}
}
import CommonIcon from '_c/common-icon'
import { showTitle } from '@/libs/util'
export default {
components: {
CommonIcon
},
methods: {
showTitle (item) {
return showTitle(item, this)
},
showChildren (item) {
return item.children && (item.children.length > 1 || (item.meta && item.meta.showAlways))
},
getNameOrHref (item, children0) {
return item.href ? `isTurnByHref_${item.href}` : (children0 ? item.children[0].name : item.name)
}
}
}
<template>
<Submenu :name="`${parentName}`">
<template slot="title">
<common-icon :type="parentItem.icon || ''"/>
<span>{{ showTitle(parentItem) }}</span>
</template>
<template v-for="item in children">
<template v-if="item.children && item.children.length === 1">
<side-menu-item v-if="showChildren(item)" :key="`menu-${item.name}`" :parent-item="item"></side-menu-item>
<menu-item v-else :name="getNameOrHref(item, true)" :key="`menu-${item.children[0].name}`"><common-icon :type="item.children[0].icon || ''"/><span>{{ showTitle(item.children[0]) }}</span></menu-item>
</template>
<template v-else>
<side-menu-item v-if="showChildren(item)" :key="`menu-${item.name}`" :parent-item="item"></side-menu-item>
<menu-item v-else :name="getNameOrHref(item)" :key="`menu-${item.name}`"><common-icon :type="item.icon || ''"/><span>{{ showTitle(item) }}</span></menu-item>
</template>
</template>
</Submenu>
</template>
<script>
import mixin from './mixin'
import itemMixin from './item-mixin'
export default {
name: 'SideMenuItem',
mixins: [ mixin, itemMixin ]
}
</script>
.side-menu-wrapper{
user-select: none;
.menu-collapsed{
padding-top: 10px;
.ivu-dropdown{
width: 100%;
.ivu-dropdown-rel a{
width: 100%;
}
}
.ivu-tooltip{
width: 100%;
.ivu-tooltip-rel{
width: 100%;
}
.ivu-tooltip-popper .ivu-tooltip-content{
.ivu-tooltip-arrow{
border-right-color: #fff;
}
.ivu-tooltip-inner{
background: #fff;
color: #495060;
}
}
}
}
a.drop-menu-a{
display: inline-block;
padding: 6px 15px;
width: 100%;
text-align: center;
color: #495060;
}
}
.menu-title{
padding-left: 6px;
}
<template>
<div class="side-menu-wrapper">
<slot></slot>
<Menu ref="menu" v-show="!collapsed" :active-name="activeName" :open-names="openedNames" :accordion="accordion" :theme="theme" width="auto" @on-select="handleSelect">
<template v-for="item in menuList">
<template v-if="item.children && item.children.length === 1">
<side-menu-item v-if="showChildren(item)" :key="`menu-${item.name}`" :parent-item="item"></side-menu-item>
<menu-item v-else :name="getNameOrHref(item, true)" :key="`menu-${item.children[0].name}`"><common-icon :type="item.children[0].icon || ''"/><span>{{ showTitle(item.children[0]) }}</span></menu-item>
</template>
<template v-else>
<side-menu-item v-if="showChildren(item)" :key="`menu-${item.name}`" :parent-item="item"></side-menu-item>
<menu-item v-else :name="getNameOrHref(item)" :key="`menu-${item.name}`"><common-icon :type="item.icon || ''"/><span>{{ showTitle(item) }}</span></menu-item>
</template>
</template>
</Menu>
<div class="menu-collapsed" v-show="collapsed" :list="menuList">
<template v-for="item in menuList">
<collapsed-menu v-if="item.children && item.children.length > 1" @on-click="handleSelect" hide-title :root-icon-size="rootIconSize" :icon-size="iconSize" :theme="theme" :parent-item="item" :key="`drop-menu-${item.name}`"></collapsed-menu>
<Tooltip transfer v-else :content="showTitle(item.children && item.children[0] ? item.children[0] : item)" placement="right" :key="`drop-menu-${item.name}`">
<a @click="handleSelect(getNameOrHref(item, true))" class="drop-menu-a" :style="{textAlign: 'center'}"><common-icon :size="rootIconSize" :color="textColor" :type="item.icon || (item.children && item.children[0].icon)"/></a>
</Tooltip>
</template>
</div>
</div>
</template>
<script>
import SideMenuItem from './side-menu-item.vue'
import CollapsedMenu from './collapsed-menu.vue'
import { getUnion } from '@/libs/tools'
import mixin from './mixin'
export default {
name: 'SideMenu',
mixins: [ mixin ],
components: {
SideMenuItem,
CollapsedMenu
},
props: {
menuList: {
type: Array,
default () {
return []
}
},
collapsed: {
type: Boolean
},
theme: {
type: String,
default: 'dark'
},
rootIconSize: {
type: Number,
default: 20
},
iconSize: {
type: Number,
default: 16
},
accordion: Boolean,
activeName: {
type: String,
default: ''
},
openNames: {
type: Array,
default: () => []
}
},
data () {
return {
openedNames: []
}
},
methods: {
handleSelect (name) {
this.$emit('on-select', name)
},
getOpenedNamesByActiveName (name) {
return this.$route.matched.map(item => item.name).filter(item => item !== name)
},
updateOpenName (name) {
if (name === this.$config.homeName) this.openedNames = []
else this.openedNames = this.getOpenedNamesByActiveName(name)
}
},
computed: {
textColor () {
return this.theme === 'dark' ? '#fff' : '#495060'
}
},
watch: {
activeName (name) {
if (this.accordion) this.openedNames = this.getOpenedNamesByActiveName(name)
else this.openedNames = getUnion(this.openedNames, this.getOpenedNamesByActiveName(name))
},
openNames (newNames) {
this.openedNames = newNames
},
openedNames () {
this.$nextTick(() => {
this.$refs.menu.updateOpened()
})
}
},
mounted () {
this.openedNames = getUnion(this.openedNames, this.getOpenedNamesByActiveName(name))
}
}
</script>
<style lang="less">
@import './side-menu.less';
</style>
import TagsNav from './tags-nav.vue'
export default TagsNav
.no-select{
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.size{
width: 100%;
height: 100%;
}
.tags-nav{
position: relative;
border-top: 1px solid #F0F0F0;
border-bottom: 1px solid #F0F0F0;
.no-select;
.size;
.close-con{
position: absolute;
right: 0;
top: 0;
height: 100%;
width: 32px;
background: #fff;
text-align: center;
z-index: 10;
}
.btn-con{
position: absolute;
top: 0px;
height: 100%;
background: #fff;
padding-top: 3px;
z-index: 10;
button{
padding: 6px 4px;
line-height: 14px;
text-align: center;
}
&.left-btn{
left: 0px;
}
&.right-btn{
right: 32px;
border-right: 1px solid #F0F0F0;
}
}
.scroll-outer{
position: absolute;
left: 28px;
right: 61px;
top: 0;
bottom: 0;
box-shadow: 0px 0 3px 2px rgba(100,100,100,.1) inset;
.scroll-body{
height: ~"calc(100% - 1px)";
display: inline-block;
padding: 1px 4px 0;
position: absolute;
overflow: visible;
white-space: nowrap;
transition: left .3s ease;
.ivu-tag-dot-inner{
transition: background .2s ease;
}
}
}
.contextmenu {
position: absolute;
margin: 0;
padding: 5px 0;
background: #fff;
z-index: 1000;
list-style-type: none;
border-radius: 4px;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .1);
li {
margin: 0;
padding: 5px 15px;
cursor: pointer;
&:hover {
background: #eee;
}
}
}
}
<template>
<div class="tags-nav">
<div class="close-con">
<Dropdown transfer @on-click="handleTagsOption" style="margin-top:7px;">
<Button size="small" type="text">
<Icon :size="18" type="ios-close-circle-outline" />
</Button>
<DropdownMenu slot="list">
<DropdownItem name="close-all">关闭所有</DropdownItem>
<DropdownItem name="close-others">关闭其他</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
<ul v-show="visible" :style="{left: contextMenuLeft + 'px', top: contextMenuTop + 'px'}" class="contextmenu">
<li v-for="(item, key) of menuList" @click="handleTagsOption(key)" :key="key">{{item}}</li>
</ul>
<div class="btn-con left-btn">
<Button type="text" @click="handleScroll(240)">
<Icon :size="18" type="ios-arrow-back" />
</Button>
</div>
<div class="btn-con right-btn">
<Button type="text" @click="handleScroll(-240)">
<Icon :size="18" type="ios-arrow-forward" />
</Button>
</div>
<div class="scroll-outer" ref="scrollOuter" @DOMMouseScroll="handlescroll" @mousewheel="handlescroll">
<div ref="scrollBody" class="scroll-body" :style="{left: tagBodyLeft + 'px'}">
<transition-group name="taglist-moving-animation">
<Tag
type="dot"
v-for="(item, index) in list"
ref="tagsPageOpened"
:key="`tag-nav-${index}`"
:name="item.name"
:data-route-item="item"
@on-close="handleClose(item)"
@click.native="handleClick(item)"
:closable="item.name !== $config.homeName"
:color="isCurrentTag(item) ? 'primary' : 'default'"
@contextmenu.prevent.native="contextMenu(item, $event)"
>{{ showTitleInside(item) }}</Tag>
</transition-group>
</div>
</div>
</div>
</template>
<script>
import { showTitle, routeEqual } from '@/libs/util'
import beforeClose from '@/router/before-close'
export default {
name: 'TagsNav',
props: {
value: Object,
list: {
type: Array,
default () {
return []
}
}
},
data () {
return {
tagBodyLeft: 0,
rightOffset: 40,
outerPadding: 4,
contextMenuLeft: 0,
contextMenuTop: 0,
visible: false,
menuList: {
others: '关闭其他',
all: '关闭所有'
}
}
},
computed: {
currentRouteObj () {
const { name, params, query } = this.value
return { name, params, query }
}
},
methods: {
handlescroll (e) {
var type = e.type
let delta = 0
if (type === 'DOMMouseScroll' || type === 'mousewheel') {
delta = (e.wheelDelta) ? e.wheelDelta : -(e.detail || 0) * 40
}
this.handleScroll(delta)
},
handleScroll (offset) {
const outerWidth = this.$refs.scrollOuter.offsetWidth
const bodyWidth = this.$refs.scrollBody.offsetWidth
if (offset > 0) {
this.tagBodyLeft = Math.min(0, this.tagBodyLeft + offset)
} else {
if (outerWidth < bodyWidth) {
if (this.tagBodyLeft < -(bodyWidth - outerWidth)) {
this.tagBodyLeft = this.tagBodyLeft
} else {
this.tagBodyLeft = Math.max(this.tagBodyLeft + offset, outerWidth - bodyWidth)
}
} else {
this.tagBodyLeft = 0
}
}
},
handleTagsOption (type) {
if (type.includes('all')) {
// 关闭所有,除了home
let res = this.list.filter(item => item.name === this.$config.homeName)
this.$emit('on-close', res, 'all')
} else if (type.includes('others')) {
// 关闭除当前页和home页的其他页
let res = this.list.filter(item => routeEqual(this.currentRouteObj, item) || item.name === this.$config.homeName)
this.$emit('on-close', res, 'others', this.currentRouteObj)
setTimeout(() => {
this.getTagElementByRoute(this.currentRouteObj)
}, 100)
}
},
handleClose (current) {
if (current.meta && current.meta.beforeCloseName && current.meta.beforeCloseName in beforeClose) {
new Promise(beforeClose[current.meta.beforeCloseName]).then(close => {
if (close) {
this.close(current)
}
})
} else {
this.close(current)
}
},
close (route) {
let res = this.list.filter(item => !routeEqual(route, item))
this.$emit('on-close', res, undefined, route)
},
handleClick (item) {
this.$emit('input', item)
},
showTitleInside (item) {
return showTitle(item, this)
},
isCurrentTag (item) {
return routeEqual(this.currentRouteObj, item)
},
moveToView (tag) {
const outerWidth = this.$refs.scrollOuter.offsetWidth
const bodyWidth = this.$refs.scrollBody.offsetWidth
if (bodyWidth < outerWidth) {
this.tagBodyLeft = 0
} else if (tag.offsetLeft < -this.tagBodyLeft) {
// 标签在可视区域左侧
this.tagBodyLeft = -tag.offsetLeft + this.outerPadding
} else if (tag.offsetLeft > -this.tagBodyLeft && tag.offsetLeft + tag.offsetWidth < -this.tagBodyLeft + outerWidth) {
// 标签在可视区域
this.tagBodyLeft = Math.min(0, outerWidth - tag.offsetWidth - tag.offsetLeft - this.outerPadding)
} else {
// 标签在可视区域右侧
this.tagBodyLeft = -(tag.offsetLeft - (outerWidth - this.outerPadding - tag.offsetWidth))
}
},
getTagElementByRoute (route) {
this.$nextTick(() => {
this.refsTag = this.$refs.tagsPageOpened
this.refsTag.forEach((item, index) => {
if (routeEqual(route, item.$attrs['data-route-item'])) {
let tag = this.refsTag[index].$el
this.moveToView(tag)
}
})
})
},
contextMenu (item, e) {
if (item.name === this.$config.homeName) {
return
}
this.visible = true
const offsetLeft = this.$el.getBoundingClientRect().left
this.contextMenuLeft = e.clientX - offsetLeft + 10
this.contextMenuTop = e.clientY - 64
},
closeMenu () {
this.visible = false
}
},
watch: {
'$route' (to) {
this.getTagElementByRoute(to)
},
visible (value) {
if (value) {
document.body.addEventListener('click', this.closeMenu)
} else {
document.body.removeEventListener('click', this.closeMenu)
}
}
},
mounted () {
setTimeout(() => {
this.getTagElementByRoute(this.$route)
}, 200)
}
}
</script>
<style lang="less">
@import './tags-nav.less';
</style>
import User from './user.vue'
export default User
.user{
&-avatar-dropdown{
cursor: pointer;
display: inline-block;
// height: 64px;
vertical-align: middle;
// line-height: 64px;
.ivu-badge-dot{
top: 16px;
}
}
}
<template>
<div class="user-avatar-dropdown">
<Dropdown @on-click="handleClick">
<Badge :dot="!!messageUnreadCount">
<Avatar :src="userAvatar"/>
</Badge>
<Icon :size="18" type="md-arrow-dropdown"></Icon>
<DropdownMenu slot="list">
<DropdownItem name="message">
消息中心<Badge style="margin-left: 10px" :count="messageUnreadCount"></Badge>
</DropdownItem>
<DropdownItem name="logout">退出登录</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
</template>
<script>
import './user.less'
import { mapActions } from 'vuex'
export default {
name: 'User',
props: {
userAvatar: {
type: String,
default: ''
},
messageUnreadCount: {
type: Number,
default: 0
}
},
methods: {
...mapActions([
'handleLogOut'
]),
logout () {
this.handleLogOut().then(() => {
this.$router.push({
name: 'login'
})
})
},
message () {
this.$router.push({
name: 'message_page'
})
},
handleClick (name) {
switch (name) {
case 'logout': this.logout()
break
case 'message': this.message()
break
}
}
}
}
</script>
import Main from './main.vue'
export default Main
.main{
.logo-con{
height: 64px;
padding: 10px;
img{
height: 44px;
width: auto;
display: block;
margin: 0 auto;
}
}
.header-con{
background: #fff;
padding: 0 20px;
width: 100%;
}
.main-layout-con{
height: 100%;
overflow: hidden;
}
.main-content-con{
height: ~"calc(100% - 60px)";
overflow: hidden;
}
.tag-nav-wrapper{
padding: 0;
height:40px;
background:#F0F0F0;
}
.content-wrapper{
padding: 18px;
height: ~"calc(100% - 80px)";
overflow: auto;
}
.left-sider{
.ivu-layout-sider-children{
overflow-y: scroll;
margin-right: -18px;
}
}
}
.ivu-menu-item > i{
margin-right: 12px !important;
}
.ivu-menu-submenu > .ivu-menu > .ivu-menu-item > i {
margin-right: 8px !important;
}
.collased-menu-dropdown{
width: 100%;
margin: 0;
line-height: normal;
padding: 7px 0 6px 16px;
clear: both;
font-size: 12px !important;
white-space: nowrap;
list-style: none;
cursor: pointer;
transition: background 0.2s ease-in-out;
&:hover{
background: rgba(100, 100, 100, 0.1);
}
& * {
color: #515a6e;
}
.ivu-menu-item > i{
margin-right: 12px !important;
}
.ivu-menu-submenu > .ivu-menu > .ivu-menu-item > i {
margin-right: 8px !important;
}
}
.ivu-select-dropdown.ivu-dropdown-transfer{
max-height: 400px;
}
<template>
<Layout style="height: 100%" class="main">
<Sider hide-trigger collapsible :width="256" :collapsed-width="64" v-model="collapsed" class="left-sider" :style="{overflow: 'hidden'}">
<side-menu accordion ref="sideMenu" :active-name="$route.name" :collapsed="collapsed" @on-select="turnToPage" :menu-list="menuList">
<!-- 需要放在菜单上面的内容,如Logo,写在side-menu标签内部,如下 -->
<div class="logo-con">
<img v-show="!collapsed" :src="maxLogo" key="max-logo" />
<img v-show="collapsed" :src="minLogo" key="min-logo" />
</div>
</side-menu>
</Sider>
<Layout>
<Header class="header-con">
<header-bar :collapsed="collapsed" @on-coll-change="handleCollapsedChange">
<user :message-unread-count="unreadCount" :user-avatar="userAvatar"/>
<language v-if="$config.useI18n" @on-lang-change="setLocal" style="margin-right: 10px;" :lang="local"/>
<error-store v-if="$config.plugin['error-store'] && $config.plugin['error-store'].showInHeader" :has-read="hasReadErrorPage" :count="errorCount"></error-store>
<fullscreen v-model="isFullscreen" style="margin-right: 10px;"/>
</header-bar>
</Header>
<Content class="main-content-con">
<Layout class="main-layout-con">
<div class="tag-nav-wrapper">
<tags-nav :value="$route" @input="handleClick" :list="tagNavList" @on-close="handleCloseTag"/>
</div>
<Content class="content-wrapper">
<keep-alive :include="cacheList">
<router-view/>
</keep-alive>
<ABackTop :height="100" :bottom="80" :right="50" container=".content-wrapper"></ABackTop>
</Content>
</Layout>
</Content>
</Layout>
</Layout>
</template>
<script>
import SideMenu from './components/side-menu'
import HeaderBar from './components/header-bar'
import TagsNav from './components/tags-nav'
import User from './components/user'
import ABackTop from './components/a-back-top'
import Fullscreen from './components/fullscreen'
import Language from './components/language'
import ErrorStore from './components/error-store'
import { mapMutations, mapActions, mapGetters } from 'vuex'
import { getNewTagList, routeEqual } from '@/libs/util'
import routers from '@/router/routers'
import minLogo from '@/assets/images/logo-min.jpg'
import maxLogo from '@/assets/images/logo.jpg'
import './main.less'
export default {
name: 'Main',
components: {
SideMenu,
HeaderBar,
Language,
TagsNav,
Fullscreen,
ErrorStore,
User,
ABackTop
},
data () {
return {
collapsed: false,
minLogo,
maxLogo,
isFullscreen: false
}
},
computed: {
...mapGetters([
'errorCount'
]),
tagNavList () {
return this.$store.state.app.tagNavList
},
tagRouter () {
return this.$store.state.app.tagRouter
},
userAvatar () {
return this.$store.state.user.avatarImgPath
},
cacheList () {
const list = ['ParentView', ...this.tagNavList.length ? this.tagNavList.filter(item => !(item.meta && item.meta.notCache)).map(item => item.name) : []]
return list
},
menuList () {
return this.$store.getters.menuList
},
local () {
return this.$store.state.app.local
},
hasReadErrorPage () {
return this.$store.state.app.hasReadErrorPage
},
unreadCount () {
return this.$store.state.user.unreadCount
}
},
methods: {
...mapMutations([
'setBreadCrumb',
'setTagNavList',
'addTag',
'setLocal',
'setHomeRoute',
'closeTag'
]),
...mapActions([
'handleLogin',
'getUnreadMessageCount'
]),
turnToPage (route) {
let { name, params, query } = {}
if (typeof route === 'string') name = route
else {
name = route.name
params = route.params
query = route.query
}
if (name.indexOf('isTurnByHref_') > -1) {
window.open(name.split('_')[1])
return
}
this.$router.push({
name,
params,
query
})
},
handleCollapsedChange (state) {
this.collapsed = state
},
handleCloseTag (res, type, route) {
if (type !== 'others') {
if (type === 'all') {
this.turnToPage(this.$config.homeName)
} else {
if (routeEqual(this.$route, route)) {
this.closeTag(route)
}
}
}
this.setTagNavList(res)
},
handleClick (item) {
this.turnToPage(item)
}
},
watch: {
'$route' (newRoute) {
const { name, query, params, meta } = newRoute
this.addTag({
route: { name, query, params, meta },
type: 'push'
})
this.setBreadCrumb(newRoute)
this.setTagNavList(getNewTagList(this.tagNavList, newRoute))
this.$refs.sideMenu.updateOpenName(newRoute.name)
}
},
mounted () {
/**
* @description 初始化设置面包屑导航和标签导航
*/
this.setTagNavList()
this.setHomeRoute(routers)
const { name, params, query, meta } = this.$route
this.addTag({
route: { name, params, query, meta }
})
this.setBreadCrumb(this.$route)
// 设置初始语言
this.setLocal(this.$i18n.locale)
// 如果当前打开页面不在标签栏中,跳到homeName页
if (!this.tagNavList.find(item => item.name === this.$route.name)) {
this.$router.push({
name: this.$config.homeName
})
}
// 获取未读消息条数
this.getUnreadMessageCount()
}
}
</script>
import MarkdownEditor from './markdown.vue'
export default MarkdownEditor
<template>
<div class="markdown-wrapper">
<textarea ref="editor"></textarea>
</div>
</template>
<script>
import Simplemde from 'simplemde'
import 'simplemde/dist/simplemde.min.css'
export default {
name: 'MarkdownEditor',
props: {
value: {
type: String,
default: ''
},
options: {
type: Object,
default: () => {
return {}
}
},
localCache: {
type: Boolean,
default: true
}
},
data () {
return {
editor: null
}
},
methods: {
addEvents () {
this.editor.codemirror.on('change', () => {
let value = this.editor.value()
if (this.localCache) localStorage.markdownContent = value
this.$emit('input', value)
this.$emit('on-change', value)
})
this.editor.codemirror.on('focus', () => {
this.$emit('on-focus', this.editor.value())
})
this.editor.codemirror.on('blur', () => {
this.$emit('on-blur', this.editor.value())
})
}
},
mounted () {
this.editor = new Simplemde(Object.assign(this.options, {
element: this.$refs.editor
}))
/**
* 事件列表为Codemirror编辑器的事件,更多事件类型,请参考:
* https://codemirror.net/doc/manual.html#events
*/
this.addEvents()
let content = localStorage.markdownContent
if (content) this.editor.value(content)
}
}
</script>
<style lang="less">
.markdown-wrapper{
.editor-toolbar.fullscreen{
z-index: 9999;
}
.CodeMirror-fullscreen{
z-index: 9999;
}
.CodeMirror-fullscreen ~ .editor-preview-side{
z-index: 9999;
}
}
</style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment