Commit 40fd0e01 by baihong

add:管理后台

parents
{
"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
# wx-admin
#### Description
{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**}
#### Software Architecture
Software architecture description
#### Installation
1. xxxx
2. xxxx
3. xxxx
#### Instructions
1. xxxx
2. xxxx
3. xxxx
#### Contribution
1. Fork the repository
2. Create Feat_xxx branch
3. Commit your code
4. Create Pull Request
#### Gitee Feature
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
4. The most valuable open source project [GVP](https://gitee.com/gvp)
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
## Getting started
```bush
// install dependencies
npm install
// develop
npm run dev
```
## Build
```bush
npm run build
```
## 记录一下ecosystem.config.js的需要注意的一些事
服务器上必须安装如下环境:
nodejs
npm
pm2
git
并且path目录下的sorce目录下必须经过 git init 初始化
需要确认好服务器上的path目录下的sorce目录的git是否指定远程
{
"pluginsFile": "tests/e2e/plugins/index.js"
}
module.exports = {
/**
* Application configuration section
* http://pm2.keymetrics.io/docs/usage/application-declaration/
*/
apps: [
{
name: 'chongsu_dashboard',
instances: 1,
exec_mode: 'cluster',
env: {
COMMON_VARIABLE: 'true'
},
env_production: {
NODE_ENV: 'production'
}
}
],
deploy: {
production: {
user: 'chongsu',
host: '123.56.237.136',
ref: 'master',
repo: 'git@gitee.com:feimantu/chongsu_dashboard.git',
path: '/home/chongsu/web_code/chongsu_dashboard',
'pre-setup': '',
'post-setup': 'ls -la',
'post-deploy': 'npm install && npm run build'
}
}
}
<!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" />
<style>
:root {
font-size: 16px;
}
html {
box-sizing: border-box;
}
body,
html {
margin: 0;
padding: 0;
height: 100%;
}
html {
line-height: 1.15;
-webkit-text-size-adjust: 100%;
}
*,
:after,
:before {
box-sizing: border-box;
}
body {
min-width: 20rem;
font-family: Metric, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
Helvetica, Arial, sans-serif;
font-weight: 400;
font-style: normal;
color: #4a4a4a;
cursor: default;
background-color: #fff;
overflow-x: hidden;
}
.shopify-section--topbar {
z-index: 110;
position: relative;
}
.topbar {
position: relative;
border-bottom-width: 0.0625rem;
border-bottom-style: solid;
border-bottom-color: transparent;
height: 2.25rem;
background-color: #f9f9f9;
}
@media only screen and (min-width: 70rem) {
.topbar__marketswitcher {
margin-left: 3.125rem;
}
}
.topbar__marketswitcher {
z-index: 4;
position: relative;
float: left;
margin-left: 2.5rem;
padding-top: 0.75rem;
font-size: 0.875rem;
line-height: 1rem;
color: #4a4a4a;
letter-spacing: 0.0625rem;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: pointer;
}
.topbar a {
color: #4a4a4a;
text-decoration: none;
}
.topbar__marketswitcher--expanded .topbar__marketswitcher-markets {
display: none;
}
.topbar__marketswitcher-markets {
display: none;
position: absolute;
top: 2.0625rem;
left: 0;
width: 10rem;
margin-top: 0.125rem;
padding-top: 0.75rem;
padding-bottom: 0.375rem;
background-color: #fff;
box-shadow: 0.0625rem 0.0625rem 0 rgba(0, 0, 0, 0.1);
}
ol,
ul {
list-style: none;
}
h1,
h2,
h3,
h4,
h5,
h6,
ol,
p,
ul {
margin: 0;
padding: 0;
}
.topbar__marketswitcher-markets .topbar__marketswitcher-subitem {
position: relative;
padding-bottom: 0.125rem;
padding-left: 1.25rem;
padding-right: 1.25rem;
}
.topbar__marketswitcher-markets .topbar__marketswitcher-subitem a {
color: #4a4a4a;
font-weight: 400;
font-size: 0.875rem;
text-shadow: none;
letter-spacing: normal;
}
.topbar__marketswitcher-markets .topbar__marketswitcher-subitem {
position: relative;
padding-bottom: 0.125rem;
padding-left: 1.25rem;
padding-right: 1.25rem;
}
@media only screen and (min-width: 61.25rem) {
.topbar__promo {
z-index: 2;
position: absolute;
top: 0;
left: 9.375rem;
bottom: 0;
right: 9.375rem;
}
}
.topbar__promo {
display: block;
font-size: 0.875rem;
line-height: 2.5rem;
letter-spacing: 0.075rem;
text-align: center;
}
@media only screen and (min-width: 48rem) {
.topbar__promo-mobile {
display: none !important;
visibility: hidden !important;
}
}
.topbar__languages + .topbar__currencies {
margin-right: 1.5625rem;
}
.shopify-section--header {
z-index: 100;
position: relative;
}
.header {
z-index: 4;
position: relative;
}
.header__banner {
z-index: 4;
position: relative;
border-width: 0 0 0.0625rem;
border-style: solid;
border-color: transparent;
padding-left: 0.9375rem;
padding-right: 0.9375rem;
height: 5rem;
background-color: #fff;
}
@media only screen and (min-width: 75rem) {
.header__banner {
padding-left: 3.75rem;
padding-right: 3.75rem;
}
}
@media only screen and (min-width: 70rem) {
.header__banner {
padding-left: 3.125rem;
padding-right: 3.125rem;
}
}
@media only screen and (min-width: 61.25rem) {
.header__banner {
padding-left: 2.5rem;
padding-right: 2.5rem;
height: 9rem;
}
}
@media only screen and (min-width: 48rem) {
.header__banner {
padding-left: 3.75rem;
padding-right: 3.75rem;
}
}
@media only screen and (min-width: 70rem) {
.header__menu {
float: left;
margin-top: 4.0625rem;
font-size: 0;
transition: margin-top 0.2s;
}
}
.header__menu-item {
display: inline-block;
vertical-align: top;
font-size: 0.9375rem;
line-height: 0.9375rem;
letter-spacing: 0.0625rem;
}
ol,
ul {
list-style: none;
}
h1,
h2,
h3,
h4,
h5,
h6,
ol,
p,
ul {
margin: 0;
padding: 0;
}
.header__menu-item + .header__menu-item {
margin-left: 1.875rem;
}
.header__menu-item a {
position: relative;
color: #4a4a4a;
text-decoration: none;
transition: color 0.2s;
}
.header__menu-item--active a:after,
.header__menu-item--hover a:after,
.header__menu-item a:hover:after {
transform: scaleX(1);
}
.header__menu-item a:after {
position: absolute;
bottom: -0.375rem;
left: 0;
width: 100%;
height: 0.0625rem;
content: "";
background-color: #4a4a4a;
transform: scaleX(0);
transform-origin: left top;
transition: transform 0.2s, background-color 0.2s;
}
*,
:after,
:before {
box-sizing: border-box;
}
.header__logo {
position: absolute;
top: 0;
left: 50%;
bottom: 0;
margin-left: -2.8125rem;
width: 5.625rem;
line-height: 5rem;
text-align: center;
text-decoration: none;
transform: translateZ(0);
}
@media only screen and (min-width: 61.25rem) {
.header__logo {
margin-left: -5rem;
width: 10rem;
line-height: 9rem;
transition: line-height 0.2s;
}
}
.header-is-light:not(.header-is-sticked) .header__logo-light,
.header__logo-dark {
opacity: 1;
}
.header__logo-dark,
.header__logo-light {
position: absolute;
top: 0;
left: 0;
right: 0;
transition: opacity 0.2s;
}
.header__logo img {
width: 5.125rem;
vertical-align: middle;
}
@media only screen and (min-width: 48rem) {
.header__logo img {
width: 9rem;
}
}
img {
max-width: 100%;
}
img {
border-style: none;
}
.header__logo-light {
opacity: 0;
}
.header__logo-dark,
.header__logo-light {
position: absolute;
top: 0;
left: 0;
right: 0;
transition: opacity 0.2s;
}
.header__actions {
z-index: 4;
position: relative;
float: right;
margin-top: 1.5rem;
font-size: 0;
}
@media only screen and (min-width: 61.25rem){
.header__actions {
margin-top: 3.4375rem;
transition: margin-top .2s;
}
}
.header__actions-item {
position: relative;
display: inline-block;
vertical-align: middle;
font-size: 1.75rem;
color: #4a4a4a;
text-decoration: none;
cursor: pointer;
transition: color .2s;
}
.topbar__marketswitcher-label{
display: flex;
align-items: center;
}
@media screen and (max-width: 900px) {
.topbar__marketswitcher-label {
display: none;
}
.shopify-section--header{
display: none;
}
}
.mheader__banner {
z-index: 4;
position: relative;
border-width: 0 0 .0625rem;
border-style: solid;
border-color: transparent;
padding-left: .9375rem;
padding-right: .9375rem;
height: 5rem;
background-color: #fff;
}
@media only screen and (min-width: 48rem){
.mheader__banner {
padding-left: 1.875rem;
padding-right: 1.875rem;
}
}
.header__offcanvas {
float: left;
margin-top: 1.875rem;
font-size: 1.125rem;
cursor: pointer;
}
@media screen and (min-width: 900px) {
.mheader__banner {
display: none;
}
}
</style>
</head>
<body>
<div
id="shopify-section-topbar"
class="shopify-section shopify-section--topbar"
>
<section
class="topbar"
data-section-id="topbar"
data-section-type="topbar"
>
<div class="topbar__marketswitcher topbar__marketswitcher--expanded">
<a class="topbar__marketswitcher-label">Eshop France
<svg id="svgone" style="margin-left: 10px;" t="1605335508134" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3807" width="12" height="12"><path d="M454.188 785.022c-145.192-150.177-290.378-300.353-435.422-450.526-59.842-61.836 37.327-154.021 97.313-91.899 129.23 133.647 258.318 267.296 387.548 400.868 133.646-134.287 267.436-268.574 401.083-402.934 60.84-61.123 158.011 31.060 97.244 91.971-150.105 150.89-300.279 301.703-450.454 452.521-24.933 24.934-72.666 25.575-97.311 0z" p-id="3808"></path></svg>
<svg id="svgtwo" style="display: none;margin-left: 10px;" t="1605335652474" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4157" width="12" height="12"><path d="M266.605714 877.129143a68.973714 68.973714 0 0 0 3.291429 97.572571 78.994286 78.994286 0 0 0 107.958857 0L809.545143 571.245714a68.973714 68.973714 0 0 0 19.602286-68.169143 68.900571 68.900571 0 0 0-19.602286-68.169142L377.856 31.670857a78.994286 78.994286 0 0 0-107.958857 0 68.973714 68.973714 0 0 0 0 100.790857L666.624 503.222857 269.897143 873.837714a69.046857 69.046857 0 0 0-1.682286 1.609143z" p-id="4158"></path></svg>
</a>
<ul class="topbar__marketswitcher-markets">
<li class="topbar__marketswitcher-subitem">
<a href="https://be.labo-svr.com/">Benelux</a>
</li>
<li class="topbar__marketswitcher-subitem">
<a href="https://es.labo-svr.com/">España</a>
</li>
<li
class="topbar__marketswitcher-subitem topbar__marketswitcher-subitem--active"
>
<a href="https://fr.labo-svr.com/">Europe</a>
</li>
<li
class="topbar__marketswitcher-subitem topbar__marketswitcher-subitem--active"
>
<a href="https://fr.labo-svr.com/">France</a>
</li>
<li class="topbar__marketswitcher-subitem">
<a href="https://it.labo-svr.com/">Italia</a>
</li>
<li class="topbar__marketswitcher-subitem">
<a href="https://ma.labo-svr.com/">Morocco</a>
</li>
<li class="topbar__marketswitcher-subitem">
<a href="https://pl.labo-svr.com/">Polska</a>
</li>
<li class="topbar__marketswitcher-subitem">
<a href="https://pt.labo-svr.com/">Portugal</a>
</li>
<li class="topbar__marketswitcher-subitem">
<a href="https://tn.labo-svr.com/">Tunisia</a>
</li>
<li class="topbar__marketswitcher-subitem">
<a href="https://uk.labo-svr.com/">United Kingdom</a>
</li>
</ul>
</div>
<div class="topbar__promo">
<span class="topbar__promo-desktop">
Enjoy 15% discount on your first order
</span>
</div>
<div class="topbar__currencies">
<span class="mw-switcher"></span>
<span class="topbar__currencies-arrow">
<span class="icon-chevron-down"></span>
</span>
</div>
</section>
</div>
<div class="shopify-section--header">
<header class="header">
<div class="header__banner">
<nav class="header__menu">
<ul>
<li class="header__menu-item">
<a href="#" title="Skin types" data-menu-item="types-de-peau"
>Skin types</a
>
</li>
<li class="header__menu-item header__menu-item--active">
<a href="#" title="Our ranges" data-menu-item="nos-gammes"
>Our ranges</a
>
</li>
<li class="header__menu-item">
<a href="#" title="The brand" data-menu-item="la-marque"
>The brand</a
>
</li>
<li class="header__menu-item">
<a
href="#"
title="Expert advice"
data-menu-item="conseils-dexperts"
>Expert advice</a
>
</li>
</ul>
</nav>
<a
class="header__logo"
href="https://fr.labo-svr.com"
title="SVR Lab FR"
>
<div class="header__logo-dark">
<img
src="//cdn.shopifycdn.net/s/files/1/0068/0932/1537/files/logo-SVR_300x.png?v=1549371212"
srcset="
//cdn.shopifycdn.net/s/files/1/0068/0932/1537/files/logo-SVR_300x.png?v=1549371212 1x,
//cdn.shopifycdn.net/s/files/1/0068/0932/1537/files/logo-SVR_600x.png?v=1549371212 2x
"
alt="SVR Lab FR"
/>
</div>
<div class="header__logo-light">
<img
src="//cdn.shopifycdn.net/s/files/1/0068/0932/1537/files/logo-SVR-light_300x.png?v=1550051852"
srcset="
//cdn.shopifycdn.net/s/files/1/0068/0932/1537/files/logo-SVR-light_300x.png?v=1550051852 1x,
//cdn.shopifycdn.net/s/files/1/0068/0932/1537/files/logo-SVR-light_600x.png?v=1550051852 2x
"
alt="SVR Lab FR"
/>
</div>
</a>
<div class="header__actions">
<a
class="header__actions-item"
href="https://fr.labo-svr.com/cart"
title="Cart"
>
<svg t="1605335198610" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2896" width="25" height="25"><path d="M832.704 367.616 193.664 367.616l-62.144 480.128 760.896 0L832.704 367.616zM241.92 415.296l542.272 0 50.624 384.96L189.184 800.256 241.92 415.296z" p-id="2897"></path><path d="M401.088 387.392 401.088 330.176c0-61.248 49.6-110.848 110.848-110.848s110.976 49.664 110.976 110.848l0 57.28 40.768 0L663.68 328.128c0-83.84-67.904-151.936-151.744-151.936S360.192 244.288 360.192 328.128l0 59.264L401.088 387.392z" p-id="2898"></path></svg>
</a>
</div>
</div>
</header>
</div>
<div class="mheader__banner">
<img class="header__offcanvas" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAgCAYAAABpRpp6AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAEISURBVFhH7ZYxC4JQFIX7fU4ugRAEQtAUgdBaQ1s/wJa2FpudwykIgzAIXGqpRQiEQARBODW88foc6r7K3gdnfecD37vXFn4MLcyNFuamVjjdeXDnrqJ4CG+iuAK58D3AxDBgqMzIRyrqKeTC+QYzkziUM+MAmainqL0S2TWAR34+hqwCXGS2T5r36L4NLcxN84ST9QyDng1bRYYTeEf5mJAL33w41KzkjOMjEfUUcuE8gmsRhzLGnL64OHC/INqGCJUkRlKK3gqa9+i+DS3MTdOEM8RLB21F/8Sm1cdi/1eLo4yx6BKHMqYzj1CIeor6O1ymOB2oIf/+ROcUhV4cH0YLc/NjwsADKlaqsndYiHkAAAAASUVORK5CYII=" alt="">
<a class="header__logo" href="https://fr.labo-svr.com" title="SVR Lab FR">
<div class="header__logo-dark">
<img src="//cdn.shopifycdn.net/s/files/1/0068/0932/1537/files/logo-SVR_300x.png?v=1549371212" srcset="//cdn.shopifycdn.net/s/files/1/0068/0932/1537/files/logo-SVR_300x.png?v=1549371212 1x, //cdn.shopifycdn.net/s/files/1/0068/0932/1537/files/logo-SVR_600x.png?v=1549371212 2x" alt="SVR Lab FR">
</div>
<div class="header__logo-light">
<img src="//cdn.shopifycdn.net/s/files/1/0068/0932/1537/files/logo-SVR-light_300x.png?v=1550051852" srcset="//cdn.shopifycdn.net/s/files/1/0068/0932/1537/files/logo-SVR-light_300x.png?v=1550051852 1x, //cdn.shopifycdn.net/s/files/1/0068/0932/1537/files/logo-SVR-light_600x.png?v=1550051852 2x" alt="SVR Lab FR">
</div>
</a>
<a
style="float: right;margin-top: 28px;"
href="https://fr.labo-svr.com/cart"
title="Cart"
>
<svg t="1605335198610" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2896" width="30" height="30"><path d="M832.704 367.616 193.664 367.616l-62.144 480.128 760.896 0L832.704 367.616zM241.92 415.296l542.272 0 50.624 384.96L189.184 800.256 241.92 415.296z" p-id="2897"></path><path d="M401.088 387.392 401.088 330.176c0-61.248 49.6-110.848 110.848-110.848s110.976 49.664 110.976 110.848l0 57.28 40.768 0L663.68 328.128c0-83.84-67.904-151.936-151.744-151.936S360.192 244.288 360.192 328.128l0 59.264L401.088 387.392z" p-id="2898"></path></svg>
</a>
</div>
</body>
<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
<script>
$(document).ready(function() {
$(".topbar__marketswitcher-label").bind("click", function() {
$(".topbar__marketswitcher-markets").toggle();
$("#svgone").toggle();
$("#svgtwo").toggle();
});
});
</script>
</html>
{
"name": "chongsu_dashboard",
"version": "0.0.1",
"author": "baihong<369325279@qq.com>",
"private": false,
"scripts": {
"dev": "vue-cli-service serve --open",
"build": "vue-cli-service build",
"deploy": "pm2 deploy ecosystem.config.js production",
"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",
"vue-clipboard2": "^0.3.1",
"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",
"vcolorpicker": "^1.0.1",
"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;
}
.content {
background: #ffffff;
padding: 30px;
height: 80vh;
}
.success-btn {
margin-right: 60px;
}
.pagBox {
height: 50px;
display: flex;
align-items: center;
justify-content: center;
}
.margin-top-10 {
margin-top: 10px;
}
.topSearch {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 40px;
.left {
display: flex;
align-items: center;
button {
margin-left: 10px;
}
.lable {
margin: 0 10px 0 30px;
}
}
.right {
&:extend(.left);
button {
margin-left: 10px;
}
}
}
</style>
import { $http } from '@/libs/http'
// 获得所有广告信息
export const getAdvertisements = (data) => {
return $http.get('/admin/advertisements', data)
}
// 增加广告
export const addAdvertisements = (data) => {
return $http.postObj('/admin/advertisements', data)
}
// 删除广告
export const delAdvertisements = (data) => {
return $http.delete('/admin/advertisements', data)
}
// 修改广告
export const editAdvertisements = (data) => {
return $http.patch('/admin/advertisements', data)
}
import { $http } from '@/libs/http'
// 测试 -- 分页查询测试列表
export const getList = data => {
return $http.post('/survey/pageList', data)
}
export const getQiNiuToken = data => {
return $http.post('/survey/qiNiuToken', data)
}
export const getDetailInfo = data => {
return $http.post('/survey/detailInfo', data)
}
export const addTest = data => {
return $http.post('/survey/add', data)
}
export const updateTest = data => {
return $http.post('/survey/update', data)
}
export const putShelve = data => {
return $http.post('/survey/putShelve', data)
}
export const offShelve = data => {
return $http.post('/survey/offShelve', data)
}
export const updateSalePrice = data => {
return $http.post('/survey/updateSalePrice', data)
}
export const updateContent = data => {
return $http.post('/treehole/updateContent', data)
}
export const treeholeList = data => {
return $http.post('/treehole/pageList', data)
}
export const pretendGreate = data => {
return $http.post('/treehole/pretendGreate', data)
}
export const relayTreeHole = data => {
return $http.post('/treehole/relayTreeHole', data)
}
export const addTreeHole = data => {
return $http.post('/treehole/addTreeHole', data)
}
export const setTop = data => {
return $http.post('/treehole/setTop', data)
}
export const setSore = data => {
return $http.post('/survey/setSore', data)
}
export const recommend = data => {
return $http.post('/survey/recommend', data)
}
import { $http } from '@/libs/http'
// 获得所有Banner信息
export const getBanners = (data) => {
return $http.get('/admin/banners', data)
}
// 增加Banner
export const addBanners = (data) => {
return $http.postObj('/admin/banners', data)
}
// 删除Banner
export const delBanners = (data) => {
return $http.delete('/admin/banners', data)
}
// 修改Banner
export const editBanners = (data) => {
return $http.patch('/admin/banners', data)
}
import { $http } from '@/libs/http'
// 获得所有栏目信息
export const getCategories = (data) => {
return $http.get('/admin/categories', data)
}
// 获得栏目类关系信息
export const getTreeCategories = (keyId) => {
return $http.get(`/admin/tree/category/${keyId}`)
}
// 获得栏目类关系信息
export const getTreeCategorie2 = (keyId) => {
return $http.get(`/admin/tree2/category/${keyId}`)
}
// 增加栏目
export const addCategories = (data) => {
return $http.postObj('/admin/categories', data)
}
// 删除栏目
export const delCategories = (data) => {
return $http.delete('/admin/categories', data)
}
// 修改栏目
export const editCategories = (data) => {
return $http.patch('/admin/categories', data)
}
import { $http } from '@/libs/http'
// 获得所有内容信息
export const getContents = (data) => {
return $http.get('/admin/contents', data)
}
// 增加内容
export const addContents = (data) => {
return $http.postObj('/admin/contents', data)
}
// 删除内容
export const delContents = (data) => {
return $http.delete('/admin/contents', data)
}
// 修改内容
export const editContents = (data) => {
return $http.patch('/admin/contents', data)
}
import { $http } from '@/libs/http'
// 获得所有条目信息
export const getCourses = (data) => {
return $http.get('/admin/courses', data)
}
// 增加条目
export const addCourses = (data) => {
return $http.postObj('/admin/courses', data)
}
// 删除条目
export const delCourses = (data) => {
return $http.delete('/admin/courses', data)
}
// 修改条目
export const editCourses = (data) => {
return $http.patch('/admin/courses', data)
}
import axios from '@/libs/api.request'
export const getTableData = () => {
return axios.request({
url: 'get_table_data',
method: 'get'
})
}
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 { $http } from '@/libs/http'
// 获得所有资源信息
export const getResources = (data) => {
return $http.get('/admin/resources', data)
}
// 增加资源
export const addResources = (data) => {
return $http.postObj('/admin/resources', data)
}
// 删除资源
export const delResources = (data) => {
return $http.delete('/admin/resources', data)
}
// 修改资源
export const editResources = (data) => {
return $http.patch('/admin/resources', data)
}
import { $http } from '@/libs/http'
// 获得所有铃声信息
export const getRings = (data) => {
return $http.get('/admin/rings', data)
}
// 增加铃声
export const addRings = (data) => {
return $http.postObj('/admin/rings', data)
}
// 删除铃声
export const delRings = (data) => {
return $http.delete('/admin/rings', data)
}
// 修改铃声
export const editRings = (data) => {
return $http.patch('/admin/rings', data)
}
import axios from '@/libs/api.request'
export const getRouterReq = (access) => {
return axios.request({
url: 'get_router',
params: {
access
},
method: 'get'
})
}
import { $http } from '@/libs/http'
// 获得所有场景信息
export const getScenes = (data) => {
return $http.get('/admin/scenes', data)
}
// 获得场景类关系信息
export const getTreeScene = (keyId) => {
return $http.get(`/admin/tree/scene/${keyId}`)
}
// 获得场景类关系信息
export const getTreeScene2 = (keyId) => {
return $http.get(`/admin/tree2/scene/${keyId}`)
}
// 增加场景
export const addScenes = (data) => {
return $http.postObj('/admin/scenes', data)
}
// 删除场景
export const delScenes = (data) => {
return $http.delete('/admin/scenes', data)
}
// 修改场景
export const editScenes = (data) => {
return $http.patch('/admin/scenes', data)
}
import { $http } from '@/libs/http'
// 获得所有条目信息
export const getSections = (data) => {
return $http.get('/admin/sections', data)
}
// 增加条目
export const addSections = (data) => {
return $http.postObj('/admin/sections', data)
}
// 删除条目
export const delSections = (data) => {
return $http.delete('/admin/sections', data)
}
// 修改条目
export const editSections = (data) => {
return $http.patch('/admin/sections', data)
}
import { $http } from '@/libs/http'
// 获得所有商品信息
export const getShop = (data) => {
return $http.get('/admin/shop/contents', data)
}
// 增加商品信息
export const addShop = (data) => {
return $http.postObj('/admin/shop/contents', data)
}
// 删除商品信息
export const delShop = (data) => {
return $http.delete('/admin/shop/contents', data)
}
// 修改商品信息
export const editShop = (data) => {
return $http.patch('/admin/shop/contents', data)
}
import { $http } from '@/libs/http'
// 获得所有主题信息
export const getThemes = (data) => {
return $http.get('/admin/themes', data)
}
// 增加主题
export const addThemes = (data) => {
return $http.postObj('/admin/themes', data)
}
// 删除主题
export const delThemes = (data) => {
return $http.delete('/admin/themes', data)
}
// 修改主题
export const editThemes = (data) => {
return $http.patch('/admin/themes', data)
}
import { $http } from '@/libs/http'
// 获得所有音轨信息
export const getTracks = (data) => {
return $http.get('/admin/tracks', data)
}
// 增加音轨
export const addTracks = (data) => {
return $http.postObj('/admin/tracks', data)
}
// 删除音轨
export const delTracks = (data) => {
return $http.delete('/admin/tracks', data)
}
// 修改音轨
export const editTracks = (data) => {
return $http.patch('/admin/tracks', data)
}
import axios from '@/libs/api.request'
export const login = ({ userName, password }) => {
const data = {
userName,
password
}
return axios.request({
url: 'login',
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
}
})
}
import { $http } from '@/libs/http'
// 获得所有版本信息
export const getVersion = (data) => {
return $http.get('/admin/app/version', data)
}
// 增加版本
export const addVersion = (data) => {
return $http.postObj('/admin/app/version', data)
}
// 删除版本
export const delVersion = (data) => {
return $http.delete('/admin/app/version', data)
}
// 修改版本
export const editVersion = (data) => {
return $http.patch('/admin/app/version', data)
}
import { $http } from '@/libs/http'
// 获得所有vip购买页套餐信息
export const getVips = (data) => {
return $http.get('/admin/shop/vips', data)
}
export const vipPagesList = (data) => {
return $http.get('/admin/shop/vip_pages_list', data)
}
// 增加vip购买页套餐
export const addVips = (data) => {
return $http.postObj('/admin/shop/vips', data)
}
// 删除vip购买页套餐
export const delVips = (data) => {
return $http.delete('/admin/shop/vips', data)
}
// 修改vip购买页套餐
export const editVips = (data) => {
return $http.patch('/admin/shop/vips', data)
}
import { $http } from '@/libs/http'
// 获得所有vip购买页信息
export const getVipPage = (data) => {
return $http.get('/admin/shop/vip_pages', data)
}
// 增加vip购买页
export const addVipPage = (data) => {
return $http.postObj('/admin/shop/vip_pages', data)
}
// 删除vip购买页
export const delVipPage = (data) => {
return $http.delete('/admin/shop/vip_pages', data)
}
// 修改vip购买页
export const editVipPage = (data) => {
return $http.patch('/admin/shop/vip_pages', data)
}
@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>
<svg id="11567813-e781-4e7f-9d62-924af5cdcf5c" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="883.34" height="785.3" viewBox="0 0 883.34 785.3"><defs><linearGradient id="de64225e-aed0-4729-9589-fefa50377364" x1="586.34" y1="777.6" x2="586.34" y2="296.43" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="gray" stop-opacity="0.25"/><stop offset="0.54" stop-color="gray" stop-opacity="0.12"/><stop offset="1" stop-color="gray" stop-opacity="0.1"/></linearGradient><linearGradient id="c2d29fed-0e3c-4665-9fc2-6bc8bc700b9c" x1="665.58" y1="818.95" x2="665.58" y2="130.43" gradientTransform="matrix(-1, 0, 0, 1, 1038, 0)" xlink:href="#de64225e-aed0-4729-9589-fefa50377364"/><linearGradient id="c8cc5a91-18e5-403f-9b97-91f48ba2f890" x1="832.33" y1="380.69" x2="1046.36" y2="380.69" gradientTransform="matrix(-1, 0, 0, 1, 1874, 0)" xlink:href="#de64225e-aed0-4729-9589-fefa50377364"/></defs><title>tasting</title><ellipse cx="428.11" cy="662.98" rx="428.11" ry="122.32" fill="#f5f5f5"/><path d="M818.56,450.17s-53,37.48-39,109-74.44,171.26-74.44,171.26.76,0,2.18,0c95.73-.47,160.12-98.82,122-186.63C815.15,511.16,805.28,474,818.56,450.17Z" transform="translate(-158.33 -57.35)" fill="#348eed"/><path d="M818.56,450.17s-35,51.08-17.27,100.7-25.15,173-96.14,179.56" transform="translate(-158.33 -57.35)" fill="none" stroke="#535461" stroke-miterlimit="10"/><path d="M867.76,647s-50.21-17.07-62.85,28.39S696.6,711,696.6,711s.54.53,1.57,1.48c69.29,64.64,147.12,58.84,147.54-11.15C845.87,675.32,850.57,650,867.76,647Z" transform="translate(-158.33 -57.35)" fill="#348eed"/><path d="M867.76,647s-50.21-17.07-62.85,28.39S696.6,711,696.6,711s.54.53,1.57,1.48c69.29,64.64,147.12,58.84,147.54-11.15C845.87,675.32,850.57,650,867.76,647Z" transform="translate(-158.33 -57.35)" fill="#f5f5f5" opacity="0.2"/><path d="M867.76,647s-41.58,1.93-44.54,38.92S750,755.8,696.6,711" transform="translate(-158.33 -57.35)" fill="none" stroke="#535461" stroke-miterlimit="10"/><path d="M779.61,543.22c3-15,2.31-36.85-22.23-47.49,13.12-21.09,6.95-36-4.91-41.29-.08-5.5-1.52-16.66-11.18-22.65,1.28-10.38,3.36-51.64-43.26-56.26-5.47-9.44-22.59-35-50.48-42.08v-37c-6.72,14.11-49,22.17-49,22.17-48.46,6.77-59.33,34.15-61.78,46.11-6.24.19-11.73,1-14.82,2.94-10.16,6.35-82.68,25.3-77,75.54-1,.34-1.94.68-2.91,1,0,0-39.67,31.65-29,56.82l-4,.29s-23,14.22-15.93,42.24a8.45,8.45,0,0,0-2,7.69l54.69,182.29A5.86,5.86,0,0,0,448,736.7c2.47,1.72,7.6,5.15,15.08,9.31l.06.29.33-.07a245.79,245.79,0,0,0,26.32,12.55l0,.21.33-.06c6.34,2.57,13.29,5.09,20.8,7.41l0,.16.33,0c6.95,2.13,14.37,4.09,22.22,5.75v.1l.33,0c7.37,1.55,15.13,2.83,23.23,3.75v0h.31a246.37,246.37,0,0,0,30,1.52v0h.67v0a246.48,246.48,0,0,0,28.06-2h.33v-.06q11.29-1.46,23.09-4.09l-.08.78.67.07.1-1q11.77-2.68,24-6.69l.33,0,0-.16q10.16-3.37,20.59-7.74l.33.06,0-.22q12.09-5.11,24.5-11.71l.33.07.06-.28q7-3.75,14.11-8a5.88,5.88,0,0,0,2.6-3.36l54.51-182.43a8.46,8.46,0,0,0-2-7.67Z" transform="translate(-158.33 -57.35)" fill="url(#de64225e-aed0-4729-9589-fefa50377364)"/><path d="M695.81,384.81s-24.6-50.5-71.22-45.32,3.24,53.09,3.24,53.09S684.81,397.11,695.81,384.81Z" transform="translate(-158.33 -57.35)" fill="#348eed"/><path d="M695.81,384.81s-24.6-50.5-71.22-45.32,3.24,53.09,3.24,53.09S684.81,397.11,695.81,384.81Z" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M537.84,376.39s0-.66.12-1.82c.83-8.09,7.46-40.67,60.09-48,0,0,40.79-7.77,47.26-21.37v41.44s-8.42,29.14-50.5,38.85S537.84,376.39,537.84,376.39Z" transform="translate(-158.33 -57.35)" fill="#348eed"/><path d="M415.47,502.64S385,521.42,406.4,558.32s361.28,0,361.28,0,22-45.32-17.48-61.51S415.47,502.64,415.47,502.64Z" transform="translate(-158.33 -57.35)" fill="#784f69"/><path d="M746.75,458.74c-.08-5.3-1.47-16-10.78-21.82,1.23-10,3.25-49.77-41.69-54.22-5.28-9.1-21.76-33.78-48.65-40.55V306.46c-6.47,13.6-47.26,21.37-47.26,21.37-46.7,6.53-57.18,32.91-59.53,44.43-6,.18-11.3,1-14.28,2.83-9.79,6.12-79.68,24.38-74.25,72.8-.94.32-1.87.66-2.8,1,0,0-55.1,44-15.15,68.31h0s18.39,35.29,72.13,8.09l.17-.11c20.24,5.81,74.47,18,106.66-6.37l2.31-1.75c12.8,7.63,43.13,21.58,68.91,1.75l.22-.17c10,9.41,30.35,21.39,54.81-3.07C766.86,486.31,761.17,465.2,746.75,458.74Z" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M675.74,509.76s26.55,39.49,61.51,4.53,20.07-58.27,0-58.92S675.74,509.76,675.74,509.76Z" transform="translate(-158.33 -57.35)" fill="#348eed"/><path d="M675.74,509.76s26.55,39.49,61.51,4.53,20.07-58.27,0-58.92S675.74,509.76,675.74,509.76Z" transform="translate(-158.33 -57.35)" opacity="0.15"/><path d="M746.32,460.56s6.47-55-77-12.95-62.16,64.1-62.16,64.1,41.44,31.73,75.1,5.83S736.6,483.87,746.32,460.56Z" transform="translate(-158.33 -57.35)" fill="#348eed"/><path d="M746.32,460.56s6.47-55-77-12.95-62.16,64.1-62.16,64.1,41.44,31.73,75.1,5.83S736.6,483.87,746.32,460.56Z" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M682.22,443.72s-31.73-22-80.28,0S496.4,521.42,496.4,521.42,570.86,548,611,517.53,680.28,464.44,682.22,443.72Z" transform="translate(-158.33 -57.35)" fill="#348eed"/><path d="M682.22,443.72s-31.73-22-80.28,0S496.4,521.42,496.4,521.42,570.86,548,611,517.53,680.28,464.44,682.22,443.72Z" transform="translate(-158.33 -57.35)" opacity="0.07"/><path d="M616.83,440.49s-64.75-9.06-94.53,9.06S432,515.92,432,515.92s18.39,35.29,72.13,8.09C504.17,524,615.53,454.73,616.83,440.49Z" transform="translate(-158.33 -57.35)" fill="#348eed"/><path d="M616.83,440.49s-64.75-9.06-94.53,9.06S432,515.92,432,515.92s18.39,35.29,72.13,8.09C504.17,524,615.53,454.73,616.83,440.49Z" transform="translate(-158.33 -57.35)" opacity="0.03"/><path d="M447.19,447.61s-57.62,46-12.3,69.93c0,0,95.82-62.8,97.77-69.93C532.66,447.61,488,432.07,447.19,447.61Z" transform="translate(-158.33 -57.35)" fill="#348eed"/><path d="M572.8,373.8s-38.2-6.47-48.56,0-88.05,26.55-72.52,81.58L475,447S519.71,390.63,572.8,373.8Z" transform="translate(-158.33 -57.35)" fill="#348eed"/><path d="M572.8,373.8s-38.2-6.47-48.56,0-88.05,26.55-72.52,81.58L475,447S519.71,390.63,572.8,373.8Z" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M636.25,382.22s-27.19-25.25-87.41-1.94-80.28,65.39-80.28,65.39l90.32,6.8S609.7,379.63,636.25,382.22Z" transform="translate(-158.33 -57.35)" fill="#348eed"/><path d="M636.25,382.22s-27.19-25.25-87.41-1.94-80.28,65.39-80.28,65.39l90.32,6.8S609.7,379.63,636.25,382.22Z" transform="translate(-158.33 -57.35)" opacity="0.07"/><path d="M678.33,385.45s-43.38-14.24-77,5.18-46,57.62-46,57.62l53.74-2.59S651.14,379.63,678.33,385.45Z" transform="translate(-158.33 -57.35)" fill="#348eed"/><path d="M678.33,385.45s-43.38-14.24-77,5.18-46,57.62-46,57.62l53.74-2.59S651.14,379.63,678.33,385.45Z" transform="translate(-158.33 -57.35)" opacity="0.03"/><path d="M735.31,437.9s14.89-75.75-83.52-52.44c0,0-55.93,49.21-40.26,62.8S735.31,437.9,735.31,437.9Z" transform="translate(-158.33 -57.35)" fill="#348eed"/><path d="M398.14,549.47l52.7,175.67a5.65,5.65,0,0,0,2.17,3c14.71,10.29,127.89,83.3,266.49,0a5.67,5.67,0,0,0,2.5-3.24l52.53-175.8a5.67,5.67,0,0,0-6.81-7.12l-22.82,5.7a5.67,5.67,0,0,1-2.4.08l-21.45-4a5.67,5.67,0,0,0-3.7.57l-25.46,13.58a5.67,5.67,0,0,1-4,.5L660.5,551.6a5.67,5.67,0,0,0-3.14.11l-31.81,10.41a5.67,5.67,0,0,1-4.35-.34L600.13,551a5.67,5.67,0,0,0-4.31-.36l-27.17,8.67a5.67,5.67,0,0,1-2.12.25l-26.16-1.83a5.67,5.67,0,0,1-1.16-.2l-20.76-5.93a5.67,5.67,0,0,0-2.69-.1L487,557.34a5.67,5.67,0,0,1-3.22-.28l-28-11.09a5.67,5.67,0,0,0-3.09-.31l-22.29,4a5.67,5.67,0,0,1-2.7-.17l-22.46-7.06A5.67,5.67,0,0,0,398.14,549.47Z" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M398.14,550.76l52.7,175.67a5.65,5.65,0,0,0,2.17,3c14.71,10.29,127.89,83.3,266.49,0a5.67,5.67,0,0,0,2.5-3.24l52.53-175.8a5.67,5.67,0,0,0-6.81-7.12L744.92,549a5.67,5.67,0,0,1-2.4.08l-21.45-4a5.67,5.67,0,0,0-3.7.57l-25.46,13.58a5.67,5.67,0,0,1-4,.5l-27.38-6.84a5.67,5.67,0,0,0-3.14.11l-31.81,10.41a5.67,5.67,0,0,1-4.35-.34l-21.07-10.8a5.67,5.67,0,0,0-4.31-.36l-27.17,8.67a5.67,5.67,0,0,1-2.12.25L540.37,559a5.67,5.67,0,0,1-1.16-.2l-20.76-5.93a5.67,5.67,0,0,0-2.69-.1L487,558.63a5.67,5.67,0,0,1-3.22-.28l-28-11.09a5.67,5.67,0,0,0-3.09-.31l-22.29,4a5.67,5.67,0,0,1-2.7-.17l-22.46-7.06A5.67,5.67,0,0,0,398.14,550.76Z" transform="translate(-158.33 -57.35)" fill="#4d4981"/><polygon points="297.62 430.78 292.11 428.86 289.02 423.91 302.76 406.71 308.27 408.63 311.36 413.58 297.62 430.78" fill="#fff" opacity="0.5"/><polygon points="558.55 459.92 553.03 458 549.95 453.05 563.68 435.85 569.19 437.76 572.28 442.71 558.55 459.92" fill="#fff" opacity="0.5"/><polygon points="443.95 366.03 438.44 364.12 435.35 359.17 449.08 341.96 454.6 343.88 457.68 348.83 443.95 366.03" fill="#fff" opacity="0.5"/><polygon points="474.88 429.29 469.55 431.65 463.91 430.15 462.18 408.2 467.51 405.84 473.15 407.34 474.88 429.29" fill="#fff" opacity="0.5"/><polygon points="385.08 375.41 381.4 379.94 375.69 381.15 364.21 362.37 367.9 357.84 373.61 356.63 385.08 375.41" fill="#fff" opacity="0.5"/><polygon points="378.61 428.5 374.92 433.03 369.22 434.24 357.74 415.46 361.42 410.93 367.13 409.72 378.61 428.5" fill="#fff" opacity="0.5"/><polygon points="536.96 433.67 532.73 437.69 526.91 438.16 517.93 418.06 522.17 414.04 527.98 413.57 536.96 433.67" fill="#fff" opacity="0.5"/><polygon points="508.11 364.79 509.54 359.13 514.2 355.63 532.54 367.81 531.11 373.47 526.45 376.98 508.11 364.79" fill="#fff" opacity="0.5"/><polygon points="313.16 373.16 307.65 371.24 304.56 366.29 318.3 349.09 323.81 351 326.9 355.95 313.16 373.16" fill="#fd6f8d" opacity="0.5"/><polygon points="382.54 340.82 381.19 335.15 383.71 329.88 405.6 332.21 406.95 337.89 404.43 343.16 382.54 340.82" fill="#fd6f8d" opacity="0.5"/><polygon points="488.62 308.54 489.71 302.81 494.16 299.03 513.18 310.11 512.09 315.84 507.64 319.62 488.62 308.54" fill="#fd6f8d" opacity="0.5"/><polygon points="446.21 279.63 451.9 278.33 457.14 280.9 454.59 302.77 448.9 304.06 443.66 301.49 446.21 279.63" fill="#fd6f8d" opacity="0.5"/><polygon points="407.15 452.19 405.8 446.51 408.31 441.24 430.2 443.57 431.55 449.25 429.04 454.52 407.15 452.19" fill="#fd6f8d" opacity="0.5"/><polygon points="326.21 456.34 322.76 451.63 323.02 445.81 344.09 439.42 347.55 444.13 347.28 449.96 326.21 456.34" fill="#fd6f8d" opacity="0.5"/><polygon points="552.8 417.34 548.01 414.01 546.38 408.4 564.25 395.55 569.04 398.88 570.67 404.48 552.8 417.34" fill="#fd6f8d" opacity="0.5"/><polygon points="401.3 421.87 396.51 418.54 394.87 412.94 412.74 400.08 417.53 403.41 419.17 409.01 401.3 421.87" fill="#fd6f8d" opacity="0.5"/><path d="M452.73,545.66l-22.29,4a5.67,5.67,0,0,1-2.7-.17l-.37-.12,40.57,188-40.57-188-22.09-6.94a5.67,5.67,0,0,0-7.13,7l52.7,175.67a5.65,5.65,0,0,0,2.17,3c4.61,3.23,18.91,12.62,40.63,21.42l-39.29-204A5.66,5.66,0,0,0,452.73,545.66Z" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M767.74,542l-22.82,5.7a5.67,5.67,0,0,1-2.4.08l-21.45-4a5.66,5.66,0,0,0-2,0L681.57,747.48l37.48-203.69a5.67,5.67,0,0,0-1.68.58l-25.46,13.58a5.67,5.67,0,0,1-3.79.55L661.4,755.06a280.26,280.26,0,0,0,44.15-19l40.89-188.75L705.54,736q6.94-3.7,14-7.92a5.67,5.67,0,0,0,2.5-3.24l52.53-175.8A5.67,5.67,0,0,0,767.74,542Z" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M687.87,558.44,660.5,551.6a5.66,5.66,0,0,0-1.61-.16L638,761.58q11.5-2.61,23.44-6.52l26.72-196.56Z" transform="translate(-158.33 -57.35)" fill="#4d4981"/><g opacity="0.5"><rect x="447.32" y="548.5" width="0.65" height="192.29" transform="translate(-284.25 51.59) rotate(-12.18)" fill="#fff"/><rect x="499.39" y="557.95" width="0.65" height="201.19" transform="translate(-247.01 20.51) rotate(-8.19)" fill="#fff"/><rect x="473.66" y="544.79" width="0.65" height="207.96" transform="translate(-272.5 44.02) rotate(-10.9)" fill="#fff"/><rect x="526.13" y="552.08" width="0.65" height="212.05" transform="translate(-214.22 -8.45) rotate(-5.05)" fill="#fff"/><rect x="555.12" y="559.81" width="0.65" height="207.61" transform="translate(-177.58 -40.75) rotate(-1.68)" fill="#fff"/><rect x="586.67" y="554.95" width="0.65" height="213.91" transform="translate(-162.63 -53.52) rotate(-0.37)" fill="#fff"/><rect x="517.7" y="665.22" width="202.95" height="0.65" transform="translate(-229.51 1199.57) rotate(-87.65)" fill="#fff"/><rect x="542.25" y="657.79" width="212.28" height="0.65" transform="translate(-229.42 1180.31) rotate(-84.28)" fill="#fff"/><rect x="574.78" y="656.84" width="200.2" height="0.65" transform="matrix(0.13, -0.99, 0.99, 0.13, -225.62, 1179.91)" fill="#fff"/><rect x="596.61" y="646.43" width="207.48" height="0.65" transform="translate(-221.08 1160.76) rotate(-79.55)" fill="#fff"/><rect x="629.42" y="642.64" width="193.13" height="0.65" transform="translate(-214.09 1159.48) rotate(-77.81)" fill="#fff"/></g><path d="M207.92,697.1c6-2,15-4.28,22.09-3.47,6.74-2.46,22.6-8.88,33.43-18.39a241,241,0,0,1,.71-28.24c5.54-63-6.92-104.49-6.92-104.49s-9.78-31.67-8.71-57.95c-.63,1.19-1,1.9-1,1.9-.86-22.67-8.58-46.13-14.36-60.74a4.88,4.88,0,0,1-.65-.24l-6.59-15.34a13.31,13.31,0,0,1-1.22-2.83l-1-2.43.31-.13c-.1-.4-.21-.82-.31-1.25h0c-4.13-17.47-7.28-58.9-7.28-58.9C205.33,301,231.63,274,231.63,274l44.63-38.41a5.66,5.66,0,0,0-.44,1.57l.37-.32c.63-1.8,1.32-4,2-6.44a33.93,33.93,0,0,1-4.46-3.77,34.27,34.27,0,0,1-10-17.85c-1.08-5.31-.87-10.78-.65-16.19l.74-18.52a58.81,58.81,0,0,1,1.1-10.8c3.37-14.74,18.43-25,33.54-25.65,4.81-.2,9.61.41,14.4.77a96.85,96.85,0,0,0,11.7.25,2.12,2.12,0,0,0,1.26-.33,1.85,1.85,0,0,0,.55-1.28,6.37,6.37,0,0,0-1.56-4.77,5.94,5.94,0,0,0-2-1.16l1.08.26a5.88,5.88,0,0,0-1.77-.95c3.86.91,7.81,1.86,11.13,4a14.38,14.38,0,0,1,2.6,2.19,11.35,11.35,0,0,1,3.76,7c1.73-1.69,3.39-3.55,4-5.87a5.93,5.93,0,0,0,0-2.84q.39.49.76,1c0-.1,0-.2-.07-.3a29.7,29.7,0,0,1,5.81,12,4.66,4.66,0,0,0,1.05-3.44c.22.68.43,1.37.62,2.07a4.79,4.79,0,0,0,.07-1.38,47.32,47.32,0,0,1,2.31,16.43c0,5.56-2.46,14.21-8.42,16.16-.3.1-.62.18-.94.25a37.35,37.35,0,0,1-25.19,57.05q-.14,1-.28,2.13l-.24,0c-.16,1.5-.29,3.08-.39,4.73a31.84,31.84,0,0,0,4.9,3.94A40.46,40.46,0,0,0,339.85,251a48.63,48.63,0,0,1,19.65,5.84c7.12,4,15,9.79,17.45,16.84,4.12,11.77,26.76,30,42,32.27l5.4-.31.39-.12v.1l1.32-.08.08,0v0l.35,0,0,.08,1.37-.08s.14.63.32,1.75c7.65-2.62,23.88-9.92,45-29.08,23.54-21.35,55.31-17.15,67.56-14.46a2.1,2.1,0,0,1,.56,3.89c-4.94,2.72-12.62,6.67-16.22,7.11,0,0,7.42,7.68-.46,11.78a5.67,5.67,0,0,1-5.93-.57c-3.79-2.77-14.33,3.51-29.59,6.78,0,0-43.08,44.58-64.45,51.48A30.74,30.74,0,0,1,423,347l-1.32-.11-.07.11h-.18l-.16,1.4s-33.25-1.75-44-13.82a17.32,17.32,0,0,1-2.11-2.25l11.56,145.48h-.11l.11,1.38h-8.5a320.62,320.62,0,0,0,2.28,53.63v94.8s.44,8.35-16.89,20.5A647.93,647.93,0,0,0,311,688.79l-2.1,1.82c3.19,29,9.86,83.08,15.42,85.1l-3.43.6c5.91,7.45,22.56,27.89,27.65,28.46,6.23.69,10.38,10.38,0,12.46-9.58,1.92-34.48,4.42-61.65-6.08a9.41,9.41,0,0,1-5.92-10l2-15.28a2.14,2.14,0,0,0-.88.93c-1.38,4.15,0-5.54,0-5.54a433,433,0,0,1-13.38-55.84l-20.52,17.78s-.42-.51-1.13-1.43c-5,11.44-14,34.81-12,51.95,2.77,23.53-23.53,2.77-20.76-11.07,2.24-11.2-7.75-58.64-11.65-76.34A7.89,7.89,0,0,1,207.92,697.1Z" transform="translate(-158.33 -57.35)" fill="url(#c2d29fed-0e3c-4665-9fc2-6bc8bc700b9c)"/><path d="M422.79,313.2s19.6-3.38,48.65-29.73c23-20.85,54-16.74,66-14.12a2,2,0,0,1,.55,3.79c-4.83,2.65-12.33,6.51-15.84,6.95,0,0,7.24,7.5-.45,11.51a5.54,5.54,0,0,1-5.79-.56c-3.7-2.7-14,3.43-28.89,6.62,0,0-58.11,60.14-71.62,50Z" transform="translate(-158.33 -57.35)" fill="#fec3be"/><path d="M322.78,231.44s-5.41,23,0,39.19L298.46,274,276.84,263.2V249s10.14-21.62,7.43-46.62Z" transform="translate(-158.33 -57.35)" fill="#fec3be"/><path d="M253.86,729.44S236.29,763.9,239,786.87s-23,2.7-20.27-10.81c2.19-10.93-7.57-57.26-11.37-74.54a7.7,7.7,0,0,1,5.12-9c8.13-2.67,21.72-6,28.56-.94C251.16,699,253.86,729.44,253.86,729.44Z" transform="translate(-158.33 -57.35)" fill="#8b416f"/><path d="M320.76,767.27s23,29.73,29.06,30.41,10.14,10.14,0,12.16c-9.35,1.87-33.67,4.32-60.2-5.94a9.19,9.19,0,0,1-5.78-9.78l3.81-28.88Z" transform="translate(-158.33 -57.35)" fill="#8b416f"/><path d="M358.6,451.72l22,2.94s-5.14,36.92.26,77.47V624.7s.43,8.15-16.49,20a632.65,632.65,0,0,0-51.24,39.71l-61.32,53.12s-24.33-29.73-20.95-47.3c0,0,40.54-12.84,45.95-33.79,3.1-12,18.82-22,31.69-28.35,16.58-8.19,25.62-26.8,21.08-44.72a34.48,34.48,0,0,0-3.45-8.69c-10.81-18.92-26.35-61.49-26.35-61.49l29.73-56.76Z" transform="translate(-158.33 -57.35)" fill="#4d4981"/><path d="M358.6,451.72l22,2.94s-5.14,36.92.26,77.47V624.7s.43,8.15-16.49,20a632.65,632.65,0,0,0-51.24,39.71l-61.32,53.12s-24.33-29.73-20.95-47.3c0,0,40.54-12.84,45.95-33.79,3.1-12,18.82-22,31.69-28.35,16.58-8.19,25.62-26.8,21.08-44.72a34.48,34.48,0,0,0-3.45-8.69c-10.81-18.92-26.35-61.49-26.35-61.49l29.73-56.76Z" transform="translate(-158.33 -57.35)" opacity="0.05"/><path d="M326.16,769.3s-39.87,6.76-41.22,10.81,0-5.41,0-5.41-23-69.6-17.57-131.09-6.76-102-6.76-102-14.19-45.95-6.08-72.3,93.25-14.87,93.25-14.87S318.05,524,318.73,557.13c0,0-3.38,100-9.46,111.49C309.27,668.62,318.73,766.6,326.16,769.3Z" transform="translate(-158.33 -57.35)" fill="#4d4981"/><path d="M321.43,249s-4.05,25-44.6,14.19l27,205.41s78.38,2.7,77.71-6.76L365.35,337.53Z" transform="translate(-158.33 -57.35)" fill="#fff"/><path d="M320.42,247.66s7.68,8.24,19.52,9.27a47.49,47.49,0,0,1,19.18,5.71c7,3.89,14.63,9.55,17,16.45,4.73,13.51,34.46,35.81,48,31.08l-4.73,40.54s-38.51-2-45.27-16.89l11.49,144.6H375.49s-5.41-39.19-9.46-47.3-12.16-52.7-12.84-63.52-10.81-73-19.6-83.79l-13.18-32.1Z" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M320.42,249.69s9,7.57,20.87,8.59A47.49,47.49,0,0,1,360.47,264c7,3.89,14.63,9.55,17,16.45,4.73,13.51,34.46,35.81,48,31.08l-4.73,40.54s-38.51-2-45.27-16.89L387,479.76H376.84s-5.41-39.19-9.46-47.3-12.16-52.7-12.84-63.52-10.81-73-19.6-83.79l-14.53-31.42Z" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M320.42,248.34s9,7.57,20.87,8.59a47.49,47.49,0,0,1,19.18,5.71c7,3.89,14.63,9.55,17,16.45,4.73,13.51,34.46,35.81,48,31.08l-4.73,40.54s-38.51-2-45.27-16.89L387,478.41H376.84s-5.41-39.19-9.46-47.3-12.16-52.7-12.84-63.52-10.81-73-19.6-83.79l-14.53-31.42Z" transform="translate(-158.33 -57.35)" fill="#fd6f8d"/><path d="M286,222.32c.88-6.4-.88-10.4-1.69-17.91l38.51,29.06s-.92,3.93-1.62,9.67a36.73,36.73,0,0,1-5.81.47C301.91,243.61,292.29,233.14,286,222.32Z" transform="translate(-158.33 -57.35)" opacity="0.1"/><circle cx="157.02" cy="147.74" r="36.49" fill="#fec3be"/><path d="M355.17,169.33a46.19,46.19,0,0,0-3-18.06,4.5,4.5,0,0,1-.77,5.83,28.91,28.91,0,0,0-9.17-16.24c2.6.42,3.93,3.65,3.3,6.21s-2.61,4.53-4.51,6.35c.22-3.87-2.33-7.47-5.57-9.6s-7.1-3-10.87-3.94a6.3,6.3,0,0,1,4.17,6.46,1.8,1.8,0,0,1-.54,1.25,2.07,2.07,0,0,1-1.23.33,94.57,94.57,0,0,1-11.42-.24,119.71,119.71,0,0,0-14.06-.75c-14.76.62-29.46,10.65-32.75,25a57.43,57.43,0,0,0-1.07,10.54L267,200.6c-.21,5.28-.42,10.63.64,15.81a33.49,33.49,0,0,0,17.21,22.74c5.3-7.68,5.77-17.55,7.61-26.7a13.51,13.51,0,0,1,2.29-5.9c1.33-1.66,3.67-2.71,5.65-1.92s2.84,3.82,1.19,5.17c1.42,1.73,4.22.44,5.85-1.1a31.56,31.56,0,0,0,9.16-16.35c.66-3.15,1.32-7,4.36-8.09a8.2,8.2,0,0,1,3.27-.22c6.43.44,16.49,3.1,22.73,1.06C352.77,183.2,355.17,174.76,355.17,169.33Z" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M354.5,168.65a46.19,46.19,0,0,0-3-18.06,4.5,4.5,0,0,1-.77,5.83,28.91,28.91,0,0,0-9.17-16.24c2.6.42,3.93,3.65,3.3,6.21s-2.61,4.53-4.51,6.35c.22-3.87-2.33-7.47-5.57-9.6s-7.1-3-10.87-3.94a6.3,6.3,0,0,1,4.17,6.46,1.8,1.8,0,0,1-.54,1.25,2.07,2.07,0,0,1-1.23.33,94.57,94.57,0,0,1-11.42-.24,119.71,119.71,0,0,0-14.06-.75c-14.76.62-29.46,10.65-32.75,25A57.43,57.43,0,0,0,267,181.84l-.72,18.08c-.21,5.28-.42,10.63.64,15.81a33.49,33.49,0,0,0,17.21,22.74c5.3-7.68,5.77-17.55,7.61-26.7a13.51,13.51,0,0,1,2.29-5.9c1.33-1.66,3.67-2.71,5.65-1.92s2.84,3.82,1.19,5.17c1.42,1.73,4.22.44,5.85-1.1a31.56,31.56,0,0,0,9.16-16.35c.66-3.15,1.32-7,4.36-8.09a8.2,8.2,0,0,1,3.27-.22c6.43.44,16.49,3.1,22.73,1.06C352.09,182.53,354.5,174.08,354.5,168.65Z" transform="translate(-158.33 -57.35)" fill="#fad375"/><path d="M427.18,310.16s5.41,25-4.73,40.54l-5.74-.47,3.13-39.65Z" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M425.83,310.16s5.41,25-4.73,40.54l-5.74-.47,3.13-39.65Z" transform="translate(-158.33 -57.35)" fill="#fd6f8d"/><path d="M280.55,241.92,237,279.42s-25.68,26.35-14.87,68.92c0,0,4.73,62.16,10.14,64.87,0,0,18.92,37.84,20.27,73.65,0,0,25.34-51,36.83-19.93s38.18,35.47,38.18,35.47L312.65,318.61S275.15,253.4,280.55,241.92Z" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M279.2,241.92l-43.58,37.5s-25.68,26.35-14.87,68.92c0,0,4.73,62.16,10.14,64.87,0,0,18.92,37.84,20.27,73.65,0,0,26.69-53.72,38.18-22.64s36.83,38.18,36.83,38.18L311.3,318.61S273.8,253.4,279.2,241.92Z" transform="translate(-158.33 -57.35)" fill="#fd6f8d"/><path d="M259.27,400.37s-10.81-7.43-28.38,12.84l43.24,78.38,14.53-22S257.92,409.15,259.27,400.37Z" transform="translate(-158.33 -57.35)" fill="#fec3be"/><path d="M269.74,404.76s-23.91,26.86-33.24,22.55l-8.64-20.11,32.42-13.93Z" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M269.74,403.41S245.83,430.27,236.5,426l-8.64-20.11,32.42-13.93Z" transform="translate(-158.33 -57.35)" fill="#fd6f8d"/><path d="M287.2,464.54s-11.38,23.33-17.46,24.69l8.78,10.14L291.36,475Z" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M287.88,465.21s-11.38,23.33-17.46,24.69L279.2,500,292,475.71Z" transform="translate(-158.33 -57.35)" fill="#4d4981"/><path d="M266.36,321s-1.35,48.65,4.05,66.89" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M375.83,330.43s-8.11-2.7-8.11-9.46" transform="translate(-158.33 -57.35)" opacity="0.1"/><g opacity="0.5"><path d="M827.65,331.62c-.06,6.39.7,12.3,2.76,16.93l2,29s.5-.28,1.41-.72c-1.21,11-.34,31.15,21.26,34.94a3.66,3.66,0,0,0,1.33-.66,98,98,0,0,0,4-9.21l.25.08-.25,1.24,1.6.5c-7.79,16.88-6,52-4.27,55.44.73,1.44,1.28,5.68,1.69,10.49a119.67,119.67,0,0,1-1,28c-1,6.39-1.3,14.23,2,17.49,6,5.92,5.33,51.33,5.33,51.33a32.84,32.84,0,0,0,1.39,3l-2.06.29s0,17.77,3.33,30.27c1.92,7.19-.8,16.56-3.45,23.22a46.94,46.94,0,0,0-2.31,28.27c1.95,8.14,6,16.26,14.43,18.93,18.67,5.92,22.67-25,22.67-25s-.67-23.69,0-27c.21-1,1.17-3.84,2.41-7.26a90.46,90.46,0,0,0,5-39l-.77-8.32-3.62.52c-.06-.86-.11-1.76-.16-2.7s-.09-1.62-.13-2.47-.08-1.72-.12-2.62-.08-1.81-.11-2.74q0-.7-.05-1.41c-.15-4.26-.28-8.81-.39-13.31,0-2-.09-4-.12-5.95,0-1.46-.05-2.91-.07-4.31q0-.7,0-1.4c0-2.3-.06-4.5-.07-6.53q0-.61,0-1.2,0-1.18,0-2.26,0-.54,0-1.06c0-.69,0-1.34,0-2s0-1.2,0-1.73,0-1,0-1.48c0-2.47.1-3.57.2-2.66.33,3,1.33,21.06,0-2-1.08-18.59,15.66-62.91,22.15-79.3a57.75,57.75,0,0,1,6,3.58c2.22,25.86-.83,75.43-1.81,90v.06c-.08,1.13-.14,2-.19,2.71v.12l-.05.72v.2l0,.21v.11s-8.67,54-8.67,66.47c0,9.63-4.75,23.56-6.93,29.44-3.17,1.38-5.74,2.8-5.74,2.8l.67,49.36s-7.33,24.35,19.33,30.27,15.33-24.35,15.33-24.35l-2-21.72,9.33-33.56-2.62-.53c5.54-17.7,20.38-66.4,19.29-77.12-1.22-12,4.77-25.08,5.84-27.3h0l.16-.34c-5.72-11.8,11.67-74,19.6-100.84,9.89-4.72,17.06-8.4,17.06-8.4s-.2-.37-.55-1l.55-.28a132.29,132.29,0,0,1-10.23-25.09l.57-.58-.43-1.21c1.36-1.31,2.1-2.08,2.1-2.08s19.33,3.29,14.67-5.92-3.33-23-3.33-23V299.41l9.33,3.07c17.55,2.89,20.69-16.71,20.6-31.74a116.66,116.66,0,0,0-1.26-17.62s-13.33-102-36.67-98.71c-21.86,3.08-41.08-9.71-43.43-11.35q-.43-1.32-.77-2.6a33,33,0,0,0,3.5-4.76c.73,0,1.47-.1,2.17-.18,0-.2,0-.39,0-.59l.67-.07a17.22,17.22,0,0,1,4.76-12.43,16.51,16.51,0,0,1,4.38-3c3.23-1.51,6.88-2,10.05-3.62a11.09,11.09,0,0,0,3-2.29c2-1.8,3.3-4.31,2.61-6.81-.31-1.11-1-2.13-1-3.28,0-1.6,1.25-2.88,2-4.29C994,95,992,89.82,989.25,86s-6.28-7.42-7.29-12c-.41-1.88-.38-3.86-1-5.69-2-5.93-9.66-7.7-16-7.37s-13.14,1.68-18.62-1.44A17.45,17.45,0,0,0,943,57.6a9.16,9.16,0,0,0-4.21,0c-8,1.39-15.37,5.36-22,10a50.64,50.64,0,0,0-7.46,6.1,27.44,27.44,0,0,0-4,4.77c-2.7,4.17-4,9.49-2.31,14.16a14.33,14.33,0,0,0,4.83,6.48,32.62,32.62,0,0,0,6.71,46.18,38.1,38.1,0,0,1-3.76,5.14v-.26s-20.33,10.2-27.67,8.88c-7.16-1.29-25.76,10.62-25.38,34.46,0,.6,0,1.22,0,1.84l-12,37.21a150.56,150.56,0,0,0-7.29,46.26S827.44,308.76,827.65,331.62Zm32.23,15.76,15.86-59.16v12.94l-10,35.54s-2,11.89-3.53,24.52Z" transform="translate(-158.33 -57.35)" fill="url(#c8cc5a91-18e5-403f-9b97-91f48ba2f890)"/></g><path d="M907.17,555.35l.74,8.13a89.47,89.47,0,0,1-4.85,38.15c-1.19,3.35-2.12,6.11-2.32,7.1-.64,3.22,0,26.37,0,26.37s-3.86,30.23-21.87,24.44c-8.1-2.6-12-10.55-13.92-18.5a46.44,46.44,0,0,1,2.23-27.62c2.56-6.51,5.18-15.66,3.33-22.69-3.22-12.22-3.22-29.58-3.22-29.58Z" transform="translate(-158.33 -57.35)" fill="#5a5773"/><path d="M926.46,415.8s-25.08,61.1-23.79,83.6.32,4.82,0,1.93c-.57-5.15,0,56,1.93,63,0,0-26.37,19.29-36.66-6.43,0,0,.64-44.37-5.14-50.16-3.18-3.18-2.87-10.84-1.93-17.09a118.45,118.45,0,0,0,1-27.33c-.39-4.7-.93-8.84-1.63-10.25-1.93-3.86-3.86-47.59,7.72-59.81Z" transform="translate(-158.33 -57.35)" fill="#605d82"/><path d="M901.38,564.36c-1.93-7.07-2.5-68.18-1.93-63,.32,2.89,1.29,20.58,0-1.93s23.79-83.6,23.79-83.6l-56.11-21.58c.26-.33.53-.64.8-.93l58.52,22.51s-25.08,61.1-23.79,83.6.32,4.82,0,1.93c-.57-5.15,0,56,1.93,63,0,0-10.53,7.7-20.88,6.88C892.92,570.54,901.38,564.36,901.38,564.36Z" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M948.32,613.88l-9,32.8,1.93,21.22s10.93,29.58-14.79,23.79-18.65-29.58-18.65-29.58l-.64-48.23s10.29-5.79,14.79-5.14S948.32,613.88,948.32,613.88Z" transform="translate(-158.33 -57.35)" fill="#5a5773"/><path d="M992.7,400.37S963.12,496.19,970.19,511c0,0-7.07,14.15-5.79,27s-20.58,81.67-20.58,81.67l-32.16-5.79s7.72-19.29,7.72-31.51,8.36-65,8.36-65,7.07-96.47-1.29-106.76Z" transform="translate(-158.33 -57.35)" fill="#605d82"/><g opacity="0.1"><path d="M928.95,418.42c5.33,21.84-.56,102.21-.56,102.21s-8.36,52.73-8.36,65c0,9.33-4.49,22.77-6.62,28.61l-1.74-.31s7.72-19.29,7.72-31.51,8.36-65,8.36-65S933.13,444,928.95,418.42Z" transform="translate(-158.33 -57.35)"/><path d="M970.19,511l-.16.33c-.05-.33-.1-.68-.13-1A7.11,7.11,0,0,0,970.19,511Z" transform="translate(-158.33 -57.35)"/><path d="M927.91,413.75a9.47,9.47,0,0,0-1.45-3.09l66.24-10.29s-.39,1.25-1.06,3.48Z" transform="translate(-158.33 -57.35)"/></g><path d="M838.35,365.64s-9.65,36,19.29,41.16a3.52,3.52,0,0,0,1.29-.64s16.72-32.8,3.86-45.66Z" transform="translate(-158.33 -57.35)" fill="#a1616a"/><path d="M956.69,126.4s-1.93,27,19.29,39.87-45,48.23-45,48.23L896.23,159.2S911,161.13,920,135.41Z" transform="translate(-158.33 -57.35)" fill="#a1616a"/><path d="M970.83,168.2s-32.8,68.17-62.38,3.86L862.79,398.44s70.74,21.87,71.38,32.16,71.38-26.37,71.38-26.37-15.43-28.3-11.58-45.66S970.83,168.2,970.83,168.2Z" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M970.83,166.92s-32.8,68.17-62.38,3.86L862.79,397.15S933.53,419,934.18,429.3s71.38-26.37,71.38-26.37-15.43-28.3-11.58-45.66S970.83,166.92,970.83,166.92Z" transform="translate(-158.33 -57.35)" fill="#e1e7ef"/><path d="M938.68,631.88s8.36-18.65-10.93-18.65S911.67,630,911.67,630Z" transform="translate(-158.33 -57.35)" fill="#5a5773"/><path d="M894.95,583.65s7.72-19.94-11.58-20.58-9.65,18-9.65,18Z" transform="translate(-158.33 -57.35)" fill="#5a5773"/><path d="M911.35,152.45s-19.61,10-26.69,8.68-25.72,10.93-24.44,35.37L848.6,233a148.9,148.9,0,0,0-7,45.21h0s-16.72,46.3-7.72,66.88l1.93,28.3S850.57,365,866,367.57l-3.86-23.15,17.36-65.6,28.94-70.1Z" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M911.35,151.16s-19.61,10-26.69,8.68-25.72,10.93-24.44,35.37L848.6,231.68a148.9,148.9,0,0,0-7,45.21h0s-16.72,46.3-7.72,66.88l1.93,28.3s14.79-8.36,30.23-5.79l-3.86-23.15,17.36-65.6,28.94-70.1Z" transform="translate(-158.33 -57.35)" fill="#67647e"/><path d="M878.87,229.3V302l-9.65,34.73s-6.43,37.94-4.5,49.52,32.8,14.15,41.16,4.5,3.86-92,3.86-92l1.61-120.58-1.29-3.86Z" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M877.58,226.08v72.67l-9.65,34.73s-6.43,37.94-4.5,49.52,32.8,14.15,41.16,4.5,3.86-92,3.86-92L910.06,175Z" transform="translate(-158.33 -57.35)" fill="#67647e"/><path d="M1007.17,286.54l-20.58,64,9.65,27.33s-45.66,48.23-74.6,25.08c0,0-3.22-98.4,3.22-121.55s37.3-101,37.3-101l6.43-20.58,34.08,19.29Z" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M1008.78,283.32v63s-1.29,13.51,3.22,22.51-14.15,5.79-14.15,5.79-45.66,48.23-74.6,25.08c0,0-3.22-98.4,3.22-121.55s37.3-101,37.3-101l6.43-20.58,34.08,19.29Z" transform="translate(-158.33 -57.35)" fill="#67647e"/><rect x="929.99" y="262.04" width="45" height="7" transform="translate(1535.67 -445.97) rotate(123)" fill="#e6e6e6"/><ellipse cx="937.75" cy="288.24" rx="14.57" ry="8" transform="matrix(0.54, -0.84, 0.84, 0.54, 26.9, 860.32)" opacity="0.1"/><ellipse cx="937.28" cy="288.96" rx="14.57" ry="8" transform="translate(26.08 860.25) rotate(-57)" fill="#e6e6e6"/><ellipse cx="937.28" cy="288.96" rx="11.84" ry="6.5" transform="translate(26.08 860.25) rotate(-57)" opacity="0.1"/><path d="M958.94,145.37s19.61,14.47,42.12,11.25,35.37,96.47,35.37,96.47,8.36,51.45-18.65,46.95L958,280.1s12.86-13.51,10.93-30.87l28.94,11.58-1.29-55.31Z" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M1035.14,251.81s8.36,51.45-18.65,46.95l-59.81-19.94s12.86-13.51,10.93-30.87l28.94,11.58Z" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M969,254.73S949,228.34,934.19,241s29.55,35.89,29.55,35.89Z" transform="translate(-158.33 -57.35)" fill="#a1616a"/><path d="M958.94,144.09s19.61,14.47,42.12,11.25,35.37,96.47,35.37,96.47,8.36,51.45-18.65,46.95L958,278.82s12.86-13.51,10.93-30.87l28.94,11.58-1.29-55.31-32.8-27Z" transform="translate(-158.33 -57.35)" fill="#67647e"/><path d="M919.38,136.69l36.66-9s-1.91,2.16.32,10.61c-5.9,6.77-13.15,15.76-22.83,15.76a32,32,0,0,1-19.22-6.37A57.82,57.82,0,0,0,919.38,136.69Z" transform="translate(-158.33 -57.35)" opacity="0.1"/><circle cx="775.84" cy="63.26" r="32.16" fill="#a1616a"/><path d="M922,106.66c-7.46-1.27-15.66-4.58-18.16-11.72-1.6-4.57-.38-9.77,2.23-13.84s6.45-7.2,10.41-10c6.43-4.52,13.52-8.4,21.26-9.76a8.73,8.73,0,0,1,4.06,0,16.76,16.76,0,0,1,3.29,1.8c5.29,3.05,11.87,1.73,18,1.41s13.48,1.41,15.41,7.2c.6,1.79.56,3.72,1,5.56,1,4.5,4.39,8,7,11.75s4.62,8.82,2.42,12.86c-.75,1.37-2,2.63-2,4.19a13,13,0,0,0,1,3.21c.91,3.32-1.74,6.67-4.8,8.25s-6.58,2.07-9.7,3.54a17,17,0,0,0-9.47,15.74c-2.84.33-6.15.5-8.07-1.62-.86-.95-1.28-2.22-2.06-3.23-1-1.32-2.56-2.1-3.84-3.16-6.73-5.53-1.95-13.17-5.76-19.13-2-3.07-5.18-2.13-8.4-2.08A74.22,74.22,0,0,1,922,106.66Z" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M922.63,106c-7.46-1.27-15.66-4.58-18.16-11.72-1.6-4.57-.38-9.77,2.23-13.84s6.45-7.2,10.41-10c6.43-4.52,13.52-8.4,21.26-9.76a8.73,8.73,0,0,1,4.06,0,16.76,16.76,0,0,1,3.29,1.8c5.29,3.05,11.87,1.73,18,1.41s13.48,1.41,15.41,7.2c.6,1.79.56,3.72,1,5.56,1,4.5,4.39,8,7,11.75s4.62,8.82,2.42,12.86c-.75,1.37-2,2.63-2,4.19a13,13,0,0,0,1,3.21c.91,3.32-1.74,6.67-4.8,8.25s-6.58,2.07-9.7,3.54a17,17,0,0,0-9.47,15.74c-2.84.33-6.15.5-8.07-1.62-.86-.95-1.28-2.22-2.06-3.23-1-1.32-2.56-2.1-3.84-3.16-6.73-5.53-1.95-13.17-5.76-19.13-2-3.07-5.18-2.13-8.4-2.08A74.22,74.22,0,0,1,922.63,106Z" transform="translate(-158.33 -57.35)" fill="#4a4347"/><path d="M883.69,214.19s-7.07,36.66-3.86,50.81a47.15,47.15,0,0,1-.84,24.92" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M998,229.62s3.08,24.44-2.05,29.58" transform="translate(-158.33 -57.35)" opacity="0.1"/><path d="M818.42,530.81a1.5,1.5,0,0,0,0-3,1.5,1.5,0,0,0,0,3Z" transform="translate(-158.33 -57.35)"/><rect x="507.29" y="273.65" width="45" height="7" transform="translate(-14.36 -236.96) rotate(22.2)" fill="#e6e6e6"/><ellipse cx="554.85" cy="287.38" rx="8" ry="14.57" transform="translate(-79.18 635.19) rotate(-67.8)" opacity="0.1"/><ellipse cx="555.65" cy="287.7" rx="8" ry="14.57" transform="translate(-78.99 636.12) rotate(-67.8)" fill="#e6e6e6"/><ellipse cx="555.65" cy="287.7" rx="6.5" ry="11.84" transform="translate(-78.99 636.12) rotate(-67.8)" opacity="0.1"/></svg>
\ No newline at end of file
<svg id="26c346b9-da0c-4941-90c9-e7f7f9ba5ac3" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="958.98" height="717.78" viewBox="0 0 958.98 717.78"><defs><linearGradient id="4ef13be0-8e55-4463-b812-64152197208f" x1="768.34" y1="226.6" x2="768.34" y2="91.11" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="gray" stop-opacity="0.25"/><stop offset="0.54" stop-color="gray" stop-opacity="0.12"/><stop offset="1" stop-color="gray" stop-opacity="0.1"/></linearGradient><linearGradient id="5bed69c4-1e02-4930-aaf9-54950fab6759" x1="324.42" y1="444.93" x2="324.42" y2="444.59" xlink:href="#4ef13be0-8e55-4463-b812-64152197208f"/><linearGradient id="a88837d3-ccd3-4ab3-a7ec-270b2797a153" x1="437.75" y1="743.35" x2="437.75" y2="288.01" xlink:href="#4ef13be0-8e55-4463-b812-64152197208f"/></defs><title>drone_delivery</title><polygon points="910 277.53 910 213.53 872 213.53 872 277.53 827 277.53 827 178.53 794 178.53 794 104.53 767 104.53 767 28.53 711 28.53 711 104.53 684 104.53 684 178.53 652 178.53 652 89.53 634 89.53 634 44.53 623 44.53 623 89.53 559 89.53 559 373.53 510 373.53 510 343.53 480 343.53 480 373.53 431 373.53 431 277.53 343 277.53 343 213.53 305 213.53 305 277.53 303 277.53 303 400.53 263 400.53 263 328.53 215 328.53 215 400.53 175 400.53 175 178.53 144 178.53 144 104.53 30 104.53 30 178.53 0 178.53 0 569.53 175 569.53 303 569.53 431 569.53 559 569.53 652 569.53 827 569.53 955 569.53 955 277.53 910 277.53" fill="#348eed" opacity="0.25"/><rect x="19" y="201.53" width="42" height="24" fill="#fff" opacity="0.4"/><rect x="233.51" y="292.64" width="42" height="24" transform="translate(388.51 518.17) rotate(-180)" fill="#fff" opacity="0.4"/><rect x="19" y="248.53" width="42" height="24" fill="#fff" opacity="0.4"/><rect x="233.51" y="339.64" width="42" height="24" transform="translate(388.51 612.17) rotate(-180)" fill="#fff" opacity="0.4"/><rect x="19" y="295.53" width="42" height="24" fill="#fff" opacity="0.4"/><rect x="233.51" y="386.64" width="42" height="24" transform="translate(388.51 706.17) rotate(-180)" fill="#fff" opacity="0.4"/><rect x="19" y="342.53" width="42" height="24" fill="#fff" opacity="0.4"/><rect x="233.51" y="433.64" width="42" height="24" transform="translate(388.51 800.17) rotate(-180)" fill="#fff" opacity="0.4"/><rect x="19" y="389.53" width="42" height="24" fill="#fff" opacity="0.4"/><rect x="233.51" y="480.64" width="42" height="24" transform="translate(388.51 894.17) rotate(-180)" fill="#fff" opacity="0.4"/><rect x="19" y="436.53" width="42" height="24" fill="#fff" opacity="0.4"/><rect x="233.51" y="527.64" width="42" height="24" transform="translate(388.51 988.17) rotate(-180)" fill="#fff" opacity="0.4"/><rect x="19" y="483.53" width="42" height="24" fill="#fff" opacity="0.4"/><rect x="233.51" y="574.64" width="42" height="24" transform="translate(388.51 1082.17) rotate(-180)" fill="#fff" opacity="0.4"/><rect x="190" y="424.53" width="98" height="12" fill="#fff" opacity="0.4"/><rect x="190" y="442.53" width="98" height="12" fill="#fff" opacity="0.4"/><rect x="190" y="460.53" width="98" height="12" fill="#fff" opacity="0.4"/><rect x="446" y="430.53" width="98" height="12" fill="#fff" opacity="0.4"/><rect x="446" y="448.53" width="98" height="12" fill="#fff" opacity="0.4"/><rect x="446" y="466.53" width="98" height="12" fill="#fff" opacity="0.4"/><rect x="190" y="478.53" width="98" height="12" fill="#fff" opacity="0.4"/><rect x="190" y="496.53" width="98" height="12" fill="#fff" opacity="0.4"/><rect x="190" y="514.53" width="98" height="12" fill="#fff" opacity="0.4"/><rect x="324" y="300.53" width="27" height="35" fill="#fff" opacity="0.4"/><rect x="503.51" y="391.64" width="27" height="35" transform="translate(913.51 727.17) rotate(-180)" fill="#fff" opacity="0.4"/><rect x="324" y="356.53" width="27" height="35" fill="#fff" opacity="0.4"/><rect x="503.51" y="447.64" width="27" height="35" transform="translate(913.51 839.17) rotate(-180)" fill="#fff" opacity="0.4"/><rect x="324" y="412.53" width="27" height="35" fill="#fff" opacity="0.4"/><rect x="503.51" y="503.64" width="27" height="35" transform="translate(913.51 951.17) rotate(-180)" fill="#fff" opacity="0.4"/><rect x="324" y="468.53" width="27" height="35" fill="#fff" opacity="0.4"/><rect x="503.51" y="559.64" width="27" height="35" transform="translate(913.51 1063.17) rotate(-180)" fill="#fff" opacity="0.4"/><rect x="559" y="112.53" width="36" height="46" fill="#fff" opacity="0.4"/><rect x="559" y="178.53" width="36" height="46" fill="#fff" opacity="0.4"/><rect x="559" y="244.53" width="36" height="46" fill="#fff" opacity="0.4"/><rect x="559" y="310.53" width="36" height="46" fill="#fff" opacity="0.4"/><rect x="559" y="376.53" width="36" height="46" fill="#fff" opacity="0.4"/><rect x="559" y="442.53" width="36" height="46" fill="#fff" opacity="0.4"/><rect x="672.5" y="206.53" width="134" height="39" fill="#fff" opacity="0.4"/><rect x="673" y="290.53" width="134" height="39" fill="#fff" opacity="0.4"/><rect x="673.5" y="374.53" width="134" height="39" fill="#fff" opacity="0.4"/><rect x="674" y="458.53" width="134" height="39" fill="#fff" opacity="0.4"/><rect x="30" y="125.53" width="18" height="38" fill="#fff" opacity="0.4"/><rect x="910" y="300.53" width="45" height="58" fill="#fff" opacity="0.4"/><rect x="910" y="384.53" width="45" height="58" fill="#fff" opacity="0.4"/><rect x="910" y="468.53" width="45" height="58" fill="#fff" opacity="0.4"/><path d="M385.19,167.32h-6.88a4.82,4.82,0,1,0,0-9.63H322.58a4.82,4.82,0,0,0,0,9.63h6.88a4.82,4.82,0,1,0,0,9.63h-9.63a4.82,4.82,0,0,0,0,9.63h55.73a4.82,4.82,0,0,0,0-9.63h9.63a4.82,4.82,0,1,0,0-9.63Z" transform="translate(-120.51 -91.11)" fill="#252223" opacity="0.1"/><path d="M631.19,288.32h-6.88a4.82,4.82,0,1,0,0-9.63H568.58a4.82,4.82,0,0,0,0,9.63h6.88a4.82,4.82,0,1,0,0,9.63h-9.63a4.82,4.82,0,1,0,0,9.63h55.73a4.82,4.82,0,1,0,0-9.63h9.63a4.82,4.82,0,1,0,0-9.63Z" transform="translate(-120.51 -91.11)" fill="#252223" opacity="0.1"/><path d="M1050.19,130.32h-6.88a4.82,4.82,0,1,0,0-9.63H987.58a4.82,4.82,0,0,0,0,9.63h6.88a4.82,4.82,0,1,0,0,9.63h-9.63a4.82,4.82,0,1,0,0,9.63h55.73a4.82,4.82,0,1,0,0-9.63h9.63a4.82,4.82,0,1,0,0-9.63Z" transform="translate(-120.51 -91.11)" fill="#252223" opacity="0.1"/><path d="M918.56,95.65a181.12,181.12,0,0,0-25.21,1.52h0a6,6,0,0,0-12.1,0h0a181.12,181.12,0,0,0-25.21-1.52c-18.65,0-33.78,2-33.78,4.54s15.12,4.54,33.78,4.54a181.12,181.12,0,0,0,25.21-1.52h0a6,6,0,0,0,.39,2.13,4,4,0,0,0-.4,6.53,11.17,11.17,0,0,0-6.63,6.1,10.33,10.33,0,0,0-5.8-2.11L806,113.06a24.54,24.54,0,0,0-19.67-9.85H746.79a24.54,24.54,0,0,0-19.78,10l-59.66,2.67a10.33,10.33,0,0,0-5.43,1.83,11.17,11.17,0,0,0-5.81-5.58,4,4,0,0,0-.95-7.19,6,6,0,0,0,.25-1.73h0a181.11,181.11,0,0,0,25.21,1.52c18.65,0,33.78-2,33.78-4.54s-15.12-4.54-33.78-4.54a181.12,181.12,0,0,0-25.21,1.52h0a6,6,0,0,0-6-6h0a6,6,0,0,0-6,6h0a181.12,181.12,0,0,0-25.21-1.52c-18.65,0-33.78,2-33.78,4.54s15.12,4.54,33.78,4.54a181.12,181.12,0,0,0,25.21-1.52h0a6,6,0,0,0,.39,2.13,4,4,0,0,0-.24,6.66,11.14,11.14,0,0,0-7.21,10.41v7.46A11.14,11.14,0,0,0,647.4,141h4.43a11.11,11.11,0,0,0,8.93-4.5,10.33,10.33,0,0,0,6.76,2.77l58.5,1.64a24.7,24.7,0,0,0,7.25,7.39c-5.6,5.69-27.44,30.92-23.92,75.38,0,0,3.53,6.55,12.6,0,0,0-5.8-49.54,26.67-69.94a8.06,8.06,0,0,0,7.61,5.41h4.15a21.68,21.68,0,1,0,15.91,0h4.15a8.06,8.06,0,0,0,7.61-5.41c32.47,20.4,26.67,69.94,26.67,69.94,9.07,6.55,12.6,0,12.6,0,4-50.92-25.21-76.62-25.21-76.62h-.53a24.7,24.7,0,0,0,5.48-6.06l61.58-1.72a10.34,10.34,0,0,0,7.05-3,11.12,11.12,0,0,0,9.14,4.78h4.43a11.14,11.14,0,0,0,11.14-11.14v-7.46a11.14,11.14,0,0,0-6.6-10.17,4,4,0,0,0-.72-7.31,6,6,0,0,0,.25-1.73h0a181.11,181.11,0,0,0,25.21,1.52c18.65,0,33.78-2,33.78-4.54S937.22,95.65,918.56,95.65Z" transform="translate(-120.51 -91.11)" fill="url(#4ef13be0-8e55-4463-b812-64152197208f)"/><circle cx="647.83" cy="87.06" r="20.44" fill="#348eed"/><circle cx="647.83" cy="87.06" r="13.79" fill="#348eed"/><circle cx="647.83" cy="87.06" r="13.79" fill="#f5f5f5" opacity="0.2"/><circle cx="647.83" cy="87.06" r="9.03" fill="#348eed"/><circle cx="647.83" cy="87.06" r="9.03" opacity="0.25"/><path d="M736.48,147.74S708.91,172,712.71,220c0,0,3.33,6.18,11.89,0,0,0-7.13-60.86,40.41-72.27Z" transform="translate(-120.51 -91.11)" fill="#348eed"/><path d="M800.19,147.74S827.77,172,824,220c0,0-3.33,6.18-11.89,0,0,0,7.13-60.86-40.41-72.27Z" transform="translate(-120.51 -91.11)" fill="#348eed"/><rect x="604.32" y="15.26" width="83.68" height="46.36" rx="23.18" ry="23.18" fill="#348eed"/><path d="M738.54,142.22l-65.29-1.83a9.79,9.79,0,0,1-9.52-9.79V128.1a9.79,9.79,0,0,1,9.35-9.78l65.29-2.93a9.79,9.79,0,0,1,10.23,9.78v7.27A9.79,9.79,0,0,1,738.54,142.22Z" transform="translate(-120.51 -91.11)" fill="#348eed"/><path d="M797.66,142.22l65.29-1.83a9.79,9.79,0,0,0,9.52-9.79V128.1a9.79,9.79,0,0,0-9.35-9.78l-65.29-2.93a9.79,9.79,0,0,0-10.23,9.78v7.27A9.79,9.79,0,0,0,797.66,142.22Z" transform="translate(-120.51 -91.11)" fill="#348eed"/><path d="M738.54,142.22l-65.29-1.83a9.79,9.79,0,0,1-9.52-9.79V128.1a9.79,9.79,0,0,1,9.35-9.78l65.29-2.93a9.79,9.79,0,0,1,10.23,9.78v7.27A9.79,9.79,0,0,1,738.54,142.22Z" transform="translate(-120.51 -91.11)" fill="#f5f5f5" opacity="0.2"/><path d="M797.66,142.22l65.29-1.83a9.79,9.79,0,0,0,9.52-9.79V128.1a9.79,9.79,0,0,0-9.35-9.78l-65.29-2.93a9.79,9.79,0,0,0-10.23,9.78v7.27A9.79,9.79,0,0,0,797.66,142.22Z" transform="translate(-120.51 -91.11)" fill="#f5f5f5" opacity="0.2"/><rect x="523.26" y="22.87" width="25.2" height="28.05" rx="11.17" ry="11.17" fill="#348eed"/><rect x="747.2" y="22.87" width="25.2" height="28.05" rx="11.17" ry="11.17" fill="#348eed"/><rect x="528.49" y="16.69" width="15.21" height="7.61" rx="3.8" ry="3.8" fill="#348eed"/><rect x="529.92" y="3.85" width="11.41" height="17.12" rx="5.71" ry="5.71" fill="#348eed"/><ellipse cx="506.14" cy="12.41" rx="31.86" ry="4.28" fill="#348eed"/><ellipse cx="565.1" cy="12.41" rx="31.86" ry="4.28" fill="#348eed"/><rect x="752.9" y="16.69" width="15.21" height="7.61" rx="3.8" ry="3.8" fill="#348eed"/><rect x="754.33" y="3.85" width="11.41" height="17.12" rx="5.71" ry="5.71" fill="#348eed"/><ellipse cx="730.56" cy="12.41" rx="31.86" ry="4.28" fill="#348eed"/><ellipse cx="789.51" cy="12.41" rx="31.86" ry="4.28" fill="#348eed"/><ellipse cx="506.14" cy="12.41" rx="31.86" ry="4.28" fill="#f5f5f5" opacity="0.2"/><ellipse cx="565.1" cy="12.41" rx="31.86" ry="4.28" fill="#f5f5f5" opacity="0.2"/><ellipse cx="730.56" cy="12.41" rx="31.86" ry="4.28" fill="#f5f5f5" opacity="0.2"/><ellipse cx="789.51" cy="12.41" rx="31.86" ry="4.28" fill="#f5f5f5" opacity="0.2"/><rect x="628.81" y="52.82" width="38.04" height="15.21" rx="7.61" ry="7.61" fill="#348eed"/><rect x="628.81" y="52.82" width="38.04" height="15.21" rx="7.61" ry="7.61" fill="#f5f5f5" opacity="0.2"/><polygon points="682.36 131.85 597.65 131.85 597.65 216.56 682.36 216.56 704 216.56 704 131.85 682.36 131.85" fill="#67647e"/><rect x="682.36" y="131.85" width="21.65" height="84.71" opacity="0.25"/><rect x="608" y="145.97" width="16" height="8.47" opacity="0.25"/><path d="M764.45,213c3.6,5.1,3,11.67,3,11.67s-6.38-1.69-10-6.79-5-10.32-3-11.67S760.85,207.89,764.45,213Z" transform="translate(-120.51 -91.11)" fill="#67647e"/><path d="M770.54,213c-3.6,5.1-3,11.67-3,11.67s6.38-1.69,10-6.79,5-10.32,3-11.67S774.14,207.89,770.54,213Z" transform="translate(-120.51 -91.11)" fill="#67647e"/><path d="M324.48,444.59l-.11.34A2,2,0,0,0,324.48,444.59Z" transform="translate(-120.51 -91.11)" fill="url(#5bed69c4-1e02-4930-aaf9-54950fab6759)"/><path d="M646.39,721.2c.07-.1.14-.19.19-.29l.11-.21c.05-.1.11-.19.15-.29l.08-.21c0-.1.08-.2.11-.3s0-.13.05-.2.05-.2.07-.31,0-.12,0-.19,0-.21,0-.32,0-.11,0-.17,0-.22,0-.33,0-.1,0-.15,0-.23,0-.34l0-.13c0-.12,0-.24-.08-.36l0-.11c0-.13-.08-.25-.12-.38l0-.08q-.07-.2-.16-.39l0-.06q-.09-.21-.2-.41l0,0-.24-.43h0a14.8,14.8,0,0,0-3.43-3.72c-10.31,4.25-23-.61-23-.61s-17-8.49-15.77-6.07c.41.82.2,2.45-.26,4.27-8.14-1.71-12.63-6.08-15.07-10l9.27-10s-37.61-21.23-41.85-32.15-24.87-41.85-24.87-41.85-6.67-24.26-18.8-29.72c0,0-24.19-28.9-38.32-53.68,5-12.49,12-32,10.42-40.33-2-10.38.07-30,.9-36.94,6.71,5.6,34,27.77,44.59,27.24,3.81-.19,7.91-2.23,11.76-4.95,9.16-5.8,17.36-16.28,17.36-16.28-1.24.12-2.25-1-3.07-2.9,10.13-11,31.11-33.64,33.4-34.1,3-.61,19.41-29.11,10.31-35.18s-25.47,15.77-26.08,20c-.41,2.88-13.1,15.52-21.22,23.36,0-.2,0-.31,0-.31l-1.2.6c0-.38,0-.6,0-.6l-20.95,10.47-17.87-24.42s-4.85-26.08-17-30.33S474.48,370,474.48,370a12.49,12.49,0,0,0-.27,1.29c-.61-.81-.94-1.29-.94-1.29s0,.11-.08.29a31.91,31.91,0,0,1-.69-11.42l.17-.1c.07-.47.15-.92.23-1.35a30.35,30.35,0,0,0,12.29-37.59c3.09-2.16,7.57-3.5,11.38-5.14a33.38,33.38,0,0,0,13.69-10.35,12.07,12.07,0,0,0-6.34.13c-.26-2.81,3-6,.46-8.14-1.56-1.33-4.47-1.21-6.69-.55s-4.25,1.74-6.61,2c-4.11.5-8-1.49-10.93-3.64s-5.69-4.67-9.61-5.69c-7.79-2-15.51,2.61-23.33,4.57-12.56,3.14-20,6.5-20.61,14.5a26,26,0,0,0-4.44,3.77,16.55,16.55,0,0,0-5.32,9c-1.06,5.44,1.14,11,3.8,15.84,1.51,2.77,8.77,17.12,13.7,15.23a30.5,30.5,0,0,0,4.54,4.25c-.16,1.65-.39,3.32-.65,5a11.28,11.28,0,0,1-3.57,1.23,42.93,42.93,0,0,1-8.23.72,120.44,120.44,0,0,1-12.91-.79,109,109,0,0,0-12.55-.72,61.25,61.25,0,0,0-6.26.3l-.32,0-1.24.15-.19,0-1.35.22-.15,0-1.1.22-.25.05-1.12.27h0l-1.1.31-.18.05-.86.27-.19.06c-.65.22-1.26.46-1.84.7l-.18.08-.72.31-.13.06-.81.38-.07,0-.7.35-.17.09-.68.35,0,0-.86.45-.52.27-.24.13-.71.36c-6.07,3-64.9,18.8-75.21,50.34l.21.19,0,0,.23.21c-.17.46-.34.92-.49,1.38,0,0,1,.9,2.56,2.36-2.77,12.06-10.19,42.88-14.09,44.34-4.85,1.82-10.31,38.82,2.43,37.61s12.74-30.93,12.74-30.93,8.4-22.52,14.59-33.95a15.94,15.94,0,0,1,2.43,4.71l0,.13,0-.12a3.41,3.41,0,0,1,.07,1.6c.6-1.76,3.71-9.84,10.2-8.76,6.93,1.16,33.11-24.63,35.59-27.1-.06.67-.14,1.51-.22,2.48h0v0c0,.57-.1,1.18-.15,1.84l0,.21,0,.5,0,.33,0,.51,0,.36,0,.51,0,.46-.05.75,0,.61,0,.43,0,.61,0,.41,0,.64,0,.43,0,.7,0,.4-.07,1.1v.15l-.06,1,0,.35,0,.84,0,.4,0,.86,0,.36-.05,1v.2q0,.65-.06,1.31v.08q0,.6-.05,1.2v.35l0,1,0,.4q0,.5,0,1v.34q0,1.37-.09,2.77v.2q0,.59,0,1.18v.36q0,.53,0,1.06v.37q0,.58,0,1.17v.23q0,1.43,0,2.88c0,.1,0,.2,0,.31q0,.56,0,1.12,0,.19,0,.39,0,.53,0,1.07c0,.1,0,.2,0,.3,0,13.23.81,26.7,3.78,34.38a37.78,37.78,0,0,1,2.48,12.92,52.73,52.73,0,0,1-6.12,22.87h0l.71-.22-.13.23.39-.12c-.6,1.25-1,1.94-1,1.94s.88-.28,2.45-.74c-2.24,7.9-3.73,20.06.58,35.32,0,0,2.43,13.34.61,18.2s-3.64,43.67-3.64,43.67-3.64,4.85-10.31,1.82-88.55-13.34-88.55-13.34-.19.33-.51.92c-2.52-.79-6.15-2-8.8-3.11,1-1.38,1.89-2.17,2.64-2.06,0,0-4.37-2.11-9.13-3.78h0l-.87-.3h0l-.85-.28-.06,0-.81-.25-.09,0-.74-.21-.15,0-.7-.18-.18,0-.67-.15-.19,0-.6-.12-.23,0-.54-.09-.26,0-.49-.06-.27,0-.46,0h-.93l-.37,0-.24,0-.33.07-.22,0-.28.1-.2.07a2.36,2.36,0,0,0-.26.15l-.15.09a1.88,1.88,0,0,0-.33.31c-3,3.64-20,73.39-12.13,78.24l.68.41.28.16.36.2.35.18.25.13.38.18.19.09.39.17.14.06.4.15.09,0,.41.13h.05c4.4,1.19,5.86-2.88,11.19-11.6,5.22-8.54,11.93-19.31,17.79-23.3.13.29.26.58.4.86,0,0,61.26,6.67,83.7,14s40,17,54.59,0c13.06-15.23,26.11-64.62,28.66-74.63h0v0l.38-1.5h0l0-.13v-.13l48.52,37,21.23,24.87s29.72,41.85,35.18,42.46S567,722.73,567,722.73l.92-1c.22,1.11.39,2.13.52,3-2,4.81-.15,10.61,1.67,14.43h0l.08.16.21.42.11.22.2.39.1.19.21.4.11.19.16.29.08.15.21.36.09.15.13.21.07.12.16.26.06.1.1.15,0,.07.1.16,0,0,.06.08v0l0,0s63.69-15.16,71.57-20c.25-.15.48-.31.7-.47l.2-.16.4-.32.2-.19.31-.29.18-.21.24-.28Z" transform="translate(-120.51 -91.11)" fill="url(#a88837d3-ccd3-4ab3-a7ec-270b2797a153)"/><path d="M276.5,602.74s-14.18-4.14-14.77-5.91-12.41,32.49-12.41,32.49l11.82,8.27s12.41-7.09,18.91-5.91S276.5,602.74,276.5,602.74Z" transform="translate(-120.51 -91.11)" fill="#4c4c56"/><path d="M581.94,687.22s1.77,16,21.27,17.72-13,16-13,16l-24.81,1.77s-.59-10-4.73-17.72S581.94,687.22,581.94,687.22Z" transform="translate(-120.51 -91.11)" fill="#4c4c56"/><path d="M376.94,507.62s-11.22,17.13-3.54,44.31c0,0,2.36,13,.59,17.72s-3.54,42.54-3.54,42.54-3.54,4.73-10,1.77-86.25-13-86.25-13-13,22.45-5.91,36c0,0,59.67,6.5,81.53,13.59s39,16.54,53.17,0,28.36-74.44,28.36-74.44l47.26,36,20.68,24.22s28.95,40.76,34.27,41.35,30.13,39.58,30.13,39.58l30.13-32.49S557.13,664.18,553,653.54s-24.22-40.76-24.22-40.76-6.5-23.63-18.31-28.95c0,0-42.54-50.81-46.67-74.44S392.89,497,392.89,497Z" transform="translate(-120.51 -91.11)" fill="#5f5d7e"/><path d="M596.71,712s4.14-9.45,3-11.82S615,706.12,615,706.12s12.41,4.73,22.45.59c0,0,8.86,6.5,1.18,11.22s-69.71,19.5-69.71,19.5-9.45-13.59-1.77-21.27Z" transform="translate(-120.51 -91.11)" fill="#986365"/><path d="M553,653.54c-4.14-10.63-24.22-40.76-24.22-40.76s-6.5-23.63-18.31-28.95c0,0-42.54-50.81-46.67-74.44-.85-4.87-4.37-8.26-9.41-10.57l-8.24,24.75-11.25,33.8c-2.17,6.52-4.23,13.79-4,20.53.29-1.12.44-1.74.44-1.74l47.26,36,20.68,24.22s28.95,40.76,34.27,41.35,30.13,39.58,30.13,39.58l30.13-32.49S557.13,664.18,553,653.54Z" transform="translate(-120.51 -91.11)" opacity="0.1"/><path d="M473.23,355.79s-5,12.7,1.48,24.52-2.66,32.79-8.57,34-29.54-14.18-31.31-24.22,7.68-30.72,3-44.31Z" transform="translate(-120.51 -91.11)" fill="#fdc2cc"/><path d="M441.33,377.65s18.31,37.22,38.4,10.63c0,0,20.09,40.76,13.59,47.26A136.22,136.22,0,0,1,478.55,448l-10.63,74.44s-60.26-20.68-85.66-17.13l39-93.34Z" transform="translate(-120.51 -91.11)" opacity="0.1"/><path d="M441.33,375.88s18.31,37.22,38.4,10.63c0,0,20.09,40.76,13.59,47.26a136.22,136.22,0,0,1-14.77,12.41l-10.63,74.44s-60.26-20.68-85.66-17.13l39-93.34Z" transform="translate(-120.51 -91.11)" fill="#d39999"/><path d="M310.77,419s-10,46.08-14.77,47.85-10,37.81,2.36,36.63,12.41-30.13,12.41-30.13,13-34.86,18.31-39S310.77,419,310.77,419Z" transform="translate(-120.51 -91.11)" fill="#fdc2cc"/><path d="M542.95,440.27s26.59-24.81,27.18-28.95,16.54-25.4,25.4-19.5-7.09,33.67-10,34.27-37.81,39-37.81,39Z" transform="translate(-120.51 -91.11)" fill="#fdc2cc"/><path d="M438.67,366.13s-3.25,3.84-25.11,1.48-27.18,1.77-33.08,4.73-63.21,18.31-73.26,49c0,0,22.45,20.09,20.09,25.4,0,0,3-10,10-8.86s34.86-26.59,34.86-26.59-5.32,52,1.77,70.3-3.54,36.63-3.54,36.63,50.22-16,72.67-8.27c0,0,13-58.49,1.77-82.12s4.73-46.08,4.73-46.08Z" transform="translate(-120.51 -91.11)" opacity="0.1"/><path d="M439.26,364.36s-3.25,3.84-25.11,1.48-27.18,1.77-33.08,4.73-63.21,18.31-73.26,49c0,0,22.45,20.09,20.09,25.4,0,0,3-10,10-8.86s34.86-26.59,34.86-26.59-5.32,52,1.77,70.3S371,516.48,371,516.48s50.22-16,72.67-8.27c0,0,13-58.49,1.77-82.12S450.19,380,450.19,380Z" transform="translate(-120.51 -91.11)" opacity="0.1"/><path d="M438.67,364.36s-3.25,3.84-25.11,1.48-27.18,1.77-33.08,4.73-63.21,18.31-73.26,49c0,0,22.45,20.09,20.09,25.4,0,0,3-10,10-8.86s34.86-26.59,34.86-26.59-5.32,52,1.77,70.3-3.54,36.63-3.54,36.63,50.22-16,72.67-8.27c0,0,13-58.49,1.77-82.12S449.6,380,449.6,380Z" transform="translate(-120.51 -91.11)" fill="#ec7580"/><path d="M471.76,375s8.57,12.7,20.38,16.84,16.54,29.54,16.54,29.54l17.72,24.22L547.67,435s.59,28.95,6.5,28.36c0,0-14.77,18.91-26.59,19.5S482.69,455,482.69,455s-3.54,26-1.18,38.4-14.77,50.22-14.77,50.22l-23.63-4.73s12.41-47.26,27.77-54.94c0,0,0-31.9-2.36-39.58s15.36-36.63,13-42.54c-1.33-3.31-6.53-10.4-9.16-16.84A16.85,16.85,0,0,1,471.76,375Z" transform="translate(-120.51 -91.11)" opacity="0.1"/><path d="M473.53,373.81s8.57,12.7,20.38,16.84,16.54,29.54,16.54,29.54l17.72,24.22,21.27-10.63s.59,28.95,6.5,28.36c0,0-14.77,18.91-26.59,19.5s-44.9-27.77-44.9-27.77-3.54,26-1.18,38.4-14.77,50.22-14.77,50.22l-23.63-4.73s12.41-47.26,27.77-54.94c0,0,0-31.9-2.36-39.58s15.36-36.63,13-42.54c-1.33-3.31-6.53-10.4-9.16-16.84A16.85,16.85,0,0,1,473.53,373.81Z" transform="translate(-120.51 -91.11)" opacity="0.1"/><path d="M472.35,373.81s8.57,12.7,20.38,16.84,16.54,29.54,16.54,29.54L527,444.41l21.27-10.63s.59,28.95,6.5,28.36c0,0-14.77,18.91-26.59,19.5s-44.9-27.77-44.9-27.77-3.54,26-1.18,38.4-14.77,50.22-14.77,50.22l-23.63-4.73s12.41-47.26,27.77-54.94c0,0,0-31.9-2.36-39.58s15.36-36.63,13-42.54c-1.33-3.31-6.53-10.4-9.16-16.84A16.85,16.85,0,0,1,472.35,373.81Z" transform="translate(-120.51 -91.11)" fill="#ec7580"/><path d="M473.35,374.81s8.57,12.7,20.38,16.84,16.54,29.54,16.54,29.54L528,445.41l21.27-10.63s.59,28.95,6.5,28.36c0,0-14.77,18.91-26.59,19.5s-44.9-27.77-44.9-27.77-3.54,26-1.18,38.4-14.77,50.22-14.77,50.22l-23.63-4.73s12.41-47.26,27.77-54.94c0,0,0-31.9-2.36-39.58s15.36-36.63,13-42.54c-1.33-3.31-6.53-10.4-9.16-16.84A16.85,16.85,0,0,1,473.35,374.81Z" transform="translate(-120.51 -91.11)" opacity="0.05"/><path d="M267.64,596.83s-17.13-8.27-20.09-4.73-19.5,71.48-11.82,76.21,8.27,1.18,14.77-9.45,15.36-24.81,21.86-24.22c0,0-18.91-6.5-18.31-8.86S263.51,596.24,267.64,596.83Z" transform="translate(-120.51 -91.11)" fill="#986365"/><path d="M438.86,361a29.53,29.53,0,0,0,32.92,1.85,29.93,29.93,0,0,1,1.46-5.9l-35.45-10C439.25,351.12,439.35,356,438.86,361Z" transform="translate(-120.51 -91.11)" opacity="0.1"/><path d="M460.78,337.89c7.2.62,16-.73,18.65-5.63.77-1.41.93-3,1.71-4.37,2.26-4.06,8.72-5.72,13.91-8a32.51,32.51,0,0,0,13.33-10.08,11.76,11.76,0,0,0-6.18.13c-.25-2.73,2.9-5.85.45-7.93-1.52-1.29-4.35-1.17-6.52-.53s-4.13,1.69-6.44,2c-4,.49-7.79-1.45-10.65-3.55s-5.54-4.55-9.36-5.54c-7.58-2-15.11,2.55-22.72,4.45-15.61,3.91-23.18,8.15-18.94,21.72C431.16,330.61,447.68,336.76,460.78,337.89Z" transform="translate(-120.51 -91.11)" fill="#865a61"/><circle cx="336.18" cy="245.18" r="29.54" fill="#fdc2cc"/><path d="M433.73,331.79c1.43-5.8,5.56-11.21,11.31-12.82,4.89-1.37,10.09.12,14.87,1.85s9.65,3.75,14.72,3.45,10.39-3.8,10.77-8.86a4.24,4.24,0,0,0-.37-2.29,5.63,5.63,0,0,0-2.11-2c-7.43-4.74-15.88-8.47-24.69-8.33-7.25.12-14.18,2.84-20.92,5.52-8.5,3.38-18.17,8.11-19.92,17.09-1,5.3,1.11,10.69,3.7,15.43,1.5,2.74,8.76,17.12,13.56,14.75C436.59,354.66,432.81,335.51,433.73,331.79Z" transform="translate(-120.51 -91.11)" opacity="0.1"/><path d="M434.91,330.61c1.43-5.8,5.56-11.21,11.31-12.82,4.89-1.37,10.09.12,14.87,1.85s9.65,3.75,14.72,3.45,10.39-3.8,10.77-8.86a4.24,4.24,0,0,0-.37-2.29,5.63,5.63,0,0,0-2.11-2c-7.43-4.74-15.88-8.47-24.69-8.33-7.25.12-14.18,2.84-20.92,5.52-8.5,3.38-18.17,8.11-19.92,17.09-1,5.3,1.11,10.69,3.7,15.43,1.5,2.74,8.76,17.12,13.56,14.75C437.77,353.48,434,334.33,434.91,330.61Z" transform="translate(-120.51 -91.11)" fill="#865a61"/><path d="M237.51,668.91c-7.68-4.73,8.86-72.67,11.82-76.21,1.29-1.55,5.31-.84,9.42.46-4.66-1.63-9.71-2.83-11.19-1-3,3.54-19.5,71.48-11.82,76.21a13.93,13.93,0,0,0,4.28,1.95A21.65,21.65,0,0,1,237.51,668.91Z" transform="translate(-120.51 -91.11)" fill="#fff" opacity="0.5"/><path d="M640.81,710.34c.8,2,.56,4.39-2.75,6.42-7.68,4.73-69.71,19.5-69.71,19.5a29.62,29.62,0,0,1-1.69-2.87,31.57,31.57,0,0,0,2.28,4.05s62-14.77,69.71-19.5C642.62,715.5,642.17,712.58,640.81,710.34Z" transform="translate(-120.51 -91.11)" fill="#fff" opacity="0.5"/><g opacity="0.05"><path d="M330.47,439.08C325.56,431.18,312,419,312,419c10-30.72,67.35-46.08,73.26-49,3.26-1.63,6.34-3.62,12.12-4.68-9,.66-12.79,3.24-16.84,5.27-5.91,3-63.21,18.31-73.26,49,0,0,21.58,19.31,20.19,25.07A18.49,18.49,0,0,1,330.47,439.08Z" transform="translate(-120.51 -91.11)"/><path d="M327.42,444.67l-.11.33A1.91,1.91,0,0,0,327.42,444.67Z" transform="translate(-120.51 -91.11)"/><path d="M439.35,365.33l-.68-1s-.75.88-3.93,1.54A34.47,34.47,0,0,0,439.35,365.33Z" transform="translate(-120.51 -91.11)"/><path d="M338,436.17a7.88,7.88,0,0,0,2.58-.7A6.42,6.42,0,0,0,338,436.17Z" transform="translate(-120.51 -91.11)"/><path d="M414.86,506.88c10.57-1.16,20.89-1.18,28.24,1.33,0,0,.14-.62.37-1.73C435.41,504.91,425.06,505.46,414.86,506.88Z" transform="translate(-120.51 -91.11)"/><path d="M375.68,514.93c2-4,9.17-19.82,3-35.67-7.09-18.31-1.77-70.3-1.77-70.3s-2,2-5.12,5c-1.09,12.89-3.67,50.84,2.17,65.93,7.09,18.31-3.54,36.63-3.54,36.63S372.39,515.86,375.68,514.93Z" transform="translate(-120.51 -91.11)"/></g><path d="M905.08,706.34c-7.12,3.67-14.58,6.84-21.94,10-27.19,11.48-54.79,23.06-84,27-54.82,7.39-111.44-12.62-165.33-.17-45.15,10.42-82.49,42.35-126.6,56.57-20.51,6.61-41.85,9.2-63.38,9.2H441q-4-.05-7.94-.2c-21.83-.82-43.75-4.05-65.15-8.18-67.57-13-136.34-36.83-188-80.7C330.34,692.33,640.19,650.15,905.08,706.34Z" transform="translate(-120.51 -91.11)" fill="#252223" opacity="0.1"/><path d="M745.48,534.72s19.42,19.42,8.46,48.24,18.8,76.75,18.8,76.75l-.91-.15c-39.89-7-59.71-52.66-37.53-86.55C742.54,560.4,749.31,545.62,745.48,534.72Z" transform="translate(-120.51 -91.11)" fill="#348eed"/><path d="M745.48,534.72s11,23.81,0,43.23-1.88,73.93,27.25,81.76" transform="translate(-120.51 -91.11)" fill="none" stroke="#535461" stroke-miterlimit="10"/><path d="M710.89,613.27s22.16-3.53,24.18,16.33,42.62,22.61,42.62,22.61l-.76.51c-33.52,22-65.56,14-60.73-15.2C718,626.66,717.84,615.75,710.89,613.27Z" transform="translate(-120.51 -91.11)" fill="#348eed"/><path d="M710.89,613.27s22.16-3.53,24.18,16.33,42.62,22.61,42.62,22.61l-.76.51c-33.52,22-65.56,14-60.73-15.2C718,626.66,717.84,615.75,710.89,613.27Z" transform="translate(-120.51 -91.11)" fill="#f5f5f5" opacity="0.2"/><path d="M710.89,613.27s17.2,3.78,15.79,19.42,25.55,34.39,51,19.53" transform="translate(-120.51 -91.11)" fill="none" stroke="#535461" stroke-miterlimit="10"/><path d="M795.6,656s-13.94-2.79-17.65-6.5-20.44-10.22-22.3-5.57-25.09,20.44-9.29,23.23,37.17,3.72,41.81,1.86S795.6,656,795.6,656Z" transform="translate(-120.51 -91.11)" fill="#a8a8a8"/><path d="M746.35,665.42c15.8,2.79,37.17,3.72,41.81,1.86,3.54-1.42,6-8.21,7-11.38l.46.1s-2.79,11.15-7.43,13-26,.93-41.81-1.86c-4.56-.8-5.86-2.69-5.37-5.09C741.37,663.62,743,664.82,746.35,665.42Z" transform="translate(-120.51 -91.11)" opacity="0.2"/><path d="M1048.26,680s13.21-44.87-2.48-77.89a70.87,70.87,0,0,1-5.73-44.46,119.19,119.19,0,0,1,6.29-20.87" transform="translate(-120.51 -91.11)" fill="none" stroke="#535461" stroke-miterlimit="10" stroke-width="2"/><path d="M1036.58,515.88c0,5.5,10,21.44,10,21.44s10-15.95,10-21.44a10,10,0,0,0-19.91,0Z" transform="translate(-120.51 -91.11)" fill="#348eed"/><path d="M1020.12,546.34c3,4.62,20,12.59,20,12.59s-.31-18.8-3.3-23.41a10,10,0,0,0-16.72,10.82Z" transform="translate(-120.51 -91.11)" fill="#348eed"/><path d="M1020.84,598.39c4.91,2.48,23.63.77,23.63.77s-9.76-16.07-14.67-18.55a10,10,0,1,0-9,17.78Z" transform="translate(-120.51 -91.11)" fill="#348eed"/><path d="M1030.25,637.65c4.42,3.27,23.16,4.75,23.16,4.75s-6.9-17.49-11.32-20.76a10,10,0,0,0-11.84,16Z" transform="translate(-120.51 -91.11)" fill="#348eed"/><path d="M1061,560c-3.94,3.83-22.32,7.8-22.32,7.8s4.5-18.25,8.44-22.08A10,10,0,0,1,1061,560Z" transform="translate(-120.51 -91.11)" fill="#348eed"/><path d="M1071.46,606.05c-4.91,2.48-23.63.77-23.63.77s9.76-16.07,14.67-18.55a10,10,0,1,1,9,17.78Z" transform="translate(-120.51 -91.11)" fill="#348eed"/><path d="M1075.45,652.2c-4.42,3.27-23.16,4.75-23.16,4.75s6.9-17.49,11.32-20.76a10,10,0,0,1,11.84,16Z" transform="translate(-120.51 -91.11)" fill="#348eed"/><path d="M1077,675.61s-24.15-4.83-30.59-11.27-35.42-17.71-38.64-9.66-43.47,35.42-16.1,40.25,64.4,6.44,72.44,3.22S1077,675.61,1077,675.61Z" transform="translate(-120.51 -91.11)" fill="#a8a8a8"/><path d="M991.68,691.94c27.37,4.83,64.4,6.44,72.44,3.22,6.13-2.45,10.39-14.23,12.07-19.72l.81.17s-4.83,19.32-12.88,22.54-45.08,1.61-72.44-3.22c-7.9-1.39-10.16-4.67-9.31-8.81C983.05,688.82,985.8,690.9,991.68,691.94Z" transform="translate(-120.51 -91.11)" opacity="0.2"/></svg>
\ No newline at end of file
<svg id="740546cb-34ca-4331-8221-ac3d3fc9ee67" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1120.61" height="862.52" viewBox="0 0 1120.61 862.52"><defs><linearGradient id="07a0b5f0-40d7-4714-acbe-80fa7c137a96" x1="922.95" y1="714.28" x2="922.95" y2="199.47" gradientTransform="matrix(1, 0.09, -0.09, 1, 45.13, -84.62)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="gray" stop-opacity="0.25"/><stop offset="0.54" stop-color="gray" stop-opacity="0.12"/><stop offset="1" stop-color="gray" stop-opacity="0.1"/></linearGradient><linearGradient id="81276d2b-ddf3-474d-b794-9223ea4701e2" x1="329.5" y1="708.38" x2="329.5" y2="197" gradientTransform="matrix(1, 0, 0, 1, 0, 0)" xlink:href="#07a0b5f0-40d7-4714-acbe-80fa7c137a96"/></defs><title>co-working</title><path d="M1089.7,507c-41.29,62.11-30.84,148.44-10.05,216.82,7,22.89,14.91,47.08,10.21,72.38-5.75,30.94-29.19,54.77-53.22,68.11-43.79,24.33-94.62,22.33-132.33-5.19-32.59-23.79-54.74-64.38-88-87C760.72,734.4,686.49,754.58,622.39,787c-45.35,22.91-95.14,52.13-138.12,33.8-30.24-12.9-49.84-47.64-59.58-85.15-4.7-18.11-7.78-37.84-17.8-51.81-6-8.3-14-14-22.29-18.63C308.89,622.47,209,654.06,135.75,606,86.32,573.6,56.91,508.46,45.93,439.31S40.36,296.61,47.7,224.23C52.91,172.8,60.38,117.78,89.07,76.55c30.35-43.61,79.58-62.18,123-56.95S293.67,50.73,326.9,82C368.43,121,406.27,170.46,459,184c35.91,9.25,74.61.57,112.35-4,63.1-7.55,125.63-3.35,187.74,1.64,59.47,4.78,119.32,10.39,175.17,30.77,39.52,14.42,70.34,43.14,107.39,62.24,24.15,12.45,51.05,14.63,73.68,31,27.88,20.15,52.52,56.69,42.83,101.84C1148.95,450.48,1111.61,474,1089.7,507Z" transform="translate(-39.7 -18.74)" fill="#348eed" opacity="0.1"/><rect x="880.8" y="256.42" width="40.89" height="61.9" fill="#348eed" opacity="0.1"/><rect x="884.77" y="262.44" width="32.94" height="49.86" fill="#348eed" opacity="0.1"/><rect x="797.8" y="213.42" width="62.8" height="61.9" fill="#348eed" opacity="0.1"/><rect x="803.9" y="219.44" width="50.59" height="49.86" fill="#348eed" opacity="0.1"/><rect x="64.62" y="121.32" width="191.95" height="5.11" fill="#348eed" opacity="0.1"/><rect x="64.62" y="146.31" width="191.95" height="5.11" fill="#348eed" opacity="0.1"/><rect x="388.43" y="452.63" width="19.61" height="219.14" fill="#d6d6e3"/><path d="M1020.54,494.38V292.24A21.25,21.25,0,0,1,1041.79,271h0A21.25,21.25,0,0,1,1063,292.24V536.88a21.25,21.25,0,0,1-21.25,21.25H910.14a21.25,21.25,0,0,1-21.25-21.25h0a21.25,21.25,0,0,1,21.25-21.25h89.15A21.25,21.25,0,0,0,1020.54,494.38Z" transform="translate(-39.7 -18.74)" fill="#aebee1"/><path d="M1020.54,494.38V292.24A21.25,21.25,0,0,1,1041.79,271h0A21.25,21.25,0,0,1,1063,292.24V536.88a21.25,21.25,0,0,1-21.25,21.25H910.14a21.25,21.25,0,0,1-21.25-21.25h0a21.25,21.25,0,0,1,21.25-21.25h89.15A21.25,21.25,0,0,0,1020.54,494.38Z" transform="translate(-39.7 -18.74)" fill="#535461"/><path d="M888.92,535.74a21.25,21.25,0,0,1,21.22-20.11h89.15a21.25,21.25,0,0,0,21.25-21.25V292.24A21.25,21.25,0,0,1,1040.77,271V535.74Z" transform="translate(-39.7 -18.74)" fill="#d6d6e3"/><path d="M232.2,501.2V299.06A21.25,21.25,0,0,0,211,277.81h0a21.25,21.25,0,0,0-21.25,21.25V543.7A21.25,21.25,0,0,0,211,565H342.6a21.25,21.25,0,0,0,21.25-21.25h0a21.25,21.25,0,0,0-21.25-21.25H253.46A21.25,21.25,0,0,1,232.2,501.2Z" transform="translate(-39.7 -18.74)" fill="#aebee1"/><path d="M232.2,501.2V299.06A21.25,21.25,0,0,0,211,277.81h0a21.25,21.25,0,0,0-21.25,21.25V543.7A21.25,21.25,0,0,0,211,565H342.6a21.25,21.25,0,0,0,21.25-21.25h0a21.25,21.25,0,0,0-21.25-21.25H253.46A21.25,21.25,0,0,1,232.2,501.2Z" transform="translate(-39.7 -18.74)" fill="#535461"/><path d="M363.82,542.56a21.25,21.25,0,0,0-21.22-20.11H253.46A21.25,21.25,0,0,1,232.2,501.2V299.06A21.25,21.25,0,0,0,212,277.84V542.56Z" transform="translate(-39.7 -18.74)" fill="#d6d6e3"/><rect x="776.4" y="452.63" width="19.61" height="224.25" fill="#d6d6e3"/><rect x="388.43" y="452.63" width="19.61" height="16.2" opacity="0.1"/><rect x="776.4" y="452.63" width="19.61" height="16.2" opacity="0.1"/><path d="M498.17,337.92q-.47,5.18-1.19,10.5h-2.77v-10.5Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><path d="M578.52,447c.06,3.55-2.5,6.46-5.67,6.46H513v-6.24H562.8c2.15,0,3.49-2.6,2.41-4.68l-45.16-91.85a4,4,0,0,0-3.5-2.26H494.21v-10.5h28.21a2.8,2.8,0,0,1,2.45,1.58l51.77,100A17,17,0,0,1,578.52,447Z" transform="translate(-39.7 -18.74)" fill="#535461"/><path d="M468.6,430.34l-.82,1.38-7.67-5.12,23.73-182.47h6.11s.25.84.66,2.44C494.7,262.34,514.79,351.65,468.6,430.34Z" transform="translate(-39.7 -18.74)" fill="#535461"/><path d="M490.6,246.57l-22,183.77-.82,1.38-7.67-5.12,23.73-182.47h6.11S490.19,245,490.6,246.57Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><rect x="376.97" y="329.82" width="189.29" height="14.5" transform="translate(41.16 746.52) rotate(-83.17)" fill="#d6d6e3"/><path d="M403.83,447.07a1.72,1.72,0,0,1-1.71,1.71h-2.56a1.71,1.71,0,0,1-1.71-1.71.83.83,0,0,1,0-.2,1.71,1.71,0,0,1,1.69-1.5h2.56a1.72,1.72,0,0,1,1.65,1.3A1.58,1.58,0,0,1,403.83,447.07Z" transform="translate(-39.7 -18.74)" fill="#e3edf9"/><path d="M411.5,447.07a1.72,1.72,0,0,1-1.71,1.71h-2.56a1.71,1.71,0,0,1-1.71-1.71,1.68,1.68,0,0,1,.07-.47,1.61,1.61,0,0,1,.43-.73,1.67,1.67,0,0,1,1.2-.5h2.56a1.72,1.72,0,0,1,1.57,1A1.76,1.76,0,0,1,411.5,447.07Z" transform="translate(-39.7 -18.74)" fill="#e3edf9"/><path d="M418.32,447.07a1.72,1.72,0,0,1-1.71,1.71h-2.56a1.71,1.71,0,0,1-1.71-1.71,1.62,1.62,0,0,1,.15-.71,1.74,1.74,0,0,1,1.55-1h2.56a1.7,1.7,0,0,1,1.71,1.71Z" transform="translate(-39.7 -18.74)" fill="#e3edf9"/><path d="M426,447.07a1.72,1.72,0,0,1-1.71,1.71h-2.56a1.71,1.71,0,0,1-1.4-2.69l.2-.22a1.67,1.67,0,0,1,1.2-.5h2.56a1.72,1.72,0,0,1,1.24.55A1.65,1.65,0,0,1,426,447.07Z" transform="translate(-39.7 -18.74)" fill="#e3edf9"/><path d="M403.78,446.66a1.58,1.58,0,0,1,.05.41,1.72,1.72,0,0,1-1.71,1.71h-2.56a1.71,1.71,0,0,1-1.71-1.71.83.83,0,0,1,0-.2Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><path d="M411.36,446.39a1.76,1.76,0,0,1,.14.67,1.72,1.72,0,0,1-1.71,1.71h-2.56a1.71,1.71,0,0,1-1.71-1.71,1.68,1.68,0,0,1,.07-.47Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><path d="M418.07,446.16a1.72,1.72,0,0,1,.26.9,1.72,1.72,0,0,1-1.71,1.71h-2.56a1.71,1.71,0,0,1-1.71-1.71,1.62,1.62,0,0,1,.15-.71Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><path d="M426,447.07a1.72,1.72,0,0,1-1.71,1.71h-2.56a1.71,1.71,0,0,1-1.4-2.69l5.2-.18A1.65,1.65,0,0,1,426,447.07Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><path d="M441.34,453.89H391.89v-2.25a3.85,3.85,0,0,1,3.72-3.85l39.66-1.37a5.88,5.88,0,0,1,6.08,5.87Z" transform="translate(-39.7 -18.74)" fill="#d6d6e3"/><path d="M685.31,447c-.06,3.55,2.5,6.46,5.67,6.46h59.88v-6.24H701c-2.15,0-3.49-2.6-2.41-4.68l45.16-91.85a4,4,0,0,1,3.5-2.26h22.35v-10.5H741.41A2.8,2.8,0,0,0,739,339.5l-51.77,100A17,17,0,0,0,685.31,447Z" transform="translate(-39.7 -18.74)" fill="#535461"/><path d="M795.24,430.34l.82,1.38,7.67-5.12L780,244.13h-6.11s-.25.84-.66,2.44C769.13,262.34,749,351.65,795.24,430.34Z" transform="translate(-39.7 -18.74)" fill="#535461"/><path d="M773.23,246.57l22,183.77.82,1.38,7.67-5.12L780,244.13h-6.11S773.64,245,773.23,246.57Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><rect x="784.97" y="242.43" width="14.5" height="189.29" transform="translate(-74.16 77.85) rotate(-6.83)" fill="#d6d6e3"/><path d="M860,447.07a1.72,1.72,0,0,0,1.71,1.71h2.56a1.71,1.71,0,0,0,1.71-1.71.83.83,0,0,0,0-.2,1.71,1.71,0,0,0-1.69-1.5h-2.56a1.72,1.72,0,0,0-1.65,1.3A1.58,1.58,0,0,0,860,447.07Z" transform="translate(-39.7 -18.74)" fill="#e3edf9"/><path d="M852.33,447.07a1.72,1.72,0,0,0,1.71,1.71h2.56a1.71,1.71,0,0,0,1.71-1.71,1.68,1.68,0,0,0-.07-.47,1.61,1.61,0,0,0-.43-.73,1.67,1.67,0,0,0-1.2-.5H854a1.72,1.72,0,0,0-1.57,1A1.76,1.76,0,0,0,852.33,447.07Z" transform="translate(-39.7 -18.74)" fill="#e3edf9"/><path d="M845.51,447.07a1.72,1.72,0,0,0,1.71,1.71h2.56a1.71,1.71,0,0,0,1.71-1.71,1.62,1.62,0,0,0-.15-.71,1.74,1.74,0,0,0-1.55-1h-2.56a1.7,1.7,0,0,0-1.71,1.71Z" transform="translate(-39.7 -18.74)" fill="#e3edf9"/><path d="M837.83,447.07a1.72,1.72,0,0,0,1.71,1.71h2.56a1.71,1.71,0,0,0,1.4-2.69l-.2-.22a1.67,1.67,0,0,0-1.2-.5h-2.56a1.72,1.72,0,0,0-1.24.55A1.65,1.65,0,0,0,837.83,447.07Z" transform="translate(-39.7 -18.74)" fill="#e3edf9"/><path d="M860.05,446.66a1.58,1.58,0,0,0-.05.41,1.72,1.72,0,0,0,1.71,1.71h2.56a1.71,1.71,0,0,0,1.71-1.71.83.83,0,0,0,0-.2Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><path d="M852.47,446.39a1.76,1.76,0,0,0-.14.67,1.72,1.72,0,0,0,1.71,1.71h2.56a1.71,1.71,0,0,0,1.71-1.71,1.68,1.68,0,0,0-.07-.47Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><path d="M845.76,446.16a1.72,1.72,0,0,0-.26.9,1.72,1.72,0,0,0,1.71,1.71h2.56a1.71,1.71,0,0,0,1.71-1.71,1.62,1.62,0,0,0-.15-.71Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><path d="M837.83,447.07a1.72,1.72,0,0,0,1.71,1.71h2.56a1.71,1.71,0,0,0,1.4-2.69l-5.2-.18A1.65,1.65,0,0,0,837.83,447.07Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><path d="M822.49,453.89h49.45v-2.25a3.85,3.85,0,0,0-3.72-3.85l-39.66-1.37a5.88,5.88,0,0,0-6.08,5.87Z" transform="translate(-39.7 -18.74)" fill="#d6d6e3"/><path d="M1029.47,481c23.07-39.13,14.71-118.06,15.64-119.57.83-1.33-2.35-38.5-23.55-47.58-3.83-7.59-7.6-16.14-7.6-16.14a6.73,6.73,0,0,1-1.22.91q-.25-.68-.49-1.38c-.27-.79-.52-1.6-.77-2.41a15.53,15.53,0,0,0,1.81.46,7.54,7.54,0,0,0,4.84-.43,5.39,5.39,0,0,0,1.39-1.07c1.67-1.11,2.59-3.11,3.37-5l6.17-14.78c2.78-6.67,5.58-13.38,7.06-20.45a57.58,57.58,0,0,0-2.46-31.86c-1.46-3.88-3.48-7.76-6.91-10.08-6.87-4.66-16.43-1.44-24.28-4.17-2.58-.9-4.91-2.42-7.49-3.34-5.57-2-11.86-.93-17.22,1.56s-9.7,6.45-14.39,10c-2.31,1.77-6,3.6-8.72,6-2.64,2.06-4.67,4.55-4.28,7.83.44,3.75,4.39,6,8.07,6.88.61.14,1.24.25,1.86.36a36,36,0,0,0,15.37,59.8,84.36,84.36,0,0,1,4.86,10.93q.27.77.53,1.55c-6.06,1.72-10.8,3.15-11.75,3.94-2.87,2.38,3.14,23.22,3.14,23.22-1.53,1.94-3.09,4.15-4.67,6.52a140.55,140.55,0,0,0-22.16,60.57c-1,7.91-2.3,16-3.95,23.69L870,412.59l-.15,1.77-1.71-.42h0l-5.38-1.33-.21,1.21-.82-.39c-13.33-6.25-35.79-14.23-51-6-16.05,8.64,18.59,20.12,40.82,26.23-7.37.36-14.42,2.13-20,6.27-16.12,12.08,29.47,18.37,51,20.6l.87.09v.46l7.46.69,1.75.16.17,2.66s10.68,1.75,24.95,3.87c-16.29,4.07-36.42,6.89-50.4,8.54-14.82,1.74-27.44,12.06-31.42,26.45a23.52,23.52,0,0,0-.36,12.72c4.16,14.53-.92,121.42-2.07,144.44l-26.35,15.17s-41,5-13.69,14.61a56.16,56.16,0,0,0,7.53,2c-3.6,2.32-3.29,5.35,7.32,9.07,27.33,9.58,90.86-.42,90.86-.42s-1.39-11.26-3.65-20.62l.7.06s-6.56-24.41,3.39-36.72-1.67-106-1.67-106,17.42-7.21,69.49-7.68c42,10.78,62.47-27.81,62.18-47.76C1029.52,481.87,1029.5,481.41,1029.47,481Z" transform="translate(-39.7 -18.74)" fill="url(#07a0b5f0-40d7-4714-acbe-80fa7c137a96)"/><path d="M874.25,420.36l-4.45,18s-4-.9-10-2.44c-20.32-5.26-63.18-17.89-46-27.16,14.81-8,36.62-.22,49.57,5.85,4.74,2.23,8.3,4.23,9.89,5.17C873.93,420.16,874.25,420.36,874.25,420.36Z" transform="translate(-39.7 -18.74)" fill="#f8b9bf"/><path d="M873,425.44l-3.2,13s-4-.9-10-2.44l3.61-21.31c4.74,2.23,8.3,4.23,9.89,5.17Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><polygon points="833.31 420.93 820.89 417.86 824.75 395.1 834.68 397.55 833.31 420.93" fill="#d9dfe8"/><polygon points="833.31 420.93 827.95 419.61 829.97 396.39 834.68 397.55 833.31 420.93" opacity="0.1"/><path d="M992.85,345.4S969,331.62,958.4,371.15s-11.6,57.74-11.6,57.74l-75.32-15.06-2.39,27.51s80.53,26,90.66,24.11,43-57,43-57S1032.63,365.74,992.85,345.4Z" transform="translate(-39.7 -18.74)" fill="#348eed"/><g opacity="0.1"><path d="M874.25,420.36l-4.45,18s-4-.9-10-2.44c-20.32-5.26-63.18-17.89-46-27.16,14.81-8,36.62-.22,49.57,5.85,4.74,2.23,8.3,4.23,9.89,5.17C873.93,420.16,874.25,420.36,874.25,420.36Z" transform="translate(-39.7 -18.74)"/><path d="M873,425.44l-3.2,13s-4-.9-10-2.44l3.61-21.31c4.74,2.23,8.3,4.23,9.89,5.17Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><polygon points="833.31 420.93 820.89 417.86 824.75 395.1 834.68 397.55 833.31 420.93"/><polygon points="833.31 420.93 827.95 419.61 829.97 396.39 834.68 397.55 833.31 420.93" opacity="0.1"/><path d="M992.85,346.4S969,332.62,958.4,372.15s-11.6,57.74-11.6,57.74l-75.32-15.06-2.39,27.51s80.53,26,90.66,24.11,43-57,43-57S1032.63,366.74,992.85,346.4Z" transform="translate(-39.7 -18.74)"/></g><path d="M841.15,651.7,810.4,669.41s-39.84,4.88-13.3,14.18,88.24-.41,88.24-.41-3.43-27.72-8.21-31.59S841.15,651.7,841.15,651.7Z" transform="translate(-39.7 -18.74)" fill="#68739d"/><path d="M961,453s-15.3,8-42.48,14.91c-15.93,4-35.87,6.84-49.65,8.46A35.81,35.81,0,0,0,838.35,502,22.84,22.84,0,0,0,838,514.4c4.53,15.83-2.23,144.51-2.23,144.51l46.7,4.32s-6.37-23.71,3.3-35.66-1.62-102.91-1.62-102.91,16.92-7,67.48-7.46c46.84,12,66.14-39.27,58.91-53.64S961,453,961,453Z" transform="translate(-39.7 -18.74)" fill="#6394e2"/><g opacity="0.1"><path d="M841.15,651.7,810.4,669.41s-39.84,4.88-13.3,14.18,88.24-.41,88.24-.41-3.43-27.72-8.21-31.59S841.15,651.7,841.15,651.7Z" transform="translate(-39.7 -18.74)"/><path d="M961,453s-15.3,8-42.48,14.91c-15.93,4-35.87,6.84-49.65,8.46A35.81,35.81,0,0,0,838.35,502,22.84,22.84,0,0,0,838,514.4c4.53,15.83-2.23,144.51-2.23,144.51l46.7,4.32s-6.37-23.71,3.3-35.66-1.62-102.91-1.62-102.91,16.92-7,67.48-7.46c46.84,12,66.14-39.27,58.91-53.64S961,453,961,453Z" transform="translate(-39.7 -18.74)"/></g><path d="M855.57,662.45l-30.75,17.71S785,685,811.52,694.34s88.24-.41,88.24-.41-3.43-27.72-8.21-31.59S855.57,662.45,855.57,662.45Z" transform="translate(-39.7 -18.74)" fill="#68739d"/><path d="M966,527.95c-50.56.46-67.48,7.46-67.48,7.46s11.29,91,1.62,102.91-3.3,35.66-3.3,35.66l-46.7-4.32s.08-1.52.21-4.24c1.12-22.36,6.05-126.16,2-140.27a22.84,22.84,0,0,1,.35-12.36,35.82,35.82,0,0,1,30.51-25.69c13.77-1.61,33.71-4.41,49.64-8.46l.78-.2c26.7-6.86,41.7-14.71,41.7-14.71s42.31-3.8,49.54,10.57a17,17,0,0,1,1.48,7.25C1026.71,500.94,1006.81,538.42,966,527.95Z" transform="translate(-39.7 -18.74)" fill="#6394e2"/><path d="M895.57,442.79l-1.71,18.5s-4.11-.29-10.22-.93c-20.87-2.17-65.14-8.27-49.49-20,13.46-10.08,36.18-5.68,49.89-1.6,5,1.5,8.84,2.95,10.55,3.64C895.22,442.64,895.57,442.79,895.57,442.79Z" transform="translate(-39.7 -18.74)" fill="#f8b9bf"/><path d="M1019.78,321.84s-6.26,4.9-13.91,10.28c-8.39,1.9-18.13,3.47-25.15,4.49,2.51-8,1-17-1.83-24.89a93.69,93.69,0,0,0-12.55-23.08l42.32-13.21c-3.32,8.41-1.82,18,1,26.34A86.85,86.85,0,0,0,1019.78,321.84Z" transform="translate(-39.7 -18.74)" fill="#f8b9bf"/><path d="M895.09,448l-1.23,13.29s-4.11-.29-10.22-.93l.4-21.61c5,1.5,8.84,2.95,10.55,3.64Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><polygon points="857.52 443.33 844.78 442.15 845.2 419.07 855.39 420.01 857.52 443.33" fill="#d9dfe8"/><path d="M1019.78,321.84s-6.26,4.9-13.91,10.28c-8.39,1.9-18.13,3.47-25.15,4.49,2.51-8,1-17-1.83-24.89,10.34-3,25.13-6.88,30.78-10A86.85,86.85,0,0,0,1019.78,321.84Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><path d="M1011.32,302.22s7.08,16.07,11.54,23.33-51.82,14-51.82,14-5.84-20.23-3.05-22.55S1006.61,306.92,1011.32,302.22Z" transform="translate(-39.7 -18.74)" fill="#d9dfe8"/><path d="M1026.43,481.56a50.63,50.63,0,0,1-8.76,11.34c-24.86,23.62-65.61-1-84-14.46,26.7-6.86,41.7-14.71,41.7-14.71s42.31-3.8,49.54,10.57A17,17,0,0,1,1026.43,481.56Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><path d="M1017.83,491.21c-28.26,26.84-77-8.7-90.14-19.17-2.13-1.7-3.32-2.75-3.32-2.75,5.19-4.86,9.75-15.16,13.36-27.76A243.61,243.61,0,0,0,945,404.75a136.5,136.5,0,0,1,21.52-58.83c1.53-2.3,3-4.45,4.53-6.33,10.67-13.57,28.6-22.19,28.6-22.19,37.84-11.06,42.93,45.07,41.92,46.69S1050.68,460,1017.83,491.21Z" transform="translate(-39.7 -18.74)" fill="#348eed"/><path d="M1021.49,414.48s-24.33,59.4-34.06,62.79c-5.57,1.93-35.32-1.73-59.74-5.22-2.13-1.7-3.32-2.75-3.32-2.75,5.19-4.86,9.75-15.16,13.36-27.76l31.45,1.5s-1.75-18.14,2.87-58.82,30.23-30.6,30.23-30.6C1044.65,367.81,1021.49,414.48,1021.49,414.48Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><polygon points="857.52 443.33 852.02 442.82 850.56 419.57 855.39 420.01 857.52 443.33" opacity="0.1"/><path d="M1001.67,351s-25.61-10.07-30.23,30.6-2.87,58.82-2.87,58.82l-76.73-3.67,1.73,27.56s83.51,13.71,93.24,10.33,34.06-62.79,34.06-62.79S1044,365.18,1001.67,351Z" transform="translate(-39.7 -18.74)" fill="#348eed"/><path d="M1003.39,290.78c-1.12-6.08,5.23-6,7.51-11.72l-42.32,13.21a93.4,93.4,0,0,1,7.84,12.47,35.11,35.11,0,0,0,6.73,1.3C993.24,307,996.39,297.07,1003.39,290.78Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><circle cx="984.12" cy="267.59" r="34.96" transform="translate(587.38 1204.16) rotate(-84.72)" fill="#f8b9bf"/><path d="M949.6,236c.43,3.64,4.27,5.86,7.84,6.68s7.44.86,10.57,2.77c5,3.08,6.35,9.92,5.72,15.8-.35,3.32-.85,7.39,1.87,9.32a7.25,7.25,0,0,0,2.8,1,26.26,26.26,0,0,0,8.62.3c1.68-.22,3.41-.6,5-.13,2.62.76,4.17,3.52,4.68,6.19s.23,5.45.64,8.14a16.9,16.9,0,0,0,13.32,13.74,7.33,7.33,0,0,0,4.7-.42c1.9-1,2.87-3.13,3.69-5.12l6-14.36c2.7-6.47,5.42-13,6.86-19.86a55.92,55.92,0,0,0-2.39-30.94c-1.42-3.76-3.38-7.53-6.71-9.79-6.67-4.53-16-1.4-23.58-4-2.51-.87-4.77-2.35-7.27-3.24-5.41-1.93-11.52-.91-16.73,1.52s-9.42,6.26-14,9.75C957.26,226.44,948.85,229.71,949.6,236Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><path d="M950.52,235.23c.43,3.64,4.27,5.86,7.84,6.68s7.44.86,10.57,2.77c5,3.08,6.35,9.92,5.72,15.8-.35,3.32-.85,7.39,1.87,9.32a7.25,7.25,0,0,0,2.8,1,26.26,26.26,0,0,0,8.62.3c1.68-.22,3.41-.6,5-.13,2.62.76,4.17,3.52,4.68,6.19s.23,5.45.64,8.14a16.9,16.9,0,0,0,13.32,13.74,7.33,7.33,0,0,0,4.7-.42c1.9-1,2.87-3.13,3.69-5.12l6-14.36c2.7-6.47,5.42-13,6.86-19.86a55.92,55.92,0,0,0-2.39-30.94c-1.42-3.76-3.38-7.53-6.71-9.79-6.67-4.53-16-1.4-23.58-4-2.51-.87-4.77-2.35-7.27-3.24-5.41-1.93-11.52-.91-16.73,1.52s-9.42,6.26-14,9.75C958.19,225.67,949.78,228.94,950.52,235.23Z" transform="translate(-39.7 -18.74)" fill="#b96b6b"/><path d="M979.48,373.34S966.13,425.2,952.84,430s-7.42,6.16-7.42,6.16" transform="translate(-39.7 -18.74)" opacity="0.1"/><rect x="925.9" y="544.58" width="14.51" height="95.37" fill="#d6d6e3"/><rect x="925.9" y="544.58" width="14.51" height="7.07" opacity="0.1"/><path d="M936.57,562.82v2a4.69,4.69,0,0,0,4.69,4.69h63.18a4.69,4.69,0,0,0,4.69-4.69v-2a4.69,4.69,0,0,0-4.69-4.69H941.26a4.69,4.69,0,0,0-4.69,4.69Z" transform="translate(-39.7 -18.74)" fill="#d6d6e3"/><path d="M937.65,559.84h70.41a4.67,4.67,0,0,0-3.62-1.71H941.26A4.67,4.67,0,0,0,937.65,559.84Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><rect x="873.56" y="634.76" width="119.21" height="11.4" rx="5.5" ry="5.5" fill="#d6d6e3"/><circle cx="986.03" cy="653.42" r="7.26" fill="#535461"/><circle cx="880.29" cy="653.42" r="7.26" fill="#535461"/><circle cx="933.16" cy="653.42" r="7.26" fill="#535461"/><rect x="917.74" y="427.44" width="114.03" height="17.62" rx="8.5" ry="8.5" fill="#d6d6e3"/><path d="M980.11,653.5s-5.7,5.63-14.51,0" transform="translate(-39.7 -18.74)" opacity="0.1"/><path d="M1071.47,455.17v1.39a7.25,7.25,0,0,1-7.25,7.25H964.69a7.25,7.25,0,0,1-7.25-7.25v-1.39Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><rect x="822.44" y="452.63" width="19.61" height="249.86" fill="#d6d6e3"/><rect x="822.44" y="452.63" width="19.61" height="16.2" opacity="0.1"/><rect x="232.94" y="551.4" width="14.51" height="95.37" fill="#d6d6e3"/><rect x="232.94" y="551.4" width="14.51" height="7.07" opacity="0.1"/><path d="M316.17,569.65v2a4.69,4.69,0,0,1-4.69,4.69H248.3a4.69,4.69,0,0,1-4.69-4.69v-2A4.69,4.69,0,0,1,248.3,565h63.18a4.69,4.69,0,0,1,4.69,4.69Z" transform="translate(-39.7 -18.74)" fill="#d6d6e3"/><path d="M315.1,566.66H244.68A4.67,4.67,0,0,1,248.3,565h63.18A4.67,4.67,0,0,1,315.1,566.66Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><rect x="180.59" y="641.58" width="119.21" height="11.4" rx="5.5" ry="5.5" fill="#d6d6e3"/><circle cx="187.33" cy="660.24" r="7.26" fill="#535461"/><circle cx="293.06" cy="660.24" r="7.26" fill="#535461"/><circle cx="240.19" cy="660.24" r="7.26" fill="#535461"/><path d="M272.64,660.32s5.7,5.63,14.51,0" transform="translate(-39.7 -18.74)" opacity="0.1"/><path d="M642.81,373.22s-16.28-29.59-10.33-55.31a51.17,51.17,0,0,0-2.85-32.24A86.06,86.06,0,0,0,622,271.93" transform="translate(-39.7 -18.74)" fill="none" stroke="#535461" stroke-miterlimit="10" stroke-width="2"/><path d="M625.61,255.69c.85,3.88-3.7,16.67-3.7,16.67s-9.49-9.7-10.35-13.58a7.19,7.19,0,1,1,14-3.09Z" transform="translate(-39.7 -18.74)" fill="#348eed"/><path d="M641.94,274.62c-1.39,3.72-12.16,12-12.16,12s-2.7-13.3-1.3-17a7.19,7.19,0,1,1,13.47,5Z" transform="translate(-39.7 -18.74)" fill="#348eed"/><path d="M649.5,311.44c-3.08,2.51-16.55,4.2-16.55,4.2s4.39-12.84,7.47-15.35a7.19,7.19,0,1,1,9.08,11.15Z" transform="translate(-39.7 -18.74)" fill="#348eed"/><path d="M648.95,340.58c-2.61,3-15.6,6.94-15.6,6.94s2.15-13.4,4.76-16.39a7.19,7.19,0,1,1,10.83,9.45Z" transform="translate(-39.7 -18.74)" fill="#348eed"/><path d="M615.2,290.59c3.38,2.09,16.95,2,16.95,2s-6-12.17-9.38-14.26a7.19,7.19,0,1,0-7.57,12.22Z" transform="translate(-39.7 -18.74)" fill="#348eed"/><path d="M615,324.69c3.85,1,16.78-3.12,16.78-3.12s-9.37-9.82-13.22-10.8A7.19,7.19,0,0,0,615,324.69Z" transform="translate(-39.7 -18.74)" fill="#348eed"/><path d="M619.33,357.85c3.62,1.62,17.07-.24,17.07-.24s-7.58-11.26-11.2-12.88a7.19,7.19,0,0,0-5.87,13.13Z" transform="translate(-39.7 -18.74)" fill="#348eed"/><path d="M625.61,255.69c.85,3.88-3.7,16.67-3.7,16.67s-9.49-9.7-10.35-13.58a7.19,7.19,0,1,1,14-3.09Z" transform="translate(-39.7 -18.74)" fill="#348eed"/><path d="M641.94,274.62c-1.39,3.72-12.16,12-12.16,12s-2.7-13.3-1.3-17a7.19,7.19,0,1,1,13.47,5Z" transform="translate(-39.7 -18.74)" fill="#348eed"/><path d="M649.5,311.44c-3.08,2.51-16.55,4.2-16.55,4.2s4.39-12.84,7.47-15.35a7.19,7.19,0,1,1,9.08,11.15Z" transform="translate(-39.7 -18.74)" fill="#348eed"/><path d="M648.95,340.58c-2.61,3-15.6,6.94-15.6,6.94s2.15-13.4,4.76-16.39a7.19,7.19,0,1,1,10.83,9.45Z" transform="translate(-39.7 -18.74)" fill="#348eed"/><path d="M615.2,290.59c3.38,2.09,16.95,2,16.95,2s-6-12.17-9.38-14.26a7.19,7.19,0,1,0-7.57,12.22Z" transform="translate(-39.7 -18.74)" fill="#348eed"/><path d="M615,324.69c3.85,1,16.78-3.12,16.78-3.12s-9.37-9.82-13.22-10.8A7.19,7.19,0,0,0,615,324.69Z" transform="translate(-39.7 -18.74)" fill="#348eed"/><path d="M619.33,357.85c3.62,1.62,17.07-.24,17.07-.24s-7.58-11.26-11.2-12.88a7.19,7.19,0,0,0-5.87,13.13Z" transform="translate(-39.7 -18.74)" fill="#348eed"/><path d="M636.19,455.29h0a17.14,17.14,0,0,1-17.11-18.22l4.79-75.9h24.64l4.79,75.9A17.14,17.14,0,0,1,636.19,455.29Z" transform="translate(-39.7 -18.74)" fill="#348eed"/><rect x="83.6" y="63.42" width="14" height="58" fill="#348eed" opacity="0.1"/><rect x="101.6" y="63.42" width="14" height="58" fill="#348eed" opacity="0.1"/><rect x="119.6" y="63.42" width="14" height="58" fill="#348eed" opacity="0.1"/><rect x="137.6" y="63.42" width="14" height="58" fill="#348eed" opacity="0.1"/><rect x="173.6" y="63.42" width="14" height="58" fill="#348eed" opacity="0.1"/><rect x="191.6" y="63.42" width="14" height="58" fill="#348eed" opacity="0.1"/><rect x="83.6" y="74.42" width="14" height="3" fill="#348eed" opacity="0.1"/><rect x="83.6" y="110.42" width="14" height="3" fill="#348eed" opacity="0.1"/><rect x="101.6" y="74.42" width="14" height="3" fill="#348eed" opacity="0.1"/><rect x="101.6" y="110.42" width="14" height="3" fill="#348eed" opacity="0.1"/><rect x="119.6" y="74.42" width="14" height="3" fill="#348eed" opacity="0.1"/><rect x="119.6" y="110.42" width="14" height="3" fill="#348eed" opacity="0.1"/><rect x="137.6" y="74.42" width="14" height="3" fill="#348eed" opacity="0.1"/><rect x="137.6" y="110.42" width="14" height="3" fill="#348eed" opacity="0.1"/><rect x="155.6" y="74.42" width="14" height="3" fill="#348eed" opacity="0.1"/><rect x="155.6" y="110.42" width="14" height="3" fill="#348eed" opacity="0.1"/><rect x="173.6" y="74.42" width="14" height="3" fill="#348eed" opacity="0.1"/><rect x="173.6" y="110.42" width="14" height="3" fill="#348eed" opacity="0.1"/><rect x="191.6" y="74.42" width="14" height="3" fill="#348eed" opacity="0.1"/><rect x="191.6" y="110.42" width="14" height="3" fill="#348eed" opacity="0.1"/><rect x="155.6" y="63.42" width="14" height="58" fill="#348eed" opacity="0.1"/><rect x="257.29" y="82.16" width="14" height="58" transform="translate(-61.94 102.56) rotate(-24.84)" fill="#348eed" opacity="0.1"/><rect x="250.36" y="94.69" width="14" height="3" transform="translate(-56.29 98.27) rotate(-24.84)" fill="#348eed" opacity="0.1"/><rect x="265.48" y="127.36" width="14" height="3" transform="translate(-68.62 107.64) rotate(-24.84)" fill="#348eed" opacity="0.1"/><path d="M428.84,680.21,403.43,664c-.13-22.83-.49-128.82,4.26-143a23.29,23.29,0,0,0,.2-12.61c-3.3-14.41-15.34-25.17-29.93-27.54-13.76-2.24-33.56-5.91-49.5-10.65,14.21-1.47,24.86-2.74,24.86-2.74l.28-2.63,9.15-.44V464l.87-.05c21.37-1.27,66.75-5.5,51.33-18.16-5.29-4.34-12.19-6.39-19.47-7.08,22.26-5.08,57.05-14.92,41.54-24.17-14.74-8.78-37.31-1.88-50.78,3.73l-.83.35-.15-1.21-5.38,1.08h0l-1.71.34-.08-1.76-75.49,11.66a257.66,257.66,0,0,0-3.08-25.81c1.69-11.26,9.55-27.68,6.78-36.49-3.22-10.23-16.44-13.58-22-22.82-1.46-2.41-2.91-4.67-4.33-6.65a70,70,0,0,0-10.65-11.51,48.6,48.6,0,0,1,3.92-17.25,83.57,83.57,0,0,1,5.28-10.6,35.6,35.6,0,0,0,26.44-41c4.78,2.05,10.59,2.26,13.58-1.74l-1.42-.55c.14-.15.28-.3.4-.47-2.91-1.11-6-2.37-8.28-4.39a9.7,9.7,0,0,1-1.45-2.15c-1.38-2.8-1.22-6.16-2.54-9a10.41,10.41,0,0,0-2.59-3.38,17.83,17.83,0,0,0-8.46-4.84c-3.61-1.05-7.41-1.58-10.87-3a36.75,36.75,0,0,0-39.11-11l-1.25.43a16.31,16.31,0,0,0-31.81-7.2,16.27,16.27,0,0,1,4.36-9.74c-.26.22-.53.43-.78.66a16.32,16.32,0,0,0-.71,23.07l.29.28a16.39,16.39,0,0,0,4.09,3.58,16.32,16.32,0,0,1-10.76-13.37c0,.34,0,.68,0,1A16.32,16.32,0,0,0,225,230.13l.45-.05-.24.33a36.71,36.71,0,0,0,3.3,45.45,17,17,0,0,0,2.62,4.2,13.61,13.61,0,0,0,1.21,1.22,15.51,15.51,0,0,0,10.46,4.82q1,1.19,2.13,2.29a59.52,59.52,0,0,1-2.58,8.24,87.34,87.34,0,0,1-7.92,15.27c-21.6,6.14-23.5,42.82-22.72,44.21.93,1.66-3.22,59.26,12.78,128.84-.66,19.89,10.94,52.78,52.12,44.11C328.11,531.81,345,539.7,345,539.7s-4.89,89.57,2.09,102.61c4.37,8.17,1.27,20.17-2,28.64a31.47,31.47,0,0,0-2.88,6.64c-1,2.11-1.73,3.39-1.73,3.39l.7,0c-2.64,9.17-4.51,20.24-4.51,20.24s62.43,12.67,89.89,4.38c10.66-3.22,11.1-6.2,7.64-8.66a55.62,55.62,0,0,0,7.54-1.65C469.22,687,428.84,680.21,428.84,680.21Z" transform="translate(-39.7 -18.74)" fill="url(#81276d2b-ddf3-474d-b794-9223ea4701e2)"/><path d="M293,255a36,36,0,1,1-36-36A36,36,0,0,1,293,255Z" transform="translate(-39.7 -18.74)" fill="#fa595f"/><path d="M374.09,424.13l3.65,18.22s4.06-.73,10.07-2c20.53-4.35,63.91-15.09,47.11-25.11-14.45-8.61-36.58-1.84-49.78,3.66-4.84,2-8.48,3.86-10.11,4.73C374.42,423.94,374.09,424.13,374.09,424.13Z" transform="translate(-39.7 -18.74)" fill="#f8b9bf"/><path d="M375.12,429.27l2.62,13.08s4.06-.73,10.07-2l-2.67-21.45c-4.84,2-8.48,3.86-10.11,4.73Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><polygon points="334.79 424.74 347.33 422.23 344.49 399.32 334.45 401.33 334.79 424.74" fill="#d9dfe8"/><polygon points="334.79 424.74 340.2 423.65 339.21 400.38 334.45 401.33 334.79 424.74" opacity="0.1"/><path d="M258.91,344s24.41-12.72,33.28,27.25,9,58.19,9,58.19l75.92-11.73,1.18,27.59s-81.6,22.43-91.63,20.09-40.48-58.85-40.48-58.85S218.28,362.59,258.91,344Z" transform="translate(-39.7 -18.74)" fill="#348eed"/><g opacity="0.1"><path d="M374.09,424.13l3.65,18.22s4.06-.73,10.07-2c20.53-4.35,63.91-15.09,47.11-25.11-14.45-8.61-36.58-1.84-49.78,3.66-4.84,2-8.48,3.86-10.11,4.73C374.42,423.94,374.09,424.13,374.09,424.13Z" transform="translate(-39.7 -18.74)"/><path d="M375.12,429.27l2.62,13.08s4.06-.73,10.07-2l-2.67-21.45c-4.84,2-8.48,3.86-10.11,4.73Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><polygon points="334.79 424.74 347.33 422.23 344.49 399.32 334.45 401.33 334.79 424.74"/><polygon points="334.79 424.74 340.2 423.65 339.21 400.38 334.45 401.33 334.79 424.74" opacity="0.1"/><path d="M260.91,346s24.41-12.72,33.28,27.25,9,58.19,9,58.19l75.92-11.73,1.18,27.59s-81.6,22.43-91.63,20.09-40.48-58.85-40.48-58.85S220.28,364.59,260.91,346Z" transform="translate(-39.7 -18.74)"/></g><path d="M397,656.71l29.94,19s39.59,6.63,12.66,14.76-88.13-4.3-88.13-4.3,4.65-27.54,9.6-31.19S397,656.71,397,656.71Z" transform="translate(-39.7 -18.74)" fill="#f37291"/><path d="M286,452.9s14.93,8.67,41.78,16.77c15.74,4.75,35.54,8.42,49.22,10.65a35.81,35.81,0,0,1,29.35,27,22.84,22.84,0,0,1-.19,12.36C400.94,535.3,402,664.15,402,664.15l-46.84,2.25s7.41-23.41-1.72-35.77S359.6,527.9,359.6,527.9s-16.59-7.74-67.09-10.43c-47.33,10-64.34-42.15-56.48-56.19S286,452.9,286,452.9Z" transform="translate(-39.7 -18.74)" fill="#96a2d0"/><g opacity="0.1"><path d="M397,656.71l29.94,19s39.59,6.63,12.66,14.76-88.13-4.3-88.13-4.3,4.65-27.54,9.6-31.19S397,656.71,397,656.71Z" transform="translate(-39.7 -18.74)"/><path d="M286,452.9s14.93,8.67,41.78,16.77c15.74,4.75,35.54,8.42,49.22,10.65a35.81,35.81,0,0,1,29.35,27,22.84,22.84,0,0,1-.19,12.36C400.94,535.3,402,664.15,402,664.15l-46.84,2.25s7.41-23.41-1.72-35.77S359.6,527.9,359.6,527.9s-16.59-7.74-67.09-10.43c-47.33,10-64.34-42.15-56.48-56.19S286,452.9,286,452.9Z" transform="translate(-39.7 -18.74)"/></g><path d="M382.08,666.82l29.94,19s39.59,6.63,12.66,14.76-88.13-4.3-88.13-4.3,4.65-27.54,9.6-31.19S382.08,666.82,382.08,666.82Z" transform="translate(-39.7 -18.74)" fill="#f37291"/><path d="M277.64,527.57c50.5,2.69,67.09,10.43,67.09,10.43s-4.79,87.82,2,100.6c7.25,13.56-6.48,37.9-6.48,37.9l46.84-2.25s0-1.52,0-4.24c-.14-22.38-.48-126.3,4.17-140.23a22.84,22.84,0,0,0,.19-12.36,35.82,35.82,0,0,0-29.35-27c-13.69-2.22-33.48-5.89-49.22-10.64l-.77-.24c-26.37-8-41-16.53-41-16.53s-31.19-2.16-39,11.88c-1,1.82-5.31,5.4-5.49,8.29C225.45,502.52,236.46,536.24,277.64,527.57Z" transform="translate(-39.7 -18.74)" fill="#96a2d0"/><path d="M351.81,445.6l.89,18.56s4.12-.11,10.26-.48c20.95-1.25,65.45-5.39,50.33-17.8-13-10.67-35.9-7.27-49.77-3.8-5.09,1.28-9,2.56-10.7,3.17C352.16,445.47,351.81,445.6,351.81,445.6Z" transform="translate(-39.7 -18.74)" fill="#f8b9bf"/><path d="M233,319.29s6,5.17,13.44,10.89c8.3,2.26,18,4.27,24.93,5.6-2.15-8.15-.24-17,2.93-24.79a93.69,93.69,0,0,1,13.56-22.51l-41.7-15.07c2.95,8.55,1,18.1-2.17,26.27A86.85,86.85,0,0,1,233,319.29Z" transform="translate(-39.7 -18.74)" fill="#f8b9bf"/><path d="M352.06,450.83l.64,13.33s4.12-.11,10.26-.48l.56-21.61c-5.09,1.28-9,2.56-10.7,3.17Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><polygon points="309.62 446.05 322.39 445.44 322.99 422.36 312.77 422.85 309.62 446.05" fill="#d9dfe8"/><path d="M227.53,488.58c27,28.07,77.35-5.29,90.89-15.17,2.2-1.61,3.43-2.6,3.43-2.6-5-5.09-13-9.8-16.07-22.55a277,277,0,0,1-5.94-39.52c-.83-10.55,10.07-30.64,6.92-40.66s-16.12-13.31-21.57-22.38c-1.43-2.36-2.85-4.58-4.25-6.52-10.06-14-27.59-23.43-27.59-23.43C216,303,213.07,356.34,214,358S210.66,418.46,227.53,488.58Z" transform="translate(-39.7 -18.74)" fill="#348eed"/><path d="M227.25,411.76s21.69,60.42,31.26,64.23c5.48,2.18,35.37-.17,59.91-2.58,2.2-1.61,3.43-2.6,3.43-2.6-5-5.09-9.07-15.58-12.13-28.32l-31.49.11s2.55-18-.27-58.89-28.85-31.9-28.85-31.9C206.18,364.12,227.25,411.76,227.25,411.76Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><polygon points="309.62 446.05 315.13 445.79 317.62 422.62 312.77 422.85 309.62 446.05" opacity="0.1"/><path d="M249.85,349.21s26-8.93,28.85,31.9S279,440,279,440l76.82-.28-2.95,27.46s-84,10-93.6,6.21S228,409.17,228,409.17,206.91,361.52,249.85,349.21Z" transform="translate(-39.7 -18.74)" fill="#348eed"/><path d="M250.8,289c1.39-6-5-6.19-7-12L285.52,292a93.4,93.4,0,0,0-8.38,12.11,35.11,35.11,0,0,1-6.78,1C260.22,305.61,257.51,295.58,250.8,289Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><circle cx="271.07" cy="266.67" r="34.96" transform="translate(-52.19 -5.41) rotate(-2.75)" fill="#f8b9bf"/><path d="M253,375.14s11.06,52.39,24.12,57.74,7.14,6.49,7.14,6.49" transform="translate(-39.7 -18.74)" opacity="0.1"/><path d="M298.58,404.73s-9.24,2.89-20-10.92" transform="translate(-39.7 -18.74)" opacity="0.1"/><circle cx="189.3" cy="199.26" r="16" fill="#fa595f"/><path d="M217.76,226.45a16,16,0,0,1,0-21.89c-.26.21-.52.42-.76.65a16,16,0,0,0,21.92,23.32c.25-.23.47-.48.69-.72A16,16,0,0,1,217.76,226.45Z" transform="translate(-39.7 -18.74)" fill="#fa595f"/><path d="M226.9,232.49a16,16,0,0,1-16.84-14c0,.33,0,.66,0,1a16,16,0,1,0,31.94-2c0-.34-.07-.66-.11-1A16,16,0,0,1,226.9,232.49Z" transform="translate(-39.7 -18.74)" fill="#fa595f"/><path d="M283.95,231.91c3.73,1.84,8,2.35,12,3.51s8.08,3.28,9.84,7.06c1.29,2.78,1.14,6.07,2.49,8.82,1.94,4,6.42,5.84,10.54,7.41-3.54,4.75-11.14,3.46-16.1.23s-9.23-8-15-9.25a14.08,14.08,0,0,0-14.81,6.59c-1.58,2.77-2.14,6-2.92,9.08-2,8.12-6,16.2-13,20.81s-17.43,4.58-22.92-1.73c-3.2-3.68-4.27-8.69-5.22-13.47a27.37,27.37,0,0,1-.77-6.81c.32-5.74,4.14-10.63,8-14.89,4.55-5,9.39-9.8,14.23-14.57,4.18-4.12,9-11.1,15.3-11.59C272.87,222.53,278,229,283.95,231.91Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><path d="M282.95,230.91c3.73,1.84,8,2.35,12,3.51s8.08,3.28,9.84,7.06c1.29,2.78,1.14,6.07,2.49,8.82,1.94,4,6.42,5.84,10.54,7.41-3.54,4.75-11.14,3.46-16.1.23s-9.23-8-15-9.25a14.08,14.08,0,0,0-14.81,6.59c-1.58,2.77-2.14,6-2.92,9.08-2,8.12-6,16.2-13,20.81s-17.43,4.58-22.92-1.73c-3.2-3.68-4.27-8.69-5.22-13.47a27.37,27.37,0,0,1-.77-6.81c.32-5.74,4.14-10.63,8-14.89,4.55-5,9.39-9.8,14.23-14.57,4.18-4.12,9-11.1,15.3-11.59C271.87,221.53,277,228,282.95,230.91Z" transform="translate(-39.7 -18.74)" fill="#fa595f"/><rect x="141.58" y="434.26" width="114.03" height="17.62" rx="8.5" ry="8.5" fill="#d6d6e3"/><path d="M181.28,462v1.39a7.25,7.25,0,0,0,7.25,7.25h99.53a7.25,7.25,0,0,0,7.25-7.25V462Z" transform="translate(-39.7 -18.74)" opacity="0.1"/><rect x="342.39" y="452.63" width="19.61" height="249.83" fill="#d6d6e3"/><rect x="342.39" y="452.63" width="19.61" height="16.2" opacity="0.1"/><rect x="333.01" y="434.72" width="518.42" height="32.4" fill="#d6d6e3"/></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="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>
{
"color": [
"#2d8cf0",
"#19be6b",
"#ff9900",
"#E46CBB",
"#9A66E4",
"#ed3f14"
],
"backgroundColor": "rgba(0,0,0,0)",
"textStyle": {},
"title": {
"textStyle": {
"color": "#516b91"
},
"subtextStyle": {
"color": "#93b7e3"
}
},
"line": {
"itemStyle": {
"normal": {
"borderWidth": "2"
}
},
"lineStyle": {
"normal": {
"width": "2"
}
},
"symbolSize": "6",
"symbol": "emptyCircle",
"smooth": true
},
"radar": {
"itemStyle": {
"normal": {
"borderWidth": "2"
}
},
"lineStyle": {
"normal": {
"width": "2"
}
},
"symbolSize": "6",
"symbol": "emptyCircle",
"smooth": true
},
"bar": {
"itemStyle": {
"normal": {
"barBorderWidth": 0,
"barBorderColor": "#ccc"
},
"emphasis": {
"barBorderWidth": 0,
"barBorderColor": "#ccc"
}
}
},
"pie": {
"itemStyle": {
"normal": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"emphasis": {
"borderWidth": 0,
"borderColor": "#ccc"
}
}
},
"scatter": {
"itemStyle": {
"normal": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"emphasis": {
"borderWidth": 0,
"borderColor": "#ccc"
}
}
},
"boxplot": {
"itemStyle": {
"normal": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"emphasis": {
"borderWidth": 0,
"borderColor": "#ccc"
}
}
},
"parallel": {
"itemStyle": {
"normal": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"emphasis": {
"borderWidth": 0,
"borderColor": "#ccc"
}
}
},
"sankey": {
"itemStyle": {
"normal": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"emphasis": {
"borderWidth": 0,
"borderColor": "#ccc"
}
}
},
"funnel": {
"itemStyle": {
"normal": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"emphasis": {
"borderWidth": 0,
"borderColor": "#ccc"
}
}
},
"gauge": {
"itemStyle": {
"normal": {
"borderWidth": 0,
"borderColor": "#ccc"
},
"emphasis": {
"borderWidth": 0,
"borderColor": "#ccc"
}
}
},
"candlestick": {
"itemStyle": {
"normal": {
"color": "#edafda",
"color0": "transparent",
"borderColor": "#d680bc",
"borderColor0": "#8fd3e8",
"borderWidth": "2"
}
}
},
"graph": {
"itemStyle": {
"normal": {
"borderWidth": 0,
"borderColor": "#ccc"
}
},
"lineStyle": {
"normal": {
"width": 1,
"color": "#aaa"
}
},
"symbolSize": "6",
"symbol": "emptyCircle",
"smooth": true,
"color": [
"#2d8cf0",
"#19be6b",
"#f5ae4a",
"#9189d5",
"#56cae2",
"#cbb0e3"
],
"label": {
"normal": {
"textStyle": {
"color": "#eee"
}
}
}
},
"map": {
"itemStyle": {
"normal": {
"areaColor": "#f3f3f3",
"borderColor": "#516b91",
"borderWidth": 0.5
},
"emphasis": {
"areaColor": "rgba(165,231,240,1)",
"borderColor": "#516b91",
"borderWidth": 1
}
},
"label": {
"normal": {
"textStyle": {
"color": "#000"
}
},
"emphasis": {
"textStyle": {
"color": "rgb(81,107,145)"
}
}
}
},
"geo": {
"itemStyle": {
"normal": {
"areaColor": "#f3f3f3",
"borderColor": "#516b91",
"borderWidth": 0.5
},
"emphasis": {
"areaColor": "rgba(165,231,240,1)",
"borderColor": "#516b91",
"borderWidth": 1
}
},
"label": {
"normal": {
"textStyle": {
"color": "#000"
}
},
"emphasis": {
"textStyle": {
"color": "rgb(81,107,145)"
}
}
}
},
"categoryAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"textStyle": {
"color": "#999999"
}
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eeeeee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"valueAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"textStyle": {
"color": "#999999"
}
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eeeeee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"logAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"textStyle": {
"color": "#999999"
}
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eeeeee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"timeAxis": {
"axisLine": {
"show": true,
"lineStyle": {
"color": "#cccccc"
}
},
"axisTick": {
"show": false,
"lineStyle": {
"color": "#333"
}
},
"axisLabel": {
"show": true,
"textStyle": {
"color": "#999999"
}
},
"splitLine": {
"show": true,
"lineStyle": {
"color": [
"#eeeeee"
]
}
},
"splitArea": {
"show": false,
"areaStyle": {
"color": [
"rgba(250,250,250,0.05)",
"rgba(200,200,200,0.02)"
]
}
}
},
"toolbox": {
"iconStyle": {
"normal": {
"borderColor": "#999"
},
"emphasis": {
"borderColor": "#666"
}
}
},
"legend": {
"textStyle": {
"color": "#999999"
}
},
"tooltip": {
"axisPointer": {
"lineStyle": {
"color": "#ccc",
"width": 1
},
"crossStyle": {
"color": "#ccc",
"width": 1
}
}
},
"timeline": {
"lineStyle": {
"color": "#8fd3e8",
"width": 1
},
"itemStyle": {
"normal": {
"color": "#8fd3e8",
"borderWidth": 1
},
"emphasis": {
"color": "#8fd3e8"
}
},
"controlStyle": {
"normal": {
"color": "#8fd3e8",
"borderColor": "#8fd3e8",
"borderWidth": 0.5
},
"emphasis": {
"color": "#8fd3e8",
"borderColor": "#8fd3e8",
"borderWidth": 0.5
}
},
"checkpointStyle": {
"color": "#8fd3e8",
"borderColor": "rgba(138,124,168,0.37)"
},
"label": {
"normal": {
"textStyle": {
"color": "#8fd3e8"
}
},
"emphasis": {
"textStyle": {
"color": "#8fd3e8"
}
}
}
},
"visualMap": {
"color": [
"#516b91",
"#59c4e6",
"#a5e7f0"
]
},
"dataZoom": {
"backgroundColor": "rgba(0,0,0,0)",
"dataBackgroundColor": "rgba(255,255,255,0.3)",
"fillerColor": "rgba(167,183,204,0.4)",
"handleColor": "#a7b7cc",
"handleSize": "100%",
"textStyle": {
"color": "#333"
}
},
"markPoint": {
"label": {
"normal": {
"textStyle": {
"color": "#eee"
}
},
"emphasis": {
"textStyle": {
"color": "#eee"
}
}
}
}
}
<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.uploadImgServer = 'https://applet.mwcx.cn/bg/survey/uploadFile'
this.editor.customConfig.uploadFileName = 'imgFile'
this.editor.customConfig.uploadImgHooks = {
// 上传图片之前
before: function(xhr) {
console.log(xhr)
},
// 图片上传并返回了结果,图片插入已成功
success: function(xhr) {
console.log('success', xhr)
},
// 图片上传并返回了结果,但图片插入时出错了
fail: function(xhr, editor, resData) {
console.log('fail', resData)
},
// 上传图片出错,一般为 http 请求的错误
error: function(xhr, editor, resData) {
console.log('error', xhr, resData)
},
// 上传图片超时
timeout: function(xhr) {
console.log('timeout')
},
// 图片上传并返回了结果,想要自己把图片插入到编辑器中
// 例如服务器端返回的不是 { errno: 0, data: [...] } 这种格式,可使用 customInsert
customInsert: function(insertImgFn, result) {
// result 即服务端返回的接口
console.log('customInsert', result)
// insertImgFn 可把图片插入到编辑器,传入图片 src ,执行函数即可
insertImgFn(result.data[0])
}
}
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: '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.$router.push({
name: 'login'
})
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;
.logo-text{
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
font-weight: bold;
color: #fff;
}
img{
height: 44px;
width: auto;
display: block;
margin-right: 6px;
}
}
.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 class="main" style="height: 100%">
<Sider
:collapsed-width="64"
:style="{overflow: 'hidden'}"
:width="256"
class="left-sider"
collapsible
hide-trigger
v-model="collapsed"
>
<side-menu
:active-name="$route.name"
:collapsed="collapsed"
:menu-list="menuList"
@on-select="turnToPage"
accordion
ref="sideMenu"
>
<!-- 需要放在菜单上面的内容,如Logo,写在side-menu标签内部,如下 -->
<div class="logo-con">
<div class="logo-text" v-show="!collapsed" style="margin-top: 8px;">
<!-- <img :src="minLogo" key="max-logo" /> -->
<span>活动后台管理</span>
</div>
<!-- <img :src="minLogo" key="min-logo" v-show="collapsed" /> -->
</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
:lang="local"
@on-lang-change="setLocal"
style="margin-right: 10px;"
v-if="$config.useI18n"
/>
<error-store
:count="errorCount"
:has-read="hasReadErrorPage"
v-if="$config.plugin['error-store'] && $config.plugin['error-store'].showInHeader"
></error-store>
<fullscreen style="margin-right: 10px;" v-model="isFullscreen" />
</header-bar>
</Header>
<Content class="main-content-con">
<Layout class="main-layout-con">
<div class="tag-nav-wrapper">
<tags-nav :list="tagNavList" :value="$route" @input="handleClick" @on-close="handleCloseTag" />
</div>
<Content class="content-wrapper">
<keep-alive :include="cacheList">
<router-view />
</keep-alive>
<ABackTop :bottom="80" :height="100" :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/logos.jpg'
import './main.less'
export default {
name: 'Main',
components: {
SideMenu,
HeaderBar,
Language,
TagsNav,
Fullscreen,
ErrorStore,
User,
ABackTop
},
data () {
return {
collapsed: false,
minLogo,
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', 'getTreeCategories', 'getTreeScene']),
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
})
}
}
}
</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>
import ParentView from './parent-view.vue'
export default ParentView
<template>
<keep-alive :include="cacheList" :exclude="notCacheName">
<router-view ref="child"/>
</keep-alive>
</template>
<script>
export default {
name: 'ParentView',
computed: {
tagNavList () {
return this.$store.state.app.tagNavList
},
notCacheName () {
return [(this.$route.meta && this.$route.meta.notCache) ? this.$route.name : '']
},
cacheList () {
return ['ParentView', ...this.tagNavList.length ? this.tagNavList.filter(item => !(item.meta && item.meta.notCache)).map(item => item.name) : []]
}
}
}
</script>
import PasteEditor from './paste-editor.vue'
export default PasteEditor
.paste-editor-wrapper{
width: 100%;
height: 100%;
border: 1px dashed gainsboro;
textarea.textarea-el{
width: 100%;
height: 100%;
}
.CodeMirror{
height: 100%;
padding: 0;
.CodeMirror-code div .CodeMirror-line > span > span.cm-tab{
&::after{
content: '→';
color: #BFBFBF;
}
}
}
.first-row{
font-weight: 700;
font-size: 14px;
}
.incorrect-row{
background: #F5CBD1;
}
}
<template>
<div class="paste-editor-wrapper">
<textarea ref="codemirror" class="textarea-el"></textarea>
</div>
</template>
<script>
import CodeMirror from 'codemirror'
import 'codemirror/lib/codemirror.css'
import { forEach } from '@/libs/tools'
import createPlaceholder from './plugins/placeholder'
export default {
name: 'PasteEditor',
props: {
value: Array,
pasteData: {
type: String,
default: ''
},
placeholder: {
type: String,
default: '从网页或其他应用软件复制表格数据,粘贴到这里 。默认第一行是表头,使用回车键添加新行,使用Tab键区分列。'
}
},
data () {
return {
pasteDataArr: [],
rowArrLength: 0,
editor: null
}
},
watch: {
pasteData (val) {
if (val === '') {
this.editor.setValue('')
}
}
},
computed: {
rowNum () {
return this.pasteDataArr.length
},
colNum () {
return this.pasteDataArr[0] ? this.pasteDataArr[0].length : 0
}
},
methods: {
handleKeyup (e) {
this.handleAreaData()
},
/**
* @description 处理粘贴操作
*/
handleContentChanged (content) {
let pasteData = content.trim()
this.$emit('on-content-change', pasteData)
let rows = pasteData.split((/[\n\u0085\u2028\u2029]|\r\n?/g)).map(row => {
return row.split('\t')
})
if (content === '') rows = []
this.pasteDataArr = rows
this.clearLineClass()
this.checkColNumInEveryRow()
this.$emit('input', this.pasteDataArr)
},
/**
* @description 检查除第一行的每一行列数是否与第一行相同
*/
checkColNumInEveryRow () {
let i = 0
const len = this.rowNum
if (len === 0) return
while (++i < len) {
let item = this.pasteDataArr[i]
if (item.length !== this.colNum && (!(i === len - 1 && item.length === 1 && item[0] === '') || i !== len - 1)) {
this.markIncorrectRow(i)
this.$emit('on-error', i)
return false
}
}
this.$emit('on-success', this.pasteDataArr)
return true
},
/**
* @description 标记不符合格式的一行
*/
markIncorrectRow (index) {
this.editor.addLineClass(index, 'text', 'incorrect-row')
},
/**
* @description 标记不符合格式的一行
*/
clearLineClass () {
forEach(this.pasteDataArr, (item, index) => {
this.editor.removeLineClass(index, 'text', 'incorrect-row')
})
}
},
mounted () {
createPlaceholder(CodeMirror)
this.editor = CodeMirror.fromTextArea(this.$refs.codemirror, {
lineNumbers: true,
tabSize: 1,
lineWrapping: true,
placeholder: this.placeholder
})
this.editor.on('change', (editor) => {
this.handleContentChanged(editor.getValue())
})
this.editor.addLineClass(0, 'text', 'first-row')
}
}
</script>
<style lang="less">
@import './paste-editor.less';
</style>
export default (codemirror) => {
(function (mod) {
mod(codemirror)
})(function (CodeMirror) {
CodeMirror.defineOption('placeholder', '', function (cm, val, old) {
var prev = old && old !== CodeMirror.Init
if (val && !prev) {
cm.on('blur', onBlur)
cm.on('change', onChange)
cm.on('swapDoc', onChange)
onChange(cm)
} else if (!val && prev) {
cm.off('blur', onBlur)
cm.off('change', onChange)
cm.off('swapDoc', onChange)
clearPlaceholder(cm)
var wrapper = cm.getWrapperElement()
wrapper.className = wrapper.className.replace(' CodeMirror-empty', '')
}
if (val && !cm.hasFocus()) onBlur(cm)
})
function clearPlaceholder (cm) {
if (cm.state.placeholder) {
cm.state.placeholder.parentNode.removeChild(cm.state.placeholder)
cm.state.placeholder = null
}
}
function setPlaceholder (cm) {
clearPlaceholder(cm)
var elt = cm.state.placeholder = document.createElement('pre')
elt.style.cssText = 'height: 0; overflow: visible; color: #80848f;'
elt.style.direction = cm.getOption('direction')
elt.className = 'CodeMirror-placeholder'
var placeHolder = cm.getOption('placeholder')
if (typeof placeHolder === 'string') placeHolder = document.createTextNode(placeHolder)
elt.appendChild(placeHolder)
cm.display.lineSpace.insertBefore(elt, cm.display.lineSpace.firstChild)
}
function onBlur (cm) {
if (isEmpty(cm)) setPlaceholder(cm)
}
function onChange (cm) {
let wrapper = cm.getWrapperElement()
let empty = isEmpty(cm)
wrapper.className = wrapper.className.replace(' CodeMirror-empty', '') + (empty ? ' CodeMirror-empty' : '')
if (empty) setPlaceholder(cm)
else clearPlaceholder(cm)
}
function isEmpty (cm) {
return (cm.lineCount() === 1) && (cm.getLine(0) === '')
}
})
}
import Split from './split.vue'
export default Split
@split-prefix-cls: ~"ivu-split";
@box-shadow: 0 0 4px 0 rgba(28, 36, 56, 0.4);
@trigger-bar-background: rgba(23, 35, 61, 0.25);
@trigger-background: #F8F8F9;
@trigger-width: 6px;
@trigger-bar-width: 4px;
@trigger-bar-offset: (@trigger-width - @trigger-bar-width) / 2;
@trigger-bar-interval: 3px;
@trigger-bar-weight: 1px;
@trigger-bar-con-height: (@trigger-bar-weight + @trigger-bar-interval) * 8;
.@{split-prefix-cls}{
&-wrapper{
position: relative;
width: 100%;
height: 100%;
}
&-pane{
position: absolute;
&.left-pane, &.right-pane{
top: 0px;
bottom: 0px;
}
&.left-pane{
left: 0px;
}
&.right-pane{
right: 0px;
}
&.top-pane, &.bottom-pane{
left: 0px;
right: 0px;
}
&.top-pane{
top: 0px;
}
&.bottom-pane{
bottom: 0px;
}
}
&-trigger{
&-con{
position: absolute;
transform: translate(-50%, -50%);
z-index: 10;
}
&-bar-con{
position: absolute;
overflow: hidden;
&.vertical{
left: @trigger-bar-offset;
top: 50%;
height: @trigger-bar-con-height;
transform: translate(0, -50%);
}
&.horizontal{
left: 50%;
top: @trigger-bar-offset;
width: @trigger-bar-con-height;
transform: translate(-50%, 0);
}
}
&-vertical{
width: @trigger-width;
height: 100%;
background: @trigger-background;
box-shadow: @box-shadow;
cursor: col-resize;
.@{split-prefix-cls}-trigger-bar{
width: @trigger-bar-width;
height: 1px;
background: @trigger-bar-background;
float: left;
margin-top: @trigger-bar-interval;
}
}
&-horizontal{
height: @trigger-width;
width: 100%;
background: @trigger-background;
box-shadow: @box-shadow;
cursor: row-resize;
.@{split-prefix-cls}-trigger-bar{
height: @trigger-bar-width;
width: 1px;
background: @trigger-bar-background;
float: left;
margin-right: @trigger-bar-interval;
}
}
}
&-horizontal{
.@{split-prefix-cls}-trigger-con{
top: 50%;
height: 100%;
width: 0;
}
}
&-vertical{
.@{split-prefix-cls}-trigger-con{
left: 50%;
height: 0;
width: 100%;
}
}
.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;
}
}
<template>
<div ref="outerWrapper" :class="wrapperClasses">
<div v-if="isHorizontal" :class="`${prefix}-horizontal`">
<div :style="{right: `${anotherOffset}%`}" :class="[`${prefix}-pane`, 'left-pane']"><slot name="left"/></div>
<div :class="`${prefix}-trigger-con`" :style="{left: `${offset}%`}" @mousedown="handleMousedown">
<slot name="trigger">
<trigger mode="vertical"/>
</slot>
</div>
<div :style="{left: `${offset}%`}" :class="[`${prefix}-pane`, 'right-pane']"><slot name="right"/></div>
</div>
<div v-else :class="`${prefix}-vertical`">
<div :style="{bottom: `${anotherOffset}%`}" :class="[`${prefix}-pane`, 'top-pane']"><slot name="top"/></div>
<div :class="`${prefix}-trigger-con`" :style="{top: `${offset}%`}" @mousedown="handleMousedown">
<slot name="trigger">
<trigger mode="horizontal"/>
</slot>
</div>
<div :style="{top: `${offset}%`}" :class="[`${prefix}-pane`, 'bottom-pane']"><slot name="bottom"/></div>
</div>
</div>
</template>
<script>
import { oneOf, on, off } from '@/libs/tools'
import Trigger from './trigger.vue'
export default {
name: 'SplitPane',
components: {
Trigger
},
props: {
value: {
type: [Number, String],
default: 0.5
},
mode: {
validator (value) {
return oneOf(value, ['horizontal', 'vertical'])
},
default: 'horizontal'
},
min: {
type: [Number, String],
default: '40px'
},
max: {
type: [Number, String],
default: '40px'
}
},
/**
* Events
* @on-move-start
* @on-moving 返回值:事件对象,但是在事件对象中加入了两个参数:atMin(当前是否在最小值处), atMax(当前是否在最大值处)
* @on-move-end
*/
data () {
return {
prefix: 'ivu-split',
offset: 0,
oldOffset: 0,
isMoving: false
}
},
computed: {
wrapperClasses () {
return [
`${this.prefix}-wrapper`,
this.isMoving ? 'no-select' : ''
]
},
isHorizontal () {
return this.mode === 'horizontal'
},
anotherOffset () {
return 100 - this.offset
},
valueIsPx () {
return typeof this.value === 'string'
},
offsetSize () {
return this.isHorizontal ? 'offsetWidth' : 'offsetHeight'
},
computedMin () {
return this.getComputedThresholdValue('min')
},
computedMax () {
return this.getComputedThresholdValue('max')
}
},
methods: {
px2percent (numerator, denominator) {
return parseFloat(numerator) / parseFloat(denominator)
},
getComputedThresholdValue (type) {
let size = this.$refs.outerWrapper[this.offsetSize]
if (this.valueIsPx) return typeof this[type] === 'string' ? this[type] : size * this[type]
else return typeof this[type] === 'string' ? this.px2percent(this[type], size) : this[type]
},
getMin (value1, value2) {
if (this.valueIsPx) return `${Math.min(parseFloat(value1), parseFloat(value2))}px`
else return Math.min(value1, value2)
},
getMax (value1, value2) {
if (this.valueIsPx) return `${Math.max(parseFloat(value1), parseFloat(value2))}px`
else return Math.max(value1, value2)
},
getAnotherOffset (value) {
let res = 0
if (this.valueIsPx) res = `${this.$refs.outerWrapper[this.offsetSize] - parseFloat(value)}px`
else res = 1 - value
return res
},
handleMove (e) {
let pageOffset = this.isHorizontal ? e.pageX : e.pageY
let offset = pageOffset - this.initOffset
let outerWidth = this.$refs.outerWrapper[this.offsetSize]
let value = this.valueIsPx ? `${parseFloat(this.oldOffset) + offset}px` : (this.px2percent(outerWidth * this.oldOffset + offset, outerWidth))
let anotherValue = this.getAnotherOffset(value)
if (parseFloat(value) <= parseFloat(this.computedMin)) value = this.getMax(value, this.computedMin)
if (parseFloat(anotherValue) <= parseFloat(this.computedMax)) value = this.getAnotherOffset(this.getMax(anotherValue, this.computedMax))
e.atMin = this.value === this.computedMin
e.atMax = this.valueIsPx ? this.getAnotherOffset(this.value) === this.computedMax : this.getAnotherOffset(this.value).toFixed(5) === this.computedMax.toFixed(5)
this.$emit('input', value)
this.$emit('on-moving', e)
},
handleUp () {
this.isMoving = false
off(document, 'mousemove', this.handleMove)
off(document, 'mouseup', this.handleUp)
this.$emit('on-move-end')
},
handleMousedown (e) {
this.initOffset = this.isHorizontal ? e.pageX : e.pageY
this.oldOffset = this.value
this.isMoving = true
on(document, 'mousemove', this.handleMove)
on(document, 'mouseup', this.handleUp)
this.$emit('on-move-start')
}
},
watch: {
value () {
this.offset = (this.valueIsPx ? this.px2percent(this.value, this.$refs.outerWrapper[this.offsetSize]) : this.value) * 10000 / 100
}
},
mounted () {
this.$nextTick(() => {
this.offset = (this.valueIsPx ? this.px2percent(this.value, this.$refs.outerWrapper[this.offsetSize]) : this.value) * 10000 / 100
})
}
}
</script>
<style lang="less">
@import './index.less';
</style>
<template>
<div :class="classes">
<div :class="barConClasses">
<i :class="`${prefix}-bar`" v-once v-for="i in 8" :key="`trigger-${i}`"></i>
</div>
</div>
</template>
<script>
export default {
name: 'Trigger',
props: {
mode: String
},
data () {
return {
prefix: 'ivu-split-trigger',
initOffset: 0
}
},
computed: {
isVertical () {
return this.mode === 'vertical'
},
classes () {
return [
this.prefix,
this.isVertical ? `${this.prefix}-vertical` : `${this.prefix}-horizontal`
]
},
barConClasses () {
return [
`${this.prefix}-bar-con`,
this.isVertical ? 'vertical' : 'horizontal'
]
}
}
}
</script>
<style lang="less">
@import './index.less';
</style>
<template>
<div class="tables-edit-outer">
<div v-if="!isEditting" class="tables-edit-con">
<span class="value-con">{{ value }}</span>
<Button v-if="editable" @click="startEdit" class="tables-edit-btn" style="padding: 2px 4px;" type="text"><Icon type="md-create"></Icon></Button>
</div>
<div v-else class="tables-editting-con">
<Input :value="value" @input="handleInput" class="tables-edit-input"/>
<Button @click="saveEdit" style="padding: 6px 4px;" type="text"><Icon type="md-checkmark"></Icon></Button>
<Button @click="canceltEdit" style="padding: 6px 4px;" type="text"><Icon type="md-close"></Icon></Button>
</div>
</div>
</template>
<script>
export default {
name: 'TablesEdit',
props: {
value: [String, Number],
edittingCellId: String,
params: Object,
editable: Boolean
},
computed: {
isEditting () {
return this.edittingCellId === `editting-${this.params.index}-${this.params.column.key}`
}
},
methods: {
handleInput (val) {
this.$emit('input', val)
},
startEdit () {
this.$emit('on-start-edit', this.params)
},
saveEdit () {
this.$emit('on-save-edit', this.params)
},
canceltEdit () {
this.$emit('on-cancel-edit', this.params)
}
}
}
</script>
<style lang="less">
.tables-edit-outer{
height: 100%;
.tables-edit-con{
position: relative;
height: 100%;
.value-con{
vertical-align: middle;
}
.tables-edit-btn{
position: absolute;
right: 10px;
top: 0;
display: none;
}
&:hover{
.tables-edit-btn{
display: inline-block;
}
}
}
.tables-editting-con{
.tables-edit-input{
width: ~"calc(100% - 60px)";
}
}
}
</style>
const btns = {
delete: (h, params, vm) => {
return h('Poptip', {
props: {
confirm: true,
title: '你确定要删除吗?'
},
on: {
'on-ok': () => {
vm.$emit('on-delete', params)
vm.$emit('input', params.tableData.filter((item, index) => index !== params.row.initRowIndex))
}
}
}, [
h('Button', {
props: {
type: 'text',
ghost: true
}
}, [
h('Icon', {
props: {
type: 'md-trash',
size: 18,
color: '#000000'
}
})
])
])
}
}
export default btns
import Tables from './tables.vue'
export default Tables
.search-con{
padding: 10px 0;
.search{
&-col{
display: inline-block;
width: 200px;
}
&-input{
display: inline-block;
width: 200px;
margin-left: 2px;
}
&-btn{
margin-left: 2px;
}
}
}
<template>
<div>
<div v-if="searchable && searchPlace === 'top'" class="search-con search-con-top">
<Select clearable v-model="searchKey" class="search-col">
<Option v-for="item in columns" v-if="item.key !== 'handle'" :value="item.key" :key="`search-col-${item.key}`">{{ item.title }}</Option>
</Select>
<Input @on-change="handleClear" clearable placeholder="输入关键字搜索" class="search-input" v-model="searchValue"/>
<Button @click="handleSearch" class="search-btn" type="primary"><Icon type="search"/>&nbsp;&nbsp;搜索</Button>
</div>
<Table
ref="tablesMain"
:data="insideTableData"
:columns="insideColumns"
:stripe="stripe"
:border="border"
:show-header="showHeader"
:width="width"
:height="height"
:loading="loading"
:disabled-hover="disabledHover"
:highlight-row="highlightRow"
:row-class-name="rowClassName"
:size="size"
:no-data-text="noDataText"
:no-filtered-data-text="noFilteredDataText"
@on-current-change="onCurrentChange"
@on-select="onSelect"
@on-select-cancel="onSelectCancel"
@on-select-all="onSelectAll"
@on-selection-change="onSelectionChange"
@on-sort-change="onSortChange"
@on-filter-change="onFilterChange"
@on-row-click="onRowClick"
@on-row-dblclick="onRowDblclick"
@on-expand="onExpand"
>
<slot name="header" slot="header"></slot>
<slot name="footer" slot="footer"></slot>
<slot name="loading" slot="loading"></slot>
</Table>
<div v-if="searchable && searchPlace === 'bottom'" class="search-con search-con-top">
<Select clearable v-model="searchKey" class="search-col">
<Option v-for="item in columns" v-if="item.key !== 'handle'" :value="item.key" :key="`search-col-${item.key}`">{{ item.title }}</Option>
</Select>
<Input placeholder="输入关键字搜索" class="search-input" v-model="searchValue"/>
<Button class="search-btn" type="primary"><Icon type="search"/>&nbsp;&nbsp;搜索</Button>
</div>
<a id="hrefToExportTable" style="display: none;width: 0px;height: 0px;"></a>
</div>
</template>
<script>
import TablesEdit from './edit.vue'
import handleBtns from './handle-btns'
import './index.less'
export default {
name: 'Tables',
props: {
value: {
type: Array,
default () {
return []
}
},
columns: {
type: Array,
default () {
return []
}
},
size: String,
width: {
type: [Number, String]
},
height: {
type: [Number, String]
},
stripe: {
type: Boolean,
default: false
},
border: {
type: Boolean,
default: false
},
showHeader: {
type: Boolean,
default: true
},
highlightRow: {
type: Boolean,
default: false
},
rowClassName: {
type: Function,
default () {
return ''
}
},
context: {
type: Object
},
noDataText: {
type: String
},
noFilteredDataText: {
type: String
},
disabledHover: {
type: Boolean
},
loading: {
type: Boolean,
default: false
},
/**
* @description 全局设置是否可编辑
*/
editable: {
type: Boolean,
default: false
},
/**
* @description 是否可搜索
*/
searchable: {
type: Boolean,
default: false
},
/**
* @description 搜索控件所在位置,'top' / 'bottom'
*/
searchPlace: {
type: String,
default: 'top'
}
},
/**
* Events
* @on-start-edit 返回值 {Object} :同iview中render函数中的params对象 { row, index, column }
* @on-cancel-edit 返回值 {Object} 同上
* @on-save-edit 返回值 {Object} :除上面三个参数外,还有一个value: 修改后的数据
*/
data () {
return {
insideColumns: [],
insideTableData: [],
edittingCellId: '',
edittingText: '',
searchValue: '',
searchKey: ''
}
},
methods: {
suportEdit (item, index) {
item.render = (h, params) => {
return h(TablesEdit, {
props: {
params: params,
value: this.insideTableData[params.index][params.column.key],
edittingCellId: this.edittingCellId,
editable: this.editable
},
on: {
'input': val => {
this.edittingText = val
},
'on-start-edit': (params) => {
this.edittingCellId = `editting-${params.index}-${params.column.key}`
this.$emit('on-start-edit', params)
},
'on-cancel-edit': (params) => {
this.edittingCellId = ''
this.$emit('on-cancel-edit', params)
},
'on-save-edit': (params) => {
this.value[params.row.initRowIndex][params.column.key] = this.edittingText
this.$emit('input', this.value)
this.$emit('on-save-edit', Object.assign(params, { value: this.edittingText }))
this.edittingCellId = ''
}
}
})
}
return item
},
surportHandle (item) {
let options = item.options || []
let insideBtns = []
options.forEach(item => {
if (handleBtns[item]) insideBtns.push(handleBtns[item])
})
let btns = item.button ? [].concat(insideBtns, item.button) : insideBtns
item.render = (h, params) => {
params.tableData = this.value
return h('div', btns.map(item => item(h, params, this)))
}
return item
},
handleColumns (columns) {
this.insideColumns = columns.map((item, index) => {
let res = item
if (res.editable) res = this.suportEdit(res, index)
if (res.key === 'handle') res = this.surportHandle(res)
return res
})
},
setDefaultSearchKey () {
this.searchKey = this.columns[0].key !== 'handle' ? this.columns[0].key : (this.columns.length > 1 ? this.columns[1].key : '')
},
handleClear (e) {
if (e.target.value === '') this.insideTableData = this.value
},
handleSearch () {
this.insideTableData = this.value.filter(item => item[this.searchKey].indexOf(this.searchValue) > -1)
},
handleTableData () {
this.insideTableData = this.value.map((item, index) => {
let res = item
res.initRowIndex = index
return res
})
},
exportCsv (params) {
this.$refs.tablesMain.exportCsv(params)
},
clearCurrentRow () {
this.$refs.talbesMain.clearCurrentRow()
},
onCurrentChange (currentRow, oldCurrentRow) {
this.$emit('on-current-change', currentRow, oldCurrentRow)
},
onSelect (selection, row) {
this.$emit('on-select', selection, row)
},
onSelectCancel (selection, row) {
this.$emit('on-select-cancel', selection, row)
},
onSelectAll (selection) {
this.$emit('on-select-all', selection)
},
onSelectionChange (selection) {
this.$emit('on-selection-change', selection)
},
onSortChange (column, key, order) {
this.$emit('on-sort-change', column, key, order)
},
onFilterChange (row) {
this.$emit('on-filter-change', row)
},
onRowClick (row, index) {
this.$emit('on-row-click', row, index)
},
onRowDblclick (row, index) {
this.$emit('on-row-dblclick', row, index)
},
onExpand (row, status) {
this.$emit('on-expand', row, status)
}
},
watch: {
columns (columns) {
this.handleColumns(columns)
this.setDefaultSearchKey()
},
value (val) {
this.handleTableData()
if (this.searchable) this.handleSearch()
}
},
mounted () {
this.handleColumns(this.columns)
this.setDefaultSearchKey()
this.handleTableData()
}
}
</script>
export { default } from './tree-select.vue'
<template>
<Tree
:data="data"
@on-check-change="handleCheckSelect"
v-on="parent.$listeners"
v-bind="parent.$attrs"
:load-data="loadDataCallback"
show-checkbox
></Tree>
</template>
<script>
import Emitter from 'iview/src/mixins/emitter.js'
const arrayEqual = (arr1, arr2) => {
// 判断数组的长度
if (arr1.length !== arr2.length) {
return false
} else {
// 循环遍历数组的值进行比较
for (let i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) {
return false
}
}
return true
}
}
export default {
name: 'TreeSelectTree',
mixins: [Emitter],
props: {
data: {
type: Array,
default: () => []
},
selectedArray: {
type: Array,
default: () => []
},
loadData: Function
},
data () {
return {
flatDic: {},
checkedArray: []
}
},
inject: ['parent'],
computed: {
expandAll () {
return this.parent.$attrs['expand-all']
}
},
watch: {
data (newData, oldVal) {
this.updateFlagDic(newData)
let selectArray = []
this.selectedArray.forEach(id => {
if (id in this.flatDic) selectArray.push(id)
})
this.$emit('on-check', selectArray.map(id => this.flatDic[id]))
if (this.expandAll) this.checkData(newData, false, true)
},
selectedArray (newVal, oldVal) {
if (arrayEqual(newVal, oldVal)) return
const filtedNewVal = newVal.filter(id => id in this.flatDic)
this.$emit('on-check', filtedNewVal.map(id => this.flatDic[id]))
this.$emit('on-clear')
this.$nextTick(() => {
this.checkData(this.data, true)
})
}
},
methods: {
checkEmit (value, label) {
this.dispatch('iSelect', 'on-select-selected', {
value,
label
})
this.$emit('on-select-selected', {
value,
label
})
},
updateFlagDic (newData) {
let newFlagDic = {}
this.setFlagDic(newData, item => {
newFlagDic[item.id] = item
})
this.flatDic = newFlagDic
},
setFlagDic (data, callback) {
data.forEach(item => {
if (item.children && item.children.length) { this.setFlagDic(item.children, callback) }
callback(item)
})
},
handleCheckSelect (selectArray, selectItem) {
this.$emit('on-check', selectArray)
this.parent.$emit('on-change', selectArray)
},
checkData (data, emit, expandAll) {
data.forEach(item => {
if (this.selectedArray.includes(item.id)) {
this.$set(item, 'checked', true)
this.checkedArray.push(item)
if (emit) this.checkEmit(item.id, item.title)
} else this.$set(item, 'checked', false)
if (item.children && item.children.length) {
if (this.expandAll && expandAll) this.$set(item, 'expand', true)
this.checkData(item.children, emit, expandAll)
}
})
},
loadDataCallback (item, callback) {
this.loadData(item, data => {
return (() => {
callback(data)
this.updateFlagDic(this.data)
})(data)
})
}
},
mounted () {
this.checkData(this.data, false, true)
this.$nextTick(() => {
this.$emit('on-check', this.checkedArray)
})
}
}
</script>
<style></style>
<template>
<Select
ref="select"
class="tree-select"
v-bind="$attrs"
@on-change="handleChange"
multiple
>
<tree-select-tree-item
:selectedArray="value"
:data="data"
@on-clear="handleClear"
:load-data="loadData"
@on-check="handleTreeCheck"
></tree-select-tree-item>
</Select>
</template>
<script>
import Emitter from 'iview/src/mixins/emitter'
import TreeSelectTreeItem from './tree-select-tree.vue'
export default {
name: 'TreeSelect',
mixins: [Emitter],
components: {
TreeSelectTreeItem
},
props: {
value: {
type: Array,
default: () => []
},
data: {
type: Array,
default: () => []
},
loadData: Function
},
data () {
return {
isChangedByTree: true,
isInit: true
}
},
provide () {
return {
parent: this
}
},
methods: {
handleChange (selected) {
if (!this.isChangedByTree) this.$emit('input', selected)
this.isChangedByTree = false
},
handleTreeCheck (selectedArray) {
this.isChangedByTree = true
this.$emit('input', selectedArray.map(item => item.id))
},
handleClear () {
this.$refs.select.reset()
}
}
}
</script>
<style lang="less">
.tree-select {
.ivu-select-dropdown {
padding: 0 6px;
}
}
</style>
export default {
/**
* @description 配置显示在浏览器标签的title
*/
title: 'iView-admin',
/**
* @description token在Cookie中存储的天数,默认1天
*/
cookieExpires: 1,
/**
* @description 是否使用国际化,默认为false
* 如果不使用,则需要在路由中给需要在菜单中展示的路由设置meta: {title: 'xxx'}
* 用来在菜单中显示文字
*/
useI18n: false,
/**
* @description api请求基础路径
*/
baseUrl: {
dev: '/bg',
pro: 'https://applet.mwcx.cn/bg'
},
/**
* @description 默认打开的首页的路由name值,默认为home
*/
homeName: 'home',
/**
* @description 需要加载的插件
*/
plugin: {
'error-store': {
showInHeader: true, // 设为false后不会在顶部显示错误日志徽标
developmentOff: true // 设为true后在开发环境不会收集错误信息,方便开发中排查错误
}
}
}
import draggable from './module/draggable'
import clipboard from './module/clipboard'
const directives = {
draggable,
clipboard
}
export default directives
import directive from './directives'
const importDirective = Vue => {
/**
* 拖拽指令 v-draggable="options"
* options = {
* trigger: /这里传入作为拖拽触发器的CSS选择器/,
* body: /这里传入需要移动容器的CSS选择器/,
* recover: /拖动结束之后是否恢复到原来的位置/
* }
*/
Vue.directive('draggable', directive.draggable)
/**
* clipboard指令 v-draggable="options"
* options = {
* value: /在输入框中使用v-model绑定的值/,
* success: /复制成功后的回调/,
* error: /复制失败后的回调/
* }
*/
Vue.directive('clipboard', directive.clipboard)
}
export default importDirective
import Clipboard from 'clipboard'
export default {
bind: (el, binding) => {
const clipboard = new Clipboard(el, {
text: () => binding.value.value
})
el.__success_callback__ = binding.value.success
el.__error_callback__ = binding.value.error
clipboard.on('success', e => {
const callback = el.__success_callback__
callback && callback(e)
})
clipboard.on('error', e => {
const callback = el.__error_callback__
callback && callback(e)
})
el.__clipboard__ = clipboard
},
update: (el, binding) => {
el.__clipboard__.text = () => binding.value.value
el.__success_callback__ = binding.value.success
el.__error_callback__ = binding.value.error
},
unbind: (el, binding) => {
delete el.__success_callback__
delete el.__error_callback__
el.__clipboard__.destroy()
delete el.__clipboard__
}
}
import { on } from '@/libs/tools'
export default {
inserted: (el, binding, vnode) => {
let triggerDom = document.querySelector(binding.value.trigger)
triggerDom.style.cursor = 'move'
let bodyDom = document.querySelector(binding.value.body)
let pageX = 0
let pageY = 0
let transformX = 0
let transformY = 0
let canMove = false
const handleMousedown = e => {
let transform = /\(.*\)/.exec(bodyDom.style.transform)
if (transform) {
transform = transform[0].slice(1, transform[0].length - 1)
let splitxy = transform.split('px, ')
transformX = parseFloat(splitxy[0])
transformY = parseFloat(splitxy[1].split('px')[0])
}
pageX = e.pageX
pageY = e.pageY
canMove = true
}
const handleMousemove = e => {
let xOffset = e.pageX - pageX + transformX
let yOffset = e.pageY - pageY + transformY
if (canMove) bodyDom.style.transform = `translate(${xOffset}px, ${yOffset}px)`
}
const handleMouseup = e => {
canMove = false
}
on(triggerDom, 'mousedown', handleMousedown)
on(document, 'mousemove', handleMousemove)
on(document, 'mouseup', handleMouseup)
},
update: (el, binding, vnode) => {
if (!binding.value.recover) return
let bodyDom = document.querySelector(binding.value.body)
bodyDom.style.transform = ''
}
}
@import '~iview/src/styles/index.less';
@menu-dark-title: #001529;
@menu-dark-active-bg: #000c17;
@layout-sider-background: #001529;
import HttpRequest from '@/libs/axios'
import config from '@/config'
const baseUrl = process.env.NODE_ENV === 'development' ? config.baseUrl.dev : config.baseUrl.pro
const axios = new HttpRequest(baseUrl)
export default axios
import axios from 'axios'
import store from '@/store'
// import { Spin } from 'iview'
const addErrorLog = errorInfo => {
const { statusText, status, request: { responseURL } } = errorInfo
let info = {
type: 'ajax',
code: status,
mes: statusText,
url: responseURL
}
if (!responseURL.includes('save_error_logger')) store.dispatch('addErrorLog', info)
}
class HttpRequest {
constructor (baseUrl = baseURL) {
this.baseUrl = baseUrl
this.queue = {}
}
getInsideConfig () {
const config = {
baseURL: this.baseUrl,
headers: {
//
}
}
return config
}
destroy (url) {
delete this.queue[url]
if (!Object.keys(this.queue).length) {
// Spin.hide()
}
}
interceptors (instance, url) {
// 请求拦截
instance.interceptors.request.use(config => {
// 添加全局的loading...
if (!Object.keys(this.queue).length) {
// Spin.show() // 不建议开启,因为界面不友好
}
this.queue[url] = true
return config
}, error => {
return Promise.reject(error)
})
// 响应拦截
instance.interceptors.response.use(res => {
this.destroy(url)
const { data, status } = res
return { data, status }
}, error => {
this.destroy(url)
let errorInfo = error.response
if (!errorInfo) {
const { request: { statusText, status }, config } = JSON.parse(JSON.stringify(error))
errorInfo = {
statusText,
status,
request: { responseURL: config.url }
}
}
addErrorLog(errorInfo)
return Promise.reject(error)
})
}
request (options) {
const instance = axios.create()
options = Object.assign(this.getInsideConfig(), options)
this.interceptors(instance, options.url)
return instance(options)
}
}
export default HttpRequest
/* eslint-disable */
import XLSX from 'xlsx';
function auto_width(ws, data){
/*set worksheet max width per col*/
const colWidth = data.map(row => row.map(val => {
/*if null/undefined*/
if (val == null) {
return {'wch': 10};
}
/*if chinese*/
else if (val.toString().charCodeAt(0) > 255) {
return {'wch': val.toString().length * 2};
} else {
return {'wch': val.toString().length};
}
}))
/*start in the first row*/
let result = colWidth[0];
for (let i = 1; i < colWidth.length; i++) {
for (let j = 0; j < colWidth[i].length; j++) {
if (result[j]['wch'] < colWidth[i][j]['wch']) {
result[j]['wch'] = colWidth[i][j]['wch'];
}
}
}
ws['!cols'] = result;
}
function json_to_array(key, jsonData){
return jsonData.map(v => key.map(j => { return v[j] }));
}
// fix data,return string
function fixdata(data) {
let o = ''
let l = 0
const w = 10240
for (; l < data.byteLength / w; ++l) o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w, l * w + w)))
o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w)))
return o
}
// get head from excel file,return array
function get_header_row(sheet) {
const headers = []
const range = XLSX.utils.decode_range(sheet['!ref'])
let C
const R = range.s.r /* start in the first row */
for (C = range.s.c; C <= range.e.c; ++C) { /* walk every column in the range */
var cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })] /* find the cell in the first row */
var hdr = 'UNKNOWN ' + C // <-- replace with your desired default
if (cell && cell.t) hdr = XLSX.utils.format_cell(cell)
headers.push(hdr)
}
return headers
}
export const export_table_to_excel= (id, filename) => {
const table = document.getElementById(id);
const wb = XLSX.utils.table_to_book(table);
XLSX.writeFile(wb, filename);
/* the second way */
// const table = document.getElementById(id);
// const wb = XLSX.utils.book_new();
// const ws = XLSX.utils.table_to_sheet(table);
// XLSX.utils.book_append_sheet(wb, ws, filename);
// XLSX.writeFile(wb, filename);
}
export const export_json_to_excel = ({data, key, title, filename, autoWidth}) => {
const wb = XLSX.utils.book_new();
data.unshift(title);
const ws = XLSX.utils.json_to_sheet(data, {header: key, skipHeader: true});
if(autoWidth){
const arr = json_to_array(key, data);
auto_width(ws, arr);
}
XLSX.utils.book_append_sheet(wb, ws, filename);
XLSX.writeFile(wb, filename + '.xlsx');
}
export const export_array_to_excel = ({key, data, title, filename, autoWidth}) => {
const wb = XLSX.utils.book_new();
const arr = json_to_array(key, data);
arr.unshift(title);
const ws = XLSX.utils.aoa_to_sheet(arr);
if(autoWidth){
auto_width(ws, arr);
}
XLSX.utils.book_append_sheet(wb, ws, filename);
XLSX.writeFile(wb, filename + '.xlsx');
}
export const read = (data, type) => {
/* if type == 'base64' must fix data first */
// const fixedData = fixdata(data)
// const workbook = XLSX.read(btoa(fixedData), { type: 'base64' })
const workbook = XLSX.read(data, { type: type });
const firstSheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[firstSheetName];
const header = get_header_row(worksheet);
const results = XLSX.utils.sheet_to_json(worksheet);
return {header, results};
}
export default {
export_table_to_excel,
export_array_to_excel,
export_json_to_excel,
read
}
import axios from 'axios'
import Qs from 'qs'
import store from '@/store'
import router from '@/router'
import config from '@/config'
import { Message } from 'iview'
import { getToken } from '@/libs/util'
const baseUrl = process.env.NODE_ENV === 'development' ? config.baseUrl.dev : config.baseUrl.pro
const _baseConfig = {
baseURL: baseUrl,
headers: {},
params: {},
paramsSerializer: function (params) {
return Qs.stringify(params)
},
validateStatus: status => {
// 处理响应状态码
switch (status) {
case 401:
router.push({ name: 'login' })
return false
case 500:
// console.log(500);
return false
}
return status >= 200 && status < 300 // 默认的, 为false时进入'响应拦截器'catch
},
transformResponse: [
function (data) {
// 对 data 进行任意转换处理
// console.log(data);
return data
}
],
timeout: 3 * 60 * 1000,
responseType: 'json'
}
const _http = axios.create(_baseConfig)
// 请求拦截器
_http.interceptors.request.use(
function (config) {
config.setLoading && store.commit('setIsLoading', true)
// 可以在这里修改请求头
config.headers.Authorization = getToken()
return config
},
function (error) {
// 对请求错误做些什么
return Promise.reject(error)
}
)
// 响应拦截器
_http.interceptors.response.use(
function (response) {
console.log(response)
// 在这里统一处理自定义状态码(如果有)
// console.log(response.headers.pragma);
store.commit('setIsLoading', false)
let result = response.data
if (result.code === -1) {
Message.error(result.message)
}
if (typeof result === 'string') {
result = JSON.parse(result)
}
return result
},
function (error) {
if (!navigator.onLine) {
console.log('无法连接网络')
}
store.commit('setIsLoading', false)
return Promise.reject(error)
}
)
const _HttpService = {
post: (url, data, config) => {
const _selfConfig = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
},
setLoading: true
}
config = Object.assign({}, _selfConfig, config)
return _http.post(url, Qs.stringify(data), config)
},
postObj: (url, data, config) => {
const _selfConfig = {
headers: {
'Content-Type': 'application/json'
},
setLoading: true
}
config = Object.assign({}, _selfConfig, config)
return _http.post(url, JSON.stringify(data), config)
},
postParams: (url, data, config) => {
const _selfConfig = {
params: data,
setLoading: true
}
config = Object.assign({}, _selfConfig, config)
return _http.post(url, null, config)
},
get: (url, data, config) => {
const _selfConfig = {
params: data,
setLoading: true
}
config = Object.assign({}, _selfConfig, config)
return _http.get(url, config)
},
delete: (url, data, config) => {
const _selfConfig = {
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify(data),
setLoading: true
}
config = Object.assign({}, _selfConfig, config)
return _http.delete(url, config)
},
patch: (url, data, config) => {
const _selfConfig = {
headers: {
'Content-Type': 'application/json'
},
setLoading: true
}
config = Object.assign({}, _selfConfig, config)
return _http.patch(url, JSON.stringify(data), config)
}
}
export const $http = _HttpService
export const $BASEURL = baseUrl
export default {
name: 'RenderDom',
functional: true,
props: {
render: Function
},
render: (h, ctx) => {
return ctx.props.render(h)
}
}
export const forEach = (arr, fn) => {
if (!arr.length || !fn) return
let i = -1
let len = arr.length
while (++i < len) {
let item = arr[i]
fn(item, i, arr)
}
}
/**
* @param {Array} arr1
* @param {Array} arr2
* @description 得到两个数组的交集, 两个数组的元素为数值或字符串
*/
export const getIntersection = (arr1, arr2) => {
let len = Math.min(arr1.length, arr2.length)
let i = -1
let res = []
while (++i < len) {
const item = arr2[i]
if (arr1.indexOf(item) > -1) res.push(item)
}
return res
}
/**
* @param {Array} arr1
* @param {Array} arr2
* @description 得到两个数组的并集, 两个数组的元素为数值或字符串
*/
export const getUnion = (arr1, arr2) => {
return Array.from(new Set([...arr1, ...arr2]))
}
/**
* @param {Array} target 目标数组
* @param {Array} arr 需要查询的数组
* @description 判断要查询的数组是否至少有一个元素包含在目标数组中
*/
export const hasOneOf = (targetarr, arr) => {
return targetarr.some(_ => arr.indexOf(_) > -1)
}
/**
* @param {String|Number} value 要验证的字符串或数值
* @param {*} validList 用来验证的列表
*/
export function oneOf (value, validList) {
for (let i = 0; i < validList.length; i++) {
if (value === validList[i]) {
return true
}
}
return false
}
/**
* @param {Number} timeStamp 判断时间戳格式是否是毫秒
* @returns {Boolean}
*/
const isMillisecond = timeStamp => {
const timeStr = String(timeStamp)
return timeStr.length > 10
}
/**
* @param {Number} timeStamp 传入的时间戳
* @param {Number} currentTime 当前时间时间戳
* @returns {Boolean} 传入的时间戳是否早于当前时间戳
*/
const isEarly = (timeStamp, currentTime) => {
return timeStamp < currentTime
}
/**
* @param {Number} num 数值
* @returns {String} 处理后的字符串
* @description 如果传入的数值小于10,即位数只有1位,则在前面补充0
*/
const getHandledValue = num => {
return num < 10 ? '0' + num : num
}
/**
* @param {Number} timeStamp 传入的时间戳
* @param {Number} startType 要返回的时间字符串的格式类型,传入'year'则返回年开头的完整时间
*/
const getDate = (timeStamp, startType) => {
const d = new Date(timeStamp * 1000)
const year = d.getFullYear()
const month = getHandledValue(d.getMonth() + 1)
const date = getHandledValue(d.getDate())
const hours = getHandledValue(d.getHours())
const minutes = getHandledValue(d.getMinutes())
const second = getHandledValue(d.getSeconds())
let resStr = ''
if (startType === 'year') resStr = year + '-' + month + '-' + date + ' ' + hours + ':' + minutes + ':' + second
else resStr = month + '-' + date + ' ' + hours + ':' + minutes
return resStr
}
/**
* @param {String|Number} timeStamp 时间戳
* @returns {String} 相对时间字符串
*/
export const getRelativeTime = timeStamp => {
// 判断当前传入的时间戳是秒格式还是毫秒
const IS_MILLISECOND = isMillisecond(timeStamp)
// 如果是毫秒格式则转为秒格式
if (IS_MILLISECOND) Math.floor(timeStamp /= 1000)
// 传入的时间戳可以是数值或字符串类型,这里统一转为数值类型
timeStamp = Number(timeStamp)
// 获取当前时间时间戳
const currentTime = Math.floor(Date.parse(new Date()) / 1000)
// 判断传入时间戳是否早于当前时间戳
const IS_EARLY = isEarly(timeStamp, currentTime)
// 获取两个时间戳差值
let diff = currentTime - timeStamp
// 如果IS_EARLY为false则差值取反
if (!IS_EARLY) diff = -diff
let resStr = ''
const dirStr = IS_EARLY ? '前' : '后'
// 少于等于59秒
if (diff <= 59) resStr = diff + '秒' + dirStr
// 多于59秒,少于等于59分钟59秒
else if (diff > 59 && diff <= 3599) resStr = Math.floor(diff / 60) + '分钟' + dirStr
// 多于59分钟59秒,少于等于23小时59分钟59秒
else if (diff > 3599 && diff <= 86399) resStr = Math.floor(diff / 3600) + '小时' + dirStr
// 多于23小时59分钟59秒,少于等于29天59分钟59秒
else if (diff > 86399 && diff <= 2623859) resStr = Math.floor(diff / 86400) + '天' + dirStr
// 多于29天59分钟59秒,少于364天23小时59分钟59秒,且传入的时间戳早于当前
else if (diff > 2623859 && diff <= 31567859 && IS_EARLY) resStr = getDate(timeStamp)
else resStr = getDate(timeStamp, 'year')
return resStr
}
/**
* @returns {String} 当前浏览器名称
*/
export const getExplorer = () => {
const ua = window.navigator.userAgent
const isExplorer = (exp) => {
return ua.indexOf(exp) > -1
}
if (isExplorer('MSIE')) return 'IE'
else if (isExplorer('Firefox')) return 'Firefox'
else if (isExplorer('Chrome')) return 'Chrome'
else if (isExplorer('Opera')) return 'Opera'
else if (isExplorer('Safari')) return 'Safari'
}
/**
* @description 绑定事件 on(element, event, handler)
*/
export const on = (function () {
if (document.addEventListener) {
return function (element, event, handler) {
if (element && event && handler) {
element.addEventListener(event, handler, false)
}
}
} else {
return function (element, event, handler) {
if (element && event && handler) {
element.attachEvent('on' + event, handler)
}
}
}
})()
/**
* @description 解绑事件 off(element, event, handler)
*/
export const off = (function () {
if (document.removeEventListener) {
return function (element, event, handler) {
if (element && event) {
element.removeEventListener(event, handler, false)
}
}
} else {
return function (element, event, handler) {
if (element && event) {
element.detachEvent('on' + event, handler)
}
}
}
})()
/**
* 判断一个对象是否存在key,如果传入第二个参数key,则是判断这个obj对象是否存在key这个属性
* 如果没有传入key这个参数,则判断obj对象是否有键值对
*/
export const hasKey = (obj, key) => {
if (key) return key in obj
else {
let keysArr = Object.keys(obj)
return keysArr.length
}
}
/**
* @param {*} obj1 对象
* @param {*} obj2 对象
* @description 判断两个对象是否相等,这两个对象的值只能是数字或字符串
*/
export const objEqual = (obj1, obj2) => {
const keysArr1 = Object.keys(obj1)
const keysArr2 = Object.keys(obj2)
if (keysArr1.length !== keysArr2.length) return false
else if (keysArr1.length === 0 && keysArr2.length === 0) return true
/* eslint-disable-next-line */
else return !keysArr1.some(key => obj1[key] != obj2[key])
}
import Cookies from 'js-cookie'
// cookie保存的天数
import config from '@/config'
import { forEach, hasOneOf, objEqual } from '@/libs/tools'
const { title, cookieExpires, useI18n } = config
export const TOKEN_KEY = 'token'
export const setToken = (token) => {
Cookies.set(TOKEN_KEY, token, { expires: cookieExpires || 1 })
}
export const getToken = () => {
const token = Cookies.get(TOKEN_KEY)
if (token) return token
else return false
}
export const hasChild = (item) => {
return item.children && item.children.length !== 0
}
const showThisMenuEle = (item, access) => {
if (item.meta && item.meta.access && item.meta.access.length) {
if (hasOneOf(item.meta.access, access)) return true
else return false
} else return true
}
/**
* @param {Array} list 通过路由列表得到菜单列表
* @returns {Array}
*/
export const getMenuByRouter = (list, access) => {
let res = []
forEach(list, item => {
if (!item.meta || (item.meta && !item.meta.hideInMenu)) {
let obj = {
icon: (item.meta && item.meta.icon) || '',
name: item.name,
meta: item.meta
}
if ((hasChild(item) || (item.meta && item.meta.showAlways)) && showThisMenuEle(item, access)) {
obj.children = getMenuByRouter(item.children, access)
}
if (item.meta && item.meta.href) obj.href = item.meta.href
if (showThisMenuEle(item, access)) res.push(obj)
}
})
return res
}
/**
* @param {Array} routeMetched 当前路由metched
* @returns {Array}
*/
export const getBreadCrumbList = (route, homeRoute) => {
let homeItem = { ...homeRoute, icon: homeRoute.meta.icon }
let routeMetched = route.matched
if (routeMetched.some(item => item.name === homeRoute.name)) return [homeItem]
let res = routeMetched.filter(item => {
return item.meta === undefined || !item.meta.hideInBread
}).map(item => {
let meta = { ...item.meta }
if (meta.title && typeof meta.title === 'function') {
meta.__titleIsFunction__ = true
meta.title = meta.title(route)
}
let obj = {
icon: (item.meta && item.meta.icon) || '',
name: item.name,
meta: meta
}
return obj
})
res = res.filter(item => {
return item.meta.showbr
})
console.log([{ ...homeItem, to: homeRoute.path }, ...res])
return [{ ...homeItem, to: homeRoute.path }, ...res]
}
export const getRouteTitleHandled = (route) => {
let router = { ...route }
let meta = { ...route.meta }
let title = ''
if (meta.title) {
if (typeof meta.title === 'function') {
meta.__titleIsFunction__ = true
title = meta.title(router)
} else title = meta.title
}
meta.title = title
router.meta = meta
return router
}
export const showTitle = (item, vm) => {
let { title, __titleIsFunction__ } = item.meta
if (!title) return
if (useI18n) {
if (title.includes('{{') && title.includes('}}') && useI18n) title = title.replace(/({{[\s\S]+?}})/, (m, str) => str.replace(/{{([\s\S]*)}}/, (m, _) => vm.$t(_.trim())))
else if (__titleIsFunction__) title = item.meta.title
else title = vm.$t(item.name)
} else title = (item.meta && item.meta.title) || item.name
return title
}
/**
* @description 本地存储和获取标签导航列表
*/
export const setTagNavListInLocalstorage = list => {
localStorage.tagNaveList = JSON.stringify(list)
}
/**
* @returns {Array} 其中的每个元素只包含路由原信息中的name, path, meta三项
*/
export const getTagNavListFromLocalstorage = () => {
const list = localStorage.tagNaveList
return list ? JSON.parse(list) : []
}
/**
* @param {Array} routers 路由列表数组
* @description 用于找到路由列表中name为home的对象
*/
export const getHomeRoute = (routers, homeName = 'home') => {
let i = -1
let len = routers.length
let homeRoute = {}
while (++i < len) {
let item = routers[i]
if (item.children && item.children.length) {
let res = getHomeRoute(item.children, homeName)
if (res.name) return res
} else {
if (item.name === homeName) homeRoute = item
}
}
return homeRoute
}
/**
* @param {*} list 现有标签导航列表
* @param {*} newRoute 新添加的路由原信息对象
* @description 如果该newRoute已经存在则不再添加
*/
export const getNewTagList = (list, newRoute) => {
const { name, path, meta } = newRoute
let newList = [...list]
if (newList.findIndex(item => item.name === name) >= 0) return newList
else newList.push({ name, path, meta })
return newList
}
/**
* @param {*} access 用户权限数组,如 ['super_admin', 'admin']
* @param {*} route 路由列表
*/
const hasAccess = (access, route) => {
if (route.meta && route.meta.access) return hasOneOf(access, route.meta.access)
else return true
}
/**
* 权鉴
* @param {*} name 即将跳转的路由name
* @param {*} access 用户权限数组
* @param {*} routes 路由列表
* @description 用户是否可跳转到该页
*/
export const canTurnTo = (name, access, routes) => {
const routePermissionJudge = (list) => {
return list.some(item => {
if (item.children && item.children.length) {
return routePermissionJudge(item.children)
} else if (item.name === name) {
return hasAccess(access, item)
}
})
}
return routePermissionJudge(routes)
}
/**
* @param {String} url
* @description 从URL中解析参数
*/
export const getParams = url => {
const keyValueArr = url.split('?')[1].split('&')
let paramObj = {}
keyValueArr.forEach(item => {
const keyValue = item.split('=')
paramObj[keyValue[0]] = keyValue[1]
})
return paramObj
}
/**
* @param {Array} list 标签列表
* @param {String} name 当前关闭的标签的name
*/
export const getNextRoute = (list, route) => {
let res = {}
if (list.length === 2) {
res = getHomeRoute(list)
} else {
const index = list.findIndex(item => routeEqual(item, route))
if (index === list.length - 1) res = list[list.length - 2]
else res = list[index + 1]
}
return res
}
/**
* @param {Number} times 回调函数需要执行的次数
* @param {Function} callback 回调函数
*/
export const doCustomTimes = (times, callback) => {
let i = -1
while (++i < times) {
callback(i)
}
}
/**
* @param {Object} file 从上传组件得到的文件对象
* @returns {Promise} resolve参数是解析后的二维数组
* @description 从Csv文件中解析出表格,解析成二维数组
*/
export const getArrayFromFile = (file) => {
let nameSplit = file.name.split('.')
let format = nameSplit[nameSplit.length - 1]
return new Promise((resolve, reject) => {
let reader = new FileReader()
reader.readAsText(file) // 以文本格式读取
let arr = []
reader.onload = function (evt) {
let data = evt.target.result // 读到的数据
let pasteData = data.trim()
arr = pasteData.split((/[\n\u0085\u2028\u2029]|\r\n?/g)).map(row => {
return row.split('\t')
}).map(item => {
return item[0].split(',')
})
if (format === 'csv') resolve(arr)
else reject(new Error('[Format Error]:你上传的不是Csv文件'))
}
})
}
/**
* @param {Array} array 表格数据二维数组
* @returns {Object} { columns, tableData }
* @description 从二维数组中获取表头和表格数据,将第一行作为表头,用于在iView的表格中展示数据
*/
export const getTableDataFromArray = (array) => {
let columns = []
let tableData = []
if (array.length > 1) {
let titles = array.shift()
columns = titles.map(item => {
return {
title: item,
key: item
}
})
tableData = array.map(item => {
let res = {}
item.forEach((col, i) => {
res[titles[i]] = col
})
return res
})
}
return {
columns,
tableData
}
}
export const findNodeUpper = (ele, tag) => {
if (ele.parentNode) {
if (ele.parentNode.tagName === tag.toUpperCase()) {
return ele.parentNode
} else {
return findNodeUpper(ele.parentNode, tag)
}
}
}
export const findNodeUpperByClasses = (ele, classes) => {
let parentNode = ele.parentNode
if (parentNode) {
let classList = parentNode.classList
if (classList && classes.every(className => classList.contains(className))) {
return parentNode
} else {
return findNodeUpperByClasses(parentNode, classes)
}
}
}
export const findNodeDownward = (ele, tag) => {
const tagName = tag.toUpperCase()
if (ele.childNodes.length) {
let i = -1
let len = ele.childNodes.length
while (++i < len) {
let child = ele.childNodes[i]
if (child.tagName === tagName) return child
else return findNodeDownward(child, tag)
}
}
}
export const showByAccess = (access, canViewAccess) => {
return hasOneOf(canViewAccess, access)
}
/**
* @description 根据name/params/query判断两个路由对象是否相等
* @param {*} route1 路由对象
* @param {*} route2 路由对象
*/
export const routeEqual = (route1, route2) => {
const params1 = route1.params || {}
const params2 = route2.params || {}
const query1 = route1.query || {}
const query2 = route2.query || {}
return (route1.name === route2.name) && objEqual(params1, params2) && objEqual(query1, query2)
}
/**
* 判断打开的标签列表里是否已存在这个新添加的路由对象
*/
export const routeHasExist = (tagNavList, routeItem) => {
let len = tagNavList.length
let res = false
doCustomTimes(len, (index) => {
if (routeEqual(tagNavList[index], routeItem)) res = true
})
return res
}
export const localSave = (key, value) => {
localStorage.setItem(key, value)
}
export const localRead = (key) => {
return localStorage.getItem(key) || ''
}
// scrollTop animation
export const scrollTop = (el, from = 0, to, duration = 500, endCallback) => {
if (!window.requestAnimationFrame) {
window.requestAnimationFrame = (
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (callback) {
return window.setTimeout(callback, 1000 / 60)
}
)
}
const difference = Math.abs(from - to)
const step = Math.ceil(difference / duration * 50)
const scroll = (start, end, step) => {
if (start === end) {
endCallback && endCallback()
return
}
let d = (start + step > end) ? end : start + step
if (start > end) {
d = (start - step < end) ? end : start - step
}
if (el === window) {
window.scrollTo(d, d)
} else {
el.scrollTop = d
}
window.requestAnimationFrame(() => scroll(d, end, step))
}
scroll(from, to, step)
}
/**
* @description 根据当前跳转的路由设置显示在浏览器标签的title
* @param {Object} routeItem 路由对象
* @param {Object} vm Vue实例
*/
export const setTitle = (routeItem, vm) => {
const handledRoute = getRouteTitleHandled(routeItem)
const pageTitle = showTitle(handledRoute, vm)
const resTitle = pageTitle ? `${title} - ${pageTitle}` : title
window.document.title = resTitle
}
// 时间截格式化
export const formatDate = time => {
if (time === null) {
return ''
}
time = new Date(time * 1000)
let year = time.getFullYear()
let month = time.getMonth() + 1
let date = time.getDate()
let hour = time.getHours()
let minute = time.getMinutes()
let second = time.getSeconds()
// month = month < 10 ? "0" + month : month;
// date = date < 10 ? "0" + date : date;
hour = hour < 10 ? '0' + hour : hour
minute = minute < 10 ? '0' + minute : minute
second = second < 10 ? '0' + second : second
return (
year +
'年' +
month +
'月' +
date +
'日' +
hour +
':' +
minute +
':' +
second
)
}
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import { localRead } from '@/libs/util'
import customZhCn from './lang/zh-CN'
import customZhTw from './lang/zh-TW'
import customEnUs from './lang/en-US'
import zhCnLocale from 'iview/src/locale/lang/zh-CN'
import enUsLocale from 'iview/src/locale/lang/en-US'
import zhTwLocale from 'iview/src/locale/lang/zh-TW'
Vue.use(VueI18n)
// 自动根据浏览器系统语言设置语言
const navLang = navigator.language
const localLang = (navLang === 'zh-CN' || navLang === 'en-US') ? navLang : false
let lang = localLang || localRead('local') || 'zh-CN'
Vue.config.lang = lang
// vue-i18n 6.x+写法
Vue.locale = () => {}
const messages = {
'zh-CN': Object.assign(zhCnLocale, customZhCn),
'zh-TW': Object.assign(zhTwLocale, customZhTw),
'en-US': Object.assign(enUsLocale, customEnUs)
}
const i18n = new VueI18n({
locale: lang,
messages
})
export default i18n
// vue-i18n 5.x写法
// Vue.locale('zh-CN', Object.assign(zhCnLocale, customZhCn))
// Vue.locale('en-US', Object.assign(zhTwLocale, customZhTw))
// Vue.locale('zh-TW', Object.assign(enUsLocale, customEnUs))
export default {
home: 'Home',
login: 'Login',
components: 'Components',
count_to_page: 'Count-to',
tables_page: 'Table',
split_pane_page: 'Split-pane',
markdown_page: 'Markdown-editor',
editor_page: 'Rich-Text-Editor',
icons_page: 'Custom-icon',
img_cropper_page: 'Image-editor',
update: 'Update',
doc: 'Document',
join_page: 'QQ Group',
update_table_page: 'Update .CSV',
update_paste_page: 'Paste Table Data',
multilevel: 'multilevel',
directive_page: 'Directive',
level_1: 'Level-1',
level_2: 'Level-2',
level_2_1: 'Level-2-1',
level_2_3: 'Level-2-3',
level_2_2: 'Level-2-2',
level_2_2_1: 'Level-2-2-1',
level_2_2_2: 'Level-2-2-2',
excel: 'Excel',
'upload-excel': 'Upload Excel',
'export-excel': 'Export Excel',
tools_methods_page: 'Tools Methods',
drag_list_page: 'Drag-list',
i18n_page: 'Internationalization',
modalTitle: 'Modal Title',
content: 'This is the modal box content.',
buttonText: 'Show Modal',
'i18n-tip': 'Note: Only this page is multi-language, other pages do not add language content to the multi-language package.',
error_store_page: 'Error Collection',
error_logger_page: 'Error Logger',
query: 'Query',
params: 'Params',
cropper_page: 'Cropper',
message_page: 'Message Center',
tree_table_page: 'Tree Table',
org_tree_page: 'Org Tree',
drag_drawer_page: 'Draggable Drawer',
tree_select_page: 'Tree Selector'
}
export default {
home: '首页',
login: '登录',
components: '组件',
count_to_page: '数字渐变',
tables_page: '多功能表格',
split_pane_page: '分割窗口',
markdown_page: 'Markdown编辑器',
editor_page: '富文本编辑器',
icons_page: '自定义图标',
img_cropper_page: '图片编辑器',
update: '上传数据',
join_page: 'QQ群',
doc: '文档',
update_table_page: '上传CSV文件',
update_paste_page: '粘贴表格数据',
multilevel: '多级菜单',
directive_page: '指令',
level_1: 'Level-1',
level_2: 'Level-2',
level_2_1: 'Level-2-1',
level_2_3: 'Level-2-3',
level_2_2: 'Level-2-2',
level_2_2_1: 'Level-2-2-1',
level_2_2_2: 'Level-2-2-2',
excel: 'Excel',
'upload-excel': '上传excel',
'export-excel': '导出excel',
tools_methods_page: '工具函数',
drag_list_page: '拖拽列表',
i18n_page: '多语言',
modalTitle: '模态框题目',
content: '这是模态框内容',
buttonText: '显示模态框',
'i18n-tip': '注:仅此页做了多语言,其他页面没有在多语言包中添加语言内容',
error_store_page: '错误收集',
error_logger_page: '错误日志',
query: '带参路由',
params: '动态路由',
cropper_page: '图片裁剪',
message_page: '消息中心',
tree_table_page: '树状表格',
org_tree_page: '组织结构树',
drag_drawer_page: '可拖动抽屉',
tree_select_page: '树状下拉选择器'
}
export default {
home: '首頁',
login: '登錄',
components: '组件',
count_to_page: '数字渐变',
tables_page: '多功能表格',
split_pane_page: '分割窗口',
markdown_page: 'Markdown編輯器',
editor_page: '富文本編輯器',
icons_page: '自定義圖標',
img_cropper_page: '圖片編輯器',
update: '上傳數據',
join_page: 'QQ群',
doc: '文檔',
update_table_page: '上傳CSV文件',
update_paste_page: '粘貼表格數據',
multilevel: '多级菜单',
directive_page: '指令',
level_1: 'Level-1',
level_2: 'Level-2',
level_2_1: 'Level-2-1',
level_2_3: 'Level-2-3',
level_2_2: 'Level-2-2',
level_2_2_1: 'Level-2-2-1',
level_2_2_2: 'Level-2-2-2',
excel: 'Excel',
'upload-excel': '上傳excel',
'export-excel': '導出excel',
tools_methods_page: '工具函數',
drag_list_page: '拖拽列表',
i18n_page: '多語言',
modalTitle: '模態框題目',
content: '這是模態框內容',
buttonText: '顯示模態框',
'i18n-tip': '注:僅此頁做了多語言,其他頁面沒有在多語言包中添加語言內容',
error_store_page: '錯誤收集',
error_logger_page: '錯誤日誌',
query: '帶參路由',
params: '動態路由',
cropper_page: '圖片裁剪',
message_page: '消息中心',
tree_table_page: '樹狀表格',
org_tree_page: '組織結構樹',
drag_drawer_page: '可拖動抽屜',
tree_select_page: '樹狀下拉選擇器'
}
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
import iView from 'iview'
import i18n from '@/locale'
import config from '@/config'
// import importDirective from '@/directive'
// import { directive as clickOutside } from 'v-click-outside-x'
import installPlugin from '@/plugin'
import vcolorpicker from 'vcolorpicker'
import VueClipboard from 'vue-clipboard2'
import './index.less'
import '@/assets/icons/iconfont.css'
import TreeTable from 'tree-table-vue'
import VOrgTree from 'v-org-tree'
import 'v-org-tree/dist/v-org-tree.css'
// 实际打包时应该不引入mock
/* eslint-disable */
// if (process.env.NODE_ENV !== 'production') require('@/mock')
Vue.use(vcolorpicker)
Vue.use(iView, {
i18n: (key, value) => i18n.t(key, value)
})
Vue.use(TreeTable)
Vue.use(VOrgTree)
Vue.use(VueClipboard)
/**
* @description 注册admin内置插件
*/
installPlugin(Vue)
/**
* @description 生产环境关掉提示
*/
Vue.config.productionTip = false
/**
* @description 全局注册应用配置
*/
Vue.prototype.$config = config
/**
* 注册指令
*/
// importDirective(Vue)
// Vue.directive('clickOutside', clickOutside)
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
i18n,
store,
render: h => h(App)
})
import Mock from 'mockjs'
import { doCustomTimes } from '@/libs/util'
import orgData from './data/org-data'
import { treeData } from './data/tree-select'
const Random = Mock.Random
export const getTableData = req => {
let tableData = []
doCustomTimes(5, () => {
tableData.push(Mock.mock({
name: '@name',
email: '@email',
createTime: '@date'
}))
})
return tableData
}
export const getDragList = req => {
let dragList = []
doCustomTimes(5, () => {
dragList.push(Mock.mock({
name: Random.csentence(10, 13),
id: Random.increment(10)
}))
})
return dragList
}
export const uploadImage = req => {
return Promise.resolve()
}
export const getOrgData = req => {
return orgData
}
export const getTreeSelectData = req => {
return treeData
}
export default {
id: 0,
label: 'XXX科技有限公司',
children: [
{
id: 2,
label: '产品研发部',
children: [
{
id: 5,
label: '研发-前端'
}, {
id: 6,
label: '研发-后端'
}, {
id: 9,
label: 'UI设计'
}, {
id: 10,
label: '产品经理'
}
]
},
{
id: 3,
label: '销售部',
children: [
{
id: 7,
label: '销售一部'
}, {
id: 8,
label: '销售二部'
}
]
},
{
id: 4,
label: '财务部'
}, {
id: 11,
label: 'HR人事'
}
]
}
export const treeData = [
{
id: 1,
title: '1',
children: [
{
id: 11,
title: '1-1',
loading: false,
children: [
// {
// id: 111,
// title: '1-1-1'
// },
// {
// id: 112,
// title: '1-1-2'
// },
// {
// id: 113,
// title: '1-1-3'
// },
// {
// id: 114,
// title: '1-1-4'
// }
]
},
{
id: 12,
title: '1-2',
children: [
{
id: 121,
title: '1-2-1'
}
]
}
]
}
]
export const newTreeData = [
{
id: 'a',
title: 'a',
children: [
{
id: 'a1',
title: 'a-1',
children: [
{
id: 112,
title: '1-1-2'
},
{
id: 'a12',
title: 'a-1-2'
},
{
id: 'a13',
title: 'a-1-3'
},
{
id: 'a14',
title: 'a-1-4'
}
]
},
{
id: 'a2',
title: 'a-2',
children: [
{
id: 'a21',
title: 'b-2-1'
}
]
}
]
}
]
import Mock from 'mockjs'
import { login, logout, getUserInfo } from './login'
import { getTableData, getDragList, uploadImage, getOrgData, getTreeSelectData } from './data'
import { getMessageInit, getContentByMsgId, hasRead, removeReaded, restoreTrash, messageCount } from './user'
// 配置Ajax请求延时,可用来测试网络延迟大时项目中一些效果
Mock.setup({
timeout: 1000
})
// 登录相关和获取用户信息
Mock.mock(/\/login/, login)
Mock.mock(/\/get_info/, getUserInfo)
Mock.mock(/\/logout/, logout)
Mock.mock(/\/get_table_data/, getTableData)
Mock.mock(/\/get_drag_list/, getDragList)
Mock.mock(/\/save_error_logger/, 'success')
Mock.mock(/\/image\/upload/, uploadImage)
Mock.mock(/\/message\/init/, getMessageInit)
Mock.mock(/\/message\/content/, getContentByMsgId)
Mock.mock(/\/message\/has_read/, hasRead)
Mock.mock(/\/message\/remove_readed/, removeReaded)
Mock.mock(/\/message\/restore/, restoreTrash)
Mock.mock(/\/message\/count/, messageCount)
Mock.mock(/\/get_org_data/, getOrgData)
Mock.mock(/\/get_tree_select_data/, getTreeSelectData)
export default Mock
import { getParams } from '@/libs/util'
const USER_MAP = {
super_admin: {
name: 'super_admin',
user_id: '1',
access: ['super_admin', 'admin'],
token: 'super_admin',
avatar: 'https://file.iviewui.com/dist/a0e88e83800f138b94d2414621bd9704.png'
},
admin: {
name: 'admin',
user_id: '2',
access: ['admin'],
token: 'admin',
avatar: 'https://avatars0.githubusercontent.com/u/20942571?s=460&v=4'
}
}
export const login = req => {
req = JSON.parse(req.body)
return { token: USER_MAP[req.userName].token }
}
export const getUserInfo = req => {
const params = getParams(req.url)
return USER_MAP[params.token]
}
export const logout = req => {
return null
}
import Mock from 'mockjs'
import { doCustomTimes } from '@/libs/util'
const Random = Mock.Random
export const getMessageInit = () => {
let unreadList = []
doCustomTimes(3, () => {
unreadList.push(Mock.mock({
title: Random.cword(10, 15),
create_time: '@date',
msg_id: Random.increment(100)
}))
})
let readedList = []
doCustomTimes(4, () => {
readedList.push(Mock.mock({
title: Random.cword(10, 15),
create_time: '@date',
msg_id: Random.increment(100)
}))
})
let trashList = []
doCustomTimes(2, () => {
trashList.push(Mock.mock({
title: Random.cword(10, 15),
create_time: '@date',
msg_id: Random.increment(100)
}))
})
return {
unread: unreadList,
readed: readedList,
trash: trashList
}
}
export const getContentByMsgId = () => {
return `<divcourier new',="" monospace;font-weight:="" normal;font-size:="" 12px;line-height:="" 18px;white-space:="" pre;"=""><div>&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="font-size: medium;">这是消息内容,这个内容是使用<span style="color: rgb(255, 255, 255); background-color: rgb(28, 72, 127);">富文本编辑器</span>编辑的,所以你可以看到一些<span style="text-decoration-line: underline; font-style: italic; color: rgb(194, 79, 74);">格式</span></span></div><ol><li>你可以查看Mock返回的数据格式,和api请求的接口,来确定你的后端接口的开发</li><li>使用你的真实接口后,前端页面基本不需要修改即可满足基本需求</li><li>快来试试吧</li></ol><p>${Random.csentence(100, 200)}</p></divcourier>`
}
export const hasRead = () => {
return true
}
export const removeReaded = () => {
return true
}
export const restoreTrash = () => {
return true
}
export const messageCount = () => {
return 3
}
import store from '@/store'
export default {
install (Vue, options) {
if (options.developmentOff && process.env.NODE_ENV === 'development') return
Vue.config.errorHandler = (error, vm, mes) => {
let info = {
type: 'script',
code: 0,
mes: error.message,
url: window.location.href
}
Vue.nextTick(() => {
store.dispatch('addErrorLog', info)
})
}
}
}
import config from '@/config'
const { plugin } = config
export default (Vue) => {
for (let name in plugin) {
const value = plugin[name]
Vue.use(require(`./${name}`).default, typeof value === 'object' ? value : undefined)
}
}
import { Modal } from 'iview'
const beforeClose = {
before_close_normal: (resolve) => {
Modal.confirm({
title: '确定要关闭这一页吗',
onOk: () => {
resolve(true)
},
onCancel: () => {
resolve(false)
}
})
}
}
export default beforeClose
import Vue from 'vue'
import Router from 'vue-router'
import routes from './routers'
// import store from '@/store'
import iView from 'iview'
// import { setToken, getToken, canTurnTo, setTitle } from '@/libs/util'
// import config from '@/config'
// const { homeName } = config
Vue.use(Router)
const router = new Router({
routes,
mode: 'hash'
})
// const LOGIN_PAGE_NAME = 'login'
// const turnTo = (to, access, next) => {
// if (canTurnTo(to.name, access, routes)) next() // 有权限,可访问
// else next({ replace: true, name: 'error_401' }) // 无权限,重定向到401页面
// }
router.beforeEach((to, from, next) => {
iView.LoadingBar.start()
next()
// const token = getToken();
// if (!token && to.name !== LOGIN_PAGE_NAME) {
// // 未登录且要跳转的页面不是登录页
// next({
// name: LOGIN_PAGE_NAME // 跳转到登录页
// });
// } else if (!token && to.name === LOGIN_PAGE_NAME) {
// // 未登陆且要跳转的页面是登录页
// next(); // 跳转
// } else if (token && to.name === LOGIN_PAGE_NAME) {
// // 已登录且要跳转的页面是登录页
// next({
// name: 'home' // 跳转到home页
// });
// } else {
// if (store.state.user.hasGetInfo) {
// turnTo(to, store.state.user.access, next);
// } else {
// store
// .dispatch('getUserInfo')
// .then(user => {
// // 拉取用户信息,通过用户权限和跳转的页面的name来判断是否有权限访问;access必须是一个数组,如:['super_admin'] ['super_admin', 'admin']
// turnTo(to, user.access, next);
// })
// .catch(() => {
// next({
// name: 'login'
// });
// });
// }
// }
})
router.afterEach(to => {
// setTitle(to, router.app)
iView.LoadingBar.finish()
window.scrollTo(0, 0)
})
export default router
import Main from '@/components/main'
/**
* iview-admin中meta除了原生参数外可配置的参数:
* meta: {
* title: { String|Number|Function }
* 显示在侧边栏、面包屑和标签栏的文字
* 使用'{{ 多语言字段 }}'形式结合多语言使用,例子看多语言的路由配置;
* 可以传入一个回调函数,参数是当前路由对象,例子看动态路由和带参路由
* hideInBread: (false) 设为true后此级路由将不会出现在面包屑中,示例看QQ群路由配置
* hideInMenu: (false) 设为true后在左侧菜单不会显示该页面选项
* notCache: (false) 设为true后页面在切换标签后不会缓存,如果需要缓存,无需设置这个字段,而且需要设置页面组件name属性和路由配置的name一致
* access: (null) 可访问该页面的权限数组,当前路由设置的权限会影响子路由
* icon: (-) 该页面在左侧菜单、面包屑和标签导航处显示的图标,如果是自定义图标,需要在图标名称前加下划线'_'
* beforeCloseName: (-) 设置该字段,则在关闭当前tab页时会去'@/router/before-close.js'里寻找该字段名对应的方法,作为关闭前的钩子函数
* }
*/
export default [
{
path: '/login',
name: 'login',
meta: {
title: 'Login - 登录',
hideInMenu: true
},
component: () => import('@/view/login/login.vue')
},
{
path: '/',
name: '_home',
redirect: '/home',
component: Main,
meta: {
hideInMenu: false,
notCache: true,
showbr: false
},
children: [
{
path: '/home',
name: 'home',
meta: {
hideInMenu: false,
title: '签到活动管理',
notCache: true,
icon: 'md-home',
showbr: true
},
component: () => import('@/view/signIn/userList.vue')
},
{
path: '/signIn/detail',
meta: {
title: '详情',
hideInMenu: true,
showbr: true
},
component: () => import('@/view/signIn/detail.vue')
}
]
},
{
path: '/cms',
name: 'cms',
meta: {
icon: 'ios-briefcase',
title: '内容管理'
},
component: Main,
children: [
{
path: '/banner',
name: 'banner',
meta: {
icon: 'md-grid',
title: '自测'
},
component: () => import('@/view/advertisingManager/banner/index.vue')
}
// {
// path: '/advertisement',
// name: 'advertisement',
// meta: {
// icon: 'md-grid',
// title: '树洞'
// },
// component: () => import('@/view/advertisingManager/advertisement/index.vue')
// }
// {
// path: '/theme',
// name: 'theme',
// meta: {
// icon: 'md-grid',
// title: '主题'
// },
// component: () => import('@/view/cms/theme.vue')
// },
// {
// path: '/categories',
// name: 'categories',
// meta: {
// icon: 'md-grid',
// title: '栏目'
// },
// component: () => import('@/view/cms/categories.vue')
// }, {
// path: '/course',
// name: 'course',
// meta: {
// icon: 'md-grid',
// title: '条目'
// },
// component: () => import('@/view/cms/course/index.vue')
// },
// {
// path: '/courseSection',
// name: 'courseSection',
// meta: {
// icon: 'md-grid',
// title: '条目板块'
// },
// component: () => import('@/view/cms/courseSection/index.vue')
// },
// {
// path: '/courseContent',
// name: 'courseContent',
// meta: {
// icon: 'md-grid',
// title: '条目内容'
// },
// component: () => import('@/view/cms/courseContent/index.vue')
// }
]
}
// {
// path: '/appManager',
// name: 'appManager',
// meta: {
// showAlways: true,
// icon: 'ios-briefcase',
// title: 'APP管理'
// },
// component: Main,
// children: [
// {
// path: '/appUpdateManage',
// name: 'appUpdateManage',
// meta: {
// icon: 'md-grid',
// title: 'APP更新管理'
// },
// component: () => import('@/view/appManager/index.vue')
// }
// ]
// },
// {
// path: '/materialManager',
// name: 'materialManager',
// meta: {
// icon: 'ios-briefcase',
// title: '素材管理'
// },
// component: Main,
// children: [
// {
// path: '/soundTrack',
// name: 'soundTrack',
// meta: {
// icon: 'md-grid',
// title: '音轨'
// },
// component: () => import('@/view/materialManager/soundTrack.vue')
// },
// {
// path: '/resource',
// name: 'resource',
// meta: {
// icon: 'md-grid',
// title: '资源库'
// },
// component: () => import('@/view/materialManager/resource/index.vue')
// },
// {
// path: '/rings',
// name: 'rings',
// meta: {
// icon: 'md-grid',
// title: '铃声'
// },
// component: () => import('@/view/materialManager/rings/index.vue')
// }
// ]
// },
// {
// path: '/advertisingManager',
// name: 'advertisingManager',
// meta: {
// icon: 'ios-briefcase',
// title: '广告管理'
// },
// component: Main,
// children: [
// {
// path: '/advertisement',
// name: 'advertisement',
// meta: {
// icon: 'md-grid',
// title: '首页广告'
// },
// component: () => import('@/view/advertisingManager/advertisement/index.vue')
// }
// ]
// },
// {
// path: '/push',
// name: 'push',
// meta: {
// showAlways: true,
// icon: 'ios-briefcase',
// title: '推送'
// },
// component: Main,
// children: [
// {
// path: '/messagePush',
// name: 'messagePush',
// meta: {
// icon: 'md-grid',
// title: '通知推送'
// },
// component: () => import('@/view/push/messagePush/index.vue')
// }
// ]
// },
// {
// path: '/userCenter',
// name: 'userCenter',
// meta: {
// icon: 'ios-briefcase',
// title: '用户中心'
// },
// component: Main,
// children: [
// {
// path: '/userList',
// name: 'userList',
// meta: {
// icon: 'md-grid',
// title: '用户列表'
// },
// component: () => import('@/view/userCenter/userList/index.vue')
// },
// {
// path: '/userReport',
// name: 'userReport',
// meta: {
// icon: 'md-grid',
// title: '用户报告'
// },
// component: () => import('@/view/userCenter/userReport/index.vue')
// },
// {
// path: '/userReviews',
// name: 'userReviews',
// meta: {
// icon: 'md-grid',
// title: '用户评论'
// },
// component: () => import('@/view/userCenter/userReviews/index.vue')
// }
// ]
// },
// {
// path: '/payInvite',
// name: 'payInvite',
// meta: {
// icon: 'ios-briefcase',
// title: '支付与邀请'
// },
// component: Main,
// children: [
// {
// path: '/goods',
// name: 'goods',
// meta: {
// icon: 'md-grid',
// title: '商品'
// },
// component: () => import('@/view/payInvite/goods/index.vue')
// },
// {
// path: '/vipBuy',
// name: 'vipBuy',
// meta: {
// icon: 'md-grid',
// title: 'VIP购买页'
// },
// component: () => import('@/view/payInvite/vipBuy/index.vue')
// },
// {
// path: '/vipMeal',
// name: 'vipMeal',
// meta: {
// icon: 'md-grid',
// title: 'VIP购买页套餐'
// },
// component: () => import('@/view/payInvite/vipMeal/index.vue')
// },
// {
// path: '/payOrder',
// name: 'payOrder',
// meta: {
// icon: 'md-grid',
// title: '支付订单'
// },
// component: () => import('@/view/payInvite/payOrder/index.vue')
// },
// {
// path: '/record',
// name: 'record',
// meta: {
// icon: 'md-grid',
// title: '邀请好友记录'
// },
// component: () => import('@/view/payInvite/record/index.vue')
// }
// ]
// },
// {
// path: '/error_logger',
// name: 'error_logger',
// meta: {
// hideInBread: true,
// hideInMenu: true
// },
// component: Main,
// children: [
// {
// path: 'error_logger_page',
// name: 'error_logger_page',
// meta: {
// icon: 'ios-bug',
// title: '错误收集'
// },
// component: () => import('@/view/single-page/error-logger.vue')
// }
// ]
// },
// {
// path: '/401',
// name: 'error_401',
// meta: {
// hideInMenu: true
// },
// component: () => import('@/view/error-page/401.vue')
// },
// {
// path: '/500',
// name: 'error_500',
// meta: {
// hideInMenu: true
// },
// component: () => import('@/view/error-page/500.vue')
// },
// {
// path: '*',
// name: 'error_404',
// meta: {
// hideInMenu: true
// },
// component: () => import('@/view/error-page/404.vue')
// }
]
import Vue from 'vue'
import Vuex from 'vuex'
import user from './module/user'
import app from './module/app'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
//
},
mutations: {
//
},
actions: {
//
},
modules: {
user,
app
}
})
import {
getBreadCrumbList,
setTagNavListInLocalstorage,
getMenuByRouter,
getTagNavListFromLocalstorage,
getHomeRoute,
getNextRoute,
routeHasExist,
routeEqual,
getRouteTitleHandled,
localSave,
localRead
} from '@/libs/util'
import {
getTreeCategories
} from '@/api/categories'
import {
getTreeScene
} from '@/api/scenes'
import router from '@/router'
import routers from '@/router/routers'
import config from '@/config'
const { homeName } = config
const closePage = (state, route) => {
const nextRoute = getNextRoute(state.tagNavList, route)
state.tagNavList = state.tagNavList.filter(item => {
return !routeEqual(item, route)
})
router.push(nextRoute)
}
export default {
state: {
isLoading: false, // 是否加载中
breadCrumbList: [],
tagNavList: [],
categoryObj: {},
sceneObj: {},
homeRoute: {},
local: localRead('local'),
errorList: [],
hasReadErrorPage: false
},
getters: {
menuList: (state, getters, rootState) => getMenuByRouter(routers, rootState.user.access),
errorCount: state => state.errorList.length
},
mutations: {
setIsLoading (state, status) {
state.isLoading = status
},
setCategoryObj (state, status) {
state.categoryObj = status
},
setSceneObj (state, status) {
state.sceneObj = status
},
setBreadCrumb (state, route) {
state.breadCrumbList = getBreadCrumbList(route, state.homeRoute)
},
setHomeRoute (state, routes) {
state.homeRoute = getHomeRoute(routes, homeName)
},
setTagNavList (state, list) {
let tagList = []
if (list) {
tagList = [...list]
} else tagList = getTagNavListFromLocalstorage() || []
if (tagList[0] && tagList[0].name !== homeName) tagList.shift()
let homeTagIndex = tagList.findIndex(item => item.name === homeName)
if (homeTagIndex > 0) {
let homeTag = tagList.splice(homeTagIndex, 1)[0]
tagList.unshift(homeTag)
}
state.tagNavList = tagList
setTagNavListInLocalstorage([...tagList])
},
closeTag (state, route) {
let tag = state.tagNavList.filter(item => routeEqual(item, route))
route = tag[0] ? tag[0] : null
if (!route) return
closePage(state, route)
},
addTag (state, { route, type = 'unshift' }) {
let router = getRouteTitleHandled(route)
if (!routeHasExist(state.tagNavList, router)) {
if (type === 'push') state.tagNavList.push(router)
else {
if (router.name === homeName) state.tagNavList.unshift(router)
else state.tagNavList.splice(1, 0, router)
}
setTagNavListInLocalstorage([...state.tagNavList])
}
},
setLocal (state, lang) {
localSave('local', lang)
state.local = lang
},
addError (state, error) {
state.errorList.push(error)
},
setHasReadErrorLoggerStatus (state, status = true) {
state.hasReadErrorPage = status
}
},
actions: {
// 获得栏目类关系信息
getTreeCategories ({ state, commit }) {
return new Promise((resolve, reject) => {
getTreeCategories('category, course, section, content, track').then((res) => {
commit('setCategoryObj', res.data)
resolve()
}).catch(err => {
reject(err)
})
})
},
// 获得场景类关系信息
getTreeScene ({ state, commit }) {
return new Promise((resolve, reject) => {
getTreeScene('scene, theme, track').then((res) => {
commit('setSceneObj', res.data)
resolve()
}).catch(err => {
reject(err)
})
})
},
addErrorLog ({ commit, rootState }, info) {
if (!window.location.href.includes('error_logger_page')) commit('setHasReadErrorLoggerStatus', false)
}
}
}
import {
login,
logout,
getUserInfo,
getMessage,
getContentByMsgId,
hasRead,
removeReaded,
restoreTrash
} from '@/api/user'
import { setToken, getToken } from '@/libs/util'
export default {
state: {
userName: '',
userId: '',
avatarImgPath: '',
token: getToken(),
access: '',
hasGetInfo: false,
unreadCount: 0,
messageUnreadList: [],
messageReadedList: [],
messageTrashList: [],
messageContentStore: {}
},
mutations: {
setAvatar (state, avatarPath) {
state.avatarImgPath = avatarPath
},
setUserId (state, id) {
state.userId = id
},
setUserName (state, name) {
state.userName = name
},
setAccess (state, access) {
state.access = access
},
setToken (state, token) {
state.token = token
setToken(token)
},
setHasGetInfo (state, status) {
state.hasGetInfo = status
},
setMessageCount (state, count) {
state.unreadCount = count
},
setMessageUnreadList (state, list) {
state.messageUnreadList = list
},
setMessageReadedList (state, list) {
state.messageReadedList = list
},
setMessageTrashList (state, list) {
state.messageTrashList = list
},
updateMessageContentStore (state, { msg_id, content }) {
state.messageContentStore[msg_id] = content
},
moveMsg (state, { from, to, msg_id }) {
const index = state[from].findIndex(_ => _.msg_id === msg_id)
const msgItem = state[from].splice(index, 1)[0]
msgItem.loading = false
state[to].unshift(msgItem)
}
},
getters: {
messageUnreadCount: state => state.messageUnreadList.length,
messageReadedCount: state => state.messageReadedList.length,
messageTrashCount: state => state.messageTrashList.length
},
actions: {
// 登录
handleLogin ({ commit }, { userName, password }) {
userName = userName.trim()
return new Promise((resolve, reject) => {
login({
userName,
password
}).then(res => {
const data = res.data
commit('setToken', data.token)
resolve()
}).catch(err => {
reject(err)
})
})
},
// 退出登录
handleLogOut ({ state, commit }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
commit('setToken', '')
commit('setAccess', [])
resolve()
}).catch(err => {
reject(err)
})
// 如果你的退出登录无需请求接口,则可以直接使用下面三行代码而无需使用logout调用接口
// commit('setToken', '')
// commit('setAccess', [])
// resolve()
})
},
// 获取用户相关信息
getUserInfo ({ state, commit }) {
return new Promise((resolve, reject) => {
try {
getUserInfo(state.token).then(res => {
const data = res.data
commit('setAvatar', data.avatar)
commit('setUserName', data.name)
commit('setUserId', data.user_id)
commit('setAccess', data.access)
commit('setHasGetInfo', true)
resolve(data)
}).catch(err => {
reject(err)
})
} catch (error) {
reject(error)
}
})
},
// 获取消息列表,其中包含未读、已读、回收站三个列表
getMessageList ({ state, commit }) {
return new Promise((resolve, reject) => {
getMessage().then(res => {
const { unread, readed, trash } = res.data
commit('setMessageUnreadList', unread.sort((a, b) => new Date(b.create_time) - new Date(a.create_time)))
commit('setMessageReadedList', readed.map(_ => {
_.loading = false
return _
}).sort((a, b) => new Date(b.create_time) - new Date(a.create_time)))
commit('setMessageTrashList', trash.map(_ => {
_.loading = false
return _
}).sort((a, b) => new Date(b.create_time) - new Date(a.create_time)))
resolve()
}).catch(error => {
reject(error)
})
})
},
// 根据当前点击的消息的id获取内容
getContentByMsgId ({ state, commit }, { msg_id }) {
return new Promise((resolve, reject) => {
let contentItem = state.messageContentStore[msg_id]
if (contentItem) {
resolve(contentItem)
} else {
getContentByMsgId(msg_id).then(res => {
const content = res.data
commit('updateMessageContentStore', { msg_id, content })
resolve(content)
})
}
})
},
// 把一个未读消息标记为已读
hasRead ({ state, commit }, { msg_id }) {
return new Promise((resolve, reject) => {
hasRead(msg_id).then(() => {
commit('moveMsg', {
from: 'messageUnreadList',
to: 'messageReadedList',
msg_id
})
commit('setMessageCount', state.unreadCount - 1)
resolve()
}).catch(error => {
reject(error)
})
})
},
// 删除一个已读消息到回收站
removeReaded ({ commit }, { msg_id }) {
return new Promise((resolve, reject) => {
removeReaded(msg_id).then(() => {
commit('moveMsg', {
from: 'messageReadedList',
to: 'messageTrashList',
msg_id
})
resolve()
}).catch(error => {
reject(error)
})
})
},
// 还原一个已删除消息到已读消息
restoreTrash ({ commit }, { msg_id }) {
return new Promise((resolve, reject) => {
restoreTrash(msg_id).then(() => {
commit('moveMsg', {
from: 'messageTrashList',
to: 'messageReadedList',
msg_id
})
resolve()
}).catch(error => {
reject(error)
})
})
}
}
}
<template>
<div>
<Drawer
:title="formTitle"
v-model="modalVisible"
width="990"
:mask-closable="false"
:styles="styles"
@on-close="close"
>
<Form
ref="formValidate"
:label-width="100"
>
<FormItem label="回复" prop="title">
<Input
type="textarea"
v-model="content"
placeholder="请输入回复内容"
></Input>
</FormItem>
<FormItem v-if="!editMode" label="情绪值类型">
<InputNumber v-model="moodType"></InputNumber>
</FormItem>
<FormItem v-if="!editMode" label="情绪值">
<InputNumber v-model="mood"></InputNumber>
</FormItem>
</Form>
<div class="demo-drawer-footer">
<Button style="margin-right: 8px" @click="close">取消</Button>
<Button type="primary" @click="submit">保存</Button>
</div>
</Drawer>
</div>
</template>
<script>
import UploadS from '@/view/common/upload.vue'
import {
updateContent,
addTreeHole,
relayTreeHole
} from '@/api/api'
export default {
props: {
search: {
type: Function,
default: null
},
treeData: {
type: Array,
default: () => []
},
row: {
type: Object,
default: () => {}
},
visible: {
type: Boolean,
default: false
}
},
components: {
UploadS
},
data () {
return {
id:null,
modalVisible: false,
editMode: false,
formTitle: '',
styles: {
height: 'calc(100% - 55px)',
overflow: 'auto',
paddingBottom: '53px',
position: 'static'
},
moodType:null,
mood:null,
content:''
}
},
watch: {
visible: {
handler: function (val) {
this.modalVisible = val
if (val) {
this.search()
}
this.formValidate = this.row
},
immediate: true
},
row: {
handler: function (val) {
console.log(val)
if (val.id === 'add') {
this.editMode = false
this.formTitle = '新增留言'
} else {
this.editMode = true
this.formTitle = '编辑回复'
// this.content=val.content
this.id = val.id
}
},
immediate: true
}
},
methods: {
submit () {
let params = {
content:this.content
}
if (this.editMode) {
params.id=this.id
relayTreeHole(params).then((res) => {
if (res.status === 200) {
this.$Message.success(res.msg)
this.close()
this.search()
} else {
this.$Message.error(res.msg)
}
})
} else {
params.mood = this.mood
params.moodType = this.moodType
addTreeHole(params).then((res) => {
if (res.status === 200) {
this.$Message.success('新增成功')
this.close()
this.search()
} else {
this.$Message.error(res.msg)
}
})
}
},
getTree (e) {
getTreeCategories(e).then((res) => {
console.log(res)
})
},
change (val, type) {
if (type === 1) {
this.formValidate.is_in_home_lis = val ? 1 : 0
}
if (type === 2) {
this.formValidate.is_in_discover_list = val ? 1 : 0
}
},
close () {
this.$emit('update:visible', false)
}
}
}
</script>
<style>
.demo-drawer-footer {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
background: #fff;
}
</style>
<template>
<div class="content">
<div class="topSearch">
<div class="left">
<Input style="width:280px" placeholder="请输入用户ID或内容" v-model="searchParams.title" search enter-button @on-search='search(searchParams)'/>
<span class="lable">
属性
</span>
<Select v-model="searchParams.type" style="width:120px" @on-change='selectChange'>
<Option
v-for="item in types"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
<span class="lable">
排序规则
</span>
<Select v-model="searchParams.orderType" style="width:120px" @on-change='selectChange'>
<Option
v-for="item in orderTypes"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
</div>
<div class="right">
<Button @click="add" type="primary">新增</Button>
<Button type="error" @click="del">删除</Button>
</div>
</div>
<Stable :data='data' @edit='edit' @select='select' @refresh='search(searchParams)'/>
<Forms :visible.sync="visible" :search='search' :row='row'/>
<div class="pagBox">
<Pager class="margin-top-10"
:config="config"
@on-change="handlePager"></Pager>
</div>
</div>
</template>
<script>
import Stable from './table.vue'
import Forms from './form.vue'
import Pager from '@/view/common/Pager.vue'
import {
treeholeList
} from '@/api/api'
export default {
name: 'categories',
components: {
Stable,
Forms,
Pager
},
data () {
return {
types: [{id:0,name:'留言'},{id:1,name:'评论'}],
orderTypes:[{id:0,name:'创建时间'},{id:1,name:'热度'}],
searchParams: {
page: 1,
rows: 10,
type: 0,
orderType:0
},
config: { total: 0, size: 10, current: 1 },
selectList: [],
visible: false,
draggableVisible: false,
row: {},
data: []
}
},
methods: {
selectChange(p) {
console.log(p);
this.search(this.searchParams);
},
handlePager (pager) {
this.searchParams.page = pager.current
this.searchParams.rows = pager.size
this.search(this.searchParams)
},
edit (row) {
this.visible = true
console.log(row)
this.row = row
},
add () {
this.visible = true
this.row = {
id: 'add'
}
},
select (val) {
this.selectList = val
},
seq () {
this.draggableVisible = true
},
del () {
delBanners(this.selectList).then((res) => {
if (res.code === 200) {
this.$Message.success('删除成功')
this.search()
} else {
this.$Message.error('删除失败')
}
})
},
search (p) {
treeholeList(p).then((res) => {
this.config.total = res.data.total
this.config.size = res.data.size
this.config.current = res.data.current
this.data = res.data.records
})
}
},
mounted () {
this.search(this.searchParams)
}
}
</script>
<style lang="less"></style>
<template>
<div class="table">
<Table :loading="$store.state.app.isLoading" border ref="selection" :columns="columns" :data="data" @on-select='onSelect' @on-select-all='onSelectAll'>
<template slot-scope="{ row }" slot="name">
<img style="width: 30px;height: 30px;margin-right: 10px;" :src="row.writerIcon" alt=""> <span>{{ row.nickName }}</span>
</template>
<template slot-scope="{ row }" slot="countdown">
<span>{{ row.countdown }}</span>
</template>
<template slot-scope="{ row }" slot="created_at">
<span>{{ row.created_at | format}}</span>
</template>
<template slot-scope="{ row }" slot="enable">
<span>{{row.type?'评论':'留言'}}</span>
</template>
<template slot-scope="{ row }" slot="action">
<a @click="editly(row,1)">编辑留言</a>
<a style="margin: 0 10px;" @click="setTops(row.id)">置顶</a>
<a @click="edit(row)">评论</a>
<a style="margin-left: 10px;" @click="editly(row,0)">点赞</a>
</template>
</Table>
<Modal
v-model="showModal"
@on-ok="submit">
<Form
ref="formValidate"
>
<FormItem v-if="type" label="留言" prop="title">
<Input
type="textarea"
v-model="content"
placeholder="请输入留言内容"
></Input>
</FormItem>
<FormItem v-else label="请输入点赞数">
<InputNumber v-model="dznum"></InputNumber>
</FormItem>
</Form>
</Modal>
</div>
</template>
<script>
import { formatDate } from '@/libs/util'
import {
setTop,
updateContent,
pretendGreate
} from '@/api/api'
export default {
props: {
data: {
type: Array,
default () {
return []
}
}
},
data () {
return {
id:null,
type:0,
content:'',
dznum:null,
showModal:false,
selectList: [],
columns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: '用户ID',
width: 200,
slot: 'name'
},
{
title: '留言内容',
width: 200,
key: 'content'
},
{
title: '属性',
slot: 'enable'
},
{
title: '用户点赞数',
key: 'greatNum'
},
{
title: '创建日期',
width: 200,
key: 'createTime'
},
{
title: '情绪值',
key: 'mood'
},
{
title: '热度',
key: 'sore'
},
{
title: '操作',
width: 200,
slot: 'action'
}
]
}
},
mounted () {
},
filters: {
format (value) {
return formatDate(value)
}
},
methods: {
edit (row) {
this.$emit('edit', row)
},
editly(val,type){
this.showModal=true;
this.type=type
this.id = val.id
this.content = val.content
},
submit(){
if (this.type) {
updateContent({id:this.id,content:this.content}).then((res) => {
this.$Message.success(res.msg)
this.$emit('refresh')
})
} else {
pretendGreate({id:this.id,num:this.dznum}).then((res) => {
this.$Message.success(res.msg)
this.$emit('refresh')
})
}
},
onSelect (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
onSelectAll (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
setTops (id) {
setTop({id}).then((res) => {
if (res.status === 200) {
this.$Message.success('操作成功')
this.$emit('refresh')
}
})
},
handleSelectAll (status) {
this.$refs.selection.selectAll(status)
}
}
}
</script>
<style lang="less">
.table {
margin-top: 20px;
}
</style>
<template>
<div>
<Drawer
:title="formTitle"
v-model="modalVisible"
width="990"
:mask-closable="false"
:styles="styles"
@on-close="close"
>
<Form
ref="formValidate"
:label-width="150"
>
</FormItem>
<FormItem label="标题">
<Input
v-model="survey.title"
placeholder=" "
></Input>
</FormItem>
</FormItem>
<FormItem label="小标题">
<Input
v-model="survey.subtitle"
placeholder=" "
></Input>
</FormItem>
</FormItem>
<FormItem label="测试内容">
<editor ref="editors" v-model="survey.remark"/>
</FormItem>
<FormItem label="测试封面">
<UploadS ref="pic" v-model="survey.pic"/>
</FormItem>
<FormItem label="测试题价格">
<InputNumber v-model="survey.price"></InputNumber>
</FormItem>
<!-- <FormItem label="优惠价格">
<InputNumber ></InputNumber>
</FormItem> -->
</FormItem>
</FormItem>
<FormItem label="测试耗时[单位分钟]">
<InputNumber v-model="survey.timeConsume"></InputNumber>
</FormItem>
</FormItem>
<FormItem label="测试过的人数">
<InputNumber v-model="survey.testedNum"></InputNumber>
</FormItem>
<div style="padding-left: 151px;margin-bottom: 15px;">
<a @click="addQue">添加问题</a><a @click="delQue" v-if="questions.length>1" style="margin-left: 20px;">删除问题</a>
</div>
<span v-for="(item,index) in questions" :key='item.id'>
<FormItem label="题目描述">
<Input
v-model="item.content"
style="margin-bottom: 10px;"
placeholder=" "
></Input>
</FormItem>
<FormItem label="选项">
<span v-for="i in item.answers" :key='i.id'>
<Input
v-model="i.content"
style="width:60%;margin-bottom: 10px;"
placeholder=" "
></Input>
<span style="margin-left: 20px;">分值:</span>
<InputNumber v-model="i.score" style="width:20%;margin-bottom: 10px; margin-left: 5px;"
placeholder="请输入分值" ></InputNumber>
</span>
<div>
<Icon
@click="addAnswers(index)"
:size="20"
color="#7744FF"
style="margin-right: 6px;"
type="md-add"
/><Icon
v-if="item.answers.length>1"
@click="delAnswers(index)"
color="#7744FF"
:size="20"
type="md-remove"
/>
</div>
</FormItem>
</span>
<div style="padding-left: 151px;margin-bottom: 15px;">
<a @click="addResults">添加结果</a><a @click="delResults" v-if="results.length>1" style="margin-left: 20px;">删除结果</a>
</div>
<span v-for="item in results" :key='item.id'>
<FormItem label="结果标题">
<Input
v-model="item.title"
style="width:50%;margin-bottom: 10px;"
placeholder=" "
></Input>
<span style="margin-left: 20px;">最小值:</span>
<InputNumber v-model="item.minData" style="width:15%;margin-bottom: 10px; margin-left: 5px;"
></InputNumber>
<span style="margin-left: 20px;">最大值:</span>
<InputNumber v-model="item.maxData" style="width:15%;margin-bottom: 10px; margin-left: 5px;"
></InputNumber>
</FormItem>
<FormItem label="结果描述">
<editor v-model="item.remark"/>
</FormItem>
</span>
</Form>
<div class="demo-drawer-footer">
<Button style="margin-right: 8px" @click="close">取消</Button>
<Button type="primary" @click="submit">保存</Button>
</div>
</Drawer>
</div>
</template>
<script>
import UploadS from '@/view/common/upload.vue'
import Editor from '_c/editor'
import {
getDetailInfo,
addTest,
updateTest
} from '@/api/api'
export default {
name: 'editTheme',
props: {
search: {
type: Function,
default: null
},
treeData: {
type: Array,
default: () => []
},
row: {
type: Object,
default: () => {}
},
visible: {
type: Boolean,
default: false
}
},
components: {
Editor,
UploadS
},
data () {
return {
formValidate: {},
survey: {},
results: [{
title: '',
minData: 0,
maxData: 0,
remark: ''
}],
questions: [
{
content: '',
answers: [{
content: '',
score: 0
}]
}
],
modalVisible: false,
editMode: false,
formTitle: '',
styles: {
height: 'calc(100% - 55px)',
overflow: 'auto',
paddingBottom: '53px',
position: 'static'
}
}
},
watch: {
visible: {
handler: function (val) {
this.modalVisible = val
if (val) {
this.search()
}
this.formValidate = this.row
},
immediate: true
},
row: {
handler: function (val) {
if (val.id === 'add') {
this.editMode = false
this.survey = {
price: 1,
timeConsume: 1,
testedNum: 1
},
this.results = [{
title: '',
minData: 0,
maxData: 0,
remark: ''
}],
this.questions = [
{
content: '',
answers: [{
content: '',
score: 0
}]
}
],
this.formTitle = '新增自测'
} else {
this.editMode = true
this.formTitle = '编辑自测'
getDetailInfo({ id: val.id }).then((res) => {
this.survey = res.data.survey
this.changeContent(this.survey.remark)
this.results = res.data.results
this.questions = res.data.questions
})
}
},
immediate: true
}
},
mounted () {
},
methods: {
changeContent (p) {
this.$refs.editors.setHtml(p)
},
handleChange (html, text) {
console.log(html, text)
},
submit () {
let pic = this.$refs['pic'].getData()
if (pic) {
this.survey.pic = pic
}
let params = this.survey
params.results = JSON.stringify(this.results)
params.questions = JSON.stringify(this.questions)
if (this.editMode) {
delete params.createTime
updateTest(params).then((res) => {
if (res.status === 200) {
this.$Message.success('修改成功')
this.close()
this.search()
} else {
this.$Message.error(res.msg)
}
})
} else {
addTest(params).then((res) => {
if (res.status === 200) {
this.$Message.success('新增成功')
this.close()
this.search()
} else {
this.$Message.error(res.msg)
}
})
}
},
change (val, type) {
if (type === 1) {
this.formValidate.is_in_home_lis = val ? 1 : 0
}
if (type === 2) {
this.formValidate.is_in_discover_list = val ? 1 : 0
}
},
addResults () {
this.results.unshift({
title: '',
minData: 0,
maxData: 0,
remark: ''
})
},
delResults () {
this.results.splice(
this.results.length - 1,
1
)
},
addQue () {
this.questions.unshift({
content: '',
answers: [{
content: '',
score: 0
}]
})
},
delQue () {
this.questions.splice(
this.questions.length - 1,
1
)
},
addAnswers (idx) {
this.questions[idx].answers.unshift({
content: '',
score: 0
})
},
delAnswers (idx) {
this.questions[idx].answers.splice(
this.questions[idx].answers.length - 1,
1
)
},
close () {
this.$emit('update:visible', false)
}
}
}
</script>
<style>
.demo-drawer-footer {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
background: #fff;
}
</style>
<template>
<div class="content">
<div class="topSearch">
<div class="left">
<Input style="width:280px" v-model="searchParams.title" search enter-button placeholder="" @on-search='search(searchParams)'/>
</div>
<div class="right">
<Button @click="add" type="primary">新增</Button>
<Button type="error" @click="del">删除</Button>
</div>
</div>
<Stable :data='data' @edit='edit' @select='select' @getlist='search'/>
<Forms :visible.sync="visible" :search='search' :row='row'/>
<div class="pagBox">
<Pager class="margin-top-10"
:config="config"
@on-change="handlePager"></Pager>
</div>
</div>
</template>
<script>
import Stable from './table.vue'
import Forms from './form.vue'
import Pager from '@/view/common/Pager.vue'
import {
getList
} from '@/api/api'
export default {
name: 'categories',
components: {
Stable,
Forms,
Pager
},
data () {
return {
searchParams: {
page: 1,
rows: 10,
title: ''
},
config: { total: 0, size: 10, current: 1 },
selectList: [],
visible: false,
draggableVisible: false,
row: {},
data: []
}
},
methods: {
handlePager (pager) {
this.searchParams.page = pager.current
this.searchParams.rows = pager.size
this.search(this.searchParams)
},
edit (row) {
this.visible = true
console.log(row)
this.row = row
},
add () {
this.visible = true
this.row = {
id: 'add'
}
},
select (val) {
this.selectList = val
},
seq () {
this.draggableVisible = true
},
del () {
delBanners(this.selectList).then((res) => {
if (res.code === 200) {
this.$Message.success('删除成功')
this.search()
} else {
this.$Message.error('删除失败')
}
})
},
search (p) {
getList(p).then((res) => {
this.config.total = res.data.total
this.config.size = res.data.size
this.config.current = res.data.current
this.data = res.data.records
})
}
},
mounted () {
this.search(this.searchParams)
}
}
</script>
<style lang="less"></style>
<template>
<div class="table">
<Table :loading="$store.state.app.isLoading" border ref="selection" :columns="columns" :data="data" @on-select='onSelect' @on-select-all='onSelectAll'>
<template slot-scope="{ row }" slot="created_at">
<span>{{ row.createTime}}</span>
</template>
<template slot-scope="{ row }" slot="remark">
<span class="remark">{{ row.remark}}</span>
</template>
<template slot-scope="{ row }" slot="enable">
<img style="width: 100px;height:100px;" :src="row.pic" alt="图片" />
</template>
<template slot-scope="{ row }" slot="recommend">
<i-switch
:value="row.recommend === 1 ? true : false"
@on-change="
value => {
change(value, row);
}
"
/>
</template>
<template slot-scope="{ row }" slot="action">
<a @click="edit(row)">编辑</a>
<a style="margin: 0 10px;" @click="putShelves(row.id)">上架</a>
<a @click="offShelves(row.id)">下架</a>
<a style="margin-left: 10px;" @click="pxedit(row.id,1)">排序</a>
<a style="margin-left: 10px;" @click="pxedit(row.id,0)">设置优惠价</a>
</template>
</Table>
<Modal
v-model="showModal"
@on-ok="submit">
<Form
ref="formValidate"
>
<FormItem v-if="type" label="排序">
<InputNumber v-model="pxnum"></InputNumber>
</FormItem>
<FormItem v-else label="优惠价">
<InputNumber v-model="salePrice"></InputNumber>
</FormItem>
</Form>
</Modal>
</div>
</template>
<script>
import { formatDate } from '@/libs/util'
import {
putShelve,
offShelve,
setSore,
recommend,
updateSalePrice
} from '@/api/api'
export default {
props: {
data: {
type: Array,
default () {
return []
}
}
},
data () {
return {
type:null,
id:null,
pxnum:null,
salePrice:null,
showModal:false,
selectList: [],
columns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: '标题',
key: 'title'
},
{
title: '副标题',
key: 'subtitle'
},
{
title: '测试描述',
slot: 'remark'
},
{
title: '价格',
key: 'price'
},
{
title: '创建日期',
slot: 'created_at',
width: 200,
key: 'created_at'
},
{
title: '测试图片',
slot: 'enable'
},
{
title: '是否设置为推荐',
slot: 'recommend'
},
{
title: '操作',
width: 250,
slot: 'action'
}
]
}
},
mounted () {
},
filters: {
format (value) {
return formatDate(value)
}
},
methods: {
edit (row) {
this.$emit('edit', row)
},
pxedit(id,type){
this.pxnum=null
this.salePrice=null
this.showModal = true
this.id = id
this.type = type
},
onSelect (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
onSelectAll (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
submit(){
if (this.type) {
this.setSores()
} else {
updateSalePrice({ id:this.id,salePrice:this.salePrice }).then((res) => {
if (res.status === 200) {
this.$Message.success('操作成功')
this.$emit('getlist')
}
})
}
},
setSores () {
setSore({ id:this.id,sore:this.pxnum }).then((res) => {
if (res.status === 200) {
this.$Message.success('操作成功')
this.$emit('getlist')
}
})
},
putShelves (id) {
putShelve({ id }).then((res) => {
if (res.status === 200) {
this.$Message.success('上架成功')
}
})
},
offShelves (id) {
offShelve({ id }).then((res) => {
if (res.status === 200) {
this.$Message.success('下架成功')
}
})
},
change (status, row) {
console.log('开关状态:' + status + row)
let falg = status ? 1 : 0
recommend({
id:row.id,
recommend:falg
}).then((res) => {
if (res.status === 200) {
this.$Message.success('设置成功')
}
})
},
handleSelectAll (status) {
this.$refs.selection.selectAll(status)
}
}
}
</script>
<style lang="less">
.table {
margin-top: 20px;
}
.remark{
width: 100%;
height: 100px;
overflow: auto;
display: block;
padding: 10px 5px;
}
</style>
<template>
<div>
<Drawer
:title="formTitle"
v-model="modalVisible"
width="720"
:mask-closable="false"
:styles="styles"
@on-close="close"
>
<Form
ref="formValidate"
:model="formValidate"
:rules="ruleValidate"
:label-width="150"
>
<FormItem label="版本号" prop="version">
<Input
v-model="formValidate.version"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="更新说明" prop="summary">
<Input
type="textarea"
v-model="formValidate.summary"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="强制更新" >
<RadioGroup v-model="formValidate.is_force">
<Radio :label="1">
<span></span>
</Radio>
<Radio :label="0">
<span></span>
</Radio>
</RadioGroup>
</FormItem>
<FormItem label="更新设备">
<Select clearable v-model="formValidate.platform" style="width:180px">
<Option
v-for="item in platform"
:value="item.name"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
</FormItem>
<FormItem label="安卓App包上传" prop="apk_url">
<FileUpload ref="apk_url" v-model="formValidate.apk_url" />
</FormItem>
<FormItem label="安卓App包下载地址" prop="res_uri">
<a href="">{{formValidate.apk_url.url}}</a> <a style="margin-left: 10px;" v-clipboard:copy="formValidate.apk_url.url" v-clipboard:success="onCopy">复制</a>
</FormItem>
</Form>
<div class="demo-drawer-footer">
<Button style="margin-right: 8px" @click="close">取消</Button>
<Button type="primary" @click="submit">保存</Button>
</div>
</Drawer>
</div>
</template>
<script>
import FileUpload from '@/view/common/FileUpload.vue'
import {
addVersion,
editVersion
} from '@/api/version'
const _requireBlur = { required: true, message: ' ', trigger: 'blur' }
const validatorPic = (rule, value, callback) => {
if (!value) {
callback(new Error(' '))
} else {
callback()
}
}
const _requirePic = { required: true, message: ' ', trigger: 'blur', validator: validatorPic }
export default {
props: {
search: {
type: Function,
default: null
},
row: {
type: Object,
default: () => {}
},
visible: {
type: Boolean,
default: false
}
},
components: {
FileUpload
},
data () {
return {
modalVisible: false,
editMode: false,
formTitle: '',
styles: {
height: 'calc(100% - 55px)',
overflow: 'auto',
paddingBottom: '53px',
position: 'static'
},
platform: [{ id: '1', name: '平台' }, { id: '2', name: 'ios' }, { id: '3', name: 'android' }],
formValidate: {},
ruleValidate: {
'details.name': [_requireBlur],
'details.summary': [_requireBlur],
'details.res_home_background_pic': [_requirePic],
'details.res_discover_list_pic': [_requirePic]
}
}
},
watch: {
visible: {
handler: function (val) {
this.modalVisible = val
},
immediate: true
},
row: {
handler: function (val) {
console.log(val)
if (val.id === 'add') {
this.editMode = false
this.formTitle = '新增更新'
this.formValidate = {
version: '',
summary: '',
is_force: 1,
platform: '',
apk_url: ''
}
} else {
this.editMode = true
this.formTitle = '编辑更新'
this.formValidate = val
this.formValidate.apk_url = {
url: val.apk_url,
name: val.apk_url
}
}
},
immediate: true
}
},
methods: {
submit () {
let apk_url = this.$refs['apk_url'].getData()
if (apk_url.url) {
this.formValidate.apk_url = apk_url.url
}
this.$refs['formValidate'].validate((valid) => {
if (valid) {
delete this.formValidate.created_at
delete this.formValidate.deleted_at
delete this.formValidate.updated_at
let params = [this.formValidate]
if (this.editMode) {
editVersion(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
this.close()
this.search()
}
})
} else {
addVersion(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('新增成功')
this.close()
this.search()
}
})
}
} else {
this.$Message.error('您还有必填项未填写')
}
})
},
change (val, type) {
if (type === 1) {
this.formValidate.is_in_home_lis = val ? 1 : 0
}
if (type === 2) {
this.formValidate.is_in_discover_list = val ? 1 : 0
}
},
// 复制成功时的回调函数
onCopy (e) {
this.$Message.success('复制成功')
},
close () {
this.$emit('update:visible', false)
}
}
}
</script>
<style>
.demo-drawer-footer {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
background: #fff;
}
</style>
<template>
<div class="content">
<div class="topSearch">
<div class="left">
<Input style="width:280px" search enter-button placeholder="" @on-search='onSearch'/>
<span class="lable">
设备
</span>
<Select clearable v-model="seleted" style="width:180px" @on-change='selectChange'>
<Option
v-for="item in platform"
:value="item.name"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
</div>
<div class="right">
<Button @click="add" type="primary">新增</Button>
</div>
</div>
<Stable :data='data' @edit='edit' @select='select'/>
<Forms :visible.sync="visible" :search='search' :row='row'/>
</div>
</template>
<script>
import Stable from './table.vue'
import Forms from './form.vue'
import {
getVersion
} from '@/api/version'
export default {
name: 'categories',
components: {
Stable,
Forms
},
data () {
return {
platform: [{ id: '1', name: '全部' }, { id: '2', name: 'ios' }, { id: '3', name: 'android' }],
seleted: null,
selectList: [],
visible: false,
draggableVisible: false,
row: {},
data: []
}
},
methods: {
selectChange (p) {
this.seleted = p
this.search({
platform: p
})
},
edit (row) {
this.visible = true
this.row = row
},
add () {
this.visible = true
this.row = {
id: 'add'
}
},
select (val) {
this.selectList = val
},
seq () {
this.draggableVisible = true
},
onSearch (p) {
this.search({
keyword: p
})
},
search (p) {
getVersion(p).then((res) => {
this.data = res.data
})
}
},
activated: function () {
},
mounted () {
this.search({})
}
}
</script>
<style lang="less"></style>
<template>
<div class="table">
<Table :loading="$store.state.app.isLoading" border ref="selection" :columns="columns" :data="data" @on-select='onSelect' @on-select-all='onSelectAll'>
<template slot-scope="{ row }" slot="name">
<span>{{ row.version }}</span>
</template>
<template slot-scope="{ row }" slot="summary">
<span>{{ row.is_force===1?'是':'否' }}</span>
</template>
<template slot-scope="{ row }" slot="created_at">
<span>{{ row.created_at | format}}</span>
</template>
<template slot-scope="{ row }" slot="updated_at">
<span>{{ row.updated_at | format}}</span>
</template>
<template slot-scope="{ row }" slot="enable">
<i-switch
:value="row.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, row);
}
"
/>
</template>
<template slot-scope="{ row }" slot="action">
<a @click="edit(row)">编辑</a>
</template>
</Table>
</div>
</template>
<script>
import { formatDate } from '@/libs/util'
import {
editVersion
} from '@/api/version'
export default {
props: {
data: {
type: Array,
default () {
return []
}
}
},
data () {
return {
selectList: [],
columns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: '版本号',
slot: 'name'
},
{
title: '版本编号',
key: 'id'
},
{
title: '更新说明',
key: 'summary',
width: 300
},
{
title: '强制更新',
slot: 'summary'
},
{
title: '发布设备',
key: 'platform'
},
{
title: '创建日期',
slot: 'created_at',
width: 200
},
{
title: '生效日期',
slot: 'updated_at',
width: 200
},
{
title: '生效',
slot: 'enable'
},
{
title: '操作',
slot: 'action'
}
]
}
},
mounted () {
},
filters: {
format (value) {
return formatDate(value)
}
},
methods: {
edit (row) {
this.$emit('edit', row)
},
onSelect (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
onSelectAll (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
change (status, row) {
console.log('开关状态:' + status + row)
row.is_enable = status ? 1 : 0
delete row.created_at
delete row.deleted_at
delete row.updated_at
editVersion([row]).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
}
})
},
handleSelectAll (status) {
this.$refs.selection.selectAll(status)
}
}
}
</script>
<style lang="less">
.table {
margin-top: 20px;
}
</style>
<template>
<div class="content">
<div class="topSearch">
<div class="left">
<Input style="width:280px" search enter-button placeholder="" @on-search='onSearch'/>
</div>
<div class="right">
<Button class='success-btn' type="success" @click="seq">栏目排序</Button>
<Button @click="add" type="primary">新增</Button>
<Button type="error" @click="del">删除</Button>
</div>
</div>
<Stable :data='data' @edit='edit' @select='select'/>
<Forms :visible.sync="visible" :search='search' :row='row'/>
<draggableList :visible.sync="draggableVisible" :list='data' @ok='ok'/>
</div>
</template>
<script>
import Stable from '@/view/components/categories/table.vue'
import Forms from '@/view/components/categories/form.vue'
import draggableList from '@/view/common/draggable.vue'
import {
getCategories,
delCategories,
editCategories
} from '@/api/categories'
export default {
components: {
Stable,
Forms,
draggableList
},
data () {
return {
selectList: [],
visible: false,
draggableVisible: false,
row: {},
data: []
}
},
methods: {
edit (row) {
this.visible = true
this.row = row
},
add () {
this.visible = true
this.row = {
id: 'add'
}
},
select (val) {
this.selectList = val
},
seq () {
this.draggableVisible = true
},
ok (list) {
editCategories(list).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('操作成功')
this.draggableVisible = false
this.search()
}
})
},
del () {
delCategories(this.selectList).then((res) => {
if (res.code === 200) {
this.$Message.success('删除成功')
this.search()
} else {
this.$Message.error('删除失败')
}
})
},
onSearch (p) {
this.search({
keyword: p
})
},
search (p) {
getCategories(p).then((res) => {
this.data = res.data
})
}
},
mounted () {
this.search({})
}
}
</script>
<style lang="less"></style>
<template>
<div>
<Drawer
:title="formTitle"
v-model="modalVisible"
width="720"
:mask-closable="false"
:styles="styles"
@on-close="close"
>
<Form
ref="formValidate"
:model="formValidate"
:rules="ruleValidate"
:label-width="150"
v-if="formValidate.details"
>
<FormItem label="关联栏目">
<Select clearable v-model="formValidate.category_id" style="width:150px">
<Option
v-for="item in $store.state.app.categoryObj.categories"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
</FormItem>
<FormItem label="条目名称" prop="details.name">
<Input
v-model="formValidate.details.name"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="简介" prop="details.summary">
<Input
v-model="formValidate.details.summary"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="是否启用板块层级">
<i-switch :value='formValidate.details.is_use_section===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否显示为首页列表">
<i-switch :value='formValidate.is_in_home_list===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="首页列表背景图" prop="details.res_home_background_pic">
<UploadS ref="res_home_background_pic" v-model="formValidate.details.res_home_background_pic" />
</FormItem>
<FormItem label="是否听完解锁">
<i-switch :value='formValidate.details.is_lock_mode===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在探索列表页显示">
<i-switch :value='formValidate.is_in_discover_list===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在热门标签显示">
<i-switch :value='formValidate.is_hottest===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否置热门顶">
<i-switch :value='formValidate.is_hottest_top===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在最新标签显示">
<i-switch :value='formValidate.is_newest===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否置最新顶">
<i-switch :value='formValidate.is_newest_top===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在全部显示">
<i-switch :value='formValidate.is_in_discover_all===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="探索页列表背景图" prop="details.res_discover_list_pic">
<UploadS ref="res_discover_list_pic" v-model="formValidate.details.res_discover_list_pic" />
</FormItem>
<FormItem label="是否开启评论">
<i-switch :value='formValidate.allow_comment===1?true:false' @on-change='(value)=>{change(value,2)}'/>
</FormItem>
<FormItem label="是否允许调整倍速">
<i-switch :value='formValidate.details.is_allow_change_speed===1?true:false' @on-change='(value)=>{change(value,2)}'/>
</FormItem>
</Form>
<div class="demo-drawer-footer">
<Button style="margin-right: 8px" @click="close">取消</Button>
<Button type="primary" @click="submit">保存</Button>
</div>
</Drawer>
</div>
</template>
<script>
import UploadS from '@/view/common/upload.vue'
import {
addCourses,
editCourses
} from '@/api/courses'
const _requireBlur = { required: true, message: ' ', trigger: 'blur' }
const validatorPic = (rule, value, callback) => {
if (!value) {
callback(new Error(' '))
} else {
callback()
}
}
const _requirePic = { required: true, message: ' ', trigger: 'blur', validator: validatorPic }
export default {
props: {
search: {
type: Function,
default: null
},
row: {
type: Object,
default: () => {}
},
visible: {
type: Boolean,
default: false
}
},
components: {
UploadS
},
data () {
return {
modalVisible: false,
editMode: false,
formTitle: '',
styles: {
height: 'calc(100% - 55px)',
overflow: 'auto',
paddingBottom: '53px',
position: 'static'
},
formValidate: {},
ruleValidate: {
'details.name': [_requireBlur],
'details.summary': [_requireBlur],
'details.res_home_background_pic': [_requirePic],
'details.res_discover_list_pic': [_requirePic]
}
}
},
watch: {
visible: {
handler: function (val) {
this.modalVisible = val
if (val) {
this.search()
}
this.formValidate = this.row
},
immediate: true
},
row: {
handler: function (val) {
console.log(val)
if (val.id === 'add') {
this.editMode = false
this.formTitle = '新增栏目'
this.formValidate = {
details: {
name: '',
summary: '',
is_use_section: 1,
is_lock_mode: 0,
is_allow_change_speed: 0,
res_home_background_pic: '',
res_discover_list_pic: ''
},
category_id: 1,
is_in_discover_list: 0,
is_hottest: 0,
is_hottest_top: 0,
is_newest: 0,
is_newest_top: 0,
is_in_discover_all: 0,
allow_comment: 0
}
} else {
this.editMode = true
this.formTitle = '编辑栏目'
this.formValidate = val
}
},
immediate: true
}
},
methods: {
submit () {
if (this.$refs['res_home_background_pic'].getData()) {
this.formValidate.details.res_home_background_pic = this.$refs['res_home_background_pic'].getData()
}
if (this.$refs['res_discover_list_pic'].getData()) {
this.formValidate.details.res_discover_list_pic = this.$refs['res_discover_list_pic'].getData()
}
console.log(this.formValidate)
this.$refs['formValidate'].validate((valid) => {
if (valid) {
delete this.formValidate.created_at
delete this.formValidate.deleted_at
delete this.formValidate.updated_at
let params = [this.formValidate]
if (this.editMode) {
editCourses(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
this.close()
this.search()
}
})
} else {
addCourses(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('新增成功')
this.close()
this.search()
}
})
}
} else {
this.$Message.error('您还有必填项未填写')
}
})
},
change (val, type) {
if (type === 1) {
this.formValidate.is_in_home_lis = val ? 1 : 0
}
if (type === 2) {
this.formValidate.is_in_discover_list = val ? 1 : 0
}
},
close () {
this.$emit('update:visible', false)
}
}
}
</script>
<style>
.demo-drawer-footer {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
background: #fff;
}
</style>
<template>
<div class="content">
<div class="topSearch">
<div class="left">
<Input style="width:280px" search enter-button placeholder="" @on-search='onSearch'/>
<span class="lable">
栏目
</span>
<Select clearable v-model="seleted" style="width:80px" @on-change='selectChange'>
<Option
v-for="item in $store.state.app.categoryObj.categories"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
</div>
<div class="right">
<Button class='success-btn' type="success" @click="seq">排序</Button>
<Button @click="add" type="primary">新增</Button>
<Button type="error" @click="del">删除</Button>
</div>
</div>
<Stable :data='data' @edit='edit' @select='select'/>
<Forms :visible.sync="visible" :search='search' :row='row'/>
<draggableList :visible.sync="draggableVisible" :list='data' @ok='ok'/>
</div>
</template>
<script>
import Stable from './table.vue'
import Forms from './form.vue'
import draggableList from '@/view/common/draggable.vue'
import {
getCourses,
delCourses,
editCourses
} from '@/api/courses'
export default {
name: 'categories',
components: {
Stable,
Forms,
draggableList
},
data () {
return {
seleted: null,
selectList: [],
visible: false,
draggableVisible: false,
row: {},
data: []
}
},
methods: {
selectChange (p) {
console.log(p)
this.search({
category: p
})
},
edit (row) {
this.visible = true
this.row = row
},
add () {
this.visible = true
this.row = {
id: 'add'
}
},
select (val) {
this.selectList = val
},
seq () {
this.draggableVisible = true
},
ok (list) {
editCourses(list).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('操作成功')
this.draggableVisible = false
this.search()
}
})
},
del () {
delCourses(this.selectList).then((res) => {
if (res.code === 200) {
this.$Message.success('删除成功')
this.search()
} else {
this.$Message.error('删除失败')
}
})
},
onSearch (p) {
this.search({
keyword: p
})
},
search (p) {
getCourses(p).then((res) => {
this.data = res.data
})
}
},
activated: function () {
let id = this.$route.query.row && this.$route.query.row.id
this.seleted = id
let parmas = {
category: id
}
if (parmas) {
this.search(parmas)
} else {
this.search({})
}
},
mounted () {
}
}
</script>
<style lang="less"></style>
<template>
<div class="table">
<Table :loading="$store.state.app.isLoading" border ref="selection" :columns="columns" :data="data" @on-select='onSelect' @on-select-all='onSelectAll'>
<template slot-scope="{ row }" slot="name">
<span>{{ row.details.name }}</span>
</template>
<template slot-scope="{ row }" slot="summary">
<span>{{ row.details.summary }}</span>
</template>
<template slot-scope="{ row }" slot="created_at">
<span>{{ row.created_at | format}}</span>
</template>
<template slot-scope="{ row }" slot="enable">
<i-switch
:value="row.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, row);
}
"
/>
</template>
<template slot-scope="{ row }" slot="action">
<a @click="edit(row)">编辑</a>
</template>
<template slot-scope="{ row }" slot="is_use_section">
<a v-if="row.details.is_section==='1'">编辑</a>
</template>
<template slot-scope="{ row }" slot="edit">
<a>编辑</a>
</template>
</Table>
</div>
</template>
<script>
import { formatDate } from '@/libs/util'
import {
editCourses
} from '@/api/courses'
export default {
props: {
data: {
type: Array,
default () {
return []
}
}
},
data () {
return {
selectList: [],
columns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: '名称',
slot: 'name'
},
{
title: '简介',
slot: 'summary',
width: 300
},
{
title: '创建日期',
slot: 'created_at',
width: 200,
key: 'created_at'
},
{
title: '生效',
slot: 'enable'
},
{
title: '操作',
slot: 'action'
},
{
title: '编辑商品',
slot: 'edit'
},
{
title: '编辑板块',
slot: 'is_use_section'
},
{
title: '编辑条目内容',
slot: 'edit'
}
]
}
},
mounted () {
},
filters: {
format (value) {
return formatDate(value)
}
},
methods: {
edit (row) {
this.$emit('edit', row)
},
onSelect (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
onSelectAll (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
change (status, row) {
console.log('开关状态:' + status + row)
row.is_enable = status ? 1 : 0
delete row.created_at
delete row.deleted_at
delete row.updated_at
editCourses([row]).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
}
})
},
handleSelectAll (status) {
this.$refs.selection.selectAll(status)
}
}
}
</script>
<style lang="less">
.table {
margin-top: 20px;
}
</style>
<template>
<div>
<Drawer
:title="formTitle"
v-model="modalVisible"
width="720"
:mask-closable="false"
:styles="styles"
@on-close="close"
>
<Form
ref="formValidate"
:model="formValidate"
:rules="ruleValidate"
:label-width="150"
v-if="formValidate.details"
>
<FormItem label="关联条目板块">
<Cascader :data="treeData" v-model="formValidate.ancestors"></Cascader>
</FormItem>
<FormItem label="条目名称" prop="details.name">
<Input
v-model="formValidate.details.name"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="简介" prop="details.summary">
<Input
type="textarea"
v-model="formValidate.details.summary"
placeholder="请输入内容"
></Input>
</FormItem>
<FormItem label="是否开启评论">
<i-switch :value='formValidate.allow_comment===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否允许调整倍速">
<i-switch :value='formValidate.is_allow_change_speed===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="唱片背景图" prop="details.res_disc_cover_pic">
<UploadS ref="res_disc_cover_pic" v-model="formValidate.details.res_disc_cover_pic" />
</FormItem>
<FormItem label="默认播放时长" prop="details.default_play_time">
<InputNumber v-model="formValidate.details.default_play_time"></InputNumber>
</FormItem>
<FormItem label="封面类型" prop="details.cover_type">
<RadioGroup v-model="formValidate.details.cover_type">
<Radio label="0">
<Icon type="logo-apple"></Icon>
<span>图片</span>
</Radio>
<Radio label="1">
<Icon type="logo-windows"></Icon>
<span>视频</span>
</Radio>
</RadioGroup>
</FormItem>
<FormItem label="封面模糊" prop="details.cover_blur">
<InputNumber v-model="formValidate.details.cover_blur"></InputNumber>
</FormItem>
<FormItem label="封面图片" prop="details.res_cover_pic">
<UploadS ref="res_cover_pic" v-model="formValidate.details.res_cover_pic" />
</FormItem>
<FormItem label="封面模式">
<Select clearable v-model="formValidate.details.cover_mode" style="width:80px">
<Option
v-for="item in cover_mode"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
</FormItem>
</Form>
<div class="demo-drawer-footer">
<Button style="margin-right: 8px" @click="close">取消</Button>
<Button type="primary" @click="submit">保存</Button>
</div>
</Drawer>
</div>
</template>
<script>
import UploadS from '@/view/common/upload.vue'
import {
addContents,
editContents
} from '@/api/contents'
const _requireBlur = { required: true, message: ' ', trigger: 'blur' }
const _requireChange = { required: true, message: ' ', trigger: 'change' }
const validatorPic = (rule, value, callback) => {
if (!value) {
callback(new Error(' '))
} else {
callback()
}
}
const _requirePic = { required: true, message: ' ', trigger: 'blur', validator: validatorPic }
export default {
name: 'editTheme',
props: {
search: {
type: Function,
default: null
},
treeData: {
type: Array,
default: () => []
},
row: {
type: Object,
default: () => {}
},
visible: {
type: Boolean,
default: false
}
},
components: {
UploadS
},
data () {
return {
modalVisible: false,
editMode: false,
formTitle: '',
styles: {
height: 'calc(100% - 55px)',
overflow: 'auto',
paddingBottom: '53px',
position: 'static'
},
cover_mode: [{ id: 0, name: '不位移' }, { id: 1, name: '随机方向' }, { id: 2, name: '上下位移' }, { id: 3, name: '左右位移' }, { id: 4, name: '从小到大' }, { id: 5, name: '从大到小' }],
formValidate: {},
ruleValidate: {
'details.name': [_requireBlur],
'details.summary': [_requireBlur],
'details.res_home_background_pic': [_requirePic],
'details.res_home_background_video': [_requirePic],
'details.res_disc_cover_pic': [_requirePic],
'details.default_play_time': [_requirePic],
'details.cover_blur': [_requirePic],
'details.cover_type': [_requireChange],
'details.res_cover_pic': [_requirePic]
}
}
},
watch: {
visible: {
handler: function (val) {
this.modalVisible = val
if (val) {
this.search()
}
this.formValidate = this.row
},
immediate: true
},
row: {
handler: function (val) {
if (val.id === 'add') {
this.editMode = false
this.formTitle = '新增栏目'
this.formValidate = {
details: {
name: '',
summary: '',
res_disc_cover_pic: '',
default_play_time: 0,
cover_type: '0',
res_cover_pic: '',
cover_blur: 0,
cover_mode: 0
},
ancestors: [],
course_section_id: 0,
allow_comment: 0,
is_allow_change_speed: 0
}
} else {
this.editMode = true
this.formTitle = '编辑栏目'
this.formValidate = val
}
},
immediate: true
}
},
mounted () {
},
methods: {
submit () {
let res_disc_cover_pic = this.$refs['res_disc_cover_pic'].getData()
let res_cover_pic = this.$refs['res_cover_pic'].getData()
if (res_disc_cover_pic) {
this.formValidate.details.res_disc_cover_pic = res_disc_cover_pic
}
if (res_cover_pic) {
this.formValidate.details.res_cover_pic = res_cover_pic
}
console.log(this.formValidate)
this.$refs['formValidate'].validate((valid) => {
if (valid) {
delete this.formValidate.created_at
delete this.formValidate.deleted_at
delete this.formValidate.updated_at
if (this.formValidate.ancestors.length > 0) {
this.formValidate.course_section_id = this.formValidate.ancestors[2]
}
let params = [this.formValidate]
if (this.editMode) {
editContents(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
this.close()
this.search()
}
})
} else {
addContents(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('新增成功')
this.close()
this.search()
}
})
}
} else {
this.$Message.error('您还有必填项未填写')
}
})
},
change (val, type) {
if (type === 1) {
this.formValidate.is_in_home_lis = val ? 1 : 0
}
if (type === 2) {
this.formValidate.is_in_discover_list = val ? 1 : 0
}
},
close () {
this.$emit('update:visible', false)
}
}
}
</script>
<style>
.demo-drawer-footer {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
background: #fff;
}
</style>
<template>
<div class="content">
<div class="topSearch">
<div class="left">
<Input style="width:280px" search enter-button placeholder="" @on-search='onSearch'/>
<span class="lable">
关联条目板块
</span>
<Cascader style="width:250px" :data="treeData" v-model="seleted" @on-change="handleChange"></Cascader>
</div>
<div class="right">
<Button class='success-btn' type="success" @click="seq">场景排序</Button>
<Button @click="add" type="primary">新增</Button>
<Button type="error" @click="del">删除</Button>
</div>
</div>
<Stable :data='data' @edit='edit' @select='select'/>
<Forms :visible.sync="visible" :search='search' :row='row' :treeData='treeData'/>
<draggableList :visible.sync="draggableVisible" :list='data' @ok='ok'/>
</div>
</template>
<script>
import Stable from './table.vue'
import Forms from './form.vue'
import draggableList from '@/view/common/draggable.vue'
import {
getContents,
delContents,
editContents
} from '@/api/contents'
import {
getTreeCategorie2
} from '@/api/categories'
export default {
name: 'categories',
components: {
Stable,
Forms,
draggableList
},
data () {
return {
seleted: [],
treeData: [],
selectList: [],
visible: false,
draggableVisible: false,
row: {},
data: []
}
},
methods: {
handleChange (val) {
console.log(val)
this.search({
section: val[val.length - 1]
})
},
edit (row) {
this.visible = true
this.row = row
},
add () {
this.visible = true
this.row = {
id: 'add'
}
},
select (val) {
this.selectList = val
},
seq () {
this.draggableVisible = true
},
ok (list) {
editContents(list).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('操作成功')
this.draggableVisible = false
this.search()
}
})
},
del () {
delContents(this.selectList).then((res) => {
if (res.code === 200) {
this.$Message.success('删除成功')
this.search()
} else {
this.$Message.error('删除失败')
}
})
},
getTree () {
getTreeCategorie2('section').then((res) => {
this.treeData = res.data.categories.map((p) => {
p.label = p.name
p.value = p.id
p.children = p.courses
if (p.courses.length > 0) {
this.recursion(p.children)
}
return p
})
})
},
recursion (data) {
data = data.map((p) => {
p.label = p.name
p.value = p.id
p.children = p.sections
if (p.children.length > 0) {
p.children = p.children.map((i) => {
i.label = i.details.name
i.value = i.id
return i
})
}
return p
})
},
onSearch (p) {
this.search({
keyword: p
})
},
search (p) {
getContents(p).then((res) => {
this.data = res.data
})
}
},
activated: function () {
let id = this.$route.query.row && this.$route.query.row.id
this.seleted = this.$route.query.row && this.$route.query.row.ancestors
this.seleted[this.seleted.length] = id
let parmas = {
section: id
}
if (parmas) {
this.search(parmas)
} else {
this.search({})
}
},
mounted () {
this.getTree()
}
}
</script>
<style lang="less"></style>
<template>
<div class="table">
<Table :loading="$store.state.app.isLoading" border ref="selection" :columns="columns" :data="data" @on-select='onSelect' @on-select-all='onSelectAll'>
<template slot-scope="{ row }" slot="name">
<span>{{ row.details.name }}</span>
</template>
<template slot-scope="{ row }" slot="summary">
<span>{{ row.details.summary }}</span>
</template>
<template slot-scope="{ row }" slot="created_at">
<span>{{ row.created_at | format}}</span>
</template>
<template slot-scope="{ row }" slot="enable">
<i-switch
:value="row.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, row);
}
"
/>
</template>
<template slot-scope="{ row }" slot="action">
<a @click="edit(row)">编辑</a>
</template>
<template slot-scope="{ row }" slot="is_use_section">
<a v-if="row.details.is_section==='1'">编辑</a>
</template>
<template slot-scope="{ row }" slot="edit">
<a>编辑</a>
</template>
</Table>
</div>
</template>
<script>
import { formatDate } from '@/libs/util'
import {
editContents
} from '@/api/contents'
export default {
props: {
data: {
type: Array,
default () {
return []
}
}
},
data () {
return {
selectList: [],
columns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: '名称',
slot: 'name'
},
{
title: '简介',
slot: 'summary',
width: 300
},
{
title: '创建日期',
slot: 'created_at',
width: 200,
key: 'created_at'
},
{
title: '生效',
slot: 'enable'
},
{
title: '操作',
slot: 'action'
},
{
title: '编辑商品',
slot: 'edit'
},
{
title: '编辑板块',
slot: 'is_use_section'
},
{
title: '编辑条目内容',
slot: 'edit'
}
]
}
},
mounted () {
},
filters: {
format (value) {
return formatDate(value)
}
},
methods: {
edit (row) {
this.$emit('edit', row)
},
onSelect (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
onSelectAll (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
change (status, row) {
console.log('开关状态:' + status + row)
row.is_enable = status ? 1 : 0
delete row.created_at
delete row.deleted_at
delete row.updated_at
editContents([row]).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
}
})
},
handleSelectAll (status) {
this.$refs.selection.selectAll(status)
}
}
}
</script>
<style lang="less">
.table {
margin-top: 20px;
}
</style>
<template>
<div>
<Drawer
:title="formTitle"
v-model="modalVisible"
width="720"
:mask-closable="false"
:styles="styles"
@on-close="close"
>
<Form
ref="formValidate"
:model="formValidate"
:rules="ruleValidate"
:label-width="150"
v-if="formValidate.details"
>
<FormItem label="关联条目">
<Cascader :data="treeData" v-model="formValidate.ancestors"></Cascader>
</FormItem>
<FormItem label="板块名称" prop="details.name">
<Input
v-model="formValidate.details.name"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="简介" prop="details.summary">
<Input
type="textarea"
v-model="formValidate.details.summary"
placeholder="请输入内容"
></Input>
</FormItem>
</Form>
<div class="demo-drawer-footer">
<Button style="margin-right: 8px" @click="close">取消</Button>
<Button type="primary" @click="submit">保存</Button>
</div>
</Drawer>
</div>
</template>
<script>
import UploadS from '@/view/common/upload.vue'
import {
addSections,
editSections
} from '@/api/sections'
import {
getTreeCategories
} from '@/api/categories'
const _requireBlur = { required: true, message: ' ', trigger: 'blur' }
const validatorPic = (rule, value, callback) => {
if (!value) {
callback(new Error(' '))
} else {
callback()
}
}
const _requirePic = { required: true, message: ' ', trigger: 'blur', validator: validatorPic }
export default {
props: {
search: {
type: Function,
default: null
},
treeData: {
type: Array,
default: () => []
},
row: {
type: Object,
default: () => {}
},
visible: {
type: Boolean,
default: false
}
},
components: {
UploadS
},
data () {
return {
modalVisible: false,
editMode: false,
formTitle: '',
styles: {
height: 'calc(100% - 55px)',
overflow: 'auto',
paddingBottom: '53px',
position: 'static'
},
formValidate: {},
ruleValidate: {
'details.name': [_requireBlur],
'details.summary': [_requireBlur],
'details.res_home_background_pic': [_requirePic],
'details.res_discover_list_pic': [_requirePic]
}
}
},
watch: {
visible: {
handler: function (val) {
this.modalVisible = val
if (val) {
this.search()
}
this.formValidate = this.row
},
immediate: true
},
row: {
handler: function (val) {
console.log(val)
if (val.id === 'add') {
this.editMode = false
this.formTitle = '新增板块'
this.formValidate = {
details: {
name: '',
summary: ''
},
ancestors: [],
course_id: 1
}
} else {
this.editMode = true
this.formTitle = '编辑板块'
this.formValidate = val
}
},
immediate: true
}
},
methods: {
submit () {
this.$refs['formValidate'].validate((valid) => {
if (valid) {
delete this.formValidate.created_at
delete this.formValidate.deleted_at
delete this.formValidate.updated_at
if (this.formValidate.ancestors.length > 0) {
this.formValidate.course_id = this.formValidate.ancestors[1]
}
let params = [this.formValidate]
if (this.editMode) {
editSections(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
this.close()
this.search()
}
})
} else {
addSections(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('新增成功')
this.close()
this.search()
}
})
}
} else {
this.$Message.error('您还有必填项未填写')
}
})
},
getTree (e) {
getTreeCategories(e).then((res) => {
console.log(res)
})
},
change (val, type) {
if (type === 1) {
this.formValidate.is_in_home_lis = val ? 1 : 0
}
if (type === 2) {
this.formValidate.is_in_discover_list = val ? 1 : 0
}
},
close () {
this.$emit('update:visible', false)
}
}
}
</script>
<style>
.demo-drawer-footer {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
background: #fff;
}
</style>
<template>
<div class="content">
<div class="topSearch">
<div class="left">
<Input style="width:280px" search enter-button placeholder="" @on-search='onSearch'/>
<span class="lable">
关联条目
</span>
<Cascader style="width:250px" :data="treeData" @on-change="handleChange"></Cascader>
</div>
<div class="right">
<Button class='success-btn' type="success" @click="seq">排序</Button>
<Button @click="add" type="primary">新增</Button>
<Button type="error" @click="del">删除</Button>
</div>
</div>
<Stable :data='data' @edit='edit' @select='select'/>
<Forms :visible.sync="visible" :search='search' :row='row' :treeData='treeData'/>
<draggableList :visible.sync="draggableVisible" :list='data' @ok='ok'/>
</div>
</template>
<script>
import Stable from './table.vue'
import Forms from './form.vue'
import draggableList from '@/view/common/draggable.vue'
import {
getSections,
delSections,
editSections
} from '@/api/sections'
import {
getTreeCategorie2
} from '@/api/categories'
export default {
name: 'sections',
components: {
Stable,
Forms,
draggableList
},
data () {
return {
treeData: [],
selectList: [],
visible: false,
draggableVisible: false,
row: {},
data: []
}
},
methods: {
handleChange (val) {
console.log(val)
this.search({
course: val[val.length - 1]
})
},
edit (row) {
this.visible = true
this.row = row
},
add () {
this.visible = true
this.row = {
id: 'add'
}
},
select (val) {
this.selectList = val
},
seq () {
this.draggableVisible = true
},
ok (list) {
editSections(list).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('操作成功')
this.draggableVisible = false
this.search()
}
})
},
del () {
delSections(this.selectList).then((res) => {
if (res.code === 200) {
this.$Message.success('删除成功')
this.search()
} else {
this.$Message.error('删除失败')
}
})
},
getTree () {
getTreeCategorie2('course').then((res) => {
this.treeData = res.data.categories.map((p) => {
p.label = p.name
p.value = p.id
p.children = p.courses
if (p.courses.length > 0) {
this.recursion(p.children)
}
return p
})
})
},
recursion (data) {
data = data.map((p) => {
p.label = p.name
p.value = p.id
return p
})
},
onSearch (p) {
this.search({
keyword: p
})
},
search (p) {
getSections(p).then((res) => {
this.data = res.data
})
}
},
mounted () {
this.search({})
this.getTree()
}
}
</script>
<style lang="less"></style>
<template>
<div class="table">
<Table :loading="$store.state.app.isLoading" border ref="selection" :columns="columns" :data="data" @on-select='onSelect' @on-select-all='onSelectAll'>
<template slot-scope="{ row }" slot="name">
<span>{{ row.details.name }}</span>
</template>
<template slot-scope="{ row }" slot="summary">
<span>{{ row.details.summary }}</span>
</template>
<template slot-scope="{ row }" slot="created_at">
<span>{{ row.created_at | format}}</span>
</template>
<template slot-scope="{ row }" slot="enable">
<i-switch
:value="row.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, row);
}
"
/>
</template>
<template slot-scope="{ row }" slot="action">
<a @click="edit(row)">编辑</a>
</template>
<template slot-scope="{ row }" slot="edit">
<a @click="editCourseContent(row)">编辑</a>
</template>
</Table>
</div>
</template>
<script>
import { formatDate } from '@/libs/util'
import {
editSections
} from '@/api/sections'
export default {
props: {
data: {
type: Array,
default () {
return []
}
}
},
data () {
return {
selectList: [],
columns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: '名称',
slot: 'name'
},
{
title: '简介',
slot: 'summary',
width: 300
},
{
title: '创建日期',
slot: 'created_at',
width: 200,
key: 'created_at'
},
{
title: '生效',
slot: 'enable'
},
{
title: '操作',
slot: 'action'
},
{
title: '编辑条目内容',
slot: 'edit'
}
]
}
},
mounted () {
},
filters: {
format (value) {
return formatDate(value)
}
},
methods: {
edit (row) {
this.$emit('edit', row)
},
editCourseContent (row) {
this.$router.push({ name: 'courseContent', query: { row: row } })
},
onSelect (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
onSelectAll (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
change (status, row) {
console.log('开关状态:' + status + row)
row.is_enable = status ? 1 : 0
delete row.created_at
delete row.deleted_at
delete row.updated_at
editSections([row]).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
}
})
},
handleSelectAll (status) {
this.$refs.selection.selectAll(status)
}
}
}
</script>
<style lang="less">
.table {
margin-top: 20px;
}
</style>
<template>
<div class="content">
<div class="topSearch">
<div class="left">
<Input style="width:280px" search enter-button placeholder="" @on-search='onSearch'/>
</div>
<div class="right">
<Button class='success-btn' type="success" @click="seq">场景排序</Button>
<Button @click="add" type="primary">新增</Button>
<Button type="error" @click="del">删除</Button>
</div>
</div>
<Stable :data='data' @edit='edit' @select='select'/>
<Forms :visible.sync="visible" :search='search' :row='row'/>
<draggableList :visible.sync="draggableVisible" :list='data' @ok='ok'/>
</div>
</template>
<script>
import Stable from '@/view/components/scene/table.vue'
import Forms from '@/view/components/scene/form.vue'
import draggableList from '@/view/common/draggable.vue'
import {
getScenes,
delScenes,
editScenes
} from '@/api/scenes'
export default {
name: 'categories',
components: {
Stable,
Forms,
draggableList
},
data () {
return {
selectList: [],
visible: false,
draggableVisible: false,
row: {},
data: []
}
},
methods: {
edit (row) {
this.visible = true
this.row = row
},
add () {
this.visible = true
this.row = {
id: 'add'
}
},
select (val) {
this.selectList = val
},
seq () {
this.draggableVisible = true
},
ok (list) {
editScenes(list).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('操作成功')
this.draggableVisible = false
this.search()
}
})
},
del () {
delScenes(this.selectList).then((res) => {
if (res.code === 200) {
this.$Message.success('删除成功')
this.search()
} else {
this.$Message.error('删除失败')
}
})
},
onSearch (p) {
this.search({
keyword: p
})
},
search (p) {
getScenes(p).then((res) => {
this.data = res.data
})
}
},
mounted () {
this.search({})
}
}
</script>
<style lang="less"></style>
<template>
<div class="content">
<div class="topSearch">
<div class="left">
<Input
style="width:280px"
search
enter-button
placeholder=""
@on-search="onSearch"
/>
<span class="lable">
场景
</span>
<Select
clearable
v-model="seleted"
style="width:80px"
@on-change="selectChange"
>
<Option v-for="item in types" :value="item.id" :key="item.id">{{
item.name
}}</Option>
</Select>
</div>
<div class="right">
<Button class="success-btn" type="success" @click="seq">排序</Button>
<Button @click="add" type="primary">新增</Button>
<Button type="error" @click="del">删除</Button>
</div>
</div>
<Stable :data="data" @edit="edit" @select="select" />
<Forms :visible.sync="visible" :search="search" :row="row" />
<draggableList :visible.sync="draggableVisible" :list="data" @ok="ok" />
</div>
</template>
<script>
import Stable from "@/view/components/theme/table.vue";
import Forms from "@/view/components/theme/form.vue";
import draggableList from "@/view/common/draggable.vue";
import { getThemes, delThemes, editThemes } from "@/api/themes";
export default {
components: {
Stable,
Forms,
draggableList
},
data() {
return {
types: [{id:0,name:'留言'},{id:1,name:'评论'}],
seleted: null,
selectList: [],
visible: false,
draggableVisible: false,
row: {},
data: []
};
},
methods: {
edit(row) {
this.visible = true;
this.row = row;
},
add() {
this.visible = true;
this.row = {
id: "add"
};
},
selectChange(p) {
console.log(p);
this.search({
scene: p
});
},
select(val) {
this.selectList = val;
},
seq() {
this.draggableVisible = true;
},
ok(list) {
editThemes(list).then(res => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success("操作成功");
this.draggableVisible = false;
this.search();
}
});
},
del() {
delThemes(this.selectList).then(res => {
if (res.code === 200) {
this.$Message.success("删除成功");
this.search();
} else {
this.$Message.error("删除失败");
}
});
},
onSearch(p) {
this.search({
keyword: p
});
},
search(p) {
getThemes(p).then(res => {
this.data = res.data;
});
}
},
mounted() {
this.seleted = this.$route.query.row && this.$route.query.row.id;
let parmas = {
scene: this.seleted
};
if (parmas) {
this.search(parmas);
} else {
this.search({});
}
}
};
</script>
<style lang="less"></style>
<template>
<div class="upload">
<Upload
ref="upload"
:show-upload-list="hideUploadList"
:default-file-list="defaultList"
:on-success="handleSuccess"
:on-format-error="handleFormatError"
:on-exceeded-size="handleMaxSize"
:before-upload="beforeUpload"
:data="uploadData"
multiple
type="drag"
:action="uploadHost">
<div style="padding: 20px 0">
<Icon type="ios-cloud-upload" size="52" style="color: #3399ff"></Icon>
<p>点击或者拖拽到这里上传</p>
</div>
</Upload>
</div>
</template>
<script>
import { oss } from '@/view/common/upload'
export default {
props: {
value: {
type: String,
default: '' // 文件url
},
video: {
type: Boolean,
default: false // 是否视频上传
},
hideUploadList: {
type: Boolean,
default: true // 是否显示上传列表
}
},
data () {
return {
// 附件上传路径
uploadHost: '',
// 附件上传携带参数
uploadData: {},
// 上传后返回的存储的文件名
fileName: '',
// 上传后返回的文件存储地址
filePath: '',
uploadList: [],
defaultList: []
}
},
watch: {
value: {
handler: function (val) {
console.log(val)
this.init(val)
}
// immediate: true
}
},
methods: {
handleRemove (file) {
const fileList = this.$refs['upload'].fileList
this.$refs['upload'].fileList.splice(fileList.indexOf(file), 1)
this.updateFileList()
},
handleSuccess (res, file) {
console.log(res)
console.log(file)
file.url = res.data.url
file.name = res.data.filename
this.$emit('success', file.name)
},
/**
* 供外部组件提取当前文件上传位置
* @return this.uploadList[0].name
*/
getData () {
if (this.uploadList.length > 0) {
const result = { url: this.uploadList[0] && this.uploadList[0].url, name: this.uploadList[0].name }
return result
} else {
console.log(this.uploadList)
}
},
handleFormatError (file) {
this.$Notice.warning({
title: 'The file format is incorrect',
desc:
'File format of ' +
file.name +
' is incorrect, please select jpg or png.'
})
},
handleMaxSize (file) {
this.$Notice.warning({
title: 'Exceeding file size limit',
desc: 'File ' + file.name + ' is too large, no more than 2M.'
})
},
clearFiles () {
// console.log('清除');
this.$refs['upload'].clearFiles()
this.updateFileList()
},
updateFileList (flag) {
// flag 是否从外部更新进来的
if (flag) {
setTimeout(() => {
this.uploadList = this.$refs['upload'].fileList
}, 0)
} else {
this.uploadList = this.$refs['upload'].fileList
}
},
init (val) {
console.log(val)
if (val) {
const result = []
result.push({
url: val.url,
name: val.name
})
this.defaultList = result
this.updateFileList(true)
console.log(this.uploadList)
} else {
this.clearFiles()
}
},
// 在Upload组件的钩子before-upload中获取到生成的参数信息
beforeUpload (file) {
if (this.uploadList.length > 0) {
this.$Message.error('请删除旧文件再进行上传!')
return false
}
return oss(file.name).then(res => {
this.uploadHost = res.host
this.uploadData = res
})
}
},
mounted () {
// 必须这样初始化一下,否则上传第一张图片时会没有进度条
this.uploadList = this.$refs['upload'].fileList
this.init(this.value)
}
}
</script>
<style lang="less">
.demo-upload-list {
display: inline-block;
width: 120px;
height: 120px;
text-align: center;
line-height: 120px;
border: 1px solid transparent;
border-radius: 4px;
overflow: hidden;
background: #fff;
position: relative;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
margin-right: 4px;
}
.demo-upload-list img {
width: 100%;
height: 100%;
}
.demo-upload-list-cover {
display: none;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.6);
}
.demo-upload-list:hover .demo-upload-list-cover {
display: block;
}
.demo-upload-list-cover i {
color: #fff;
font-size: 20px;
cursor: pointer;
margin: 0 2px;
}
</style>
<template>
<section class="pager">
<Page :total="pager.total" :current="pager.current" :page-size="pager.size" :page-size-opts="pager.pageSizeOpts" :show-sizer="showSizer" :show-elevator="showElevator" :show-total="showTotal" :simple="simple" :size="sizeSelf" @on-change="handleChange" @on-page-size-change="handlePageSizeChange"></Page>
</section>
</template>
<script>
/**
* 分页组件
* @param {Object} config {total: 0,size: 10, current: 1}
* @returns {Function} on-change({current: '', size:''})
*/
export default {
'name': 'Pager', // 公用组件单词首字母大写,业务组件驼峰命名
'props': {
'config': {
'type': Object
},
'showSizer': {
'type': Boolean,
'default': true
},
'showElevator': {
'type': Boolean,
'default': true
},
'showTotal': {
'type': Boolean,
'default': true
},
'size': {
'type': String,
validator (value) {
return ['small'].includes(value)
}
},
'simple': {
'type': Boolean,
'default': false
}
},
'computed': {
'pager': function () {
// console.log('===设置分页数据===');
const _config = {
'total': 0,
'size': 10,
'pageSizeOpts': [10, 20, 30, 40],
'current': 1
}
const _result = Object.assign({}, _config, this.config)
// console.log(_result.total, _result.size, _result.current);
return _result
},
'sizeSelf': function () {
let _result = this.size
// if (window.innerWidth < 768) {
// _result = 'small'
// }
return _result
}
},
'methods': {
handleChange (currentPage) {
this.$emit('on-change', { 'current': currentPage, 'size': this.pager.size })
},
handlePageSizeChange (size) {
this.$emit('on-change', { 'current': this.pager.current, 'size': size })
}
},
'components': {},
'filters': {},
'watch': {},
mounted () { }
}
</script>
<style lang="less">
.pager {
text-align: center;
.ivu-page {
display: inline-block;
}
@media screen and (max-width: 768px) {
.ivu-page-total {
display: none;
height: 24px;
line-height: 24px;
}
.ivu-page-next,
.ivu-page-prev {
margin: 0;
min-width: 24px;
height: 24px;
line-height: 22px;
border: 0;
}
.ivu-page-item {
border: 0;
margin: 0;
min-width: 24px;
height: 24px;
line-height: 24px;
}
.ivu-page-options-elevator input {
padding: 1px 7px;
height: 24px;
border-radius: 3px;
width: 44px;
}
.ivu-select-single .ivu-select-selection {
height: 24px;
border-radius: 3px;
.ivu-select-placeholder,
.ivu-select-selected-value {
height: 22px;
line-height: 22px;
}
}
}
}
</style>
<template>
<Modal
v-model="modalVisible"
title="排序"
@on-ok="ok"
@on-cancel="close"
>
<div class="row">
<div class="col-6">
<draggable
:list="dataList"
:disabled="!enabled"
class="list-group"
ghost-class="ghost"
:move="checkMove"
@start="dragging = true"
@end="dragging = false"
>
<div
class="list-group-item"
v-for="(element,index) in dataList"
:key="element.id"
>
<span>{{ index+1 }}</span><span>{{ element.details.name }}</span>
</div>
</draggable>
</div>
</div>
</Modal>
</template>
<script>
import draggable from 'vuedraggable'
export default {
name: 'simple',
components: {
draggable
},
props: {
visible: {
type: Boolean,
default: false
},
list: {
type: Array,
default: () => []
}
},
watch: {
visible: {
handler: function (val) {
this.modalVisible = val
},
immediate: true
},
list: {
handler: function (val) {
this.dataList = JSON.parse(JSON.stringify(val)).sort((one, two) => {
return one.seq - two.seq
})
},
immediate: true
}
},
data () {
return {
enabled: true,
dataList: [],
modalVisible: false,
dragging: false
}
},
computed: {
},
methods: {
close () {
this.$emit('update:visible', false)
},
ok () {
let list = this.dataList.map((p, index) => {
delete p.created_at
delete p.deleted_at
delete p.updated_at
p.seq = index + 1
return p
})
console.log(list)
this.$emit('ok', list)
},
checkMove: function (e) {
window.console.log('Future index: ' + e.draggedContext.futureIndex)
}
}
}
</script>
<style scoped>
.buttons {
margin-top: 35px;
}
.ghost {
opacity: 0.5;
background: #c8ebfb;
}
.list-group-item {
width: 100%;
height: 50px;
display: flex;
align-items: center;
padding: 0 10px;
background-color: #e2e6e7;
border-left: 3px solid #8937d5;;
margin: 10px 0;
font-size: 14px;
cursor:move
}
span {
margin-left: 10px;
}
</style>
import { $http } from '@/libs/http'
let accessid = ''
let policyBase64 = ''
let signature = ''
let callbackbody = ''
let key = ''
// let expire = 0
let host = ''
let g_object_name = ''
// let now = Date.parse(new Date()) / 1000
const getSignature = () => {
return $http.get('admin/oss/token')
}
// 生成随机字符串
function random_string (len) {
len = len || 32
var chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'
var maxPos = chars.length
var pwd = ''
for (let i = 0; i < len; i++) {
pwd += chars.charAt(Math.floor(Math.random() * maxPos))
}
return pwd
}
// 获取用户上传原始文件名
function get_file_name (filename) {
let pos = filename.lastIndexOf('.')
let suffix = ''
if (pos !== -1) {
suffix = filename.substring(pos)
}
return suffix
}
// 把随机生成的字符串拼接在原始上传文件名后面生成新的唯一文件名
function set_file_name (filename) {
let suffix = get_file_name(filename)
g_object_name = key + random_string(10) + suffix
return ''
}
// 获取后端返回的签名信息,生成oss参数
function oss (filename = null) {
// 可以判断当前expire是否超过了当前时间, 如果超过了当前时间, 就重新取一下, 3 s 作为缓冲。
// now = Date.parse(new Date()) / 1000
// if (expire < now + 3) {
// 调用后端服务器接口获取签名信息,利用axios返回promise,可以链式调用
return getSignature().then(res => {
console.log(res)
/* 返回的签名策略信息包含:
{
accessid: "LTAI*******UPPr", // 用户请求的accessid
callback: "eyJjYWxs************H0ifQ==", // 回调
dir: "test/file-dir/", // 上传文件的存储位置
expire: "1557974779", // 上传策略Policy失效时间
host: "http://xxxxxxxxx.com", // 上传文件服务器地址
policy: "eyJleHBp***********6/EMG7U=" ,// 用户表单上传的策略(Policy)
signature: "JumJy*****k6/EMG7U=" // 签名信息
}
*/
policyBase64 = res['policy']
accessid = res['accessid']
signature = res['signature']
// expire = parseInt(res['expire'])
callbackbody = res['callback']
host = res['host']
key = res['dir']
if (filename != null) {
set_file_name(filename)
}
// 返回表单上传需要的参数信息
return {
'host': host,
'key': g_object_name,
'policy': policyBase64,
'OSSAccessKeyId': accessid,
'success_action_status': '200', // 让服务端返回200,不然,默认会返回204
'callback': callbackbody,
'signature': signature
}
})
// }
}
export { oss }
<template>
<div class="upload">
<div class="demo-upload-list" v-for="(item, idx) in uploadList" :key="idx">
<template v-if="item.status === 'finished'">
<video v-if="video" :src="item.url" controls="controls"></video>
<img v-else :src="item.url" />
<div class="demo-upload-list-cover">
<Icon
type="ios-trash-outline"
@click.native="handleRemove(item)"
></Icon>
</div>
</template>
<template v-else>
<Progress
v-if="item.showProgress"
:percent="item.percentage"
hide-info
></Progress>
</template>
</div>
<Upload
ref="upload"
v-show="uploadList.length<maxLength"
:show-upload-list="hideUploadList"
:default-file-list="defaultList"
:on-success="handleSuccess"
:max-size="1024*150"
:on-format-error="handleFormatError"
:on-exceeded-size="handleMaxSize"
:before-upload="beforeUpload"
:data="uploadData"
:multiple="multiple"
type="drag"
:action="uploadHost"
style="display: inline-block;width:120px;"
>
<div style="width: 120px;height:120px;line-height: 120px;">
<Icon type="ios-camera" size="20"></Icon>
</div>
</Upload>
</div>
</template>
<script>
// import { oss } from '@/view/common/upload'
import {
getQiNiuToken
} from '@/api/api'
export default {
props: {
value: {
type: [String, Array],
required: true // 文件url
},
maxLength: {
type: [String, Number],
default: 1, // 文件个数, 大于一时, multiple=true
validator (value) {
value = String(value)
return !isNaN(parseInt(value, 10))
}
},
video: {
type: Boolean,
default: false // 是否视频上传
},
hideUploadList: {
type: Boolean,
default: false // 是否显示上传列表
}
},
data () {
return {
// 附件上传路径
uploadHost: 'http://up-z2.qiniup.com/',
// 附件上传携带参数
uploadData: {},
// 上传后返回的存储的文件名
fileName: '',
// 上传后返回的文件存储地址
filePath: '',
uploadList: [],
defaultList: [],
baseUrl: ''
}
},
computed: {
multiple: function () {
return this.maxLength > 1
}
},
watch: {
value: {
handler: function (val) {
console.log(val)
this.init(val)
}
// immediate: true
}
},
methods: {
random_string (len) {
len = len || 32
var chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'
var maxPos = chars.length
var pwd = ''
for (let i = 0; i < len; i++) {
pwd += chars.charAt(Math.floor(Math.random() * maxPos))
}
return pwd
},
handleRemove (file) {
const fileList = this.$refs['upload'].fileList
this.$refs['upload'].fileList.splice(fileList.indexOf(file), 1)
this.updateFileList()
this.$emit('del', file.url)
},
handleSuccess (res, file) {
console.log(res)
console.log(file)
file.url = this.baseUrl + '/' + res.key
file.name = res.key
this.$emit('success', file.url)
},
/**
* 供外部组件提取当前文件上传位置
* @return this.uploadList[0].name
*/
getData () {
if (this.uploadList.length === 1) {
const result = this.uploadList[0] && this.uploadList[0].url
return result
}
if (this.uploadList.length > 1) {
const urlList = this.uploadList.map((p) => {
return p.url
})
return urlList
}
},
handleFormatError (file) {
this.$Notice.warning({
title: 'The file format is incorrect',
desc:
'File format of ' +
file.name +
' is incorrect, please select jpg or png.'
})
},
handleMaxSize (file) {
this.$Notice.warning({
title: 'Exceeding file size limit',
desc: 'File ' + file.name + ' is too large, no more than 2M.'
})
},
clearFiles () {
// console.log('清除');
this.$refs['upload'].clearFiles()
this.updateFileList()
},
updateFileList (flag) {
// flag 是否从外部更新进来的
if (flag) {
setTimeout(() => {
this.uploadList = this.$refs['upload'].fileList
}, 0)
} else {
this.uploadList = this.$refs['upload'].fileList
}
},
init (val) {
if (val) {
console.log(val)
const result = []
if (typeof val === 'string') {
result.push({
url: val
})
} else {
for (const f of val) {
result.push({
url: f
})
}
}
this.defaultList = result
this.updateFileList(true)
} else {
this.clearFiles()
}
},
// 在Upload组件的钩子before-upload中获取到生成的参数信息
beforeUpload (file) {
let fileNames = this.random_string(20) + file.name
let parms = {
fileName: fileNames
}
return getQiNiuToken(parms).then((res) => {
this.baseUrl = res.data.domain
this.$set(this.uploadData, 'token', res.data.token)
this.$set(this.uploadData, 'key', fileNames)
})
// return oss(file.name).then(res => {
// this.uploadHost = res.host
// this.uploadData = res
// })
}
},
mounted () {
// 必须这样初始化一下,否则上传第一张图片时会没有进度条
this.uploadList = this.$refs['upload'].fileList
this.init(this.value)
}
}
</script>
<style lang="less">
.demo-upload-list {
display: inline-block;
width: 120px;
height: 120px;
text-align: center;
line-height: 120px;
border: 1px solid transparent;
border-radius: 4px;
overflow: hidden;
background: #fff;
position: relative;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
margin-right: 4px;
}
.demo-upload-list img {
width: 100%;
height: 100%;
}
.demo-upload-list-cover {
display: none;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.6);
}
.demo-upload-list:hover .demo-upload-list-cover {
display: block;
}
.demo-upload-list-cover i {
color: #fff;
font-size: 20px;
cursor: pointer;
margin: 0 2px;
}
</style>
<template>
<div>
<Drawer
:title="formTitle"
v-model="modalVisible"
width="720"
:mask-closable="false"
:styles="styles"
@on-close="close"
>
<Form
ref="formValidate"
:model="formValidate"
:rules="ruleValidate"
:label-width="150"
v-if="formValidate.details"
>
<FormItem label="栏目名称" prop="details.name">
<Input
v-model="formValidate.details.name"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="副标题" prop="details.title">
<Input
v-model="formValidate.details.title"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="是否显示为首页列表">
<i-switch :value='formValidate.is_in_home_list===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在探索页显示">
<i-switch :value='formValidate.is_in_discover_list===1?true:false' @on-change='(value)=>{change(value,2)}'/>
</FormItem>
<FormItem label="展示图片" prop="details.res_show_pic">
<UploadS ref="res_show_pic" v-model="formValidate.details.res_show_pic" />
</FormItem>
<FormItem label="封面模糊" prop="details.pic_blur">
<InputNumber v-model="formValidate.details.pic_blur"></InputNumber>
</FormItem>
<FormItem label="喜欢Icon">
<UploadS ref="res_show_icon" v-model="formValidate.details.res_show_icon" />
</FormItem>
</Form>
<div class="demo-drawer-footer">
<Button style="margin-right: 8px" @click="close">取消</Button>
<Button type="primary" @click="submit">保存</Button>
</div>
</Drawer>
</div>
</template>
<script>
import UploadS from '@/view/common/upload.vue'
import {
addCategories,
editCategories
} from '@/api/categories'
const _requireBlur = { required: true, message: ' ', trigger: 'blur' }
const validatorPic = (rule, value, callback) => {
if (!value) {
callback(new Error(' '))
} else {
callback()
}
}
const _requirePic = { required: true, message: ' ', trigger: 'blur', validator: validatorPic }
export default {
name: 'AddTracks',
props: {
search: {
type: Function,
default: null
},
row: {
type: Object,
default: () => {}
},
visible: {
type: Boolean,
default: false
}
},
components: {
UploadS
},
data () {
return {
modalVisible: false,
editMode: false,
formTitle: '',
styles: {
height: 'calc(100% - 55px)',
overflow: 'auto',
paddingBottom: '53px',
position: 'static'
},
formValidate: {},
ruleValidate: {
'details.name': [_requireBlur],
'details.title': [_requireBlur],
'details.res_show_pic': [_requirePic],
'details.pic_blur': [_requirePic]
}
}
},
watch: {
visible: {
handler: function (val) {
this.modalVisible = val
if (val) {
this.search()
}
this.formValidate = this.row
},
immediate: true
},
row: {
handler: function (val) {
console.log(val)
if (val.id === 'add') {
this.editMode = false
this.formTitle = '新增栏目'
this.formValidate = {
details: {
name: '',
title: '',
res_show_pic: '',
res_show_icon: '',
pic_blur: 1
},
is_in_home_lis: 0,
is_in_discover_list: 1
}
} else {
this.editMode = true
this.formTitle = '编辑栏目'
this.formValidate = val
}
},
immediate: true
}
},
methods: {
submit () {
if (this.$refs['res_show_pic'].getData()) {
this.formValidate.details.res_show_pic = this.$refs['res_show_pic'].getData()
}
if (this.$refs['res_show_icon'].getData()) {
this.formValidate.details.res_show_icon = this.$refs['res_show_icon'].getData()
}
console.log(this.formValidate)
this.$refs['formValidate'].validate((valid) => {
if (valid) {
delete this.formValidate.created_at
delete this.formValidate.deleted_at
delete this.formValidate.updated_at
let params = [this.formValidate]
if (this.editMode) {
editCategories(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
this.close()
this.search()
}
})
} else {
addCategories(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('新增成功')
this.close()
this.search()
}
})
}
} else {
this.$Message.error('您还有必填项未填写')
}
})
},
change (val, type) {
if (type === 1) {
this.formValidate.is_in_home_lis = val ? 1 : 0
}
if (type === 2) {
this.formValidate.is_in_discover_list = val ? 1 : 0
}
},
close () {
this.$emit('update:visible', false)
}
}
}
</script>
<style>
.demo-drawer-footer {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
background: #fff;
}
</style>
<template>
<div class="table">
<Table :loading="$store.state.app.isLoading" border ref="selection" :columns="columns" :data="data" @on-select='onSelect' @on-select-all='onSelectAll'>
<template slot-scope="{ row }" slot="name">
<span>{{ row.details.name }}</span>
</template>
<template slot-scope="{ row }" slot="title">
<span>{{ row.details.title }}</span>
</template>
<template slot-scope="{ row }" slot="created_at">
<span>{{ row.created_at | format}}</span>
</template>
<template slot-scope="{ row }" slot="enable">
<i-switch
:value="row.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, row);
}
"
/>
</template>
<template slot-scope="{ row }" slot="action">
<a @click="edit(row)">编辑</a>
</template>
<template slot-scope="{ row }" slot="edit">
<a @click="editCategories(row)">编辑</a>
</template>
</Table>
</div>
</template>
<script>
import { formatDate } from '@/libs/util'
import {
editCategories
} from '@/api/categories'
export default {
props: {
data: {
type: Array,
default () {
return []
}
}
},
data () {
return {
selectList: [],
columns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: '名称',
slot: 'name'
},
{
title: '副标题',
slot: 'title',
width: 300
},
{
title: '创建日期',
slot: 'created_at',
key: 'created_at'
},
{
title: '生效',
slot: 'enable'
},
{
title: '操作',
slot: 'action'
},
{
title: '编辑条目',
slot: 'edit'
}
]
}
},
mounted () {
},
filters: {
format (value) {
return formatDate(value)
}
},
methods: {
edit (row) {
this.$emit('edit', row)
},
onSelect (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
onSelectAll (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
change (status, row) {
console.log('开关状态:' + status + row)
row.is_enable = status ? 1 : 0
let data = (({ details, id, is_enable, is_in_discover_list, is_in_home_list }) => ({ details, id, is_enable, is_in_discover_list, is_in_home_list }))(row)
editCategories([data]).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
}
})
},
editCategories (row) {
this.$router.push({ name: 'course', query: { row: row } })
},
handleSelectAll (status) {
this.$refs.selection.selectAll(status)
}
}
}
</script>
<style lang="less">
.table {
margin-top: 20px;
}
</style>
<template>
<div class="table">
<Table border ref="selection" :columns="columns" :data="data">
<template slot-scope="{ row }" slot="action">
<a>预览</a>
<a>编辑</a>
</template>
</Table>
<Button @click="handleSelectAll(true)">Set all selected</Button>
<Button @click="handleSelectAll(false)">Cancel all selected</Button>
</div>
</template>
<script>
export default {
data () {
return {
columns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: '名称',
key: 'age'
},
{
title: '描述',
key: 'address'
},
{
title: '创建日期',
key: 'address'
},
{
title: '生效',
key: 'address'
},
{
title: '操作',
slot: 'action'
}
],
data: [
{
name: 'John Brown',
age: 18,
address: 'New York No. 1 Lake Park',
date: '2016-10-03'
},
{
name: 'Jim Green',
age: 24,
address: 'London No. 1 Lake Park',
date: '2016-10-01'
},
{
name: 'Joe Black',
age: 30,
address: 'Sydney No. 1 Lake Park',
date: '2016-10-02'
},
{
name: 'Jon Snow',
age: 26,
address: 'Ottawa No. 2 Lake Park',
date: '2016-10-04'
}
]
}
},
methods: {
handleSelectAll (status) {
this.$refs.selection.selectAll(status)
}
}
}
</script>
<style lang="less">
.table {
margin-top: 20px;
}
</style>
<template>
<div>
<Drawer
:title="formTitle"
v-model="modalVisible"
width="720"
:mask-closable="false"
:styles="styles"
@on-close="close"
>
<Form
ref="formValidate"
:model="formValidate"
:rules="ruleValidate"
:label-width="150"
v-if="formValidate.details"
>
<FormItem label="场景名称" prop="details.name">
<Input
v-model="formValidate.details.name"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="副标题" prop="details.title">
<Input
v-model="formValidate.details.title"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="是否显示为首页按钮">
<i-switch :value='formValidate.is_home_button===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="首页按钮icon" prop="details.res_icon">
<UploadS ref="res_icon" v-model="formValidate.details.res_icon" />
</FormItem>
<FormItem label="首页按钮点击icon" prop="details.res_icon_press">
<UploadS ref="res_icon_press" v-model="formValidate.details.res_icon_press" />
</FormItem>
<FormItem label="是否显示为首页列表">
<i-switch :value='formValidate.is_in_home_list===1?true:false' @on-change='(value)=>{change(value,2)}'/>
</FormItem>
<FormItem label="是否在探索页显示">
<i-switch :value='formValidate.is_in_discover_list===1?true:false' @on-change='(value)=>{change(value,3)}'/>
</FormItem>
<FormItem label="展示图片" prop="details.res_show_pic">
<UploadS ref="res_show_pic" v-model="formValidate.details.res_show_pic" />
</FormItem>
<FormItem label="封面模糊" prop="details.pic_blur">
<InputNumber v-model="formValidate.details.pic_blur"></InputNumber>
</FormItem>
</Form>
<div class="demo-drawer-footer">
<Button style="margin-right: 8px" @click="close">取消</Button>
<Button type="primary" @click="submit">保存</Button>
</div>
</Drawer>
</div>
</template>
<script>
import UploadS from '@/view/common/upload.vue'
import {
addScenes,
editScenes
} from '@/api/scenes'
const _requireBlur = { required: true, message: ' ', trigger: 'blur' }
const validatorPic = (rule, value, callback) => {
if (!value) {
callback(new Error(' '))
} else {
callback()
}
}
const _requirePic = { required: true, message: ' ', trigger: 'blur', validator: validatorPic }
export default {
name: 'AddTracks',
props: {
search: {
type: Function,
default: null
},
row: {
type: Object,
default: () => {}
},
visible: {
type: Boolean,
default: false
}
},
components: {
UploadS
},
data () {
return {
modalVisible: false,
editMode: false,
formTitle: '',
styles: {
height: 'calc(100% - 55px)',
overflow: 'auto',
paddingBottom: '53px',
position: 'static'
},
formValidate: {},
ruleValidate: {
'details.name': [_requireBlur],
'details.title': [_requireBlur],
'details.res_show_pic': [_requirePic],
'details.res_icon': [_requirePic],
'details.res_icon_press': [_requirePic],
'details.pic_blur': [_requirePic]
}
}
},
watch: {
visible: {
handler: function (val) {
this.modalVisible = val
if (val) {
this.search()
}
this.formValidate = this.row
},
immediate: true
},
row: {
handler: function (val) {
console.log(val)
if (val.id === 'add') {
this.editMode = false
this.formTitle = '新增栏目'
this.formValidate = {
details: {
name: '',
title: '',
res_icon: '',
res_icon_press: '',
res_show_pic: '',
res_show_icon: '',
pic_blur: 1
},
is_home_button: 0,
is_in_home_list: 0,
is_in_discover_list: 1
}
} else {
this.editMode = true
this.formTitle = '编辑栏目'
this.formValidate = val
}
},
immediate: true
}
},
methods: {
submit () {
if (this.$refs['res_show_pic'].getData()) {
this.formValidate.details.res_show_pic = this.$refs['res_show_pic'].getData()
}
if (this.$refs['res_icon'].getData()) {
this.formValidate.details.res_icon = this.$refs['res_icon'].getData()
}
if (this.$refs['res_icon_press'].getData()) {
this.formValidate.details.res_icon_press = this.$refs['res_icon_press'].getData()
}
console.log(this.formValidate)
this.$refs['formValidate'].validate((valid) => {
if (valid) {
delete this.formValidate.created_at
delete this.formValidate.deleted_at
delete this.formValidate.updated_at
let params = [this.formValidate]
if (this.editMode) {
editScenes(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
this.close()
this.search()
}
})
} else {
addScenes(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('新增成功')
this.close()
this.search()
}
})
}
} else {
this.$Message.error('您还有必填项未填写')
}
})
},
change (val, type) {
if (type === 1) {
this.formValidate.is_home_button = val ? 1 : 0
}
if (type === 2) {
this.formValidate.is_in_home_list = val ? 1 : 0
}
if (type === 3) {
this.formValidate.is_in_discover_list = val ? 1 : 0
}
},
close () {
this.$emit('update:visible', false)
}
}
}
</script>
<style>
.demo-drawer-footer {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
background: #fff;
}
</style>
<template>
<div class="table">
<Table :loading="$store.state.app.isLoading" border ref="selection" :columns="columns" :data="data" @on-select='onSelect' @on-select-all='onSelectAll'>
<template slot-scope="{ row }" slot="name">
<span>{{ row.details.name }}</span>
</template>
<template slot-scope="{ row }" slot="title">
<span>{{ row.details.title }}</span>
</template>
<template slot-scope="{ row }" slot="created_at">
<span>{{ row.created_at | format}}</span>
</template>
<template slot-scope="{ row }" slot="enable">
<i-switch
:value="row.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, row);
}
"
/>
</template>
<template slot-scope="{ row }" slot="action">
<a @click="edit(row)">编辑</a>
</template>
<template slot-scope="{ row }" slot="edit">
<a @click="goTheme(row)">编辑</a>
</template>
</Table>
</div>
</template>
<script>
import { formatDate } from '@/libs/util'
import {
editScenes
} from '@/api/scenes'
export default {
props: {
data: {
type: Array,
default () {
return []
}
}
},
data () {
return {
selectList: [],
columns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: '名称',
slot: 'name'
},
{
title: '副标题',
slot: 'title',
width: 300
},
{
title: '创建日期',
slot: 'created_at',
key: 'created_at'
},
{
title: '生效',
slot: 'enable'
},
{
title: '操作',
slot: 'action'
},
{
title: '编辑主题',
slot: 'edit'
}
]
}
},
mounted () {
},
filters: {
format (value) {
return formatDate(value)
}
},
methods: {
edit (row) {
this.$emit('edit', row)
},
goTheme (row) {
this.$router.push({ name: 'theme', query: { row: row } })
},
onSelect (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
onSelectAll (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
change (status, row) {
console.log('开关状态:' + status + row)
row.is_enable = status ? 1 : 0
let data = (({ details, id, is_enable, is_in_discover_list, is_in_home_list }) => ({ details, id, is_enable, is_in_discover_list, is_in_home_list }))(row)
editScenes([data]).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
}
})
},
handleSelectAll (status) {
this.$refs.selection.selectAll(status)
}
}
}
</script>
<style lang="less">
.table {
margin-top: 20px;
}
</style>
<template>
<div>
<Drawer
:title="formTitle"
v-model="modalVisible"
width="720"
:mask-closable="false"
:styles="styles"
@on-close="close"
>
<Form
ref="formValidate"
:model="formValidate"
:rules="ruleValidate"
:label-width="150"
v-if="formValidate.details"
>
<FormItem label="关联">
<Cascader :data="treeData" v-model="formValidate.ancestors"></Cascader>
</FormItem>
<FormItem label="音轨名称" prop="details.name">
<Input
v-model="formValidate.details.name"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="简介">
<Input
type="textarea"
v-model="formValidate.details.summary"
placeholder="请输入内容"
></Input>
</FormItem>
<FormItem label="默认icon" prop="details.res_icon">
<UploadS ref="res_icon" v-model="formValidate.details.res_icon" />
</FormItem>
<FormItem label="开始播放间隔" >
<InputNumber v-model="formValidate.details.start_space"></InputNumber>
</FormItem>
<FormItem label="距结束播放间隔" >
<InputNumber v-model="formValidate.details.end_space"></InputNumber>
</FormItem>
<FormItem label="播放模式">
<Select clearable v-model="formValidate.details.play_mode" style="width:180px">
<Option
v-for="item in play_mode"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
</FormItem>
<FormItem label="默认音量" prop="details.volume">
<InputNumber v-model="formValidate.details.volume"></InputNumber>
</FormItem>
<FormItem label="音轨类型">
<Select clearable v-model="formValidate.details.track_type" style="width:180px">
<Option
v-for="item in track_type"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
</FormItem>
</Form>
<div class="demo-drawer-footer">
<Button style="margin-right: 8px" @click="close">取消</Button>
<Button type="primary" @click="submit">保存</Button>
</div>
</Drawer>
</div>
</template>
<script>
import UploadS from '@/view/common/upload.vue'
import {
addTracks,
editTracks
} from '@/api/tracks'
const _requireBlur = { required: true, message: ' ', trigger: 'blur' }
const validatorPic = (rule, value, callback) => {
if (!value) {
callback(new Error(' '))
} else {
callback()
}
}
const _requirePic = { required: true, message: ' ', trigger: 'blur', validator: validatorPic }
export default {
name: 'editTheme',
props: {
search: {
type: Function,
default: null
},
treeData: {
type: Array,
default: () => []
},
row: {
type: Object,
default: () => {}
},
type: {
type: Number,
default: 1
},
visible: {
type: Boolean,
default: false
}
},
components: {
UploadS
},
data () {
return {
modalVisible: false,
editMode: false,
formTitle: '',
styles: {
height: 'calc(100% - 55px)',
overflow: 'auto',
paddingBottom: '53px',
position: 'static'
},
play_mode: [{ id: 0, name: '随机播放' }, { id: 1, name: '顺序' }, { id: 2, name: '单次' }, { id: 3, name: '重复' }],
track_type: [{ id: 0, name: '常规类型' }, { id: 1, name: '语音' }, { id: 2, name: '脑波' }, { id: 3, name: '呼吸引导' }, { id: 4, name: '单音频' }, { id: 5, name: '效果类短音频' }, { id: 6, name: '旋律类短音频' }, { id: 7, name: '混音音轨' }, { id: 8, name: '自定义' }],
formValidate: {},
ruleValidate: {
'details.name': [_requireBlur],
'details.res_icon': [_requirePic],
'details.volume': [_requirePic]
}
}
},
watch: {
visible: {
handler: function (val) {
this.modalVisible = val
if (val) {
this.search()
}
this.formValidate = this.row
},
immediate: true
},
row: {
handler: function (val) {
if (val.id === 'add') {
this.editMode = false
this.formTitle = '新增音轨'
this.formValidate = {
details: {
name: '',
summary: '',
res_icon: '',
start_space: 0,
end_space: 0,
play_mode: 0,
volume: 0,
track_type: 0
},
ancestors: [],
model_type: this.type,
model_id: 0
}
} else {
this.editMode = true
this.formTitle = '编辑音轨'
this.formValidate = val
}
},
immediate: true
}
},
mounted () {
},
methods: {
submit () {
let res_icon = this.$refs['res_icon'].getData()
if (res_icon) {
this.formValidate.details.res_icon = res_icon
}
this.$refs['formValidate'].validate((valid) => {
if (valid) {
delete this.formValidate.created_at
delete this.formValidate.deleted_at
delete this.formValidate.updated_at
if (this.formValidate.ancestors.length > 0) {
this.formValidate.model_id = this.formValidate.ancestors[this.formValidate.ancestors.length - 1]
}
let params = [this.formValidate]
if (this.editMode) {
editTracks(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
this.close()
this.search()
}
})
} else {
addTracks(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('新增成功')
this.close()
this.search()
}
})
}
} else {
this.$Message.error('您还有必填项未填写')
}
})
},
close () {
this.$emit('update:visible', false)
}
}
}
</script>
<style>
.demo-drawer-footer {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
background: #fff;
}
</style>
<template>
<div class="table">
<Table
:columns="columns"
:data="data"
:loading="$store.state.app.isLoading"
@on-select="onSelect"
@on-select-all="onSelectAll"
border
ref="selection"
>
<template slot="track_type" slot-scope="{ row }">
<span v-if="i.id===row.details.track_type" v-for="(i,idx) in track_type" :key='idx'>{{i.name}}</span>
</template>
<template slot="name" slot-scope="{ row }">
<span>{{row.details.name}}</span>
</template>
<template slot="title" slot-scope="{ row }">
<span>{{row.details.title}}</span>
</template>
<template slot="summary" slot-scope="{ row }">
<span>{{row.details.summary}}</span>
</template>
<template slot="created_at" slot-scope="{ row }">
<span>{{ row.created_at | format}}</span>
</template>
<template slot="icon" slot-scope="{ row }">
<img :src="row.details.res_icon" alt="音轨图标" />
</template>
<template slot="enable" slot-scope="{ row }">
<i-switch :value="row.is_enable===1?true:false" @on-change="(value)=>{change(value,row)}" />
</template>
<template slot="action" slot-scope="{ row }">
<a @click="edit(row)">编辑</a>
</template>
<template slot="edit" slot-scope="{ row }">
<a>编辑</a>
</template>
</Table>
</div>
</template>
<script>
import { formatDate } from '@/libs/util'
import { editTracks } from '@/api/tracks'
export default {
props: {
data: {
type: Array,
default () {
return []
}
}
},
filters: {
format (value) {
return formatDate(value)
}
},
data () {
return {
track_type: [{ id: 0, name: '常规类型' }, { id: 1, name: '语音' }, { id: 2, name: '脑波' }, { id: 3, name: '呼吸引导' }, { id: 4, name: '单音频' }, { id: 5, name: '效果类短音频' }, { id: 6, name: '旋律类短音频' }, { id: 7, name: '混音音轨' }, { id: 8, name: '自定义' }],
columns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: '类型',
slot: 'track_type'
},
{
title: '名称',
slot: 'name'
},
{
title: '介绍',
slot: 'summary',
width: 300
},
{
title: '音轨ICON',
slot: 'icon'
},
{
title: '创建日期',
slot: 'created_at',
width: 200
},
{
title: '生效',
slot: 'enable'
},
{
title: '关联主题',
key: 'theme_name'
},
{
title: '关联条目内容',
key: 'course_content_name',
width: 110
},
{
title: '操作',
slot: 'action'
},
{
title: '编辑素材',
slot: 'edit'
}
]
}
},
methods: {
edit (row) {
this.$emit('edit', row)
},
onSelect (val) {
this.selectList = val.map(p => {
return p.id
})
this.$emit('select', this.selectList)
},
onSelectAll (val) {
this.selectList = val.map(p => {
return p.id
})
this.$emit('select', this.selectList)
},
change (status, row) {
console.log('开关状态:' + status + row)
row.is_enable = status ? 1 : 0
delete row.created_at
delete row.deleted_at
delete row.updated_at
editTracks([row]).then(res => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
}
})
},
handleSelectAll (status) {
this.$refs.selection.selectAll(status)
}
}
}
</script>
<style lang="less">
.table {
margin-top: 20px;
img {
background: #2d3864;
width: 51px;
height: 51px;
}
}
</style>
<template>
<div>
<Drawer
:title="formTitle"
v-model="modalVisible"
width="720"
:mask-closable="false"
:styles="styles"
@on-close="close"
>
<Form
ref="formValidate"
:model="formValidate"
:rules="ruleValidate"
:label-width="150"
v-if="formValidate.details"
>
<FormItem label="关联场景">
<Select clearable v-model="formValidate.scene_id" style="width:80px">
<Option
v-for="item in $store.state.app.sceneObj.scenes"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
</FormItem>
<FormItem label="主题名称" prop="details.name">
<Input
v-model="formValidate.details.name"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="简介" prop="details.summary">
<Input
type="textarea"
v-model="formValidate.details.summary"
placeholder="请输入内容"
></Input>
</FormItem>
<FormItem label="是否显示在首页背景">
<i-switch :value='formValidate.is_home_background===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="首页背景图片" prop="details.res_home_background_pic">
<UploadS ref="res_home_background_pic" v-model="formValidate.details.res_home_background_pic" @del='formValidate.details.res_home_background_pic=""'/>
</FormItem>
<FormItem label="首页背景视频" prop="details.res_home_background_video">
<UploadS ref="res_home_background_video" v-model="formValidate.details.res_home_background_video" :video='true'/>
</FormItem>
<FormItem label="是否显示为首页列表">
<i-switch :value='formValidate.is_in_home_list===1?true:false' @on-change='(value)=>{change(value,2)}'/>
</FormItem>
<FormItem label="是否在探索列表页显示">
<i-switch :value='formValidate.is_in_discover_list===1?true:false' @on-change='(value)=>{change(value,2)}'/>
</FormItem>
<FormItem label="是否在热门标签显示">
<i-switch :value='formValidate.is_hottest===1?true:false' @on-change='(value)=>{change(value,2)}'/>
</FormItem>
<FormItem label="是否置热门顶">
<i-switch :value='formValidate.is_hottest_top===1?true:false' @on-change='(value)=>{change(value,2)}'/>
</FormItem>
<FormItem label="是否在最新标签显示">
<i-switch :value='formValidate.is_newest===1?true:false' @on-change='(value)=>{change(value,2)}'/>
</FormItem>
<FormItem label="是否置最新顶">
<i-switch :value='formValidate.is_newest_top===1?true:false' @on-change='(value)=>{change(value,2)}'/>
</FormItem>
<FormItem label="是否在全部显示">
<i-switch :value='formValidate.is_in_discover_all===1?true:false' @on-change='(value)=>{change(value,2)}'/>
</FormItem>
<FormItem label="首页列表背景图" prop="details.res_home_list_pic">
<UploadS ref="res_home_list_pic" v-model="formValidate.details.res_home_list_pic" />
</FormItem>
<FormItem label="探索页列表背景图" prop="details.res_discover_list_pic">
<UploadS ref="res_discover_list_pic" v-model="formValidate.details.res_discover_list_pic" />
</FormItem>
<FormItem label="唱片背景图" prop="details.res_disc_cover_pic">
<UploadS ref="res_disc_cover_pic" v-model="formValidate.details.res_disc_cover_pic" />
</FormItem>
<FormItem label="默认播放时长" >
<InputNumber v-model="formValidate.details.default_play_time"></InputNumber>
</FormItem>
<FormItem label="封面类型" >
<RadioGroup v-model="formValidate.details.cover_type">
<Radio label="0">
<Icon type="logo-apple"></Icon>
<span>图片</span>
</Radio>
<Radio label="1">
<Icon type="logo-windows"></Icon>
<span>视频</span>
</Radio>
</RadioGroup>
</FormItem>
<FormItem label="封面模糊" >
<InputNumber v-model="formValidate.details.cover_blur"></InputNumber>
</FormItem>
<FormItem label="封面图片" prop="details.res_cover_pic">
<UploadS ref="res_cover_pic" v-model="formValidate.details.res_cover_pic" />
</FormItem>
<FormItem label="封面模式">
<Select clearable v-model="formValidate.details.cover_mode" style="width:80px">
<Option
v-for="item in cover_mode"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
</FormItem>
<FormItem label="封面视频" prop="details.res_cover_video">
<UploadS ref="res_cover_video" v-model="formValidate.details.res_cover_video" :video='true'/>
</FormItem>
</Form>
<div class="demo-drawer-footer">
<Button style="margin-right: 8px" @click="close">取消</Button>
<Button type="primary" @click="submit">保存</Button>
</div>
</Drawer>
</div>
</template>
<script>
import UploadS from '@/view/common/upload.vue'
import {
addThemes,
editThemes
} from '@/api/themes'
const _requireBlur = { required: true, message: ' ', trigger: 'blur' }
const validatorPic = (rule, value, callback) => {
if (!value) {
callback(new Error(' '))
} else {
callback()
}
}
const _requirePic = { required: true, message: ' ', trigger: 'blur', validator: validatorPic }
export default {
name: 'editTheme',
props: {
search: {
type: Function,
default: null
},
row: {
type: Object,
default: () => {}
},
visible: {
type: Boolean,
default: false
}
},
components: {
UploadS
},
data () {
return {
modalVisible: false,
editMode: false,
formTitle: '',
styles: {
height: 'calc(100% - 55px)',
overflow: 'auto',
paddingBottom: '53px',
position: 'static'
},
cover_mode: [{ id: 0, name: '不位移' }, { id: 1, name: '随机方向' }, { id: 2, name: '上下位移' }, { id: 3, name: '左右位移' }, { id: 4, name: '从小到大' }, { id: 5, name: '从大到小' }],
formValidate: {},
ruleValidate: {
'details.name': [_requireBlur],
'details.summary': [_requireBlur],
'details.res_home_background_pic': [_requirePic],
'details.res_home_background_video': [_requirePic],
'details.res_home_list_pic': [_requirePic],
'details.res_discover_list_pic': [_requirePic],
'details.res_disc_cover_pic': [_requirePic],
'details.res_cover_pic': [_requirePic],
'details.res_cover_video': [_requirePic]
}
}
},
watch: {
visible: {
handler: function (val) {
this.modalVisible = val
if (val) {
this.search()
}
this.formValidate = this.row
},
immediate: true
},
row: {
handler: function (val) {
if (val.id === 'add') {
this.editMode = false
this.formTitle = '新增栏目'
this.formValidate = {
details: {
name: '',
summary: '',
default_play_time: 1,
cover_type: '',
res_home_background_pic: '',
res_home_background_video: '',
res_home_list_pic: '',
res_discover_list_pic: '',
res_disc_cover_pic: '',
res_cover_pic: '',
res_cover_video: '',
cover_blur: 0,
cover_mode: 0
},
scene_id: 0,
is_home_background: 0,
is_in_discover_list: 0,
is_in_home_list: 0,
is_hottest: 1,
is_hottest_top: 0,
is_newest: 0,
is_newest_top: 0,
is_in_discover_all: 0
}
} else {
this.editMode = true
this.formTitle = '编辑栏目'
this.formValidate = val
}
},
immediate: true
}
},
methods: {
submit () {
let res_home_background_pic = this.$refs['res_home_background_pic'].getData()
let res_home_background_video = this.$refs['res_home_background_video'].getData()
let res_home_list_pic = this.$refs['res_home_list_pic'].getData()
let res_discover_list_pic = this.$refs['res_discover_list_pic'].getData()
let res_disc_cover_pic = this.$refs['res_disc_cover_pic'].getData()
let res_cover_pic = this.$refs['res_cover_pic'].getData()
let res_cover_video = this.$refs['res_cover_video'].getData()
if (res_home_background_pic) {
this.formValidate.details.res_home_background_pic = res_home_background_pic
}
if (res_home_background_video) {
this.formValidate.details.res_home_background_video = res_home_background_video
}
if (res_home_list_pic) {
this.formValidate.details.res_home_list_pic = res_home_list_pic
}
if (res_discover_list_pic) {
this.formValidate.details.res_discover_list_pic = res_discover_list_pic
}
if (res_disc_cover_pic) {
this.formValidate.details.res_disc_cover_pic = res_disc_cover_pic
}
if (res_cover_pic) {
this.formValidate.details.res_cover_pic = res_cover_pic
}
if (res_cover_video) {
this.formValidate.details.res_cover_video = res_cover_video
}
console.log(this.formValidate)
this.$refs['formValidate'].validate((valid) => {
if (valid) {
delete this.formValidate.created_at
delete this.formValidate.deleted_at
delete this.formValidate.updated_at
let params = [this.formValidate]
if (this.editMode) {
editThemes(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
this.close()
this.search()
}
})
} else {
addThemes(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('新增成功')
this.close()
this.search()
}
})
}
} else {
this.$Message.error('您还有必填项未填写')
}
})
},
change (val, type) {
if (type === 1) {
this.formValidate.is_in_home_lis = val ? 1 : 0
}
if (type === 2) {
this.formValidate.is_in_discover_list = val ? 1 : 0
}
},
close () {
this.$emit('update:visible', false)
}
}
}
</script>
<style>
.demo-drawer-footer {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
background: #fff;
}
</style>
<template>
<div class="table">
<Table :loading="$store.state.app.isLoading" border ref="selection" :columns="columns" :data="data" @on-select='onSelect' @on-select-all='onSelectAll'>
<template slot-scope="{ row }" slot="name">
<span>{{ row.details.name }}</span>
</template>
<template slot-scope="{ row }" slot="summary">
<span>{{ row.details.summary }}</span>
</template>
<template slot-scope="{ row }" slot="default_play_time">
<span>{{ row.details.play_duration }}</span>
</template>
<template slot-scope="{ row }" slot="created_at">
<span>{{ row.created_at | format}}</span>
</template>
<template slot-scope="{ row }" slot="enable">
<i-switch
:value="row.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, row);
}
"
/>
</template>
<template slot-scope="{ row }" slot="action">
<a @click="edit(row)">编辑</a>
</template>
<template slot-scope="{ row }" slot="editGood">
<a>编辑</a>
</template>
<template slot-scope="{ row }" slot="editSoundTrack">
<a @click="editSoundTrack(row)">编辑</a>
</template>
</Table>
</div>
</template>
<script>
import { formatDate } from '@/libs/util'
import {
editThemes
} from '@/api/themes'
export default {
props: {
data: {
type: Array,
default () {
return []
}
}
},
data () {
return {
selectList: [],
columns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: '名称',
slot: 'name'
},
{
title: '简介',
slot: 'summary',
width: 300
},
{
title: '默认播放时长',
slot: 'default_play_time'
},
{
title: '关联场景',
key: 'scene_name'
},
{
title: '创建日期',
slot: 'created_at',
width: 200,
key: 'created_at'
},
{
title: '生效',
slot: 'enable'
},
{
title: '操作',
slot: 'action'
},
{
title: '编辑商品',
slot: 'editGood'
},
{
title: '编辑音轨',
slot: 'editSoundTrack'
}
]
}
},
mounted () {
},
filters: {
format (value) {
return formatDate(value)
}
},
methods: {
edit (row) {
this.$emit('edit', row)
},
editSoundTrack (row) {
this.$router.push({ name: 'soundTrack', query: { row: row } })
},
onSelect (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
onSelectAll (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
change (status, row) {
console.log('开关状态:' + status + row)
row.is_enable = status ? 1 : 0
let data = (({ details, id, is_enable, is_in_discover_list, is_in_home_list }) => ({ details, id, is_enable, is_in_discover_list, is_in_home_list }))(row)
editThemes([data]).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
}
})
},
handleSelectAll (status) {
this.$refs.selection.selectAll(status)
}
}
}
</script>
<style lang="less">
.table {
margin-top: 20px;
}
</style>
<template>
<error-content code="401" desc="Oh~~您没有浏览这个页面的权限~" :src="src"/>
</template>
<script>
import error401 from '@/assets/images/error-page/error-401.svg'
import errorContent from './error-content.vue'
export default {
name: 'error_401',
components: {
errorContent
},
data () {
return {
src: error401
}
}
}
</script>
<template>
<error-content code="404" desc="Oh~~您的页面好像飞走了~" :src="src"/>
</template>
<script>
import error404 from '@/assets/images/error-page/error-404.svg'
import errorContent from './error-content.vue'
export default {
name: 'error_404',
components: {
errorContent
},
data () {
return {
src: error404
}
}
}
</script>
<template>
<error-content code="500" desc="Oh~~鬼知道服务器经历了什么~" :src="src"/>
</template>
<script>
import error404 from '@/assets/images/error-page/error-500.svg'
import errorContent from './error-content.vue'
export default {
name: 'error_500',
components: {
errorContent
},
data () {
return {
src: error404
}
}
}
</script>
<template>
<div>
<Button size="large" type="text" @click="backHome">返回首页</Button>
<Button size="large" type="text" @click="backPrev">返回上一页({{ second }}s)</Button>
</div>
</template>
<script>
import './error.less'
export default {
name: 'backBtnGroup',
data () {
return {
second: 5,
timer: null
}
},
methods: {
backHome () {
this.$router.replace({
name: this.$config.homeName
})
},
backPrev () {
this.$router.go(-1)
}
},
mounted () {
this.timer = setInterval(() => {
if (this.second === 0) this.backPrev()
else this.second--
}, 1000)
},
beforeDestroy () {
clearInterval(this.timer)
}
}
</script>
<template>
<div class="error-page">
<div class="content-con">
<img :src="src" :alt="code">
<div class="text-con">
<h4>{{ code }}</h4>
<h5>{{ desc }}</h5>
</div>
<back-btn-group class="back-btn-group"></back-btn-group>
</div>
</div>
</template>
<script>
import './error.less'
import backBtnGroup from './back-btn-group.vue'
export default {
name: 'error_content',
components: {
backBtnGroup
},
props: {
code: String,
desc: String,
src: String
}
}
</script>
.error-page{
width: 100%;
height: 100%;
position: relative;
background: #f8f8f9;
.content-con{
width: 700px;
height: 600px;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -60%);
img{
display: block;
width: 100%;
height: 100%;
}
.text-con{
position: absolute;
left: 0px;
top: 0px;
h4{
position: absolute;
left: 0px;
top: 0px;
font-size: 80px;
font-weight: 700;
color: #348EED;
}
h5{
position: absolute;
width: 700px;
left: 0px;
top: 100px;
font-size: 20px;
font-weight: 700;
color: #67647D;
}
}
.back-btn-group{
position: absolute;
right: 0px;
bottom: 20px;
}
}
}
.login{
width: 100%;
height: 100%;
background-image: url('../../assets/images/login-bg.jpg');
background-size: cover;
background-position: center;
position: relative;
&-con{
position: absolute;
right: 160px;
top: 50%;
transform: translateY(-60%);
width: 300px;
&-header{
font-size: 16px;
font-weight: 300;
text-align: center;
padding: 30px 0;
}
.form-con{
padding: 10px 0 0;
}
.login-tip{
font-size: 10px;
text-align: center;
color: #c3c3c3;
}
}
}
<style lang="less">
@import './login.less';
</style>
<template>
<div class="login">
<div class="login-con">
<Card icon="log-in" title="欢迎登录" :bordered="false">
<div class="form-con">
<login-form @on-success-valid="handleSubmit"></login-form>
<!-- <p class="login-tip">输入任意用户名和密码即可</p> -->
</div>
</Card>
</div>
</div>
</template>
<script>
import LoginForm from '_c/login-form'
import { mapActions } from 'vuex'
import { $http } from '@/libs/http'
import { setToken } from '@/libs/util'
export default {
components: {
LoginForm
},
methods: {
...mapActions([
'handleLogin',
'getUserInfo'
]),
handleSubmit ({ username, password }) {
$http
.postParams('/manager/login', {
account: username,
pwd: password
})
.then(data => {
console.log(data)
if (data.status === 200) {
let token = data.data
setToken(token)
this.$Message.success('登录成功')
this.$router.push({
name: this.$config.homeName
})
} else {
this.errorMsg = data.msg
}
})
.catch(() => { })
}
}
}
</script>
<style>
</style>
<template>
<div>
<Drawer
:title="formTitle"
v-model="modalVisible"
width="720"
:mask-closable="false"
:styles="styles"
@on-close="close"
>
<Form
ref="formValidate"
:model="formValidate"
:rules="ruleValidate"
:label-width="150"
v-if="formValidate.details"
>
<FormItem label="资源名称" prop="details.name">
<Input
v-model="formValidate.details.name"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="描述" prop="details.summary">
<Input
type="textarea"
v-model="formValidate.details.summary"
placeholder="请输入内容"
></Input>
</FormItem>
<FormItem label="资源文件" prop="res_uri">
<FileUpload ref="res_uri" v-model="formValidate.res_uri" />
</FormItem>
<FormItem label="资源长度" prop="details.length">
<Input
v-model="formValidate.details.length"
placeholder="Enter something"
></Input>
</FormItem>
</Form>
<div class="demo-drawer-footer">
<Button style="margin-right: 8px" @click="close">取消</Button>
<Button type="primary" @click="submit">保存</Button>
</div>
</Drawer>
</div>
</template>
<script>
import FileUpload from '@/view/common/FileUpload.vue'
import {
addResources,
editResources
} from '@/api/resources'
import {
getTreeCategories
} from '@/api/categories'
const _requireBlur = { required: true, message: ' ', trigger: 'blur' }
const validatorPic = (rule, value, callback) => {
if (!value) {
callback(new Error(' '))
} else {
callback()
}
}
const _requirePic = { required: true, message: ' ', trigger: 'blur', validator: validatorPic }
export default {
props: {
search: {
type: Function,
default: null
},
treeData: {
type: Array,
default: () => []
},
row: {
type: Object,
default: () => {}
},
visible: {
type: Boolean,
default: false
}
},
components: {
FileUpload
},
data () {
return {
modalVisible: false,
editMode: false,
formTitle: '',
styles: {
height: 'calc(100% - 55px)',
overflow: 'auto',
paddingBottom: '53px',
position: 'static'
},
formValidate: {},
ruleValidate: {
'details.name': [_requireBlur],
'details.summary': [_requireBlur],
'res_uri': [_requirePic],
'details.length': [_requireBlur]
}
}
},
watch: {
visible: {
handler: function (val) {
this.modalVisible = val
if (val) {
this.search()
}
this.formValidate = this.row
},
immediate: true
},
row: {
handler: function (val) {
console.log(val)
if (val.id === 'add') {
this.editMode = false
this.formTitle = '新增板块'
this.formValidate = {
details: {
name: '',
summary: '',
length: ''
},
res_uri: ''
}
} else {
this.editMode = true
this.formTitle = '编辑板块'
this.formValidate = val
this.formValidate.res_uri = {
url: val.res_uri,
name: val.res_digest
}
}
},
immediate: true
}
},
methods: {
submit () {
let res_uri = this.$refs['res_uri'].getData()
if (res_uri.url) {
this.formValidate.res_uri = res_uri.url
this.formValidate.res_digest = res_uri.name
}
console.log(res_uri)
this.$refs['formValidate'].validate((valid) => {
if (valid) {
delete this.formValidate.created_at
delete this.formValidate.deleted_at
delete this.formValidate.updated_at
let params = [this.formValidate]
if (this.editMode) {
editResources(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
this.close()
this.search()
}
})
} else {
addResources(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('新增成功')
this.close()
this.search()
}
})
}
} else {
this.$Message.error('您还有必填项未填写')
}
})
},
getTree (e) {
getTreeCategories(e).then((res) => {
console.log(res)
})
},
change (val, type) {
if (type === 1) {
this.formValidate.is_in_home_lis = val ? 1 : 0
}
if (type === 2) {
this.formValidate.is_in_discover_list = val ? 1 : 0
}
},
close () {
this.$emit('update:visible', false)
}
}
}
</script>
<style>
.demo-drawer-footer {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
background: #fff;
}
</style>
<template>
<div class="content">
<div class="topSearch">
<div class="left">
<Input style="width:280px" search enter-button placeholder="" @on-search='search'/>
<span class="lable">
场景
</span>
<Select style="width:80px">
<Option
v-for="item in $store.state.app.sceneObj.scenes"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
<span class="lable">
主题
</span>
<Select style="width:80px">
<Option
v-for="item in $store.state.app.sceneObj.themes"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
</div>
<div class="right">
<Button class='success-btn' type="success" @click="seq">排序</Button>
<Button @click="add" type="primary">新增</Button>
<Button type="error" @click="del">删除</Button>
</div>
</div>
<Stable :data='data' @edit='edit' @select='select'/>
<Forms :visible.sync="visible" :search='search' :row='row' :treeData='treeData'/>
<draggableList :visible.sync="draggableVisible" :list='data' @ok='ok'/>
</div>
</template>
<script>
import Stable from './table.vue'
import Forms from './form.vue'
import draggableList from '@/view/common/draggable.vue'
import {
getResources,
delResources,
editResources
} from '@/api/resources'
import {
getTreeCategorie2
} from '@/api/categories'
export default {
name: 'sections',
components: {
Stable,
Forms,
draggableList
},
data () {
return {
treeData: [],
selectList: [],
visible: false,
draggableVisible: false,
row: {},
data: []
}
},
methods: {
edit (row) {
this.visible = true
this.row = row
},
add () {
this.visible = true
this.row = {
id: 'add'
}
},
select (val) {
this.selectList = val
},
seq () {
this.draggableVisible = true
},
ok (list) {
editResources(list).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('操作成功')
this.draggableVisible = false
this.search()
}
})
},
del () {
delResources(this.selectList).then((res) => {
if (res.code === 200) {
this.$Message.success('删除成功')
this.search()
} else {
this.$Message.error('删除失败')
}
})
},
getTree () {
getTreeCategorie2('course').then((res) => {
this.treeData = res.data.categories.map((p) => {
p.label = p.name
p.value = p.id
p.children = p.courses
if (p.courses.length > 0) {
this.recursion(p.children)
}
return p
})
})
},
recursion (data) {
data = data.map((p) => {
p.label = p.name
p.value = p.id
return p
})
},
search () {
getResources({}).then((res) => {
this.data = res.data
})
}
},
mounted () {
this.search()
this.getTree()
}
}
</script>
<style lang="less"></style>
<template>
<div class="table">
<Table :loading="$store.state.app.isLoading" border ref="selection" :columns="columns" :data="data" @on-select='onSelect' @on-select-all='onSelectAll'>
<template slot-scope="{ row }" slot="name">
<span>{{ row.details.name }}</span>
</template>
<template slot-scope="{ row }" slot="summary">
<span>{{ row.details.summary }}</span>
</template>
<template slot-scope="{ row }" slot="created_at">
<span>{{ row.created_at | format}}</span>
</template>
<template slot-scope="{ row }" slot="enable">
<i-switch
:value="row.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, row);
}
"
/>
</template>
<template slot-scope="{ row }" slot="action">
<a @click="edit(row)">编辑</a>
</template>
</Table>
</div>
</template>
<script>
import { formatDate } from '@/libs/util'
import {
editResources
} from '@/api/resources'
export default {
props: {
data: {
type: Array,
default () {
return []
}
}
},
data () {
return {
selectList: [],
columns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: '名称',
slot: 'name'
},
{
title: '描述',
slot: 'summary'
},
{
title: '所属音轨',
key: 'track_name'
},
{
title: '创建日期',
slot: 'created_at',
width: 200,
key: 'created_at'
},
{
title: '生效',
slot: 'enable'
},
{
title: '操作',
slot: 'action'
}
]
}
},
mounted () {
},
filters: {
format (value) {
return formatDate(value)
}
},
methods: {
edit (row) {
this.$emit('edit', row)
},
onSelect (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
onSelectAll (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
change (status, row) {
console.log('开关状态:' + status + row)
row.is_enable = status ? 1 : 0
delete row.created_at
delete row.deleted_at
delete row.updated_at
editResources([row]).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
}
})
},
handleSelectAll (status) {
this.$refs.selection.selectAll(status)
}
}
}
</script>
<style lang="less">
.table {
margin-top: 20px;
}
</style>
<template>
<div class="content">
<div class="topSearch">
<div class="left">
<Input style="width:280px" search enter-button placeholder="" />
</div>
<div class="right">
<Button type="success">排序</Button>
<Button type="primary">新增</Button>
<Button type="error">删除</Button>
</div>
</div>
<Stable/>
</div>
</template>
<script>
import Stable from '@/view/components/ringTone/table.vue'
export default {
name: 'ringTone',
components: {
Stable
},
data () {
return {
cityList: [
{
value: 'New York',
label: 'New York'
},
{
value: 'London',
label: 'London'
},
{
value: 'Sydney',
label: 'Sydney'
},
{
value: 'Ottawa',
label: 'Ottawa'
},
{
value: 'Paris',
label: 'Paris'
},
{
value: 'Canberra',
label: 'Canberra'
}
],
model1: ''
}
},
mounted () {
//
}
}
</script>
<style lang="less"></style>
<template>
<div>
<Drawer
:title="formTitle"
v-model="modalVisible"
width="720"
:mask-closable="false"
:styles="styles"
@on-close="close"
>
<Form
ref="formValidate"
:model="formValidate"
:rules="ruleValidate"
:label-width="150"
>
<FormItem label="资源名称" prop="title">
<Input
v-model="formValidate.title"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="描述" prop="introduction">
<Input
type="textarea"
v-model="formValidate.introduction"
placeholder="请输入内容"
></Input>
</FormItem>
<FormItem label="资源文件" prop="res_uri">
<FileUpload ref="res_uri" v-model="formValidate.res_uri" />
</FormItem>
<FormItem label="资源长度" prop="play_duration">
<InputNumber v-model="formValidate.play_duration"></InputNumber>
</FormItem>
</Form>
<div class="demo-drawer-footer">
<Button style="margin-right: 8px" @click="close">取消</Button>
<Button type="primary" @click="submit">保存</Button>
</div>
</Drawer>
</div>
</template>
<script>
import FileUpload from '@/view/common/FileUpload.vue'
import {
addRings,
editRings
} from '@/api/rings'
import {
getTreeCategories
} from '@/api/categories'
const _requireBlur = { required: true, message: ' ', trigger: 'blur' }
const validatorPic = (rule, value, callback) => {
if (!value) {
callback(new Error(' '))
} else {
callback()
}
}
const _requirePic = { required: true, message: ' ', trigger: 'blur', validator: validatorPic }
export default {
props: {
search: {
type: Function,
default: null
},
treeData: {
type: Array,
default: () => []
},
row: {
type: Object,
default: () => {}
},
visible: {
type: Boolean,
default: false
}
},
components: {
FileUpload
},
data () {
return {
modalVisible: false,
editMode: false,
formTitle: '',
styles: {
height: 'calc(100% - 55px)',
overflow: 'auto',
paddingBottom: '53px',
position: 'static'
},
formValidate: {},
ruleValidate: {
'title': [_requireBlur],
'introduction': [_requireBlur],
'res_uri': [_requirePic],
'play_duration': [_requirePic]
}
}
},
watch: {
visible: {
handler: function (val) {
this.modalVisible = val
if (val) {
this.search()
}
this.formValidate = this.row
},
immediate: true
},
row: {
handler: function (val) {
console.log(val)
if (val.id === 'add') {
this.editMode = false
this.formTitle = '新增铃声'
this.formValidate = {
title: '',
introduction: '',
res_uri: '',
play_duration: ''
}
} else {
this.editMode = true
this.formTitle = '编辑铃声'
this.formValidate = val
this.formValidate.res_uri = {
url: val.res_uri,
name: val.res_digest
}
}
},
immediate: true
}
},
methods: {
submit () {
let res_uri = this.$refs['res_uri'].getData()
if (res_uri.url) {
this.formValidate.res_uri = res_uri.url
this.formValidate.res_digest = res_uri.name
}
console.log(res_uri)
this.$refs['formValidate'].validate((valid) => {
if (valid) {
delete this.formValidate.created_at
delete this.formValidate.deleted_at
delete this.formValidate.updated_at
let params = [this.formValidate]
if (this.editMode) {
editRings(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
this.close()
this.search()
}
})
} else {
addRings(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('新增成功')
this.close()
this.search()
}
})
}
} else {
this.$Message.error('您还有必填项未填写')
}
})
},
getTree (e) {
getTreeCategories(e).then((res) => {
console.log(res)
})
},
change (val, type) {
if (type === 1) {
this.formValidate.is_in_home_lis = val ? 1 : 0
}
if (type === 2) {
this.formValidate.is_in_discover_list = val ? 1 : 0
}
},
close () {
this.$emit('update:visible', false)
}
}
}
</script>
<style>
.demo-drawer-footer {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
background: #fff;
}
</style>
<template>
<div class="content">
<div class="topSearch">
<div class="left">
<Input style="width:280px" search enter-button placeholder="" @on-search='search'/>
<span class="lable">
场景
</span>
<Select style="width:80px">
<Option
v-for="item in $store.state.app.sceneObj.scenes"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
<span class="lable">
主题
</span>
<Select style="width:80px">
<Option
v-for="item in $store.state.app.sceneObj.themes"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
</div>
<div class="right">
<Button class='success-btn' type="success" @click="seq">排序</Button>
<Button @click="add" type="primary">新增</Button>
<Button type="error" @click="del">删除</Button>
</div>
</div>
<Stable :data='data' @edit='edit' @select='select'/>
<Forms :visible.sync="visible" :search='search' :row='row' :treeData='treeData'/>
<draggableList :visible.sync="draggableVisible" :list='data' @ok='ok'/>
</div>
</template>
<script>
import Stable from './table.vue'
import Forms from './form.vue'
import draggableList from '@/view/common/draggable.vue'
import {
getRings,
delRings,
editRings
} from '@/api/rings'
import {
getTreeCategorie2
} from '@/api/categories'
export default {
name: 'sections',
components: {
Stable,
Forms,
draggableList
},
data () {
return {
treeData: [],
selectList: [],
visible: false,
draggableVisible: false,
row: {},
data: []
}
},
methods: {
edit (row) {
this.visible = true
this.row = row
},
add () {
this.visible = true
this.row = {
id: 'add'
}
},
select (val) {
this.selectList = val
},
seq () {
this.draggableVisible = true
},
ok (list) {
editRings(list).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('操作成功')
this.draggableVisible = false
this.search()
}
})
},
del () {
delRings(this.selectList).then((res) => {
if (res.code === 200) {
this.$Message.success('删除成功')
this.search()
} else {
this.$Message.error('删除失败')
}
})
},
getTree () {
getTreeCategorie2('course').then((res) => {
this.treeData = res.data.categories.map((p) => {
p.label = p.name
p.value = p.id
p.children = p.courses
if (p.courses.length > 0) {
this.recursion(p.children)
}
return p
})
})
},
recursion (data) {
data = data.map((p) => {
p.label = p.name
p.value = p.id
return p
})
},
search () {
getRings({}).then((res) => {
this.data = res.data.map((p) => {
p.name = p.title
return p
})
})
}
},
mounted () {
this.search()
this.getTree()
}
}
</script>
<style lang="less"></style>
<template>
<div class="table">
<Table :loading="$store.state.app.isLoading" border ref="selection" :columns="columns" :data="data" @on-select='onSelect' @on-select-all='onSelectAll'>
<template slot-scope="{ row }" slot="name">
<span>{{ row.name }}</span>
</template>
<template slot-scope="{ row }" slot="summary">
<span>{{ row.introduction }}</span>
</template>
<template slot-scope="{ row }" slot="created_at">
<span>{{ row.created_at | format}}</span>
</template>
<template slot-scope="{ row }" slot="enable">
<i-switch
:value="row.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, row);
}
"
/>
</template>
<template slot-scope="{ row }" slot="action">
<a @click="edit(row)">编辑</a>
</template>
</Table>
</div>
</template>
<script>
import { formatDate } from '@/libs/util'
import {
editRings
} from '@/api/rings'
export default {
props: {
data: {
type: Array,
default () {
return []
}
}
},
data () {
return {
selectList: [],
columns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: '名称',
slot: 'name'
},
{
title: '描述',
slot: 'summary'
},
{
title: '创建日期',
slot: 'created_at',
width: 200,
key: 'created_at'
},
{
title: '生效',
slot: 'enable'
},
{
title: '操作',
slot: 'action'
}
]
}
},
mounted () {
},
filters: {
format (value) {
return formatDate(value)
}
},
methods: {
edit (row) {
this.$emit('edit', row)
},
onSelect (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
onSelectAll (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
change (status, row) {
console.log('开关状态:' + status + row)
row.is_enable = status ? 1 : 0
delete row.created_at
delete row.deleted_at
delete row.updated_at
editRings([row]).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
}
})
},
handleSelectAll (status) {
this.$refs.selection.selectAll(status)
}
}
}
</script>
<style lang="less">
.table {
margin-top: 20px;
}
</style>
<template>
<div class="soundTrack">
<Tabs type="card" :animated="false" @on-click='onClick'>
<TabPane label="放松">
<div class="topSearch">
<div class="left">
<Input style="width:200px" search enter-button placeholder="" @on-search='onSearch'/>
<span class="lable">
关联主题
</span>
<Cascader style="width:250px" :data="treeData" @on-change="handleChange" v-model="seleted"></Cascader>
<!-- <span class="lable">
场景
</span>
<Select clearable v-model="model1" style="width:80px">
<Option
v-for="item in $store.state.app.sceneObj.scenes"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
<span class="lable">
主题
</span>
<Select clearable v-model="model1" style="width:80px">
<Option
v-for="item in $store.state.app.sceneObj.themes"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select> -->
</div>
<div class="right">
<Button type="success" @click="seq">排序</Button>
<Button type="info">预览</Button>
<Button type="warning">复制</Button>
<Button @click="add" type="primary">新增</Button>
<Button type="error" @click="del">删除</Button>
</div>
</div>
<Stable :data='data' @edit='edit' @select='select'/>
</TabPane>
<TabPane label="常规">
<div class="topSearch">
<div class="left">
<Input style="width:200px" search enter-button placeholder="" @on-search='onSearch'/>
<span class="lable">
关联内容
</span>
<Cascader style="width:250px" :data="treeData2" @on-change="handleChange"></Cascader>
<!-- <span class="lable">
栏目
</span>
<Select clearable v-model="model1" style="width:80px">
<Option
v-for="item in $store.state.app.categoryObj.categories"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
<span class="lable">
条目
</span>
<Select clearable v-model="model1" style="width:80px">
<Option
v-for="item in $store.state.app.categoryObj.courses"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
<span class="lable">
板块
</span>
<Select clearable v-model="model1" style="width:80px">
<Option
v-for="item in $store.state.app.categoryObj.sections"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
<span class="lable">
内容
</span>
<Select clearable v-model="model1" style="width:80px">
<Option
v-for="item in $store.state.app.categoryObj.contents"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select> -->
</div>
<div class="right">
<Button type="success" @click="seq">排序</Button>
<Button type="info">预览</Button>
<Button type="warning">复制</Button>
<Button @click="add" type="primary">新增</Button>
<Button type="error" @click="del">删除</Button>
</div>
</div>
<Stable :data='data' @edit='edit' @select='select'/>
</TabPane>
</Tabs>
<Forms :visible.sync="visible" :search='search' :row='row' :treeData='type===1?treeData:treeData2' :type='type'/>
<draggableList :visible.sync="draggableVisible" :list='data' @ok='ok'/>
</div>
</template>
<script>
import Forms from '@/view/components/soundTrack/form.vue'
import Stable from '@/view/components/soundTrack/table.vue'
import draggableList from '@/view/common/draggable.vue'
import {
getTracks,
editTracks,
delTracks
} from '@/api/tracks'
import {
getTreeScene2
} from '@/api/scenes'
import {
getTreeCategorie2
} from '@/api/categories'
export default {
name: 'soundTrack',
components: {
Forms,
Stable,
draggableList
},
data () {
return {
type: 1,
seleted: [],
visible: false,
draggableVisible: false,
treeData: [],
treeData2: [],
selectList: [],
categoryObj: {},
sceneObj: {},
row: {},
data: []
}
},
methods: {
handleChange (val) {
console.log(val)
this.search({
theme: val[val.length - 1]
})
},
edit (row) {
this.visible = true
this.row = row
},
add () {
this.visible = true
this.row = {
id: 'add'
}
},
select (val) {
this.selectList = val
},
seq () {
this.draggableVisible = true
},
ok (list) {
editTracks(list).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('操作成功')
this.draggableVisible = false
this.search()
}
})
},
del () {
delTracks(this.selectList).then((res) => {
if (res.code === 200) {
this.$Message.success('删除成功')
this.search()
} else {
this.$Message.error('删除失败')
}
})
},
getTree () {
getTreeScene2('track').then((res) => {
this.treeData = res.data.scenes.map((p) => {
p.label = p.name
p.value = p.id
p.children = p.themes
if (p.themes.length > 0) {
this.recursion(p.children)
}
return p
})
})
},
recursion (data) {
data = data.map((p) => {
p.label = p.name
p.value = p.id
return p
})
},
getTree2 () {
getTreeCategorie2('content').then((res) => {
this.treeData2 = res.data.categories.map((p) => {
p.label = p.name
p.value = p.id
p.children = p.courses
if (p.courses.length > 0) {
this.recursion2(p.children)
}
return p
})
})
},
recursion2 (data) {
data = data.map((p) => {
p.label = p.name
p.value = p.id
p.children = p.sections
if (p.children.length > 0) {
p.children = p.children.map((i) => {
i.label = i.details.name
i.value = i.id
return i
})
}
return p
})
},
onClick (e) {
this.type = e + 1
console.log(this.type)
},
onSearch (p) {
this.search({
keyword: p
})
},
search (p) {
getTracks(p).then((res) => {
this.data = res.data
})
}
},
activated: function () {
let id = this.$route.query.row && this.$route.query.row.id
this.seleted = this.$route.query.row && this.$route.query.row.ancestors
this.seleted[this.seleted.length] = id
let parmas = {
theme: id
}
if (parmas) {
this.search(parmas)
} else {
this.search({})
}
},
mounted () {
this.getTree()
this.getTree2()
}
}
</script>
<style lang="less">
.soundTrack {
position: relative;
/deep/ .ivu-tabs{
min-height: 60vh;
}
}
</style>
<template>
<div>
<Drawer
:title="formTitle"
v-model="modalVisible"
width="720"
:mask-closable="false"
:styles="styles"
@on-close="close"
>
<Form
ref="formValidate"
:model="formValidate"
:rules="ruleValidate"
:label-width="150"
>
<Tabs type="card" @on-click='onClick'>
<TabPane label="商品"></TabPane>
<TabPane label="展示页"></TabPane>
</Tabs>
<template v-if="type===0">
<FormItem label="商品名称" prop="title">
<Input
v-model="formValidate.title"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="是否所有用户免费">
<i-switch :value='formValidate.is_all_free===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否要求VIP">
<i-switch :value='formValidate.need_vip===1?true:false' @on-change='(value)=>{change(value,2)}'/>
</FormItem>
<FormItem label="VIP用户免费">
<i-switch :value='formValidate.is_vip_free ===1?true:false' @on-change='(value)=>{change(value,3)}'/>
</FormItem>
<FormItem label="VIP折扣" prop="vip_off">
<InputNumber v-model="formValidate.vip_off"></InputNumber>
</FormItem>
<FormItem label="划去价格" prop="normal_price">
<InputNumber v-model="formValidate.normal_price"></InputNumber>
</FormItem>
<FormItem label="实际价格" prop="real_price">
<InputNumber v-model="formValidate.real_price"></InputNumber>
</FormItem>
</template>
<template v-else>
<FormItem label="页面主标题" prop="show_title">
<Input
v-model="formValidate.show_title"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="是否开启介绍板块">
<i-switch :value='formValidate.intro_section.is_enable===1?true:false' @on-change='(value)=>{change(value,4)}'/>
</FormItem>
<FormItem label="标题" prop="intro_section.title">
<Input
v-model="formValidate.intro_section.title"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="是否显示热销角标" >
<i-switch :value='formValidate.intro_section.is_hot===1?true:false' @on-change='(value)=>{change(value,5)}'/>
</FormItem>
<FormItem label="介绍" prop="show_title">
<Input
type="textarea"
v-model="formValidate.intro_section.introduction"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="是否开启特色板块">
<i-switch :value='formValidate.special_section.is_enable===1?true:false' @on-change='(value)=>{change(value,6)}'/>
</FormItem>
<FormItem label="标题" prop="show_title">
<Input
v-model="formValidate.special_section.title"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="特色" prop="show_title">
<Input
style="margin-bottom: 10px;"
v-for="(item, idx) in formValidate.special_section.features"
:key="idx"
v-model="item.name"
placeholder="Enter something"
></Input>
<div>
<Icon
@click="addFeatures"
:size="20"
color="#7744FF"
style="margin-right: 6px;"
type="md-add"
/><Icon
v-if="formValidate.special_section.features.length > 1"
@click="delFeatures"
color="#7744FF"
:size="20"
type="md-remove"
/>
</div>
</FormItem>
<FormItem label="是否开启课程体系板块">
<i-switch :value='formValidate.content_section.is_enable===1?true:false' @on-change='(value)=>{change(value,7)}'/>
</FormItem>
<FormItem label="标题" prop="show_title">
<Input
v-model="formValidate.content_section.title"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="展示图片" prop="show_title">
<UploadS maxLength="20" ref="content_section_res_content_pics" v-model="formValidate.content_section.res_content_pics" />
</FormItem>
<FormItem label="是否开启课程目录">
<i-switch :value='formValidate.content_table.is_enable===1?true:false' @on-change='(value)=>{change(value,8)}'/>
</FormItem>
<FormItem label="标题" prop="show_title">
<Input
v-model="formValidate.content_table.title"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="课程目录" prop="show_title">
<div ref="editor" style="text-align:left"></div>
</FormItem>
<FormItem label="是否开启条目内容板块">
<i-switch :value='formValidate.course_struct_section.is_enable===1?true:false' @on-change='(value)=>{change(value,9)}'/>
</FormItem>
<FormItem label="标题" prop="show_title">
<Input
v-model="formValidate.course_struct_section.title"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="展示图片" prop="show_title">
<UploadS maxLength="20" ref="course_struct_section_res_content_pics" v-model="formValidate.course_struct_section.res_content_pics" />
</FormItem>
<FormItem label="是否开启用户评价板块">
<i-switch
:value="formValidate.user_comment_section.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, 10);
}
"
/>
</FormItem>
<FormItem label="标题" prop="show_title">
<Input
v-model="formValidate.user_comment_section.title"
placeholder="Enter something"
></Input>
</FormItem>
<div v-for="(item, idx) in formValidate.user_comment_section.comments"
:key="idx">
<FormItem label="用户头像" prop="show_title">
<UploadS
:ref="gravatar+idx.toString()"
v-model="item.gravatar"
/>
</FormItem>
<FormItem label="用户昵称" prop="show_title">
<Input
v-model="item.nickname"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="评论内容" prop="show_title">
<Input
v-model="item.comment" type="textarea"
placeholder="Enter something"
></Input>
</FormItem>
</div>
<div style="margin-left: 150px;margin-bottom: 20px;">
<Icon
@click="addComments"
:size="20"
color="#7744FF"
style="margin-right: 6px;"
type="md-add"
/><Icon
v-if="formValidate.user_comment_section.comments.length > 1"
@click="delComments"
color="#7744FF"
:size="20"
type="md-remove"
/>
</div>
<FormItem label="是否开启纯图展示板块">
<i-switch :value='formValidate.show_section.is_enable===1?true:false' @on-change='(value)=>{change(value,11)}'/>
</FormItem>
<FormItem label="标题" prop="show_title">
<Input
v-model="formValidate.show_section.title"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="展示图片" prop="show_title">
<UploadS maxLength="20" ref="show_section_res_show_pics" v-model="formValidate.show_section.res_show_pics" />
</FormItem>
</template>
</Form>
<div class="demo-drawer-footer">
<Button style="margin-right: 8px" @click="close">取消</Button>
<Button type="primary" @click="submit">保存</Button>
</div>
</Drawer>
</div>
</template>
<script>
import UploadS from '@/view/common/upload.vue'
import E from 'wangeditor'
import {
addShop,
editShop
} from '@/api/shop'
const _requireBlur = { required: true, message: ' ', trigger: 'blur' }
const validatorPic = (rule, value, callback) => {
if (!value) {
callback(new Error(' '))
} else {
callback()
}
}
const _requirePic = { required: true, message: ' ', trigger: 'blur', validator: validatorPic }
export default {
props: {
search: {
type: Function,
default: null
},
row: {
type: Object,
default: () => {}
},
visible: {
type: Boolean,
default: false
}
},
components: {
UploadS
},
data () {
return {
type: 0,
gravatar: 'gravatar',
modalVisible: false,
editMode: false,
formTitle: '',
editorContent: '',
styles: {
height: 'calc(100% - 55px)',
overflow: 'auto',
paddingBottom: '53px',
position: 'static'
},
formValidate: {},
ruleValidate: {
'title': [_requireBlur],
'vip_off': [_requirePic],
'normal_price': [_requirePic],
'real_price': [_requirePic],
'show_title': [_requireBlur],
'intro_section.title': [_requireBlur],
'intro_section.introduction': [_requireBlur],
'details.res_home_background_pic': [_requirePic],
'details.res_discover_list_pic': [_requirePic]
}
}
},
watch: {
visible: {
handler: function (val) {
this.modalVisible = val
},
immediate: true
},
row: {
handler: function (val) {
console.log(val)
if (val.id === 'add') {
this.editMode = false
this.formTitle = '新增商品'
this.formValidate = {
title: '',
need_vip: 0,
is_all_free: 0,
is_vip_free: 0,
vip_off: 0,
normal_price: 0,
real_price: 0,
show_title: '',
intro_section: {
title: '',
is_hot: 0,
is_enable: 1,
introduction: ''
},
special_section: {
features: [{ name: '' }],
is_enable: 1,
title: ''
},
content_section: {
is_enable: 1,
res_content_pics: [],
title: ''
},
course_struct_section: {
is_enable: 1,
res_content_pics: [],
title: ''
},
content_table: {
is_enable: 1,
tables: '',
title: ''
},
user_comment_section: {
is_enable: 1,
title: '',
comments: [{ comment: '', gravatar: '', nickname: '' }]
},
show_section: {
is_enable: 1,
res_show_pics: [],
title: ''
}
}
} else {
this.editMode = true
this.formTitle = '编辑商品'
this.formValidate = val
this.formValidate.special_section.features = val.special_section.features !== null && val.special_section.features.map((p) => {
let params = {
'name': p
}
return params
})
}
},
immediate: true
}
},
mounted () {
},
methods: {
addFeatures () {
this.formValidate.special_section.features.push({ name: '' })
},
delFeatures () {
this.formValidate.special_section.features.splice(
this.formValidate.special_section.features.length - 1,
1
)
},
addComments () {
this.formValidate.user_comment_section.comments.push({ comment: '', gravatar: '', nickname: '' })
},
delComments () {
this.formValidate.user_comment_section.comments.splice(
this.formValidate.user_comment_section.comments.length - 1,
1
)
},
onClick (e) {
this.type = e
if (e === 0) {
this.formTitle = '商品'
} else {
this.formTitle = '展示页'
this.$nextTick(() => {
var editor = new E(this.$refs.editor)
editor.customConfig.onchange = (html) => {
this.editorContent = html
}
editor.create()
editor.txt.html(this.row.content_table && this.row.content_table.tables)
})
}
console.log(this.type)
},
submit () {
if (this.type === 1) {
this.formValidate.content_table.tables = this.editorContent
console.log(this.editorContent)
let content_section_res_content_pics = this.$refs['content_section_res_content_pics'].getData()
if (content_section_res_content_pics) {
this.formValidate.content_section.res_content_pics = typeof content_section_res_content_pics === 'string' ? [content_section_res_content_pics] : content_section_res_content_pics
}
let course_struct_section_res_content_pics = this.$refs['course_struct_section_res_content_pics'].getData()
if (course_struct_section_res_content_pics) {
this.formValidate.course_struct_section.res_content_pics = typeof course_struct_section_res_content_pics === 'string' ? [course_struct_section_res_content_pics] : course_struct_section_res_content_pics
}
let show_section_res_show_pics = this.$refs['show_section_res_show_pics'].getData()
if (show_section_res_show_pics) {
this.formValidate.show_section.res_show_pics = typeof show_section_res_show_pics === 'string' ? [show_section_res_show_pics] : show_section_res_show_pics
}
this.formValidate.user_comment_section.comments.forEach((p, idx) => {
let refgravatar = this.gravatar + idx.toString()
let gravatars = this.$refs[refgravatar][0].getData()
if (gravatars) {
p.gravatar = gravatars
}
})
}
this.$refs['formValidate'].validate((valid) => {
if (valid) {
this.formValidate.special_section.features = this.formValidate.special_section.features.map((p) => {
return p.name
})
delete this.formValidate.created_at
delete this.formValidate.deleted_at
delete this.formValidate.updated_at
let params = [this.formValidate]
if (this.editMode) {
editShop(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
this.close()
this.search()
}
})
} else {
addShop(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('新增成功')
this.close()
this.search()
}
})
}
} else {
this.$Message.error('您还有必填项未填写')
}
})
},
change (val, type) {
switch (type) {
case 1:
this.formValidate.is_all_free = val ? 1 : 0
break
case 2:
this.formValidate.need_vip = val ? 1 : 0
break
case 3:
this.formValidate.is_vip_free = val ? 1 : 0
break
case 4:
this.formValidate.intro_section.is_enable = val ? 1 : 0
break
case 5:
this.formValidate.intro_section.is_hot = val ? 1 : 0
break
case 6:
this.formValidate.special_section.is_enable = val ? 1 : 0
break
case 7:
this.formValidate.content_section.is_enable = val ? 1 : 0
break
case 8:
this.formValidate.content_table.is_enable = val ? 1 : 0
break
case 9:
this.formValidate.course_struct_section.is_enable = val ? 1 : 0
break
case 10:
this.formValidate.user_comment_section.is_enable = val ? 1 : 0
break
default:
this.formValidate.show_section.is_enable = val ? 1 : 0
break
}
},
close () {
this.$emit('update:visible', false)
}
}
}
</script>
<style>
.demo-drawer-footer {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
background: #fff;
}
</style>
<template>
<div class="content">
<Tabs type="card">
<TabPane label="放松"></TabPane>
<TabPane label="常规"></TabPane>
</Tabs>
<div class="topSearch">
<div class="left">
<Input style="width:280px" search enter-button placeholder="" @on-search='search'/>
<span class="lable">
场景
</span>
<Select clearable v-model="model1" style="width:90px" :filterable='true'>
<Option
v-for="item in $store.state.app.sceneObj.scenes"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
<span class="lable">
主题
</span>
<Select clearable v-model="model1" style="width:90px" :filterable='true'>
<Option
v-for="item in $store.state.app.sceneObj.themes"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
<span class="lable">
栏目
</span>
<Select clearable v-model="model1" style="width:90px" :filterable='true'>
<Option
v-for="item in $store.state.app.categoryObj.categories"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
<span class="lable">
条目
</span>
<Select clearable v-model="model1" style="width:90px" :filterable='true' >
<Option
v-for="item in $store.state.app.categoryObj.courses"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
</div>
<div class="right">
<Button @click="add" type="primary">新增</Button>
<Button type="error" @click="del">删除</Button>
</div>
</div>
<Stable :data='data' @edit='edit' @select='select'/>
<Forms :visible.sync="visible" :search='search' :row='row'/>
</div>
</template>
<script>
import Stable from './table.vue'
import Forms from './form.vue'
import {
getShop,
delShop
} from '@/api/shop'
export default {
name: 'goods',
components: {
Stable,
Forms
},
data () {
return {
model1: null,
selectList: [],
visible: false,
draggableVisible: false,
row: {},
data: []
}
},
methods: {
edit (row) {
this.visible = true
this.row = row
},
add () {
this.visible = true
this.row = {
id: 'add'
}
},
select (val) {
this.selectList = val
},
del () {
delShop(this.selectList).then((res) => {
if (res.code === 200) {
this.$Message.success('删除成功')
this.search()
} else {
this.$Message.error('删除失败')
}
})
},
search (p) {
getShop(p).then((res) => {
this.data = res.data
})
}
},
mounted () {
this.search({})
}
}
</script>
<style lang="less"></style>
<template>
<div class="table">
<Table :loading="$store.state.app.isLoading" border ref="selection" :columns="columns" :data="data" @on-select='onSelect' @on-select-all='onSelectAll'>
<template slot-scope="{ row }" slot="name">
<span>{{ row.title }}</span>
</template>
<template slot-scope="{ row }" slot="summary">
<span>{{ row.details.summary }}</span>
</template>
<template slot-scope="{ row }" slot="created_at">
<span>{{ row.created_at | format}}</span>
</template>
<template slot-scope="{ row }" slot="is_vip_free">
<span>{{ row.is_vip_free===1?'是':'否'}}</span>
</template>
<template slot-scope="{ row }" slot="need_vip">
<span>{{ row.need_vip===1?'是':'否'}}</span>
</template>
<template slot-scope="{ row }" slot="enable">
<i-switch
:value="row.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, row);
}
"
/>
</template>
<template slot-scope="{ row }" slot="action">
<a @click="edit(row)">编辑</a>
</template>
</Table>
</div>
</template>
<script>
import { formatDate } from '@/libs/util'
import {
editShop
} from '@/api/shop'
export default {
props: {
data: {
type: Array,
default () {
return []
}
}
},
data () {
return {
selectList: [],
columns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: '名称',
slot: 'name'
},
{
title: '实际价格',
key: 'real_price'
},
{
title: '划去价格',
key: 'normal_price'
},
{
title: '是否VIP免费',
slot: 'is_vip_free'
},
{
title: '是否要求VIP',
slot: 'need_vip'
},
{
title: 'VIP折扣',
key: 'vip_off'
},
{
title: '创建日期',
slot: 'created_at',
width: 200,
key: 'created_at'
},
{
title: '生效',
slot: 'enable'
},
{
title: '操作',
slot: 'action'
}
]
}
},
mounted () {
},
filters: {
format (value) {
return formatDate(value)
}
},
methods: {
edit (row) {
this.$emit('edit', row)
},
onSelect (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
onSelectAll (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
change (status, row) {
console.log('开关状态:' + status + row)
row.is_enable = status ? 1 : 0
delete row.created_at
delete row.deleted_at
delete row.updated_at
editShop([row]).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
}
})
},
handleSelectAll (status) {
this.$refs.selection.selectAll(status)
}
}
}
</script>
<style lang="less">
.table {
margin-top: 20px;
}
</style>
<template>
<div>
<Drawer
:title="formTitle"
v-model="modalVisible"
width="720"
:mask-closable="false"
:styles="styles"
@on-close="close"
>
<Form
ref="formValidate"
:model="formValidate"
:rules="ruleValidate"
:label-width="150"
v-if="formValidate.details"
>
<FormItem label="关联栏目">
<Select clearable v-model="formValidate.category_id" style="width:150px">
<Option
v-for="item in $store.state.app.categoryObj.categories"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
</FormItem>
<FormItem label="条目名称" prop="details.name">
<Input
v-model="formValidate.details.name"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="简介" prop="details.summary">
<Input
v-model="formValidate.details.summary"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="是否启用板块层级">
<i-switch :value='formValidate.details.is_use_section===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否显示为首页列表">
<i-switch :value='formValidate.is_in_home_list===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="首页列表背景图" prop="details.res_home_background_pic">
<UploadS ref="res_home_background_pic" v-model="formValidate.details.res_home_background_pic" />
</FormItem>
<FormItem label="是否听完解锁">
<i-switch :value='formValidate.details.is_lock_mode===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在探索列表页显示">
<i-switch :value='formValidate.is_in_discover_list===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在热门标签显示">
<i-switch :value='formValidate.is_hottest===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否置热门顶">
<i-switch :value='formValidate.is_hottest_top===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在最新标签显示">
<i-switch :value='formValidate.is_newest===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否置最新顶">
<i-switch :value='formValidate.is_newest_top===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在全部显示">
<i-switch :value='formValidate.is_in_discover_all===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="探索页列表背景图" prop="details.res_discover_list_pic">
<UploadS ref="res_discover_list_pic" v-model="formValidate.details.res_discover_list_pic" />
</FormItem>
<FormItem label="是否开启评论">
<i-switch :value='formValidate.allow_comment===1?true:false' @on-change='(value)=>{change(value,2)}'/>
</FormItem>
<FormItem label="是否允许调整倍速">
<i-switch :value='formValidate.details.is_allow_change_speed===1?true:false' @on-change='(value)=>{change(value,2)}'/>
</FormItem>
</Form>
<div class="demo-drawer-footer">
<Button style="margin-right: 8px" @click="close">取消</Button>
<Button type="primary" @click="submit">保存</Button>
</div>
</Drawer>
</div>
</template>
<script>
import UploadS from '@/view/common/upload.vue'
import {
addCourses,
editCourses
} from '@/api/courses'
const _requireBlur = { required: true, message: ' ', trigger: 'blur' }
const validatorPic = (rule, value, callback) => {
if (!value) {
callback(new Error(' '))
} else {
callback()
}
}
const _requirePic = { required: true, message: ' ', trigger: 'blur', validator: validatorPic }
export default {
props: {
search: {
type: Function,
default: null
},
row: {
type: Object,
default: () => {}
},
visible: {
type: Boolean,
default: false
}
},
components: {
UploadS
},
data () {
return {
modalVisible: false,
editMode: false,
formTitle: '',
styles: {
height: 'calc(100% - 55px)',
overflow: 'auto',
paddingBottom: '53px',
position: 'static'
},
formValidate: {},
ruleValidate: {
'details.name': [_requireBlur],
'details.summary': [_requireBlur],
'details.res_home_background_pic': [_requirePic],
'details.res_discover_list_pic': [_requirePic]
}
}
},
watch: {
visible: {
handler: function (val) {
this.modalVisible = val
if (val) {
this.search()
}
this.formValidate = this.row
},
immediate: true
},
row: {
handler: function (val) {
console.log(val)
if (val.id === 'add') {
this.editMode = false
this.formTitle = '新增栏目'
this.formValidate = {
details: {
name: '',
summary: '',
is_use_section: 1,
is_lock_mode: 0,
is_allow_change_speed: 0,
res_home_background_pic: '',
res_discover_list_pic: ''
},
category_id: 1,
is_in_discover_list: 0,
is_hottest: 0,
is_hottest_top: 0,
is_newest: 0,
is_newest_top: 0,
is_in_discover_all: 0,
allow_comment: 0
}
} else {
this.editMode = true
this.formTitle = '编辑栏目'
this.formValidate = val
}
},
immediate: true
}
},
methods: {
submit () {
if (this.$refs['res_home_background_pic'].getData()) {
this.formValidate.details.res_home_background_pic = this.$refs['res_home_background_pic'].getData()
}
if (this.$refs['res_discover_list_pic'].getData()) {
this.formValidate.details.res_discover_list_pic = this.$refs['res_discover_list_pic'].getData()
}
console.log(this.formValidate)
this.$refs['formValidate'].validate((valid) => {
if (valid) {
delete this.formValidate.created_at
delete this.formValidate.deleted_at
delete this.formValidate.updated_at
let params = [this.formValidate]
if (this.editMode) {
editCourses(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
this.close()
this.search()
}
})
} else {
addCourses(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('新增成功')
this.close()
this.search()
}
})
}
} else {
this.$Message.error('您还有必填项未填写')
}
})
},
change (val, type) {
if (type === 1) {
this.formValidate.is_in_home_lis = val ? 1 : 0
}
if (type === 2) {
this.formValidate.is_in_discover_list = val ? 1 : 0
}
},
close () {
this.$emit('update:visible', false)
}
}
}
</script>
<style>
.demo-drawer-footer {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
background: #fff;
}
</style>
<template>
<div class="content">
<div class="topSearch">
<div class="left">
<Input style="width:280px" search enter-button placeholder="" @on-search='search'/>
<span class="lable">
栏目
</span>
<Select clearable v-model="model1" style="width:80px">
<Option
v-for="item in $store.state.app.categoryObj.categories"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
</div>
<div class="right">
<Button class='success-btn' type="success" @click="seq">排序</Button>
<Button @click="add" type="primary">新增</Button>
<Button type="error" @click="del">删除</Button>
</div>
</div>
<Stable :data='data' @edit='edit' @select='select'/>
<Forms :visible.sync="visible" :search='search' :row='row'/>
<draggableList :visible.sync="draggableVisible" :list='data' @ok='ok'/>
</div>
</template>
<script>
import Stable from './table.vue'
import Forms from './form.vue'
import draggableList from '@/view/common/draggable.vue'
import {
getCourses,
delCourses,
editCourses
} from '@/api/courses'
export default {
name: 'categories',
components: {
Stable,
Forms,
draggableList
},
data () {
return {
selectList: [],
visible: false,
draggableVisible: false,
row: {},
data: []
}
},
methods: {
edit (row) {
this.visible = true
this.row = row
},
add () {
this.visible = true
this.row = {
id: 'add'
}
},
select (val) {
this.selectList = val
},
seq () {
this.draggableVisible = true
},
ok (list) {
editCourses(list).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('操作成功')
this.draggableVisible = false
this.search()
}
})
},
del () {
delCourses(this.selectList).then((res) => {
if (res.code === 200) {
this.$Message.success('删除成功')
this.search()
} else {
this.$Message.error('删除失败')
}
})
},
search () {
getCourses({}).then((res) => {
this.data = res.data
})
}
},
mounted () {
this.search()
}
}
</script>
<style lang="less"></style>
<template>
<div class="table">
<Table :loading="$store.state.app.isLoading" border ref="selection" :columns="columns" :data="data" @on-select='onSelect' @on-select-all='onSelectAll'>
<template slot-scope="{ row }" slot="name">
<span>{{ row.details.name }}</span>
</template>
<template slot-scope="{ row }" slot="summary">
<span>{{ row.details.summary }}</span>
</template>
<template slot-scope="{ row }" slot="created_at">
<span>{{ row.created_at | format}}</span>
</template>
<template slot-scope="{ row }" slot="enable">
<i-switch
:value="row.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, row);
}
"
/>
</template>
<template slot-scope="{ row }" slot="action">
<a @click="edit(row)">编辑</a>
</template>
<template slot-scope="{ row }" slot="is_use_section">
<a v-if="row.details.is_section==='1'">编辑</a>
</template>
<template slot-scope="{ row }" slot="edit">
<a>编辑</a>
</template>
</Table>
</div>
</template>
<script>
import { formatDate } from '@/libs/util'
import {
editCourses
} from '@/api/courses'
export default {
props: {
data: {
type: Array,
default () {
return []
}
}
},
data () {
return {
selectList: [],
columns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: '名称',
slot: 'name'
},
{
title: '简介',
slot: 'summary',
width: 300
},
{
title: '创建日期',
slot: 'created_at',
width: 200,
key: 'created_at'
},
{
title: '生效',
slot: 'enable'
},
{
title: '操作',
slot: 'action'
},
{
title: '编辑商品',
slot: 'edit'
},
{
title: '编辑板块',
slot: 'is_use_section'
},
{
title: '编辑条目内容',
slot: 'edit'
}
]
}
},
mounted () {
},
filters: {
format (value) {
return formatDate(value)
}
},
methods: {
edit (row) {
this.$emit('edit', row)
},
onSelect (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
onSelectAll (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
change (status, row) {
console.log('开关状态:' + status + row)
row.is_enable = status ? 1 : 0
delete row.created_at
delete row.deleted_at
delete row.updated_at
editCourses([row]).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
}
})
},
handleSelectAll (status) {
this.$refs.selection.selectAll(status)
}
}
}
</script>
<style lang="less">
.table {
margin-top: 20px;
}
</style>
<template>
<div>
<Drawer
:title="formTitle"
v-model="modalVisible"
width="720"
:mask-closable="false"
:styles="styles"
@on-close="close"
>
<Form
ref="formValidate"
:model="formValidate"
:rules="ruleValidate"
:label-width="150"
v-if="formValidate.details"
>
<FormItem label="关联栏目">
<Select clearable v-model="formValidate.category_id" style="width:150px">
<Option
v-for="item in $store.state.app.categoryObj.categories"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
</FormItem>
<FormItem label="条目名称" prop="details.name">
<Input
v-model="formValidate.details.name"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="简介" prop="details.summary">
<Input
v-model="formValidate.details.summary"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="是否启用板块层级">
<i-switch :value='formValidate.details.is_use_section===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否显示为首页列表">
<i-switch :value='formValidate.is_in_home_list===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="首页列表背景图" prop="details.res_home_background_pic">
<UploadS ref="res_home_background_pic" v-model="formValidate.details.res_home_background_pic" />
</FormItem>
<FormItem label="是否听完解锁">
<i-switch :value='formValidate.details.is_lock_mode===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在探索列表页显示">
<i-switch :value='formValidate.is_in_discover_list===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在热门标签显示">
<i-switch :value='formValidate.is_hottest===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否置热门顶">
<i-switch :value='formValidate.is_hottest_top===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在最新标签显示">
<i-switch :value='formValidate.is_newest===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否置最新顶">
<i-switch :value='formValidate.is_newest_top===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在全部显示">
<i-switch :value='formValidate.is_in_discover_all===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="探索页列表背景图" prop="details.res_discover_list_pic">
<UploadS ref="res_discover_list_pic" v-model="formValidate.details.res_discover_list_pic" />
</FormItem>
<FormItem label="是否开启评论">
<i-switch :value='formValidate.allow_comment===1?true:false' @on-change='(value)=>{change(value,2)}'/>
</FormItem>
<FormItem label="是否允许调整倍速">
<i-switch :value='formValidate.details.is_allow_change_speed===1?true:false' @on-change='(value)=>{change(value,2)}'/>
</FormItem>
</Form>
<div class="demo-drawer-footer">
<Button style="margin-right: 8px" @click="close">取消</Button>
<Button type="primary" @click="submit">保存</Button>
</div>
</Drawer>
</div>
</template>
<script>
import UploadS from '@/view/common/upload.vue'
import {
addCourses,
editCourses
} from '@/api/courses'
const _requireBlur = { required: true, message: ' ', trigger: 'blur' }
const validatorPic = (rule, value, callback) => {
if (!value) {
callback(new Error(' '))
} else {
callback()
}
}
const _requirePic = { required: true, message: ' ', trigger: 'blur', validator: validatorPic }
export default {
props: {
search: {
type: Function,
default: null
},
row: {
type: Object,
default: () => {}
},
visible: {
type: Boolean,
default: false
}
},
components: {
UploadS
},
data () {
return {
modalVisible: false,
editMode: false,
formTitle: '',
styles: {
height: 'calc(100% - 55px)',
overflow: 'auto',
paddingBottom: '53px',
position: 'static'
},
formValidate: {},
ruleValidate: {
'details.name': [_requireBlur],
'details.summary': [_requireBlur],
'details.res_home_background_pic': [_requirePic],
'details.res_discover_list_pic': [_requirePic]
}
}
},
watch: {
visible: {
handler: function (val) {
this.modalVisible = val
if (val) {
this.search()
}
this.formValidate = this.row
},
immediate: true
},
row: {
handler: function (val) {
console.log(val)
if (val.id === 'add') {
this.editMode = false
this.formTitle = '新增栏目'
this.formValidate = {
details: {
name: '',
summary: '',
is_use_section: 1,
is_lock_mode: 0,
is_allow_change_speed: 0,
res_home_background_pic: '',
res_discover_list_pic: ''
},
category_id: 1,
is_in_discover_list: 0,
is_hottest: 0,
is_hottest_top: 0,
is_newest: 0,
is_newest_top: 0,
is_in_discover_all: 0,
allow_comment: 0
}
} else {
this.editMode = true
this.formTitle = '编辑栏目'
this.formValidate = val
}
},
immediate: true
}
},
methods: {
submit () {
if (this.$refs['res_home_background_pic'].getData()) {
this.formValidate.details.res_home_background_pic = this.$refs['res_home_background_pic'].getData()
}
if (this.$refs['res_discover_list_pic'].getData()) {
this.formValidate.details.res_discover_list_pic = this.$refs['res_discover_list_pic'].getData()
}
console.log(this.formValidate)
this.$refs['formValidate'].validate((valid) => {
if (valid) {
delete this.formValidate.created_at
delete this.formValidate.deleted_at
delete this.formValidate.updated_at
let params = [this.formValidate]
if (this.editMode) {
editCourses(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
this.close()
this.search()
}
})
} else {
addCourses(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('新增成功')
this.close()
this.search()
}
})
}
} else {
this.$Message.error('您还有必填项未填写')
}
})
},
change (val, type) {
if (type === 1) {
this.formValidate.is_in_home_lis = val ? 1 : 0
}
if (type === 2) {
this.formValidate.is_in_discover_list = val ? 1 : 0
}
},
close () {
this.$emit('update:visible', false)
}
}
}
</script>
<style>
.demo-drawer-footer {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
background: #fff;
}
</style>
<template>
<div class="content">
<div class="topSearch">
<div class="left">
<Input style="width:280px" search enter-button placeholder="" @on-search='search'/>
<span class="lable">
栏目
</span>
<Select clearable v-model="model1" style="width:80px">
<Option
v-for="item in $store.state.app.categoryObj.categories"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
</div>
<div class="right">
<Button class='success-btn' type="success" @click="seq">排序</Button>
<Button @click="add" type="primary">新增</Button>
<Button type="error" @click="del">删除</Button>
</div>
</div>
<Stable :data='data' @edit='edit' @select='select'/>
<Forms :visible.sync="visible" :search='search' :row='row'/>
<draggableList :visible.sync="draggableVisible" :list='data' @ok='ok'/>
</div>
</template>
<script>
import Stable from './table.vue'
import Forms from './form.vue'
import draggableList from '@/view/common/draggable.vue'
import {
getCourses,
delCourses,
editCourses
} from '@/api/courses'
export default {
name: 'categories',
components: {
Stable,
Forms,
draggableList
},
data () {
return {
selectList: [],
visible: false,
draggableVisible: false,
row: {},
data: []
}
},
methods: {
edit (row) {
this.visible = true
this.row = row
},
add () {
this.visible = true
this.row = {
id: 'add'
}
},
select (val) {
this.selectList = val
},
seq () {
this.draggableVisible = true
},
ok (list) {
editCourses(list).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('操作成功')
this.draggableVisible = false
this.search()
}
})
},
del () {
delCourses(this.selectList).then((res) => {
if (res.code === 200) {
this.$Message.success('删除成功')
this.search()
} else {
this.$Message.error('删除失败')
}
})
},
search () {
getCourses({}).then((res) => {
this.data = res.data
})
}
},
mounted () {
this.search()
}
}
</script>
<style lang="less"></style>
<template>
<div class="table">
<Table :loading="$store.state.app.isLoading" border ref="selection" :columns="columns" :data="data" @on-select='onSelect' @on-select-all='onSelectAll'>
<template slot-scope="{ row }" slot="name">
<span>{{ row.details.name }}</span>
</template>
<template slot-scope="{ row }" slot="summary">
<span>{{ row.details.summary }}</span>
</template>
<template slot-scope="{ row }" slot="created_at">
<span>{{ row.created_at | format}}</span>
</template>
<template slot-scope="{ row }" slot="enable">
<i-switch
:value="row.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, row);
}
"
/>
</template>
<template slot-scope="{ row }" slot="action">
<a @click="edit(row)">编辑</a>
</template>
<template slot-scope="{ row }" slot="is_use_section">
<a v-if="row.details.is_section==='1'">编辑</a>
</template>
<template slot-scope="{ row }" slot="edit">
<a>编辑</a>
</template>
</Table>
</div>
</template>
<script>
import { formatDate } from '@/libs/util'
import {
editCourses
} from '@/api/courses'
export default {
props: {
data: {
type: Array,
default () {
return []
}
}
},
data () {
return {
selectList: [],
columns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: '名称',
slot: 'name'
},
{
title: '简介',
slot: 'summary',
width: 300
},
{
title: '创建日期',
slot: 'created_at',
width: 200,
key: 'created_at'
},
{
title: '生效',
slot: 'enable'
},
{
title: '操作',
slot: 'action'
},
{
title: '编辑商品',
slot: 'edit'
},
{
title: '编辑板块',
slot: 'is_use_section'
},
{
title: '编辑条目内容',
slot: 'edit'
}
]
}
},
mounted () {
},
filters: {
format (value) {
return formatDate(value)
}
},
methods: {
edit (row) {
this.$emit('edit', row)
},
onSelect (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
onSelectAll (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
change (status, row) {
console.log('开关状态:' + status + row)
row.is_enable = status ? 1 : 0
delete row.created_at
delete row.deleted_at
delete row.updated_at
editCourses([row]).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
}
})
},
handleSelectAll (status) {
this.$refs.selection.selectAll(status)
}
}
}
</script>
<style lang="less">
.table {
margin-top: 20px;
}
</style>
<template>
<div>
<Drawer
:title="formTitle"
v-model="modalVisible"
width="720"
:mask-closable="false"
:styles="styles"
@on-close="close"
>
<Form
ref="formValidate"
:model="formValidate"
:rules="ruleValidate"
:label-width="200"
>
<FormItem label="VIP购买页说明" prop="title">
<Input
v-model="formValidate.title"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="是否开启VIP会员专属权益">
<i-switch
:value="formValidate.exclusive_rights.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, 1);
}
"
/>
</FormItem>
<FormItem label="标题" prop="exclusive_rights.title">
<Input
v-model="formValidate.exclusive_rights.title"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="特色" prop="title">
<Input
style="margin-bottom: 10px;"
v-for="(item, idx) in formValidate.exclusive_rights.features"
:key="idx"
v-model="item.name"
placeholder="Enter something"
></Input>
<div>
<Icon
@click="addFeatures"
:size="20"
color="#7744FF"
style="margin-right: 6px;"
type="md-add"
/><Icon
v-if="formValidate.exclusive_rights.features.length > 1"
@click="delFeatures"
color="#7744FF"
:size="20"
type="md-remove"
/>
</div>
</FormItem>
<FormItem label="是否开启用户评价板块">
<i-switch
:value="formValidate.user_comment_section.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, 2);
}
"
/>
</FormItem>
<FormItem label="标题" prop="user_comment_section.title">
<Input
v-model="formValidate.user_comment_section.title"
placeholder="Enter something"
></Input>
</FormItem>
<div v-for="(item, idx) in formValidate.user_comment_section.comments"
:key="idx">
<FormItem label="用户头像" prop="title">
<UploadS
:ref="gravatar+idx.toString()"
v-model="item.gravatar"
/>
</FormItem>
<FormItem label="用户昵称" prop="user_comment_section.title">
<Input
v-model="item.nickname"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="评论内容" prop="user_comment_section.title">
<Input
v-model="item.comment" type="textarea"
placeholder="Enter something"
></Input>
</FormItem>
</div>
<div style="margin-left: 200px;margin-bottom: 20px;">
<Icon
@click="addComments"
:size="20"
color="#7744FF"
style="margin-right: 6px;"
type="md-add"
/><Icon
v-if="formValidate.user_comment_section.comments.length > 1"
@click="delComments"
color="#7744FF"
:size="20"
type="md-remove"
/>
</div>
<FormItem label="是否开启睡眠主题">
<i-switch
:value="formValidate.sleep_content_section.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, 3);
}
"
/>
</FormItem>
<FormItem label="标题" prop="sleep_content_section.title">
<Input
v-model="formValidate.sleep_content_section.title"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="展示图片" prop="sleep_content_section.res_show_pic">
<UploadS
ref="sleep_content_section_res_show_pic"
v-model="formValidate.sleep_content_section.res_show_pic"
/>
</FormItem>
<FormItem label="内容图片" prop="title">
<UploadS maxLength="20"
ref="res_content_pics"
v-model="formValidate.sleep_content_section.res_content_pics"
/>
</FormItem>
<FormItem label="是否开启冥想主题展示">
<i-switch
:value="formValidate.meditation_content_section.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, 4);
}
"
/>
</FormItem>
<FormItem label="标题" prop="meditation_content_section.title">
<Input
v-model="formValidate.meditation_content_section.title"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="展示图片" prop="meditation_content_section.res_show_pic">
<UploadS
ref="meditation_content_section_res_show_pic"
v-model="formValidate.meditation_content_section.res_show_pic"
/>
</FormItem>
<FormItem label="内容图片" prop="title">
<UploadS maxLength="20"
ref="meditation_content_section_res_content_pics"
v-model="formValidate.meditation_content_section.res_content_pics"
/>
</FormItem>
<FormItem label="是否开启纯图展示板块">
<i-switch
:value="formValidate.show_section.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, 5);
}
"
/>
</FormItem>
<FormItem label="标题" prop="show_section.title">
<Input
v-model="formValidate.show_section.title"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="展示图片" prop="title">
<UploadS
maxLength="20"
ref="show_section_res_show_pics"
v-model="formValidate.show_section.res_show_pics"
/>
</FormItem>
</Form>
<div class="demo-drawer-footer">
<Button style="margin-right: 8px" @click="close">取消</Button>
<Button type="primary" @click="submit">保存</Button>
</div>
</Drawer>
</div>
</template>
<script>
import UploadS from '@/view/common/upload.vue'
import { addVipPage, editVipPage } from '@/api/vipPage'
const _requireBlur = { required: true, message: ' ', trigger: 'blur' }
const validatorPic = (rule, value, callback) => {
if (!value) {
callback(new Error(' '))
} else {
callback()
}
}
const _requirePic = {
required: true,
message: ' ',
trigger: 'blur',
validator: validatorPic
}
export default {
props: {
search: {
type: Function,
default: null
},
row: {
type: Object,
default: () => {}
},
visible: {
type: Boolean,
default: false
}
},
components: {
UploadS
},
data () {
return {
gravatar: 'gravatar',
modalVisible: false,
editMode: false,
formTitle: '',
styles: {
height: 'calc(100% - 55px)',
overflow: 'auto',
paddingBottom: '53px',
position: 'static'
},
formValidate: {},
ruleValidate: {
'title': [_requireBlur],
'exclusive_rights.title': [_requireBlur],
'user_comment_section.title': [_requireBlur],
'user_comment_section.nickname': [_requireBlur],
'user_comment_section.comment': [_requireBlur],
'sleep_content_section.title': [_requireBlur],
'show_section.title': [_requireBlur],
'meditation_content_section.title': [_requireBlur],
'sleep_content_section.res_show_pic': [_requirePic],
'sleep_content_section.res_content_pics': [_requirePic],
'show_section.res_content_pics': [_requirePic],
'meditation_content_section.res_show_pic': [_requirePic],
'user_comment_section.gravatar': [_requirePic]
}
}
},
watch: {
visible: {
handler: function (val) {
this.modalVisible = val
if (val) {
this.search()
}
this.formValidate = this.row
},
immediate: true
},
row: {
handler: function (val) {
console.log(val)
if (val.id === 'add') {
this.editMode = false
this.formTitle = '新增VIP购买页'
this.formValidate = {
title: '',
exclusive_rights: {
is_enable: 1,
title: '',
features: [{ name: '' }]
},
user_comment_section: {
is_enable: 1,
title: '',
comments: [{ comment: '', gravatar: '', nickname: '' }]
},
sleep_content_section: {
is_enable: 1,
title: '',
res_content_pics: [],
res_show_pic: ''
},
meditation_content_section: {
is_enable: 1,
res_content_pics: [],
res_show_pic: '',
title: ''
},
show_section: {
is_enable: 1,
title: '',
res_show_pics: []
}
}
} else {
this.editMode = true
this.formTitle = '编辑VIP购买页'
this.formValidate = val
this.formValidate.exclusive_rights.features = val.exclusive_rights.features !== null && val.exclusive_rights.features.map((p) => {
let name = {
'name': p
}
return name
})
}
},
immediate: true
}
},
methods: {
addFeatures () {
this.formValidate.exclusive_rights.features.push({ name: '' })
},
delFeatures () {
this.formValidate.exclusive_rights.features.splice(
this.formValidate.exclusive_rights.features.length - 1,
1
)
},
addComments () {
this.formValidate.user_comment_section.comments.push({ comment: '', gravatar: '', nickname: '' })
},
delComments () {
this.formValidate.user_comment_section.comments.splice(
this.formValidate.user_comment_section.comments.length - 1,
1
)
},
submit () {
if (this.$refs['sleep_content_section_res_show_pic'].getData()) {
this.formValidate.sleep_content_section.res_show_pic = this.$refs['sleep_content_section_res_show_pic'].getData()
}
if (this.$refs['meditation_content_section_res_show_pic'].getData()) {
this.formValidate.meditation_content_section.res_show_pic = this.$refs['meditation_content_section_res_show_pic'].getData()
}
let meditation_content_section_res_content_pics = this.$refs['meditation_content_section_res_content_pics'].getData()
if (meditation_content_section_res_content_pics) {
this.formValidate.meditation_content_section.res_content_pics = typeof meditation_content_section_res_content_pics === 'string' ? [meditation_content_section_res_content_pics] : meditation_content_section_res_content_pics
}
let res_content_pics = this.$refs['res_content_pics'].getData()
if (res_content_pics) {
this.formValidate.sleep_content_section.res_content_pics = typeof res_content_pics === 'string' ? [res_content_pics] : res_content_pics
}
let show_section_res_show_pics = this.$refs['show_section_res_show_pics'].getData()
if (show_section_res_show_pics) {
this.formValidate.show_section.res_show_pics = typeof show_section_res_show_pics === 'string' ? [show_section_res_show_pics] : show_section_res_show_pics
}
this.formValidate.user_comment_section.comments.forEach((p, idx) => {
let refgravatar = this.gravatar + idx.toString()
let gravatars = this.$refs[refgravatar][0].getData()
if (gravatars) {
p.gravatar = gravatars
}
})
console.log(this.formValidate)
this.$refs['formValidate'].validate(valid => {
if (valid) {
this.formValidate.exclusive_rights.features = this.formValidate.exclusive_rights.features.map((p) => {
return p.name
})
delete this.formValidate.created_at
delete this.formValidate.deleted_at
delete this.formValidate.updated_at
let params = [this.formValidate]
if (this.editMode) {
editVipPage(params).then(res => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
this.close()
this.search()
}
})
} else {
addVipPage(params).then(res => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('新增成功')
this.close()
this.search()
}
})
}
} else {
this.$Message.error('您还有必填项未填写')
}
})
},
change (val, type) {
if (type === 1) {
this.formValidate.exclusive_rights.is_enable = val ? 1 : 0
}
if (type === 2) {
this.formValidate.user_comment_section.is_enable = val ? 1 : 0
}
if (type === 3) {
this.formValidate.sleep_content_section.is_enable = val ? 1 : 0
}
if (type === 4) {
this.formValidate.meditation_content_section.is_enable = val ? 1 : 0
}
if (type === 5) {
this.formValidate.show_section.is_enable = val ? 1 : 0
}
},
close () {
this.$emit('update:visible', false)
}
}
}
</script>
<style>
.demo-drawer-footer {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
background: #fff;
}
</style>
<template>
<div class="content">
<div class="topSearch">
<div class="left">
<Input style="width:280px" search enter-button placeholder="" @on-search='search'/>
</div>
<div class="right">
<Button @click="add" type="primary">新增</Button>
<Button type="error" @click="del">删除</Button>
</div>
</div>
<Stable :data='data' @edit='edit' @select='select' :search='search'/>
<Forms :visible.sync="visible" :search='search' :row='row'/>
</div>
</template>
<script>
import Stable from './table.vue'
import Forms from './form.vue'
import {
getVipPage,
editVipPage,
delVipPage
} from '@/api/vipPage'
export default {
name: 'categories',
components: {
Stable,
Forms
},
data () {
return {
selectList: [],
visible: false,
draggableVisible: false,
row: {},
data: []
}
},
methods: {
edit (row) {
this.visible = true
this.row = row
},
add () {
this.visible = true
this.row = {
id: 'add'
}
},
select (val) {
this.selectList = val
},
ok (list) {
editVipPage(list).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('操作成功')
this.draggableVisible = false
this.search()
}
})
},
del () {
delVipPage(this.selectList).then((res) => {
if (res.code === 200) {
this.$Message.success('删除成功')
this.search()
} else {
this.$Message.error('删除失败')
}
})
},
search () {
getVipPage({}).then((res) => {
this.data = res.data
})
}
},
mounted () {
this.search()
}
}
</script>
<style lang="less"></style>
<template>
<div class="table">
<Table :loading="$store.state.app.isLoading" border ref="selection" :columns="columns" :data="data" @on-select='onSelect' @on-select-all='onSelectAll'>
<template slot-scope="{ row }" slot="name">
<span>{{ row.title }}</span>
</template>
<template slot-scope="{ row }" slot="created_at">
<span>{{ row.created_at | format}}</span>
</template>
<template slot-scope="{ row }" slot="enable">
<i-switch
:value="row.is_enable === 1 ? true : false"
@on-change="
value => {
confirm(value, row);
}
"
/>
</template>
<template slot-scope="{ row }" slot="action">
<a @click="edit(row)">编辑</a>
</template>
<template slot-scope="{ row }" slot="edit">
<a @click="editVip(row)">编辑</a>
</template>
</Table>
</div>
</template>
<script>
import { formatDate } from '@/libs/util'
import {
editVipPage
} from '@/api/vipPage'
export default {
props: {
search: {
type: Function,
default: null
},
data: {
type: Array,
default () {
return []
}
}
},
data () {
return {
selectList: [],
columns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: '页面说明',
width: 600,
slot: 'name'
},
{
title: '创建日期',
slot: 'created_at',
width: 200,
key: 'created_at'
},
{
title: '生效',
slot: 'enable'
},
{
title: '操作',
slot: 'action'
},
{
title: '配置套餐',
slot: 'edit'
}
]
}
},
mounted () {
},
filters: {
format (value) {
return formatDate(value)
}
},
methods: {
edit (row) {
this.$emit('edit', row)
},
editVip (row) {
this.$router.push({ name: 'vipMeal', query: { row: row } })
},
onSelect (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
onSelectAll (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
change (status, row) {
console.log('开关状态:' + status + row)
row.is_enable = status ? 1 : 0
delete row.created_at
delete row.deleted_at
delete row.updated_at
editVipPage([row]).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
this.search()
}
})
},
confirm (status, row) {
this.$Modal.confirm({
title: status ? '确定要启用该VIP购买页吗?' : '确定要关闭该VIP购买页吗?',
onOk: () => {
this.change(status, row)
},
onCancel: () => {
}
})
},
handleSelectAll (status) {
this.$refs.selection.selectAll(status)
}
}
}
</script>
<style lang="less">
.table {
margin-top: 20px;
}
</style>
<template>
<div>
<Drawer
:title="formTitle"
v-model="modalVisible"
width="720"
:mask-closable="false"
:styles="styles"
@on-close="close"
>
<Form
ref="formValidate"
:model="formValidate"
:rules="ruleValidate"
:label-width="150"
v-if="formValidate.documents"
>
<FormItem label="关联购买页">
<Select clearable v-model="formValidate.page_id" style="width:150px">
<Option
v-for="item in list"
:value="item.id"
:key="item.id"
>{{ item.title }}</Option
>
</Select>
</FormItem>
<FormItem label="套餐名" prop="title">
<Input
style="width: 60%;margin-right: 10px;"
v-model="formValidate.title"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="左侧角标文案">
<Input
style="width: 60%;margin-right: 10px;"
v-model="formValidate.documents.left_side"
placeholder="Enter something"
></Input>
<colorPicker v-model="formValidate.documents.left_side_color" @change='(value)=>{change(value,1)}'></colorPicker>
</FormItem>
<FormItem label="右侧角标文案">
<Input
style="width: 60%;margin-right: 10px;"
v-model="formValidate.documents.right_side"
placeholder="Enter something"
></Input>
<colorPicker v-model="formValidate.documents.right_side_color" @change='(value)=>{change(value,2)}'></colorPicker>
</FormItem>
<FormItem label="免费天数" prop="extra_days">
<InputNumber v-model="formValidate.extra_days"></InputNumber>
</FormItem>
<FormItem label="订阅周期" >
<Select clearable v-model="formValidate.sub_type" style="width:150px">
<Option
v-for="item in typeList"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
</FormItem>
<FormItem label="购买天数" prop="days">
<InputNumber v-model="formValidate.days"></InputNumber>
</FormItem>
<FormItem label="物品价格" prop="real_price">
<InputNumber v-model="formValidate.real_price"></InputNumber>
</FormItem>
<FormItem label="套餐第一行文案" prop="documents.first_doc">
<Input
style="width: 60%;margin-right: 10px;"
v-model="formValidate.documents.first_doc"
placeholder="Enter something"
></Input>
<colorPicker v-model="formValidate.documents.first_doc_color" @change='(value)=>{change(value,3)}'></colorPicker>
</FormItem>
<FormItem label="套餐第二行文案" prop="documents.second_doc">
<Input
style="width: 60%;margin-right: 10px;"
v-model="formValidate.documents.second_doc"
placeholder="Enter something"
></Input>
<colorPicker v-model="formValidate.documents.second_doc_color" @change='(value)=>{change(value,4)}'></colorPicker>
</FormItem>
<FormItem label="套餐第三行文案" prop="documents.third_doc">
<Input
style="width: 60%;margin-right: 10px;"
v-model="formValidate.documents.third_doc"
placeholder="Enter something"
></Input>
<colorPicker v-model="formValidate.documents.third_doc_color" @change='(value)=>{change(value,5)}'></colorPicker>
</FormItem>
</Form>
<div class="demo-drawer-footer">
<Button style="margin-right: 8px" @click="close">取消</Button>
<Button type="primary" @click="submit">保存</Button>
</div>
</Drawer>
</div>
</template>
<script>
import UploadS from '@/view/common/upload.vue'
import {
addVips,
editVips
} from '@/api/vip'
const _requireBlur = { required: true, message: ' ', trigger: 'blur' }
const validatorPic = (rule, value, callback) => {
if (!value) {
callback(new Error(' '))
} else {
callback()
}
}
const _requirePic = { required: true, message: ' ', trigger: 'blur', validator: validatorPic }
export default {
props: {
search: {
type: Function,
default: null
},
row: {
type: Object,
default: () => {}
},
list: {
type: Array,
default: () => []
},
visible: {
type: Boolean,
default: false
}
},
components: {
UploadS
},
data () {
return {
color: '#ff0000',
modalVisible: false,
editMode: false,
formTitle: '',
styles: {
height: 'calc(100% - 55px)',
overflow: 'auto',
paddingBottom: '53px',
position: 'static'
},
formValidate: {},
typeList: [{ id: 1, name: '月付' }, { id: 2, name: '季付' }, { id: 3, name: '年付' }],
ruleValidate: {
'documents.first_doc': [_requireBlur],
'documents.second_doc': [_requireBlur],
'documents.third_doc': [_requireBlur],
'title': [_requireBlur],
'days': [_requirePic],
'real_price': [_requirePic],
'sub_type': [_requirePic],
'extra_days': [_requirePic]
}
}
},
watch: {
visible: {
handler: function (val) {
this.modalVisible = val
this.formValidate = this.row
},
immediate: true
},
row: {
handler: function (val) {
console.log(val)
if (val.id === 'add') {
this.editMode = false
this.formTitle = '新增VIP购买套餐'
this.formValidate = {
documents: {
left_side: '',
left_side_color: '#c18960',
right_side: '',
right_side_color: '#c18960',
first_doc: '',
first_doc_color: '#c18960',
second_doc: '',
second_doc_color: '#c18960',
third_doc: '',
third_doc_color: '#c18960'
},
page_id: null,
title: '',
extra_days: 0,
sub_type: 0,
real_price: 0
}
} else {
this.editMode = true
this.formTitle = '编辑VIP购买套餐'
this.formValidate = val
}
},
immediate: true
}
},
methods: {
submit () {
this.$refs['formValidate'].validate((valid) => {
if (valid) {
delete this.formValidate.created_at
delete this.formValidate.deleted_at
delete this.formValidate.updated_at
let params = [this.formValidate]
if (this.editMode) {
editVips(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
this.close()
this.search()
}
})
} else {
addVips(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('新增成功')
this.close()
this.search()
}
})
}
} else {
this.$Message.error('您还有必填项未填写')
}
})
},
change (val, type) {
switch (type) {
case 1:
this.formValidate.documents.left_side_color = val
break
case 2:
this.formValidate.documents.right_side_color = val
break
case 3:
this.formValidate.documents.first_doc_color = val
break
case 4:
this.formValidate.documents.second_doc_color = val
break
default:
this.formValidate.documents.third_doc_color = val
break
}
},
close () {
this.$emit('update:visible', false)
}
}
}
</script>
<style>
.demo-drawer-footer {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
background: #fff;
}
</style>
<template>
<div class="content">
<div class="topSearch">
<div class="left">
<Input style="width:280px" search enter-button placeholder="" @on-search='onSearch'/>
<span class="lable">
购买页
</span>
<Select clearable v-model="seleted" style="width:80px" @on-change='selectChange' >
<Option
v-for="item in pagesList"
:value="item.id"
:key="item.id"
>{{ item.title }}</Option
>
</Select>
</div>
<div class="right">
<Button class='success-btn' type="success" @click="seq">排序</Button>
<Button @click="add" type="primary">新增</Button>
<Button type="error" @click="del">删除</Button>
</div>
</div>
<Stable :data='data' @edit='edit' @select='select' :search='search'/>
<Forms :visible.sync="visible" :search='search' :row='row' :list="pagesList"/>
<!-- <draggableList :visible.sync="draggableVisible" :list='data' @ok='ok'/> -->
</div>
</template>
<script>
import Stable from './table.vue'
import Forms from './form.vue'
import draggableList from '@/view/common/draggable.vue'
import {
getVips,
delVips,
vipPagesList,
editVips
} from '@/api/vip'
export default {
name: 'categories',
components: {
Stable,
Forms,
draggableList
},
data () {
return {
seleted: null,
selectList: [],
pagesList: [],
visible: false,
draggableVisible: false,
row: {},
data: []
}
},
methods: {
edit (row) {
this.visible = true
this.row = row
},
add () {
this.visible = true
this.row = {
id: 'add'
}
},
selectChange (p) {
console.log(p)
this.search({
page_id: p
})
},
select (val) {
this.selectList = val
},
seq () {
this.draggableVisible = true
},
ok (list) {
editVips(list).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('操作成功')
this.draggableVisible = false
this.search()
}
})
},
del () {
delVips(this.selectList).then((res) => {
if (res.code === 200) {
this.$Message.success('删除成功')
this.search()
} else {
this.$Message.error('删除失败')
}
})
},
getVipPagesList () {
vipPagesList({}).then((res) => {
this.pagesList = res.data
})
},
onSearch (p) {
this.search({
keyword: p
})
},
search (p) {
getVips(p).then((res) => {
this.data = res.data
})
}
},
mounted () {
this.seleted = this.$route.query.row && this.$route.query.row.id
let parmas = {
page_id: this.seleted
}
this.search(parmas)
this.getVipPagesList()
}
}
</script>
<style lang="less"></style>
<template>
<div class="table">
<Table :loading="$store.state.app.isLoading" border ref="selection" :columns="columns" :data="data" @on-select='onSelect' @on-select-all='onSelectAll'>
<template slot-scope="{ row }" slot="name">
<span>{{ row.title }}</span>
</template>
<template slot-scope="{ row }" slot="summary">
<span v-if="row.shop_type===1">购买 </span>
<span v-else>订阅</span>
</template>
<template slot-scope="{ row }" slot="created_at">
<span>{{ row.created_at | format}}</span>
</template>
<template slot-scope="{ row }" slot="enable">
<i-switch
:value="row.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, row);
}
"
/>
</template>
<template slot-scope="{ row }" slot="action">
<a @click="edit(row)">编辑</a>
</template>
</Table>
</div>
</template>
<script>
import { formatDate } from '@/libs/util'
import {
editVips
} from '@/api/vip'
export default {
props: {
search: {
type: Function,
default: null
},
data: {
type: Array,
default () {
return []
}
}
},
data () {
return {
selectList: [],
columns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: '套餐名',
slot: 'name'
},
{
title: '购买类型',
slot: 'summary'
},
{
title: '价格(¥)',
key: 'real_price'
},
{
title: '创建日期',
slot: 'created_at',
width: 200,
key: 'created_at'
},
{
title: '生效',
slot: 'enable'
},
{
title: '操作',
slot: 'action'
}
]
}
},
mounted () {
},
filters: {
format (value) {
return formatDate(value)
}
},
methods: {
edit (row) {
this.$emit('edit', row)
},
onSelect (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
onSelectAll (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
change (status, row) {
console.log('开关状态:' + status + row)
row.is_enable = status ? 1 : 0
delete row.created_at
delete row.deleted_at
delete row.updated_at
editVips([row]).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
this.search()
}
})
},
handleSelectAll (status) {
this.$refs.selection.selectAll(status)
}
}
}
</script>
<style lang="less">
.table {
margin-top: 20px;
}
</style>
<template>
<div>
<Drawer
:title="formTitle"
v-model="modalVisible"
width="720"
:mask-closable="false"
:styles="styles"
@on-close="close"
>
<Form
ref="formValidate"
:model="formValidate"
:rules="ruleValidate"
:label-width="150"
>
<FormItem label="广告名称" prop="title">
<Input
v-model="formValidate.title"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="广告倒计时" prop="countdown">
<InputNumber v-model="formValidate.countdown"></InputNumber>
</FormItem>
<FormItem label="slogen" prop="slogen">
<Input
v-model="formValidate.slogen"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="广告图片" prop="res_show_pic">
<UploadS ref="res_show_pic" v-model="formValidate.res_show_pic" />
</FormItem>
</Form>
<div class="demo-drawer-footer">
<Button style="margin-right: 8px" @click="close">取消</Button>
<Button type="primary" @click="submit">保存</Button>
</div>
</Drawer>
</div>
</template>
<script>
import UploadS from '@/view/common/upload.vue'
import {
addAdvertisements,
editAdvertisements
} from '@/api/advertisement'
import {
getTreeCategories
} from '@/api/categories'
const _requireBlur = { required: true, message: ' ', trigger: 'blur' }
const validatorPic = (rule, value, callback) => {
if (!value) {
callback(new Error(' '))
} else {
callback()
}
}
const _requirePic = { required: true, message: ' ', trigger: 'blur', validator: validatorPic }
export default {
props: {
search: {
type: Function,
default: null
},
treeData: {
type: Array,
default: () => []
},
row: {
type: Object,
default: () => {}
},
visible: {
type: Boolean,
default: false
}
},
components: {
UploadS
},
data () {
return {
modalVisible: false,
editMode: false,
formTitle: '',
styles: {
height: 'calc(100% - 55px)',
overflow: 'auto',
paddingBottom: '53px',
position: 'static'
},
formValidate: {},
ruleValidate: {
'title': [_requireBlur],
'slogen': [_requireBlur],
'countdown': [_requirePic],
'res_show_pic': [_requirePic]
}
}
},
watch: {
visible: {
handler: function (val) {
this.modalVisible = val
if (val) {
this.search()
}
this.formValidate = this.row
},
immediate: true
},
row: {
handler: function (val) {
console.log(val)
if (val.id === 'add') {
this.editMode = false
this.formTitle = '新增首页广告'
this.formValidate = {
title: '',
countdown: 0,
slogen: '',
res_show_pic: ''
}
} else {
this.editMode = true
this.formTitle = '编辑首页广告'
this.formValidate = val
}
},
immediate: true
}
},
methods: {
submit () {
let res_show_pic = this.$refs['res_show_pic'].getData()
if (res_show_pic) {
this.formValidate.res_show_pic = res_show_pic
}
this.$refs['formValidate'].validate((valid) => {
if (valid) {
delete this.formValidate.created_at
delete this.formValidate.deleted_at
delete this.formValidate.updated_at
let params = [this.formValidate]
if (this.editMode) {
editAdvertisements(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
this.close()
this.search()
}
})
} else {
addAdvertisements(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('新增成功')
this.close()
this.search()
}
})
}
} else {
this.$Message.error('您还有必填项未填写')
}
})
},
getTree (e) {
getTreeCategories(e).then((res) => {
console.log(res)
})
},
change (val, type) {
if (type === 1) {
this.formValidate.is_in_home_lis = val ? 1 : 0
}
if (type === 2) {
this.formValidate.is_in_discover_list = val ? 1 : 0
}
},
close () {
this.$emit('update:visible', false)
}
}
}
</script>
<style>
.demo-drawer-footer {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
background: #fff;
}
</style>
<template>
<div class="content">
<div class="topSearch">
<div class="left">
<Input style="width:280px" search enter-button placeholder="" @on-search='search'/>
</div>
<div class="right">
<Button class='success-btn' type="success" @click="seq">排序</Button>
<Button @click="add" type="primary">新增</Button>
<Button type="error" @click="del">删除</Button>
</div>
</div>
<Stable :data='data' @edit='edit' @select='select'/>
<Forms :visible.sync="visible" :search='search' :row='row' :treeData='treeData'/>
<draggableList :visible.sync="draggableVisible" :list='data' @ok='ok'/>
</div>
</template>
<script>
import Stable from './table.vue'
import Forms from './form.vue'
import draggableList from '@/view/common/draggable.vue'
import {
getAdvertisements,
delAdvertisements,
editAdvertisements
} from '@/api/advertisement'
import {
getTreeCategorie2
} from '@/api/categories'
export default {
name: 'sections',
components: {
Stable,
Forms,
draggableList
},
data () {
return {
treeData: [],
selectList: [],
visible: false,
draggableVisible: false,
row: {},
data: []
}
},
methods: {
edit (row) {
this.visible = true
this.row = row
},
add () {
this.visible = true
this.row = {
id: 'add'
}
},
select (val) {
this.selectList = val
},
seq () {
this.draggableVisible = true
},
ok (list) {
editAdvertisements(list).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('操作成功')
this.draggableVisible = false
this.search()
}
})
},
del () {
delAdvertisements(this.selectList).then((res) => {
if (res.code === 200) {
this.$Message.success('删除成功')
this.search()
} else {
this.$Message.error('删除失败')
}
})
},
getTree () {
getTreeCategorie2('course').then((res) => {
this.treeData = res.data.categories.map((p) => {
p.label = p.name
p.value = p.id
p.children = p.courses
if (p.courses.length > 0) {
this.recursion(p.children)
}
return p
})
})
},
recursion (data) {
data = data.map((p) => {
p.label = p.name
p.value = p.id
return p
})
},
search () {
getAdvertisements({}).then((res) => {
this.data = res.data
})
}
},
mounted () {
this.search()
this.getTree()
}
}
</script>
<style lang="less"></style>
<template>
<div class="table">
<Table :loading="$store.state.app.isLoading" border ref="selection" :columns="columns" :data="data" @on-select='onSelect' @on-select-all='onSelectAll'>
<template slot-scope="{ row }" slot="name">
<span>{{ row.title }}</span>
</template>
<template slot-scope="{ row }" slot="countdown">
<span>{{ row.countdown }}</span>
</template>
<template slot-scope="{ row }" slot="created_at">
<span>{{ row.created_at | format}}</span>
</template>
<template slot-scope="{ row }" slot="enable">
<i-switch
:value="row.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, row);
}
"
/>
</template>
<template slot-scope="{ row }" slot="action">
<a @click="edit(row)">编辑</a>
</template>
</Table>
</div>
</template>
<script>
import { formatDate } from '@/libs/util'
import {
editAdvertisements
} from '@/api/advertisement'
export default {
props: {
data: {
type: Array,
default () {
return []
}
}
},
data () {
return {
selectList: [],
columns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: '广告名称',
slot: 'name'
},
{
title: '倒计时时长',
slot: 'countdown'
},
{
title: '创建日期',
slot: 'created_at',
width: 200,
key: 'created_at'
},
{
title: '生效',
slot: 'enable'
},
{
title: '操作',
slot: 'action'
}
]
}
},
mounted () {
},
filters: {
format (value) {
return formatDate(value)
}
},
methods: {
edit (row) {
this.$emit('edit', row)
},
onSelect (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
onSelectAll (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
change (status, row) {
console.log('开关状态:' + status + row)
row.is_enable = status ? 1 : 0
delete row.created_at
delete row.deleted_at
delete row.updated_at
editAdvertisements([row]).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
}
})
},
handleSelectAll (status) {
this.$refs.selection.selectAll(status)
}
}
}
</script>
<style lang="less">
.table {
margin-top: 20px;
}
</style>
<template>
<div class="detail">
<Card dis-hover>
<p slot="title">基础信息</p>
<Row>
<Col span="8">用户ID:123123123</Col>
<Col span="8">头像:<Avatar icon="ios-person"/></Col>
<Col span="8">姓名:阿斯顿马丁</Col>
</Row>
<Row style="margin-top: 20px;">
<Col span="8">手机号:17762662211</Col>
<Col span="8">真我币:1233123</Col>
<Col span="8">签到次数:365</Col>
</Row>
</Card>
<div class="pc-main">
<Tabs v-model="tab">
<TabPane label="签到活动" name="1"></TabPane>
<TabPane label="优惠卷" name="2"></TabPane>
</Tabs>
<Table
v-if="tab==='1'"
:loading="$store.state.app.isLoading"
border
ref="selection"
:columns="columns"
:data="data"
>
<template slot-scope="{ row }" slot="name">
<span>{{ row.details.name }}</span>
</template>
<template slot-scope="{ row }" slot="title">
<span>{{ row.details.title }}</span>
</template>
<template slot-scope="{ row }" slot="created_at">
<span>{{ row.created_at | format }}</span>
</template>
<template slot-scope="{ row }" slot="enable">
<i-switch
:value="row.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, row);
}
"
/>
</template>
<template slot-scope="{ row }" slot="action">
<a @click="edit(row)">编辑</a>
</template>
<template slot-scope="{ row }" slot="edit">
<a @click="goTheme(row)">编辑</a>
</template>
</Table>
<Table
v-else
:loading="$store.state.app.isLoading"
border
ref="selection"
:columns="yhqcolumns"
:data="data"
>
<template slot-scope="{ row }" slot="action">
<a @click="edit(row)">编辑</a>
</template>
</Table>
</div>
<div class="btn">
<Button type="primary" @click="$router.go(-1)">返回</Button>
</div>
</div>
</template>
<script>
import { getScenes, delScenes, editScenes } from "@/api/scenes";
export default {
name: "categories",
components: {},
data() {
return {
tab: "1",
data: [],
yhqcolumns: [
{
title: "优惠卷名称",
key: "created_at"
},
{
title: "获奖时间",
slot: "name"
}
],
columns: [
{
title: "签到日期",
key: "created_at"
},
{
title: "获得收益",
slot: "name"
},
{
title: "签到时间",
slot: "title"
},
{
title: "分享收益",
key: "created_at"
},
{
title: "总收益",
slot: "enable"
},
{
title: "操作",
slot: "action"
}
]
};
},
methods: {},
mounted() {}
};
</script>
<style lang="less" scoped>
.pc-main {
padding: 20px;
margin-top: 20px;
background-color: #fff;
}
.btn {
text-align: right;
width: 80%;
position: fixed;
background-color: #fff;
bottom: 10px;
padding: 10px 30px;
}
</style>
<template>
<div class="content">
<div class="topSearch">
<div class="left">
用户搜索:
<Input
style="width:200px"
enter-button
placeholder="请输入"
/>
<Button type="primary" @click="goDetail">查 询</Button>
<Button type="primary" ghost>重 置</Button>
</div>
<div class="right">
<Button type="primary">导 出</Button>
</div>
</div>
<Table :loading="$store.state.app.isLoading" border ref="selection" :columns="columns" :data="data">
<template slot-scope="{ row }" slot="name">
<span>{{ row.details.name }}</span>
</template>
<template slot-scope="{ row }" slot="title">
<span>{{ row.details.title }}</span>
</template>
<template slot-scope="{ row }" slot="created_at">
<span>{{ row.created_at | format}}</span>
</template>
<template slot-scope="{ row }" slot="enable">
<i-switch
:value="row.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, row);
}
"
/>
</template>
<template slot-scope="{ row }" slot="action">
<a @click="edit(row)">编辑</a>
</template>
<template slot-scope="{ row }" slot="edit">
<a @click="goTheme(row)">编辑</a>
</template>
</Table>
</div>
</template>
<script>
import { getScenes, delScenes, editScenes } from "@/api/scenes";
export default {
name: "categories",
components: {
},
data() {
return {
data:[],
columns: [
{
title: '用户ID',
key: 'created_at'
},
{
title: '头像',
slot: 'name'
},
{
title: '姓名',
slot: 'title',
},
{
title: '手机号',
key: 'created_at'
},
{
title: '真我币',
slot: 'enable'
},
{
title: '操作',
slot: 'action'
}
]
};
},
methods: {
goDetail(){
this.$router.push({ path: "/signIn/detail", query: { id: "new" } });
}
},
mounted() {
}
};
</script>
<style lang="less"></style>
<template>
<div>
<Button @click="exportData" type="primary" style="margin: 0 10px 10px 0;">导出日志记录</Button>
<b>注:这里只会显示成功保存到服务端的错误日志,而且页面错误日志不会在浏览器持久化存储,刷新页面即会丢失</b>
<Table ref="table" :columns="columns" :data="errorList"></Table>
</div>
</template>
<script>
import dayjs from 'dayjs'
import { mapMutations } from 'vuex'
export default {
name: 'error_logger_page',
data () {
return {
columns: [
{
type: 'index',
title: '序号',
width: 100
},
{
key: 'type',
title: '类型',
width: 100,
render: (h, { row }) => {
return (
<div>
<icon size={16} type={row.type === 'ajax' ? 'md-link' : 'md-code-working'}></icon>
</div>
)
}
},
{
key: 'code',
title: '编码',
render: (h, { row }) => {
return (
<span>{ row.code === 0 ? '-' : row.code }</span>
)
}
},
{
key: 'mes',
title: '信息'
},
{
key: 'url',
title: 'URL'
},
{
key: 'time',
title: '时间',
render: (h, { row }) => {
return (
<span>{ dayjs(row.time).format('YYYY-MM-DD HH:mm:ss') }</span>
)
},
sortable: true,
sortType: 'desc'
}
]
}
},
computed: {
errorList () {
return this.$store.state.app.errorList
}
},
methods: {
...mapMutations([
'setHasReadErrorLoggerStatus'
]),
exportData () {
this.$refs.table.exportCsv({
filename: '错误日志.csv'
})
}
},
activated () {
this.setHasReadErrorLoggerStatus()
},
mounted () {
this.setHasReadErrorLoggerStatus()
}
}
</script>
<style>
</style>
<template>
<div>
<Row :gutter="20">
<i-col :xs="12" :md="8" :lg="6" v-for="(infor, i) in inforCardData" :key="`infor-${i}`" style="height: 120px;padding-bottom: 10px;">
<infor-card shadow :color="infor.color" :icon="infor.icon" :icon-size="36">
<count-to :end="infor.count" count-class="count-style"/>
<p>{{ infor.title }}</p>
</infor-card>
</i-col>
</Row>
</div>
</template>
<script>
import InforCard from '_c/info-card'
import CountTo from '_c/count-to'
export default {
name: 'home',
components: {
InforCard,
CountTo
},
data () {
return {
inforCardData: [
{ title: '累计已注册用户', icon: 'md-person-add', count: 803, color: '#2d8cf0' },
{ title: '今天登陆用户', icon: 'md-locate', count: 232, color: '#19be6b' },
{ title: '累计开通订阅数', icon: 'md-help-circle', count: 142, color: '#ff9900' },
{ title: '累计邀请好友数', icon: 'md-share', count: 657, color: '#ed3f14' }
]
}
},
mounted () {
//
}
}
</script>
<style lang="less">
.count-style{
font-size: 50px;
}
</style>
import home from './home.vue'
export default home
<template>
<Card shadow>
<div>
<div class="message-page-con message-category-con">
<Menu width="auto" active-name="unread" @on-select="handleSelect">
<MenuItem name="unread">
<span class="category-title">未读消息</span><Badge style="margin-left: 10px" :count="messageUnreadCount"></Badge>
</MenuItem>
<MenuItem name="readed">
<span class="category-title">已读消息</span><Badge style="margin-left: 10px" class-name="gray-dadge" :count="messageReadedCount"></Badge>
</MenuItem>
<MenuItem name="trash">
<span class="category-title">回收站</span><Badge style="margin-left: 10px" class-name="gray-dadge" :count="messageTrashCount"></Badge>
</MenuItem>
</Menu>
</div>
<div class="message-page-con message-list-con">
<Spin fix v-if="listLoading" size="large"></Spin>
<Menu
width="auto"
active-name=""
:class="titleClass"
@on-select="handleView"
>
<MenuItem v-for="item in messageList" :name="item.msg_id" :key="`msg_${item.msg_id}`">
<div>
<p class="msg-title">{{ item.title }}</p>
<Badge status="default" :text="item.create_time" />
<Button
style="float: right;margin-right: 20px;"
:style="{ display: item.loading ? 'inline-block !important' : '' }"
:loading="item.loading"
size="small"
:icon="currentMessageType === 'readed' ? 'md-trash' : 'md-redo'"
:title="currentMessageType === 'readed' ? '删除' : '还原'"
type="text"
v-show="currentMessageType !== 'unread'"
@click.native.stop="removeMsg(item)"></Button>
</div>
</MenuItem>
</Menu>
</div>
<div class="message-page-con message-view-con">
<Spin fix v-if="contentLoading" size="large"></Spin>
<div class="message-view-header">
<h2 class="message-view-title">{{ showingMsgItem.title }}</h2>
<time class="message-view-time">{{ showingMsgItem.create_time }}</time>
</div>
<div v-html="messageContent"></div>
</div>
</div>
</Card>
</template>
<script>
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
const listDic = {
unread: 'messageUnreadList',
readed: 'messageReadedList',
trash: 'messageTrashList'
}
export default {
name: 'message_page',
data () {
return {
listLoading: true,
contentLoading: false,
currentMessageType: 'unread',
messageContent: '',
showingMsgItem: {}
}
},
computed: {
...mapState({
messageUnreadList: state => state.user.messageUnreadList,
messageReadedList: state => state.user.messageReadedList,
messageTrashList: state => state.user.messageTrashList,
messageList () {
return this[listDic[this.currentMessageType]]
},
titleClass () {
return {
'not-unread-list': this.currentMessageType !== 'unread'
}
}
}),
...mapGetters([
'messageUnreadCount',
'messageReadedCount',
'messageTrashCount'
])
},
methods: {
...mapMutations([
//
]),
...mapActions([
'getContentByMsgId',
'getMessageList',
'hasRead',
'removeReaded',
'restoreTrash'
]),
stopLoading (name) {
this[name] = false
},
handleSelect (name) {
this.currentMessageType = name
},
handleView (msg_id) {
this.contentLoading = true
this.getContentByMsgId({ msg_id }).then(content => {
this.messageContent = content
const item = this.messageList.find(item => item.msg_id === msg_id)
if (item) this.showingMsgItem = item
if (this.currentMessageType === 'unread') this.hasRead({ msg_id })
this.stopLoading('contentLoading')
}).catch(() => {
this.stopLoading('contentLoading')
})
},
removeMsg (item) {
item.loading = true
const msg_id = item.msg_id
if (this.currentMessageType === 'readed') this.removeReaded({ msg_id })
else this.restoreTrash({ msg_id })
}
},
mounted () {
this.listLoading = true
// 请求获取消息列表
this.getMessageList().then(() => this.stopLoading('listLoading')).catch(() => this.stopLoading('listLoading'))
}
}
</script>
<style lang="less">
.message-page{
&-con{
height: ~"calc(100vh - 176px)";
display: inline-block;
vertical-align: top;
position: relative;
&.message-category-con{
border-right: 1px solid #e6e6e6;
width: 200px;
}
&.message-list-con{
border-right: 1px solid #e6e6e6;
width: 230px;
}
&.message-view-con{
position: absolute;
left: 446px;
top: 16px;
right: 16px;
bottom: 16px;
overflow: auto;
padding: 12px 20px 0;
.message-view-header{
margin-bottom: 20px;
.message-view-title{
display: inline-block;
}
.message-view-time{
margin-left: 20px;
}
}
}
.category-title{
display: inline-block;
width: 65px;
}
.gray-dadge{
background: gainsboro;
}
.not-unread-list{
.msg-title{
color: rgb(170, 169, 169);
}
.ivu-menu-item{
.ivu-btn.ivu-btn-text.ivu-btn-small.ivu-btn-icon-only{
display: none;
}
&:hover{
.ivu-btn.ivu-btn-text.ivu-btn-small.ivu-btn-icon-only{
display: inline-block;
}
}
}
}
}
}
</style>
<template>
<div>
<Drawer
:title="formTitle"
v-model="modalVisible"
width="720"
:mask-closable="false"
:styles="styles"
@on-close="close"
>
<Form
ref="formValidate"
:model="formValidate"
:rules="ruleValidate"
:label-width="150"
v-if="formValidate.details"
>
<FormItem label="关联栏目">
<Select clearable v-model="formValidate.category_id" style="width:150px">
<Option
v-for="item in $store.state.app.categoryObj.categories"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
</FormItem>
<FormItem label="条目名称" prop="details.name">
<Input
v-model="formValidate.details.name"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="简介" prop="details.summary">
<Input
v-model="formValidate.details.summary"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="是否启用板块层级">
<i-switch :value='formValidate.details.is_use_section===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否显示为首页列表">
<i-switch :value='formValidate.is_in_home_list===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="首页列表背景图" prop="details.res_home_background_pic">
<UploadS ref="res_home_background_pic" v-model="formValidate.details.res_home_background_pic" />
</FormItem>
<FormItem label="是否听完解锁">
<i-switch :value='formValidate.details.is_lock_mode===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在探索列表页显示">
<i-switch :value='formValidate.is_in_discover_list===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在热门标签显示">
<i-switch :value='formValidate.is_hottest===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否置热门顶">
<i-switch :value='formValidate.is_hottest_top===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在最新标签显示">
<i-switch :value='formValidate.is_newest===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否置最新顶">
<i-switch :value='formValidate.is_newest_top===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在全部显示">
<i-switch :value='formValidate.is_in_discover_all===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="探索页列表背景图" prop="details.res_discover_list_pic">
<UploadS ref="res_discover_list_pic" v-model="formValidate.details.res_discover_list_pic" />
</FormItem>
<FormItem label="是否开启评论">
<i-switch :value='formValidate.allow_comment===1?true:false' @on-change='(value)=>{change(value,2)}'/>
</FormItem>
<FormItem label="是否允许调整倍速">
<i-switch :value='formValidate.details.is_allow_change_speed===1?true:false' @on-change='(value)=>{change(value,2)}'/>
</FormItem>
</Form>
<div class="demo-drawer-footer">
<Button style="margin-right: 8px" @click="close">取消</Button>
<Button type="primary" @click="submit">保存</Button>
</div>
</Drawer>
</div>
</template>
<script>
import UploadS from '@/view/common/upload.vue'
import {
addCourses,
editCourses
} from '@/api/courses'
const _requireBlur = { required: true, message: ' ', trigger: 'blur' }
const validatorPic = (rule, value, callback) => {
if (!value) {
callback(new Error(' '))
} else {
callback()
}
}
const _requirePic = { required: true, message: ' ', trigger: 'blur', validator: validatorPic }
export default {
props: {
search: {
type: Function,
default: null
},
row: {
type: Object,
default: () => {}
},
visible: {
type: Boolean,
default: false
}
},
components: {
UploadS
},
data () {
return {
modalVisible: false,
editMode: false,
formTitle: '',
styles: {
height: 'calc(100% - 55px)',
overflow: 'auto',
paddingBottom: '53px',
position: 'static'
},
formValidate: {},
ruleValidate: {
'details.name': [_requireBlur],
'details.summary': [_requireBlur],
'details.res_home_background_pic': [_requirePic],
'details.res_discover_list_pic': [_requirePic]
}
}
},
watch: {
visible: {
handler: function (val) {
this.modalVisible = val
if (val) {
this.search()
}
this.formValidate = this.row
},
immediate: true
},
row: {
handler: function (val) {
console.log(val)
if (val.id === 'add') {
this.editMode = false
this.formTitle = '新增栏目'
this.formValidate = {
details: {
name: '',
summary: '',
is_use_section: 1,
is_lock_mode: 0,
is_allow_change_speed: 0,
res_home_background_pic: '',
res_discover_list_pic: ''
},
category_id: 1,
is_in_discover_list: 0,
is_hottest: 0,
is_hottest_top: 0,
is_newest: 0,
is_newest_top: 0,
is_in_discover_all: 0,
allow_comment: 0
}
} else {
this.editMode = true
this.formTitle = '编辑栏目'
this.formValidate = val
}
},
immediate: true
}
},
methods: {
submit () {
if (this.$refs['res_home_background_pic'].getData()) {
this.formValidate.details.res_home_background_pic = this.$refs['res_home_background_pic'].getData()
}
if (this.$refs['res_discover_list_pic'].getData()) {
this.formValidate.details.res_discover_list_pic = this.$refs['res_discover_list_pic'].getData()
}
console.log(this.formValidate)
this.$refs['formValidate'].validate((valid) => {
if (valid) {
delete this.formValidate.created_at
delete this.formValidate.deleted_at
delete this.formValidate.updated_at
let params = [this.formValidate]
if (this.editMode) {
editCourses(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
this.close()
this.search()
}
})
} else {
addCourses(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('新增成功')
this.close()
this.search()
}
})
}
} else {
this.$Message.error('您还有必填项未填写')
}
})
},
change (val, type) {
if (type === 1) {
this.formValidate.is_in_home_lis = val ? 1 : 0
}
if (type === 2) {
this.formValidate.is_in_discover_list = val ? 1 : 0
}
},
close () {
this.$emit('update:visible', false)
}
}
}
</script>
<style>
.demo-drawer-footer {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
background: #fff;
}
</style>
<template>
<div class="content">
<div class="topSearch">
<div class="left">
<Input style="width:280px" search enter-button placeholder="" @on-search='search'/>
<span class="lable">
栏目
</span>
<Select clearable v-model="model1" style="width:80px">
<Option
v-for="item in $store.state.app.categoryObj.categories"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
</div>
<div class="right">
<Button class='success-btn' type="success" @click="seq">排序</Button>
<Button @click="add" type="primary">新增</Button>
<Button type="error" @click="del">删除</Button>
</div>
</div>
<Stable :data='data' @edit='edit' @select='select'/>
<Forms :visible.sync="visible" :search='search' :row='row'/>
<draggableList :visible.sync="draggableVisible" :list='data' @ok='ok'/>
</div>
</template>
<script>
import Stable from './table.vue'
import Forms from './form.vue'
import draggableList from '@/view/common/draggable.vue'
import {
getCourses,
delCourses,
editCourses
} from '@/api/courses'
export default {
name: 'categories',
components: {
Stable,
Forms,
draggableList
},
data () {
return {
selectList: [],
visible: false,
draggableVisible: false,
row: {},
data: []
}
},
methods: {
edit (row) {
this.visible = true
this.row = row
},
add () {
this.visible = true
this.row = {
id: 'add'
}
},
select (val) {
this.selectList = val
},
seq () {
this.draggableVisible = true
},
ok (list) {
editCourses(list).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('操作成功')
this.draggableVisible = false
this.search()
}
})
},
del () {
delCourses(this.selectList).then((res) => {
if (res.code === 200) {
this.$Message.success('删除成功')
this.search()
} else {
this.$Message.error('删除失败')
}
})
},
search () {
getCourses({}).then((res) => {
this.data = res.data
})
}
},
mounted () {
this.search()
}
}
</script>
<style lang="less"></style>
<template>
<div class="table">
<Table :loading="$store.state.app.isLoading" border ref="selection" :columns="columns" :data="data" @on-select='onSelect' @on-select-all='onSelectAll'>
<template slot-scope="{ row }" slot="name">
<span>{{ row.details.name }}</span>
</template>
<template slot-scope="{ row }" slot="summary">
<span>{{ row.details.summary }}</span>
</template>
<template slot-scope="{ row }" slot="created_at">
<span>{{ row.created_at | format}}</span>
</template>
<template slot-scope="{ row }" slot="enable">
<i-switch
:value="row.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, row);
}
"
/>
</template>
<template slot-scope="{ row }" slot="action">
<a @click="edit(row)">编辑</a>
</template>
<template slot-scope="{ row }" slot="is_use_section">
<a v-if="row.details.is_section==='1'">编辑</a>
</template>
<template slot-scope="{ row }" slot="edit">
<a>编辑</a>
</template>
</Table>
</div>
</template>
<script>
import { formatDate } from '@/libs/util'
import {
editCourses
} from '@/api/courses'
export default {
props: {
data: {
type: Array,
default () {
return []
}
}
},
data () {
return {
selectList: [],
columns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: '名称',
slot: 'name'
},
{
title: '简介',
slot: 'summary',
width: 300
},
{
title: '创建日期',
slot: 'created_at',
width: 200,
key: 'created_at'
},
{
title: '生效',
slot: 'enable'
},
{
title: '操作',
slot: 'action'
},
{
title: '编辑商品',
slot: 'edit'
},
{
title: '编辑板块',
slot: 'is_use_section'
},
{
title: '编辑条目内容',
slot: 'edit'
}
]
}
},
mounted () {
},
filters: {
format (value) {
return formatDate(value)
}
},
methods: {
edit (row) {
this.$emit('edit', row)
},
onSelect (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
onSelectAll (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
change (status, row) {
console.log('开关状态:' + status + row)
row.is_enable = status ? 1 : 0
delete row.created_at
delete row.deleted_at
delete row.updated_at
editCourses([row]).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
}
})
},
handleSelectAll (status) {
this.$refs.selection.selectAll(status)
}
}
}
</script>
<style lang="less">
.table {
margin-top: 20px;
}
</style>
<template>
<div>
<Drawer
:title="formTitle"
v-model="modalVisible"
width="720"
:mask-closable="false"
:styles="styles"
@on-close="close"
>
<Form
ref="formValidate"
:model="formValidate"
:rules="ruleValidate"
:label-width="150"
v-if="formValidate.details"
>
<FormItem label="关联栏目">
<Select clearable v-model="formValidate.category_id" style="width:150px">
<Option
v-for="item in $store.state.app.categoryObj.categories"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
</FormItem>
<FormItem label="条目名称" prop="details.name">
<Input
v-model="formValidate.details.name"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="简介" prop="details.summary">
<Input
v-model="formValidate.details.summary"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="是否启用板块层级">
<i-switch :value='formValidate.details.is_use_section===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否显示为首页列表">
<i-switch :value='formValidate.is_in_home_list===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="首页列表背景图" prop="details.res_home_background_pic">
<UploadS ref="res_home_background_pic" v-model="formValidate.details.res_home_background_pic" />
</FormItem>
<FormItem label="是否听完解锁">
<i-switch :value='formValidate.details.is_lock_mode===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在探索列表页显示">
<i-switch :value='formValidate.is_in_discover_list===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在热门标签显示">
<i-switch :value='formValidate.is_hottest===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否置热门顶">
<i-switch :value='formValidate.is_hottest_top===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在最新标签显示">
<i-switch :value='formValidate.is_newest===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否置最新顶">
<i-switch :value='formValidate.is_newest_top===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在全部显示">
<i-switch :value='formValidate.is_in_discover_all===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="探索页列表背景图" prop="details.res_discover_list_pic">
<UploadS ref="res_discover_list_pic" v-model="formValidate.details.res_discover_list_pic" />
</FormItem>
<FormItem label="是否开启评论">
<i-switch :value='formValidate.allow_comment===1?true:false' @on-change='(value)=>{change(value,2)}'/>
</FormItem>
<FormItem label="是否允许调整倍速">
<i-switch :value='formValidate.details.is_allow_change_speed===1?true:false' @on-change='(value)=>{change(value,2)}'/>
</FormItem>
</Form>
<div class="demo-drawer-footer">
<Button style="margin-right: 8px" @click="close">取消</Button>
<Button type="primary" @click="submit">保存</Button>
</div>
</Drawer>
</div>
</template>
<script>
import UploadS from '@/view/common/upload.vue'
import {
addCourses,
editCourses
} from '@/api/courses'
const _requireBlur = { required: true, message: ' ', trigger: 'blur' }
const validatorPic = (rule, value, callback) => {
if (!value) {
callback(new Error(' '))
} else {
callback()
}
}
const _requirePic = { required: true, message: ' ', trigger: 'blur', validator: validatorPic }
export default {
props: {
search: {
type: Function,
default: null
},
row: {
type: Object,
default: () => {}
},
visible: {
type: Boolean,
default: false
}
},
components: {
UploadS
},
data () {
return {
modalVisible: false,
editMode: false,
formTitle: '',
styles: {
height: 'calc(100% - 55px)',
overflow: 'auto',
paddingBottom: '53px',
position: 'static'
},
formValidate: {},
ruleValidate: {
'details.name': [_requireBlur],
'details.summary': [_requireBlur],
'details.res_home_background_pic': [_requirePic],
'details.res_discover_list_pic': [_requirePic]
}
}
},
watch: {
visible: {
handler: function (val) {
this.modalVisible = val
if (val) {
this.search()
}
this.formValidate = this.row
},
immediate: true
},
row: {
handler: function (val) {
console.log(val)
if (val.id === 'add') {
this.editMode = false
this.formTitle = '新增栏目'
this.formValidate = {
details: {
name: '',
summary: '',
is_use_section: 1,
is_lock_mode: 0,
is_allow_change_speed: 0,
res_home_background_pic: '',
res_discover_list_pic: ''
},
category_id: 1,
is_in_discover_list: 0,
is_hottest: 0,
is_hottest_top: 0,
is_newest: 0,
is_newest_top: 0,
is_in_discover_all: 0,
allow_comment: 0
}
} else {
this.editMode = true
this.formTitle = '编辑栏目'
this.formValidate = val
}
},
immediate: true
}
},
methods: {
submit () {
if (this.$refs['res_home_background_pic'].getData()) {
this.formValidate.details.res_home_background_pic = this.$refs['res_home_background_pic'].getData()
}
if (this.$refs['res_discover_list_pic'].getData()) {
this.formValidate.details.res_discover_list_pic = this.$refs['res_discover_list_pic'].getData()
}
console.log(this.formValidate)
this.$refs['formValidate'].validate((valid) => {
if (valid) {
delete this.formValidate.created_at
delete this.formValidate.deleted_at
delete this.formValidate.updated_at
let params = [this.formValidate]
if (this.editMode) {
editCourses(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
this.close()
this.search()
}
})
} else {
addCourses(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('新增成功')
this.close()
this.search()
}
})
}
} else {
this.$Message.error('您还有必填项未填写')
}
})
},
change (val, type) {
if (type === 1) {
this.formValidate.is_in_home_lis = val ? 1 : 0
}
if (type === 2) {
this.formValidate.is_in_discover_list = val ? 1 : 0
}
},
close () {
this.$emit('update:visible', false)
}
}
}
</script>
<style>
.demo-drawer-footer {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
background: #fff;
}
</style>
<template>
<div class="content">
<div class="topSearch">
<div class="left">
<Input style="width:280px" search enter-button placeholder="" @on-search='search'/>
<span class="lable">
栏目
</span>
<Select clearable v-model="model1" style="width:80px">
<Option
v-for="item in $store.state.app.categoryObj.categories"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
</div>
<div class="right">
<Button class='success-btn' type="success" @click="seq">排序</Button>
<Button @click="add" type="primary">新增</Button>
<Button type="error" @click="del">删除</Button>
</div>
</div>
<Stable :data='data' @edit='edit' @select='select'/>
<Forms :visible.sync="visible" :search='search' :row='row'/>
<draggableList :visible.sync="draggableVisible" :list='data' @ok='ok'/>
</div>
</template>
<script>
import Stable from './table.vue'
import Forms from './form.vue'
import draggableList from '@/view/common/draggable.vue'
import {
getCourses,
delCourses,
editCourses
} from '@/api/courses'
export default {
name: 'categories',
components: {
Stable,
Forms,
draggableList
},
data () {
return {
selectList: [],
visible: false,
draggableVisible: false,
row: {},
data: []
}
},
methods: {
edit (row) {
this.visible = true
this.row = row
},
add () {
this.visible = true
this.row = {
id: 'add'
}
},
select (val) {
this.selectList = val
},
seq () {
this.draggableVisible = true
},
ok (list) {
editCourses(list).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('操作成功')
this.draggableVisible = false
this.search()
}
})
},
del () {
delCourses(this.selectList).then((res) => {
if (res.code === 200) {
this.$Message.success('删除成功')
this.search()
} else {
this.$Message.error('删除失败')
}
})
},
search () {
getCourses({}).then((res) => {
this.data = res.data
})
}
},
mounted () {
this.search()
}
}
</script>
<style lang="less"></style>
<template>
<div class="table">
<Table :loading="$store.state.app.isLoading" border ref="selection" :columns="columns" :data="data" @on-select='onSelect' @on-select-all='onSelectAll'>
<template slot-scope="{ row }" slot="name">
<span>{{ row.details.name }}</span>
</template>
<template slot-scope="{ row }" slot="summary">
<span>{{ row.details.summary }}</span>
</template>
<template slot-scope="{ row }" slot="created_at">
<span>{{ row.created_at | format}}</span>
</template>
<template slot-scope="{ row }" slot="enable">
<i-switch
:value="row.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, row);
}
"
/>
</template>
<template slot-scope="{ row }" slot="action">
<a @click="edit(row)">编辑</a>
</template>
<template slot-scope="{ row }" slot="is_use_section">
<a v-if="row.details.is_section==='1'">编辑</a>
</template>
<template slot-scope="{ row }" slot="edit">
<a>编辑</a>
</template>
</Table>
</div>
</template>
<script>
import { formatDate } from '@/libs/util'
import {
editCourses
} from '@/api/courses'
export default {
props: {
data: {
type: Array,
default () {
return []
}
}
},
data () {
return {
selectList: [],
columns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: '名称',
slot: 'name'
},
{
title: '简介',
slot: 'summary',
width: 300
},
{
title: '创建日期',
slot: 'created_at',
width: 200,
key: 'created_at'
},
{
title: '生效',
slot: 'enable'
},
{
title: '操作',
slot: 'action'
},
{
title: '编辑商品',
slot: 'edit'
},
{
title: '编辑板块',
slot: 'is_use_section'
},
{
title: '编辑条目内容',
slot: 'edit'
}
]
}
},
mounted () {
},
filters: {
format (value) {
return formatDate(value)
}
},
methods: {
edit (row) {
this.$emit('edit', row)
},
onSelect (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
onSelectAll (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
change (status, row) {
console.log('开关状态:' + status + row)
row.is_enable = status ? 1 : 0
delete row.created_at
delete row.deleted_at
delete row.updated_at
editCourses([row]).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
}
})
},
handleSelectAll (status) {
this.$refs.selection.selectAll(status)
}
}
}
</script>
<style lang="less">
.table {
margin-top: 20px;
}
</style>
<template>
<div>
<Drawer
:title="formTitle"
v-model="modalVisible"
width="720"
:mask-closable="false"
:styles="styles"
@on-close="close"
>
<Form
ref="formValidate"
:model="formValidate"
:rules="ruleValidate"
:label-width="150"
v-if="formValidate.details"
>
<FormItem label="关联栏目">
<Select clearable v-model="formValidate.category_id" style="width:150px">
<Option
v-for="item in $store.state.app.categoryObj.categories"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
</FormItem>
<FormItem label="条目名称" prop="details.name">
<Input
v-model="formValidate.details.name"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="简介" prop="details.summary">
<Input
v-model="formValidate.details.summary"
placeholder="Enter something"
></Input>
</FormItem>
<FormItem label="是否启用板块层级">
<i-switch :value='formValidate.details.is_use_section===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否显示为首页列表">
<i-switch :value='formValidate.is_in_home_list===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="首页列表背景图" prop="details.res_home_background_pic">
<UploadS ref="res_home_background_pic" v-model="formValidate.details.res_home_background_pic" />
</FormItem>
<FormItem label="是否听完解锁">
<i-switch :value='formValidate.details.is_lock_mode===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在探索列表页显示">
<i-switch :value='formValidate.is_in_discover_list===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在热门标签显示">
<i-switch :value='formValidate.is_hottest===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否置热门顶">
<i-switch :value='formValidate.is_hottest_top===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在最新标签显示">
<i-switch :value='formValidate.is_newest===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否置最新顶">
<i-switch :value='formValidate.is_newest_top===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="是否在全部显示">
<i-switch :value='formValidate.is_in_discover_all===1?true:false' @on-change='(value)=>{change(value,1)}'/>
</FormItem>
<FormItem label="探索页列表背景图" prop="details.res_discover_list_pic">
<UploadS ref="res_discover_list_pic" v-model="formValidate.details.res_discover_list_pic" />
</FormItem>
<FormItem label="是否开启评论">
<i-switch :value='formValidate.allow_comment===1?true:false' @on-change='(value)=>{change(value,2)}'/>
</FormItem>
<FormItem label="是否允许调整倍速">
<i-switch :value='formValidate.details.is_allow_change_speed===1?true:false' @on-change='(value)=>{change(value,2)}'/>
</FormItem>
</Form>
<div class="demo-drawer-footer">
<Button style="margin-right: 8px" @click="close">取消</Button>
<Button type="primary" @click="submit">保存</Button>
</div>
</Drawer>
</div>
</template>
<script>
import UploadS from '@/view/common/upload.vue'
import {
addCourses,
editCourses
} from '@/api/courses'
const _requireBlur = { required: true, message: ' ', trigger: 'blur' }
const validatorPic = (rule, value, callback) => {
if (!value) {
callback(new Error(' '))
} else {
callback()
}
}
const _requirePic = { required: true, message: ' ', trigger: 'blur', validator: validatorPic }
export default {
props: {
search: {
type: Function,
default: null
},
row: {
type: Object,
default: () => {}
},
visible: {
type: Boolean,
default: false
}
},
components: {
UploadS
},
data () {
return {
modalVisible: false,
editMode: false,
formTitle: '',
styles: {
height: 'calc(100% - 55px)',
overflow: 'auto',
paddingBottom: '53px',
position: 'static'
},
formValidate: {},
ruleValidate: {
'details.name': [_requireBlur],
'details.summary': [_requireBlur],
'details.res_home_background_pic': [_requirePic],
'details.res_discover_list_pic': [_requirePic]
}
}
},
watch: {
visible: {
handler: function (val) {
this.modalVisible = val
if (val) {
this.search()
}
this.formValidate = this.row
},
immediate: true
},
row: {
handler: function (val) {
console.log(val)
if (val.id === 'add') {
this.editMode = false
this.formTitle = '新增栏目'
this.formValidate = {
details: {
name: '',
summary: '',
is_use_section: 1,
is_lock_mode: 0,
is_allow_change_speed: 0,
res_home_background_pic: '',
res_discover_list_pic: ''
},
category_id: 1,
is_in_discover_list: 0,
is_hottest: 0,
is_hottest_top: 0,
is_newest: 0,
is_newest_top: 0,
is_in_discover_all: 0,
allow_comment: 0
}
} else {
this.editMode = true
this.formTitle = '编辑栏目'
this.formValidate = val
}
},
immediate: true
}
},
methods: {
submit () {
if (this.$refs['res_home_background_pic'].getData()) {
this.formValidate.details.res_home_background_pic = this.$refs['res_home_background_pic'].getData()
}
if (this.$refs['res_discover_list_pic'].getData()) {
this.formValidate.details.res_discover_list_pic = this.$refs['res_discover_list_pic'].getData()
}
console.log(this.formValidate)
this.$refs['formValidate'].validate((valid) => {
if (valid) {
delete this.formValidate.created_at
delete this.formValidate.deleted_at
delete this.formValidate.updated_at
let params = [this.formValidate]
if (this.editMode) {
editCourses(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
this.close()
this.search()
}
})
} else {
addCourses(params).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('新增成功')
this.close()
this.search()
}
})
}
} else {
this.$Message.error('您还有必填项未填写')
}
})
},
change (val, type) {
if (type === 1) {
this.formValidate.is_in_home_lis = val ? 1 : 0
}
if (type === 2) {
this.formValidate.is_in_discover_list = val ? 1 : 0
}
},
close () {
this.$emit('update:visible', false)
}
}
}
</script>
<style>
.demo-drawer-footer {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
border-top: 1px solid #e8e8e8;
padding: 10px 16px;
text-align: right;
background: #fff;
}
</style>
<template>
<div class="content">
<div class="topSearch">
<div class="left">
<Input style="width:280px" search enter-button placeholder="" @on-search='search'/>
<span class="lable">
栏目
</span>
<Select clearable v-model="model1" style="width:80px">
<Option
v-for="item in $store.state.app.categoryObj.categories"
:value="item.id"
:key="item.id"
>{{ item.name }}</Option
>
</Select>
</div>
<div class="right">
<Button class='success-btn' type="success" @click="seq">排序</Button>
<Button @click="add" type="primary">新增</Button>
<Button type="error" @click="del">删除</Button>
</div>
</div>
<Stable :data='data' @edit='edit' @select='select'/>
<Forms :visible.sync="visible" :search='search' :row='row'/>
<draggableList :visible.sync="draggableVisible" :list='data' @ok='ok'/>
</div>
</template>
<script>
import Stable from './table.vue'
import Forms from './form.vue'
import draggableList from '@/view/common/draggable.vue'
import {
getCourses,
delCourses,
editCourses
} from '@/api/courses'
export default {
name: 'categories',
components: {
Stable,
Forms,
draggableList
},
data () {
return {
selectList: [],
visible: false,
draggableVisible: false,
row: {},
data: []
}
},
methods: {
edit (row) {
this.visible = true
this.row = row
},
add () {
this.visible = true
this.row = {
id: 'add'
}
},
select (val) {
this.selectList = val
},
seq () {
this.draggableVisible = true
},
ok (list) {
editCourses(list).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('操作成功')
this.draggableVisible = false
this.search()
}
})
},
del () {
delCourses(this.selectList).then((res) => {
if (res.code === 200) {
this.$Message.success('删除成功')
this.search()
} else {
this.$Message.error('删除失败')
}
})
},
search () {
getCourses({}).then((res) => {
this.data = res.data
})
}
},
mounted () {
this.search()
}
}
</script>
<style lang="less"></style>
<template>
<div class="table">
<Table :loading="$store.state.app.isLoading" border ref="selection" :columns="columns" :data="data" @on-select='onSelect' @on-select-all='onSelectAll'>
<template slot-scope="{ row }" slot="name">
<span>{{ row.details.name }}</span>
</template>
<template slot-scope="{ row }" slot="summary">
<span>{{ row.details.summary }}</span>
</template>
<template slot-scope="{ row }" slot="created_at">
<span>{{ row.created_at | format}}</span>
</template>
<template slot-scope="{ row }" slot="enable">
<i-switch
:value="row.is_enable === 1 ? true : false"
@on-change="
value => {
change(value, row);
}
"
/>
</template>
<template slot-scope="{ row }" slot="action">
<a @click="edit(row)">编辑</a>
</template>
<template slot-scope="{ row }" slot="is_use_section">
<a v-if="row.details.is_section==='1'">编辑</a>
</template>
<template slot-scope="{ row }" slot="edit">
<a>编辑</a>
</template>
</Table>
</div>
</template>
<script>
import { formatDate } from '@/libs/util'
import {
editCourses
} from '@/api/courses'
export default {
props: {
data: {
type: Array,
default () {
return []
}
}
},
data () {
return {
selectList: [],
columns: [
{
type: 'selection',
width: 60,
align: 'center'
},
{
title: '名称',
slot: 'name'
},
{
title: '简介',
slot: 'summary',
width: 300
},
{
title: '创建日期',
slot: 'created_at',
width: 200,
key: 'created_at'
},
{
title: '生效',
slot: 'enable'
},
{
title: '操作',
slot: 'action'
},
{
title: '编辑商品',
slot: 'edit'
},
{
title: '编辑板块',
slot: 'is_use_section'
},
{
title: '编辑条目内容',
slot: 'edit'
}
]
}
},
mounted () {
},
filters: {
format (value) {
return formatDate(value)
}
},
methods: {
edit (row) {
this.$emit('edit', row)
},
onSelect (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
onSelectAll (val) {
this.selectList = val.map((p) => {
return p.id
})
this.$emit('select', this.selectList)
},
change (status, row) {
console.log('开关状态:' + status + row)
row.is_enable = status ? 1 : 0
delete row.created_at
delete row.deleted_at
delete row.updated_at
editCourses([row]).then((res) => {
if (res.code === 200 && res.data.length > 0) {
this.$Message.success('修改成功')
}
})
},
handleSelectAll (status) {
this.$refs.selection.selectAll(status)
}
}
}
</script>
<style lang="less">
.table {
margin-top: 20px;
}
</style>
{
"plugins": [
"cypress"
],
"env": {
"mocha": true,
"cypress/globals": true
},
"rules": {
"strict": "off"
}
}
// https://docs.cypress.io/guides/guides/plugins-guide.html
module.exports = (on, config) => Object.assign({}, config, {
fixturesFolder: 'tests/e2e/fixtures',
integrationFolder: 'tests/e2e/specs',
screenshotsFolder: 'tests/e2e/screenshots',
videosFolder: 'tests/e2e/videos',
supportFile: 'tests/e2e/support/index.js'
})
// https://docs.cypress.io/api/introduction/api.html
describe('My First Test', () => {
it('Visits the app root url', () => {
cy.visit('/')
cy.contains('h1', 'Welcome to Your Vue.js App')
})
})
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')
module.exports = {
env: {
mocha: true
},
rules: {
'import/no-extraneous-dependencies': 'off'
}
}
import { expect } from 'chai'
import { shallow } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'
describe('HelloWorld.vue', () => {
it('renders props.msg when passed', () => {
const msg = 'new message'
const wrapper = shallow(HelloWorld, {
propsData: { msg }
})
expect(wrapper.text()).to.include(msg)
})
})
const path = require('path')
const resolve = dir => {
return path.join(__dirname, dir)
}
// 项目部署基础
// 默认情况下,我们假设你的应用将被部署在域的根目录下,
// 例如:https://www.my-app.com/
// 默认:'/'
// 如果您的应用程序部署在子路径中,则需要在这指定子路径
// 例如:https://www.foobar.com/my-app/
// 需要将它改为'/my-app/'
// iview-admin线上演示打包路径: https://file.iviewui.com/admin-dist/
const BASE_URL = process.env.NODE_ENV === 'production'
? '/'
: '/'
module.exports = {
// Project deployment base
// By default we assume your app will be deployed at the root of a domain,
// e.g. https://www.my-app.com/
// If your app is deployed at a sub-path, you will need to specify that
// sub-path here. For example, if your app is deployed at
// https://www.foobar.com/my-app/
// then change this to '/my-app/'
publicPath: BASE_URL,
// tweak internal webpack configuration.
// see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md
// 如果你不需要使用eslint,把lintOnSave设为false即可
lintOnSave: true,
chainWebpack: config => {
config.resolve.alias
.set('@', resolve('src')) // key,value自行定义,比如.set('@@', resolve('src/components'))
.set('_c', resolve('src/components'))
},
// 设为false打包时不生成.map文件
productionSourceMap: false,
// 这里写你调用接口的基础路径,来解决跨域,如果设置了代理,那你本地开发环境的axios的baseUrl要写为 '' ,即空字符串
devServer: {
disableHostCheck: true,
proxy: {
'/bg': {
'target': 'https://applet.mwcx.cn',
'changeOrigin': true,
'pathRewirte': {
'^/bg': ''
}
}
}
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
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