增加了管理H5用户信息
This commit is contained in:
commit
14e094f08e
|
@ -0,0 +1,8 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = true
|
|
@ -0,0 +1,108 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
#ide
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,45 @@
|
|||
# wx-manage
|
||||
wx-manage是一个支持公众号管理系统,支持多公众号接入。
|
||||
wx-manage提供公众号菜单、自动回复、公众号素材、简易CMS、等管理功能,请注意本项目仅为管理后台界面,需配合后端程序[wx-api](https://github.com/niefy/wx-api)一起使用
|
||||
|
||||
### [📖项目文档](https://www.yuque.com/nifury/wx) | [Github仓库](https://github.com/niefy/wx-manage) | [码云仓库](https://gitee.com/niefy/wx-manage)
|
||||
|
||||
## 项目说明
|
||||
- wx-api是一个轻量级的公众号开发种子项目,可快速接入微信公众号管理功能
|
||||
- 管理后台前端项目wx-manage:https://github.com/niefy/wx-manage
|
||||
- 移动端示例wx-client: https://github.com/niefy/wx-client
|
||||
|
||||
## [docker方式启动文档](https://www.yuque.com/nifury/wx/nf1rvm)
|
||||
## [开发环境启动文档](https://www.yuque.com/nifury/wx/guobb7)
|
||||
## [生产环境部署步骤](https://www.yuque.com/nifury/wx/ofehhv)
|
||||
|
||||
## 技术选型:
|
||||
- 核心框架:Spring Boot
|
||||
- 安全框架:Apache Shiro
|
||||
- 持久层框架:MyBatis-Plus
|
||||
- 公众号开发框架:[WxJava](https://github.com/Wechat-Group/WxJava)
|
||||
- 后端脚手架:[renren-fast](https://gitee.com/renrenio/renren-fast)
|
||||
- 页面交互:[Vue2.x](https://cn.vuejs.org/v2/guide/)
|
||||
- UI框架:[ElementUI](https://element.eleme.cn/#/zh-CN/component/quickstart)
|
||||
- 管理后台界面模板:[renren-fast-vue](https://gitee.com/renrenio/renren-fast-vue)
|
||||
- 富文本编辑器:[tinymce5](https://www.tiny.cloud/docs/quick-start/)
|
||||
|
||||
## 截图
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
## [项目开发进度](https://www.yuque.com/nifury/wx/kens6d)
|
||||
## [代码贡献指南](https://www.yuque.com/nifury/wx/ykqswi)
|
||||
## 开发交流
|
||||
QQ群:1023785886(已满)、993128490 技术交流群严禁广告,发广告立即踢出+拉黑+举报,加群密码:wx
|
|
@ -0,0 +1,9 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-syntax-dynamic-import"
|
||||
],
|
||||
sourceType: 'unambiguous'
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"name": "wx-manage",
|
||||
"version": "0.8.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tinymce/tinymce-vue": "^3.2.6",
|
||||
"axios": "^1.4.0",
|
||||
"element-ui": "^2.15.8",
|
||||
"moment": "^2.29.3",
|
||||
"vue": "^2.6.12",
|
||||
"vue-clipboard2": "^0.3.1",
|
||||
"vue-cookie": "^1.1.4",
|
||||
"vue-router": "^3.4.9",
|
||||
"vuex": "^3.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||
"@vue/cli-plugin-babel": "^5.0.8",
|
||||
"@vue/cli-service": "^5.0.8",
|
||||
"sass": "^1.51.0",
|
||||
"sass-loader": "10.2.0",
|
||||
"vue-template-compiler": "^2.6.12"
|
||||
},
|
||||
"postcss": {
|
||||
"plugins": {
|
||||
"autoprefixer": {}
|
||||
}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="referrer" content="never">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>微信后台管理系统</title>
|
||||
<!-- tinymce编辑器 -->
|
||||
<script src="https://cdn.bootcdn.net/ajax/libs/tinymce/5.10.4/tinymce.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but weixin-manage doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,96 @@
|
|||
/* html相关样式 */
|
||||
a {
|
||||
color: #4285f4;
|
||||
}
|
||||
h1,h2,h3,h4,h5,h6{
|
||||
margin: 0.3rem 0;
|
||||
color: #0064A8;
|
||||
line-height: 2rem;
|
||||
}
|
||||
h1{
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
h2{
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
h3{
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 0.2em;
|
||||
border: 0;
|
||||
color: #CCCCCC;
|
||||
background-color: #CCCCCC;
|
||||
}
|
||||
|
||||
p,
|
||||
blockquote,
|
||||
ul,
|
||||
ol,
|
||||
dl,
|
||||
li,
|
||||
table,
|
||||
pre {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 1em 0;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: #F8F8F8;
|
||||
border: 1px solid #CCCCCC;
|
||||
border-radius: 3px;
|
||||
overflow: auto;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
color: #666666;
|
||||
margin: 0;
|
||||
border-left: 0.2em #EEE solid;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
margin: 1em 0;
|
||||
padding: 0 0 0 2em;
|
||||
}
|
||||
|
||||
li p:last-child {
|
||||
margin: 0
|
||||
}
|
||||
|
||||
dd {
|
||||
margin: 0 0 0 2em;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
max-width: 300px;
|
||||
display: block;
|
||||
object-fit: contain;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
border: 1px solid #eee;
|
||||
}
|
||||
|
||||
td {
|
||||
vertical-align: top;
|
||||
padding: 0.2em 0;
|
||||
border-top: 1px solid #EEEEEE;
|
||||
}
|
|
@ -0,0 +1,389 @@
|
|||
tinymce.addI18n('zh_CN',{
|
||||
"Redo": "\u91cd\u505a",
|
||||
"Undo": "\u64a4\u9500",
|
||||
"Cut": "\u526a\u5207",
|
||||
"Copy": "\u590d\u5236",
|
||||
"Paste": "\u7c98\u8d34",
|
||||
"Select all": "\u5168\u9009",
|
||||
"New document": "\u65b0\u6587\u4ef6",
|
||||
"Ok": "\u786e\u5b9a",
|
||||
"Cancel": "\u53d6\u6d88",
|
||||
"Visual aids": "\u7f51\u683c\u7ebf",
|
||||
"Bold": "\u7c97\u4f53",
|
||||
"Italic": "\u659c\u4f53",
|
||||
"Underline": "\u4e0b\u5212\u7ebf",
|
||||
"Strikethrough": "\u5220\u9664\u7ebf",
|
||||
"Superscript": "\u4e0a\u6807",
|
||||
"Subscript": "\u4e0b\u6807",
|
||||
"Clear formatting": "\u6e05\u9664\u683c\u5f0f",
|
||||
"Align left": "\u5de6\u8fb9\u5bf9\u9f50",
|
||||
"Align center": "\u4e2d\u95f4\u5bf9\u9f50",
|
||||
"Align right": "\u53f3\u8fb9\u5bf9\u9f50",
|
||||
"Justify": "\u4e24\u7aef\u5bf9\u9f50",
|
||||
"Bullet list": "\u9879\u76ee\u7b26\u53f7",
|
||||
"Numbered list": "\u7f16\u53f7\u5217\u8868",
|
||||
"Decrease indent": "\u51cf\u5c11\u7f29\u8fdb",
|
||||
"Increase indent": "\u589e\u52a0\u7f29\u8fdb",
|
||||
"Close": "\u5173\u95ed",
|
||||
"Formats": "\u683c\u5f0f",
|
||||
"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "\u4f60\u7684\u6d4f\u89c8\u5668\u4e0d\u652f\u6301\u6253\u5f00\u526a\u8d34\u677f\uff0c\u8bf7\u4f7f\u7528Ctrl+X\/C\/V\u7b49\u5feb\u6377\u952e\u3002",
|
||||
"Headers": "\u6807\u9898",
|
||||
"Header 1": "\u6807\u98981",
|
||||
"Header 2": "\u6807\u98982",
|
||||
"Header 3": "\u6807\u98983",
|
||||
"Header 4": "\u6807\u98984",
|
||||
"Header 5": "\u6807\u98985",
|
||||
"Header 6": "\u6807\u98986",
|
||||
"Headings": "\u6807\u9898",
|
||||
"Heading 1": "\u6807\u98981",
|
||||
"Heading 2": "\u6807\u98982",
|
||||
"Heading 3": "\u6807\u98983",
|
||||
"Heading 4": "\u6807\u98984",
|
||||
"Heading 5": "\u6807\u98985",
|
||||
"Heading 6": "\u6807\u98986",
|
||||
"Preformatted": "\u9884\u5148\u683c\u5f0f\u5316\u7684",
|
||||
"Div": "Div",
|
||||
"Pre": "Pre",
|
||||
"Code": "\u4ee3\u7801",
|
||||
"Paragraph": "\u6bb5\u843d",
|
||||
"Blockquote": "\u5f15\u6587\u533a\u5757",
|
||||
"Inline": "\u6587\u672c",
|
||||
"Blocks": "\u57fa\u5757",
|
||||
"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "\u5f53\u524d\u4e3a\u7eaf\u6587\u672c\u7c98\u8d34\u6a21\u5f0f\uff0c\u518d\u6b21\u70b9\u51fb\u53ef\u4ee5\u56de\u5230\u666e\u901a\u7c98\u8d34\u6a21\u5f0f\u3002",
|
||||
"Fonts": "\u5b57\u4f53",
|
||||
"Font Sizes": "\u5b57\u53f7",
|
||||
"Class": "\u7c7b\u578b",
|
||||
"Browse for an image": "\u6d4f\u89c8\u56fe\u50cf",
|
||||
"OR": "\u6216",
|
||||
"Drop an image here": "\u62d6\u653e\u4e00\u5f20\u56fe\u50cf\u81f3\u6b64",
|
||||
"Upload": "\u4e0a\u4f20",
|
||||
"Block": "\u5757",
|
||||
"Align": "\u5bf9\u9f50",
|
||||
"Default": "\u9ed8\u8ba4",
|
||||
"Circle": "\u7a7a\u5fc3\u5706",
|
||||
"Disc": "\u5b9e\u5fc3\u5706",
|
||||
"Square": "\u65b9\u5757",
|
||||
"Lower Alpha": "\u5c0f\u5199\u82f1\u6587\u5b57\u6bcd",
|
||||
"Lower Greek": "\u5c0f\u5199\u5e0c\u814a\u5b57\u6bcd",
|
||||
"Lower Roman": "\u5c0f\u5199\u7f57\u9a6c\u5b57\u6bcd",
|
||||
"Upper Alpha": "\u5927\u5199\u82f1\u6587\u5b57\u6bcd",
|
||||
"Upper Roman": "\u5927\u5199\u7f57\u9a6c\u5b57\u6bcd",
|
||||
"Anchor...": "\u951a\u70b9...",
|
||||
"Name": "\u540d\u79f0",
|
||||
"Id": "\u6807\u8bc6\u7b26",
|
||||
"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "\u6807\u8bc6\u7b26\u5e94\u8be5\u4ee5\u5b57\u6bcd\u5f00\u5934\uff0c\u540e\u8ddf\u5b57\u6bcd\u3001\u6570\u5b57\u3001\u7834\u6298\u53f7\u3001\u70b9\u3001\u5192\u53f7\u6216\u4e0b\u5212\u7ebf\u3002",
|
||||
"You have unsaved changes are you sure you want to navigate away?": "\u4f60\u8fd8\u6709\u6587\u6863\u5c1a\u672a\u4fdd\u5b58\uff0c\u786e\u5b9a\u8981\u79bb\u5f00\uff1f",
|
||||
"Restore last draft": "\u6062\u590d\u4e0a\u6b21\u7684\u8349\u7a3f",
|
||||
"Special characters...": "\u7279\u6b8a\u5b57\u7b26...",
|
||||
"Source code": "\u6e90\u4ee3\u7801",
|
||||
"Insert\/Edit code sample": "\u63d2\u5165\/\u7f16\u8f91\u4ee3\u7801\u793a\u4f8b",
|
||||
"Language": "\u8bed\u8a00",
|
||||
"Code sample...": "\u793a\u4f8b\u4ee3\u7801...",
|
||||
"Color Picker": "\u9009\u8272\u5668",
|
||||
"R": "R",
|
||||
"G": "G",
|
||||
"B": "B",
|
||||
"Left to right": "\u4ece\u5de6\u5230\u53f3",
|
||||
"Right to left": "\u4ece\u53f3\u5230\u5de6",
|
||||
"Emoticons...": "\u8868\u60c5\u7b26\u53f7...",
|
||||
"Metadata and Document Properties": "\u5143\u6570\u636e\u548c\u6587\u6863\u5c5e\u6027",
|
||||
"Title": "\u6807\u9898",
|
||||
"Keywords": "\u5173\u952e\u8bcd",
|
||||
"Description": "\u63cf\u8ff0",
|
||||
"Robots": "\u673a\u5668\u4eba",
|
||||
"Author": "\u4f5c\u8005",
|
||||
"Encoding": "\u7f16\u7801",
|
||||
"Fullscreen": "\u5168\u5c4f",
|
||||
"Action": "\u64cd\u4f5c",
|
||||
"Shortcut": "\u5feb\u6377\u952e",
|
||||
"Help": "\u5e2e\u52a9",
|
||||
"Address": "\u5730\u5740",
|
||||
"Focus to menubar": "\u79fb\u52a8\u7126\u70b9\u5230\u83dc\u5355\u680f",
|
||||
"Focus to toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u5de5\u5177\u680f",
|
||||
"Focus to element path": "\u79fb\u52a8\u7126\u70b9\u5230\u5143\u7d20\u8def\u5f84",
|
||||
"Focus to contextual toolbar": "\u79fb\u52a8\u7126\u70b9\u5230\u4e0a\u4e0b\u6587\u83dc\u5355",
|
||||
"Insert link (if link plugin activated)": "\u63d2\u5165\u94fe\u63a5 (\u5982\u679c\u94fe\u63a5\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
|
||||
"Save (if save plugin activated)": "\u4fdd\u5b58(\u5982\u679c\u4fdd\u5b58\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
|
||||
"Find (if searchreplace plugin activated)": "\u67e5\u627e(\u5982\u679c\u67e5\u627e\u66ff\u6362\u63d2\u4ef6\u5df2\u6fc0\u6d3b)",
|
||||
"Plugins installed ({0}):": "\u5df2\u5b89\u88c5\u63d2\u4ef6 ({0}):",
|
||||
"Premium plugins:": "\u4f18\u79c0\u63d2\u4ef6\uff1a",
|
||||
"Learn more...": "\u4e86\u89e3\u66f4\u591a...",
|
||||
"You are using {0}": "\u4f60\u6b63\u5728\u4f7f\u7528 {0}",
|
||||
"Plugins": "\u63d2\u4ef6",
|
||||
"Handy Shortcuts": "\u5feb\u6377\u952e",
|
||||
"Horizontal line": "\u6c34\u5e73\u5206\u5272\u7ebf",
|
||||
"Insert\/edit image": "\u63d2\u5165\/\u7f16\u8f91\u56fe\u7247",
|
||||
"Image description": "\u56fe\u7247\u63cf\u8ff0",
|
||||
"Source": "\u5730\u5740",
|
||||
"Dimensions": "\u5927\u5c0f",
|
||||
"Constrain proportions": "\u4fdd\u6301\u7eb5\u6a2a\u6bd4",
|
||||
"General": "\u666e\u901a",
|
||||
"Advanced": "\u9ad8\u7ea7",
|
||||
"Style": "\u6837\u5f0f",
|
||||
"Vertical space": "\u5782\u76f4\u8fb9\u8ddd",
|
||||
"Horizontal space": "\u6c34\u5e73\u8fb9\u8ddd",
|
||||
"Border": "\u8fb9\u6846",
|
||||
"Insert image": "\u63d2\u5165\u56fe\u7247",
|
||||
"Image...": "\u56fe\u7247...",
|
||||
"Image list": "\u56fe\u7247\u5217\u8868",
|
||||
"Rotate counterclockwise": "\u9006\u65f6\u9488\u65cb\u8f6c",
|
||||
"Rotate clockwise": "\u987a\u65f6\u9488\u65cb\u8f6c",
|
||||
"Flip vertically": "\u5782\u76f4\u7ffb\u8f6c",
|
||||
"Flip horizontally": "\u6c34\u5e73\u7ffb\u8f6c",
|
||||
"Edit image": "\u7f16\u8f91\u56fe\u7247",
|
||||
"Image options": "\u56fe\u7247\u9009\u9879",
|
||||
"Zoom in": "\u653e\u5927",
|
||||
"Zoom out": "\u7f29\u5c0f",
|
||||
"Crop": "\u88c1\u526a",
|
||||
"Resize": "\u8c03\u6574\u5927\u5c0f",
|
||||
"Orientation": "\u65b9\u5411",
|
||||
"Brightness": "\u4eae\u5ea6",
|
||||
"Sharpen": "\u9510\u5316",
|
||||
"Contrast": "\u5bf9\u6bd4\u5ea6",
|
||||
"Color levels": "\u989c\u8272\u5c42\u6b21",
|
||||
"Gamma": "\u4f3d\u9a6c\u503c",
|
||||
"Invert": "\u53cd\u8f6c",
|
||||
"Apply": "\u5e94\u7528",
|
||||
"Back": "\u540e\u9000",
|
||||
"Insert date\/time": "\u63d2\u5165\u65e5\u671f\/\u65f6\u95f4",
|
||||
"Date\/time": "\u65e5\u671f\/\u65f6\u95f4",
|
||||
"Insert\/Edit Link": "\u63d2\u5165\/\u7f16\u8f91\u94fe\u63a5",
|
||||
"Insert\/edit link": "\u63d2\u5165\/\u7f16\u8f91\u94fe\u63a5",
|
||||
"Text to display": "\u663e\u793a\u6587\u5b57",
|
||||
"Url": "\u5730\u5740",
|
||||
"Open link in...": "\u94fe\u63a5\u6253\u5f00\u4f4d\u7f6e...",
|
||||
"Current window": "\u5f53\u524d\u7a97\u53e3",
|
||||
"None": "\u65e0",
|
||||
"New window": "\u5728\u65b0\u7a97\u53e3\u6253\u5f00",
|
||||
"Remove link": "\u5220\u9664\u94fe\u63a5",
|
||||
"Anchors": "\u951a\u70b9",
|
||||
"Link...": "\u94fe\u63a5...",
|
||||
"Paste or type a link": "\u7c98\u8d34\u6216\u8f93\u5165\u94fe\u63a5",
|
||||
"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u4e3a\u90ae\u4ef6\u5730\u5740\uff0c\u9700\u8981\u52a0\u4e0amailto:\u524d\u7f00\u5417\uff1f",
|
||||
"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u5c5e\u4e8e\u5916\u90e8\u94fe\u63a5\uff0c\u9700\u8981\u52a0\u4e0ahttp:\/\/:\u524d\u7f00\u5417\uff1f",
|
||||
"Link list": "\u94fe\u63a5\u5217\u8868",
|
||||
"Insert video": "\u63d2\u5165\u89c6\u9891",
|
||||
"Insert\/edit video": "\u63d2\u5165\/\u7f16\u8f91\u89c6\u9891",
|
||||
"Insert\/edit media": "\u63d2\u5165\/\u7f16\u8f91\u5a92\u4f53",
|
||||
"Alternative source": "\u955c\u50cf",
|
||||
"Alternative source URL": "\u66ff\u4ee3\u6765\u6e90\u7f51\u5740",
|
||||
"Media poster (Image URL)": "\u5c01\u9762(\u56fe\u7247\u5730\u5740)",
|
||||
"Paste your embed code below:": "\u5c06\u5185\u5d4c\u4ee3\u7801\u7c98\u8d34\u5728\u4e0b\u9762:",
|
||||
"Embed": "\u5185\u5d4c",
|
||||
"Media...": "\u591a\u5a92\u4f53...",
|
||||
"Nonbreaking space": "\u4e0d\u95f4\u65ad\u7a7a\u683c",
|
||||
"Page break": "\u5206\u9875\u7b26",
|
||||
"Paste as text": "\u7c98\u8d34\u4e3a\u6587\u672c",
|
||||
"Preview": "\u9884\u89c8",
|
||||
"Print...": "\u6253\u5370...",
|
||||
"Save": "\u4fdd\u5b58",
|
||||
"Find": "\u67e5\u627e",
|
||||
"Replace with": "\u66ff\u6362\u4e3a",
|
||||
"Replace": "\u66ff\u6362",
|
||||
"Replace all": "\u5168\u90e8\u66ff\u6362",
|
||||
"Previous": "\u4e0a\u4e00\u4e2a",
|
||||
"Next": "\u4e0b\u4e00\u4e2a",
|
||||
"Find and replace...": "\u67e5\u627e\u5e76\u66ff\u6362...",
|
||||
"Could not find the specified string.": "\u672a\u627e\u5230\u641c\u7d22\u5185\u5bb9.",
|
||||
"Match case": "\u533a\u5206\u5927\u5c0f\u5199",
|
||||
"Find whole words only": "\u5168\u5b57\u5339\u914d",
|
||||
"Spell check": "\u62fc\u5199\u68c0\u67e5",
|
||||
"Ignore": "\u5ffd\u7565",
|
||||
"Ignore all": "\u5168\u90e8\u5ffd\u7565",
|
||||
"Finish": "\u5b8c\u6210",
|
||||
"Add to Dictionary": "\u6dfb\u52a0\u5230\u5b57\u5178",
|
||||
"Insert table": "\u63d2\u5165\u8868\u683c",
|
||||
"Table properties": "\u8868\u683c\u5c5e\u6027",
|
||||
"Delete table": "\u5220\u9664\u8868\u683c",
|
||||
"Cell": "\u5355\u5143\u683c",
|
||||
"Row": "\u884c",
|
||||
"Column": "\u5217",
|
||||
"Cell properties": "\u5355\u5143\u683c\u5c5e\u6027",
|
||||
"Merge cells": "\u5408\u5e76\u5355\u5143\u683c",
|
||||
"Split cell": "\u62c6\u5206\u5355\u5143\u683c",
|
||||
"Insert row before": "\u5728\u4e0a\u65b9\u63d2\u5165",
|
||||
"Insert row after": "\u5728\u4e0b\u65b9\u63d2\u5165",
|
||||
"Delete row": "\u5220\u9664\u884c",
|
||||
"Row properties": "\u884c\u5c5e\u6027",
|
||||
"Cut row": "\u526a\u5207\u884c",
|
||||
"Copy row": "\u590d\u5236\u884c",
|
||||
"Paste row before": "\u7c98\u8d34\u5230\u4e0a\u65b9",
|
||||
"Paste row after": "\u7c98\u8d34\u5230\u4e0b\u65b9",
|
||||
"Insert column before": "\u5728\u5de6\u4fa7\u63d2\u5165",
|
||||
"Insert column after": "\u5728\u53f3\u4fa7\u63d2\u5165",
|
||||
"Delete column": "\u5220\u9664\u5217",
|
||||
"Cols": "\u5217",
|
||||
"Rows": "\u884c",
|
||||
"Width": "\u5bbd",
|
||||
"Height": "\u9ad8",
|
||||
"Cell spacing": "\u5355\u5143\u683c\u5916\u95f4\u8ddd",
|
||||
"Cell padding": "\u5355\u5143\u683c\u5185\u8fb9\u8ddd",
|
||||
"Show caption": "\u663e\u793a\u6807\u9898",
|
||||
"Left": "\u5de6\u5bf9\u9f50",
|
||||
"Center": "\u5c45\u4e2d",
|
||||
"Right": "\u53f3\u5bf9\u9f50",
|
||||
"Cell type": "\u5355\u5143\u683c\u7c7b\u578b",
|
||||
"Scope": "\u8303\u56f4",
|
||||
"Alignment": "\u5bf9\u9f50\u65b9\u5f0f",
|
||||
"H Align": "\u6c34\u5e73\u5bf9\u9f50",
|
||||
"V Align": "\u5782\u76f4\u5bf9\u9f50",
|
||||
"Top": "\u9876\u90e8\u5bf9\u9f50",
|
||||
"Middle": "\u5782\u76f4\u5c45\u4e2d",
|
||||
"Bottom": "\u5e95\u90e8\u5bf9\u9f50",
|
||||
"Header cell": "\u8868\u5934\u5355\u5143\u683c",
|
||||
"Row group": "\u884c\u7ec4",
|
||||
"Column group": "\u5217\u7ec4",
|
||||
"Row type": "\u884c\u7c7b\u578b",
|
||||
"Header": "\u8868\u5934",
|
||||
"Body": "\u8868\u4f53",
|
||||
"Footer": "\u8868\u5c3e",
|
||||
"Border color": "\u8fb9\u6846\u989c\u8272",
|
||||
"Insert template...": "\u63d2\u5165\u6a21\u677f...",
|
||||
"Templates": "\u6a21\u677f",
|
||||
"Template": "\u6a21\u677f",
|
||||
"Text color": "\u6587\u5b57\u989c\u8272",
|
||||
"Background color": "\u80cc\u666f\u8272",
|
||||
"Custom...": "\u81ea\u5b9a\u4e49...",
|
||||
"Custom color": "\u81ea\u5b9a\u4e49\u989c\u8272",
|
||||
"No color": "\u65e0",
|
||||
"Remove color": "\u79fb\u9664\u989c\u8272",
|
||||
"Table of Contents": "\u5185\u5bb9\u5217\u8868",
|
||||
"Show blocks": "\u663e\u793a\u533a\u5757\u8fb9\u6846",
|
||||
"Show invisible characters": "\u663e\u793a\u4e0d\u53ef\u89c1\u5b57\u7b26",
|
||||
"Word count": "\u5b57\u6570",
|
||||
"Words: {0}": "\u5b57\u6570\uff1a{0}",
|
||||
"{0} words": "{0} \u5b57",
|
||||
"File": "\u6587\u4ef6",
|
||||
"Edit": "\u7f16\u8f91",
|
||||
"Insert": "\u63d2\u5165",
|
||||
"View": "\u89c6\u56fe",
|
||||
"Format": "\u683c\u5f0f",
|
||||
"Table": "\u8868\u683c",
|
||||
"Tools": "\u5de5\u5177",
|
||||
"Powered by {0}": "\u7531{0}\u9a71\u52a8",
|
||||
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "\u5728\u7f16\u8f91\u533a\u6309ALT-F9\u6253\u5f00\u83dc\u5355\uff0c\u6309ALT-F10\u6253\u5f00\u5de5\u5177\u680f\uff0c\u6309ALT-0\u67e5\u770b\u5e2e\u52a9",
|
||||
"Image title": "\u56fe\u7247\u6807\u9898",
|
||||
"Border width": "\u8fb9\u6846\u5bbd\u5ea6",
|
||||
"Border style": "\u8fb9\u6846\u6837\u5f0f",
|
||||
"Error": "\u9519\u8bef",
|
||||
"Warn": "\u8b66\u544a",
|
||||
"Valid": "\u6709\u6548",
|
||||
"To open the popup, press Shift+Enter": "\u6309Shitf+Enter\u952e\u6253\u5f00\u5bf9\u8bdd\u6846",
|
||||
"Rich Text Area. Press ALT-0 for help.": "\u7f16\u8f91\u533a\u3002\u6309Alt+0\u952e\u6253\u5f00\u5e2e\u52a9\u3002",
|
||||
"System Font": "\u7cfb\u7edf\u5b57\u4f53",
|
||||
"Failed to upload image: {0}": "\u56fe\u7247\u4e0a\u4f20\u5931\u8d25: {0}",
|
||||
"Failed to load plugin: {0} from url {1}": "\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25: {0} \u6765\u81ea\u94fe\u63a5 {1}",
|
||||
"Failed to load plugin url: {0}": "\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25 \u94fe\u63a5: {0}",
|
||||
"Failed to initialize plugin: {0}": "\u63d2\u4ef6\u521d\u59cb\u5316\u5931\u8d25: {0}",
|
||||
"example": "\u793a\u4f8b",
|
||||
"Search": "\u641c\u7d22",
|
||||
"All": "\u5168\u90e8",
|
||||
"Currency": "\u8d27\u5e01",
|
||||
"Text": "\u6587\u5b57",
|
||||
"Quotations": "\u5f15\u7528",
|
||||
"Mathematical": "\u6570\u5b66",
|
||||
"Extended Latin": "\u62c9\u4e01\u8bed\u6269\u5145",
|
||||
"Symbols": "\u7b26\u53f7",
|
||||
"Arrows": "\u7bad\u5934",
|
||||
"User Defined": "\u81ea\u5b9a\u4e49",
|
||||
"dollar sign": "\u7f8e\u5143\u7b26\u53f7",
|
||||
"currency sign": "\u8d27\u5e01\u7b26\u53f7",
|
||||
"euro-currency sign": "\u6b27\u5143\u7b26\u53f7",
|
||||
"colon sign": "\u5192\u53f7",
|
||||
"cruzeiro sign": "\u514b\u9c81\u8d5b\u7f57\u5e01\u7b26\u53f7",
|
||||
"french franc sign": "\u6cd5\u90ce\u7b26\u53f7",
|
||||
"lira sign": "\u91cc\u62c9\u7b26\u53f7",
|
||||
"mill sign": "\u5bc6\u5c14\u7b26\u53f7",
|
||||
"naira sign": "\u5948\u62c9\u7b26\u53f7",
|
||||
"peseta sign": "\u6bd4\u585e\u5854\u7b26\u53f7",
|
||||
"rupee sign": "\u5362\u6bd4\u7b26\u53f7",
|
||||
"won sign": "\u97e9\u5143\u7b26\u53f7",
|
||||
"new sheqel sign": "\u65b0\u8c22\u514b\u5c14\u7b26\u53f7",
|
||||
"dong sign": "\u8d8a\u5357\u76fe\u7b26\u53f7",
|
||||
"kip sign": "\u8001\u631d\u57fa\u666e\u7b26\u53f7",
|
||||
"tugrik sign": "\u56fe\u683c\u91cc\u514b\u7b26\u53f7",
|
||||
"drachma sign": "\u5fb7\u62c9\u514b\u9a6c\u7b26\u53f7",
|
||||
"german penny symbol": "\u5fb7\u56fd\u4fbf\u58eb\u7b26\u53f7",
|
||||
"peso sign": "\u6bd4\u7d22\u7b26\u53f7",
|
||||
"guarani sign": "\u74dc\u62c9\u5c3c\u7b26\u53f7",
|
||||
"austral sign": "\u6fb3\u5143\u7b26\u53f7",
|
||||
"hryvnia sign": "\u683c\u91cc\u592b\u5c3c\u4e9a\u7b26\u53f7",
|
||||
"cedi sign": "\u585e\u5730\u7b26\u53f7",
|
||||
"livre tournois sign": "\u91cc\u5f17\u5f17\u5c14\u7b26\u53f7",
|
||||
"spesmilo sign": "spesmilo\u7b26\u53f7",
|
||||
"tenge sign": "\u575a\u6208\u7b26\u53f7",
|
||||
"indian rupee sign": "\u5370\u5ea6\u5362\u6bd4",
|
||||
"turkish lira sign": "\u571f\u8033\u5176\u91cc\u62c9",
|
||||
"nordic mark sign": "\u5317\u6b27\u9a6c\u514b",
|
||||
"manat sign": "\u9a6c\u7eb3\u7279\u7b26\u53f7",
|
||||
"ruble sign": "\u5362\u5e03\u7b26\u53f7",
|
||||
"yen character": "\u65e5\u5143\u5b57\u6837",
|
||||
"yuan character": "\u4eba\u6c11\u5e01\u5143\u5b57\u6837",
|
||||
"yuan character, in hong kong and taiwan": "\u5143\u5b57\u6837\uff08\u6e2f\u53f0\u5730\u533a\uff09",
|
||||
"yen\/yuan character variant one": "\u5143\u5b57\u6837\uff08\u5927\u5199\uff09",
|
||||
"Loading emoticons...": "\u52a0\u8f7d\u8868\u60c5\u7b26\u53f7...",
|
||||
"Could not load emoticons": "\u4e0d\u80fd\u52a0\u8f7d\u8868\u60c5\u7b26\u53f7",
|
||||
"People": "\u4eba\u7c7b",
|
||||
"Animals and Nature": "\u52a8\u7269\u548c\u81ea\u7136",
|
||||
"Food and Drink": "\u98df\u7269\u548c\u996e\u54c1",
|
||||
"Activity": "\u6d3b\u52a8",
|
||||
"Travel and Places": "\u65c5\u6e38\u548c\u5730\u70b9",
|
||||
"Objects": "\u7269\u4ef6",
|
||||
"Flags": "\u65d7\u5e1c",
|
||||
"Characters": "\u5b57\u7b26",
|
||||
"Characters (no spaces)": "\u5b57\u7b26(\u65e0\u7a7a\u683c)",
|
||||
"Error: Form submit field collision.": "\u9519\u8bef: \u8868\u5355\u63d0\u4ea4\u5b57\u6bb5\u51b2\u7a81\u3002",
|
||||
"Error: No form element found.": "\u9519\u8bef: \u6ca1\u6709\u8868\u5355\u63a7\u4ef6\u3002",
|
||||
"Update": "\u66f4\u65b0",
|
||||
"Color swatch": "\u989c\u8272\u6837\u672c",
|
||||
"Turquoise": "\u9752\u7eff\u8272",
|
||||
"Green": "\u7eff\u8272",
|
||||
"Blue": "\u84dd\u8272",
|
||||
"Purple": "\u7d2b\u8272",
|
||||
"Navy Blue": "\u6d77\u519b\u84dd",
|
||||
"Dark Turquoise": "\u6df1\u84dd\u7eff\u8272",
|
||||
"Dark Green": "\u6df1\u7eff\u8272",
|
||||
"Medium Blue": "\u4e2d\u84dd\u8272",
|
||||
"Medium Purple": "\u4e2d\u7d2b\u8272",
|
||||
"Midnight Blue": "\u6df1\u84dd\u8272",
|
||||
"Yellow": "\u9ec4\u8272",
|
||||
"Orange": "\u6a59\u8272",
|
||||
"Red": "\u7ea2\u8272",
|
||||
"Light Gray": "\u6d45\u7070\u8272",
|
||||
"Gray": "\u7070\u8272",
|
||||
"Dark Yellow": "\u6697\u9ec4\u8272",
|
||||
"Dark Orange": "\u6df1\u6a59\u8272",
|
||||
"Dark Red": "\u6df1\u7ea2\u8272",
|
||||
"Medium Gray": "\u4e2d\u7070\u8272",
|
||||
"Dark Gray": "\u6df1\u7070\u8272",
|
||||
"Black": "\u9ed1\u8272",
|
||||
"White": "\u767d\u8272",
|
||||
"Switch to or from fullscreen mode": "\u5207\u6362\u5168\u5c4f\u6a21\u5f0f",
|
||||
"Open help dialog": "\u6253\u5f00\u5e2e\u52a9\u5bf9\u8bdd\u6846",
|
||||
"history": "\u5386\u53f2",
|
||||
"styles": "\u6837\u5f0f",
|
||||
"formatting": "\u683c\u5f0f\u5316",
|
||||
"alignment": "\u5bf9\u9f50",
|
||||
"indentation": "\u7f29\u8fdb",
|
||||
"permanent pen": "\u8bb0\u53f7\u7b14",
|
||||
"comments": "\u5907\u6ce8",
|
||||
"Anchor": "\u951a\u70b9",
|
||||
"Special character": "\u7279\u6b8a\u7b26\u53f7",
|
||||
"Code sample": "\u4ee3\u7801\u793a\u4f8b",
|
||||
"Color": "\u989c\u8272",
|
||||
"Emoticons": "\u8868\u60c5",
|
||||
"Document properties": "\u6587\u6863\u5c5e\u6027",
|
||||
"Image": "\u56fe\u7247",
|
||||
"Insert link": "\u63d2\u5165\u94fe\u63a5",
|
||||
"Target": "\u6253\u5f00\u65b9\u5f0f",
|
||||
"Link": "\u94fe\u63a5",
|
||||
"Poster": "\u5c01\u9762",
|
||||
"Media": "\u5a92\u4f53",
|
||||
"Print": "\u6253\u5370",
|
||||
"Prev": "\u4e0a\u4e00\u4e2a",
|
||||
"Find and replace": "\u67e5\u627e\u548c\u66ff\u6362",
|
||||
"Whole words": "\u5168\u5b57\u5339\u914d",
|
||||
"Spellcheck": "\u62fc\u5199\u68c0\u67e5",
|
||||
"Caption": "\u6807\u9898",
|
||||
"Insert template": "\u63d2\u5165\u6a21\u677f"
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<transition name="fade">
|
||||
<router-view />
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
img.image-sm {
|
||||
max-width: 80px;
|
||||
max-height: 80px;
|
||||
}
|
||||
.el-col .el-select,
|
||||
.el-col .el-date-editor {
|
||||
width: 100%;
|
||||
}
|
||||
.demo-table-expand {
|
||||
font-size: 0;
|
||||
}
|
||||
.demo-table-expand label {
|
||||
width: 90px;
|
||||
color: #99a9bf;
|
||||
}
|
||||
.demo-table-expand .el-form-item {
|
||||
margin-right: 0;
|
||||
margin-bottom: 0;
|
||||
width: 50%;
|
||||
}
|
||||
.text-warning {
|
||||
color: #e6a23c;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,847 @@
|
|||
/* 常用辅助css */
|
||||
|
||||
/* ==================
|
||||
布局
|
||||
==================== */
|
||||
|
||||
/* -- flex弹性布局 -- */
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.basis-xs {
|
||||
flex-basis: 20%;
|
||||
}
|
||||
|
||||
.basis-sm {
|
||||
flex-basis: 40%;
|
||||
}
|
||||
|
||||
.basis-df {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
|
||||
.basis-lg {
|
||||
flex-basis: 60%;
|
||||
}
|
||||
|
||||
.basis-xl {
|
||||
flex-basis: 80%;
|
||||
}
|
||||
|
||||
.flex-sub {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.flex-twice {
|
||||
flex: 2;
|
||||
}
|
||||
|
||||
.flex-treble {
|
||||
flex: 3;
|
||||
}
|
||||
|
||||
.flex-direction {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.flex-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.align-start {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.align-end {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.align-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.align-stretch {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.self-start {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.self-center {
|
||||
align-self: flex-center;
|
||||
}
|
||||
|
||||
.self-end {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.self-stretch {
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.align-stretch {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.justify-start {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.justify-end {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.justify-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.justify-around {
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
/* -- 内外边距 -- */
|
||||
|
||||
.margin-0 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.margin-xs {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.margin-sm {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.margin {
|
||||
margin: 15px;
|
||||
}
|
||||
|
||||
.margin-lg {
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.margin-xl {
|
||||
margin: 25px;
|
||||
}
|
||||
|
||||
.margin-top-xs {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.margin-top-sm {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.margin-top {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.margin-top-lg {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.margin-top-xl {
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.margin-right-xs {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.margin-right-sm {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.margin-right {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.margin-right-lg {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.margin-right-xl {
|
||||
margin-right: 25px;
|
||||
}
|
||||
|
||||
.margin-bottom-xs {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.margin-bottom-sm {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.margin-bottom {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.margin-bottom-lg {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.margin-bottom-xl {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.margin-left-xs {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.margin-left-sm {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.margin-left {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.margin-left-lg {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.margin-left-xl {
|
||||
margin-left: 25px;
|
||||
}
|
||||
|
||||
.margin-lr-xs {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.margin-lr-sm {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.margin-lr {
|
||||
margin-left: 15px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.margin-lr-lg {
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.margin-lr-xl {
|
||||
margin-left: 25px;
|
||||
margin-right: 25px;
|
||||
}
|
||||
|
||||
.margin-tb-xs {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.margin-tb-sm {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.margin-tb {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.margin-tb-lg {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.margin-tb-xl {
|
||||
margin-top: 25px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.padding-0 {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.padding-xs {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.padding-sm {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.padding {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.padding-lg {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.padding-xl {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.padding-top-xs {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.padding-top-sm {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.padding-top {
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.padding-top-lg {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.padding-top-xl {
|
||||
padding-top: 25px;
|
||||
}
|
||||
|
||||
.padding-right-xs {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.padding-right-sm {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.padding-right {
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.padding-right-lg {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.padding-right-xl {
|
||||
padding-right: 25px;
|
||||
}
|
||||
|
||||
.padding-bottom-xs {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.padding-bottom-sm {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.padding-bottom {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.padding-bottom-lg {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.padding-bottom-xl {
|
||||
padding-bottom: 25px;
|
||||
}
|
||||
|
||||
.padding-left-xs {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.padding-left-sm {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.padding-left {
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.padding-left-lg {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.padding-left-xl {
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
.padding-lr-xs {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.padding-lr-sm {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.padding-lr {
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.padding-lr-lg {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.padding-lr-xl {
|
||||
padding-left: 25px;
|
||||
padding-right: 25px;
|
||||
}
|
||||
|
||||
.padding-tb-xs {
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.padding-tb-sm {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.padding-tb {
|
||||
padding-top: 15px;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.padding-tb-lg {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.padding-tb-xl {
|
||||
padding-top: 25px;
|
||||
padding-bottom: 25px;
|
||||
}
|
||||
|
||||
/* -- 浮动 -- */
|
||||
|
||||
.cf::after,
|
||||
.cf::before {
|
||||
content: " ";
|
||||
display: table;
|
||||
}
|
||||
|
||||
.cf::after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.fl {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.fr {
|
||||
float: right;
|
||||
}
|
||||
|
||||
|
||||
/* ==================
|
||||
背景
|
||||
==================== */
|
||||
|
||||
.line-red::after,
|
||||
.lines-red::after {
|
||||
border-color: #e54d42;
|
||||
}
|
||||
|
||||
.line-orange::after,
|
||||
.lines-orange::after {
|
||||
border-color: #f37b1d;
|
||||
}
|
||||
|
||||
.line-yellow::after,
|
||||
.lines-yellow::after {
|
||||
border-color: #fbbd08;
|
||||
}
|
||||
|
||||
.line-olive::after,
|
||||
.lines-olive::after {
|
||||
border-color: #8dc63f;
|
||||
}
|
||||
|
||||
.line-green::after,
|
||||
.lines-green::after {
|
||||
border-color: #39b54a;
|
||||
}
|
||||
|
||||
.line-cyan::after,
|
||||
.lines-cyan::after {
|
||||
border-color: #1cbbb4;
|
||||
}
|
||||
|
||||
.line-blue::after,
|
||||
.lines-blue::after {
|
||||
border-color: #0081ff;
|
||||
}
|
||||
|
||||
.line-purple::after,
|
||||
.lines-purple::after {
|
||||
border-color: #6739b6;
|
||||
}
|
||||
|
||||
.line-mauve::after,
|
||||
.lines-mauve::after {
|
||||
border-color: #9c26b0;
|
||||
}
|
||||
|
||||
.line-pink::after,
|
||||
.lines-pink::after {
|
||||
border-color: #e03997;
|
||||
}
|
||||
|
||||
.line-brown::after,
|
||||
.lines-brown::after {
|
||||
border-color: #a5673f;
|
||||
}
|
||||
|
||||
.line-grey::after,
|
||||
.lines-grey::after {
|
||||
border-color: #8799a3;
|
||||
}
|
||||
|
||||
.line-gray::after,
|
||||
.lines-gray::after {
|
||||
border-color: #aaaaaa;
|
||||
}
|
||||
|
||||
.line-black::after,
|
||||
.lines-black::after {
|
||||
border-color: #333333;
|
||||
}
|
||||
|
||||
.line-white::after,
|
||||
.lines-white::after {
|
||||
border-color: #ffffff;
|
||||
}
|
||||
|
||||
.bg-red {
|
||||
background-color: #e54d42;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.bg-orange {
|
||||
background-color: #f37b1d;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.bg-yellow {
|
||||
background-color: #fbbd08;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.bg-olive {
|
||||
background-color: #8dc63f;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.bg-green {
|
||||
background-color: #39b54a;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.bg-cyan {
|
||||
background-color: #1cbbb4;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.bg-blue {
|
||||
background-color: #0081ff;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.bg-purple {
|
||||
background-color: #6739b6;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.bg-mauve {
|
||||
background-color: #9c26b0;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.bg-pink {
|
||||
background-color: #e03997;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.bg-brown {
|
||||
background-color: #a5673f;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.bg-grey {
|
||||
background-color: #8799a3;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.bg-gray {
|
||||
background-color: #f0f0f0;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.bg-black {
|
||||
background-color: #333333;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.bg-white {
|
||||
background-color: #ffffff;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.bg-red.light {
|
||||
color: #e54d42;
|
||||
background-color: #fadbd9;
|
||||
}
|
||||
|
||||
.bg-orange.light {
|
||||
color: #f37b1d;
|
||||
background-color: #fde6d2;
|
||||
}
|
||||
|
||||
.bg-yellow.light {
|
||||
color: #fbbd08;
|
||||
background-color: #fef2ced2;
|
||||
}
|
||||
|
||||
.bg-olive.light {
|
||||
color: #8dc63f;
|
||||
background-color: #e8f4d9;
|
||||
}
|
||||
|
||||
.bg-green.light {
|
||||
color: #39b54a;
|
||||
background-color: #d7f0dbff;
|
||||
}
|
||||
|
||||
.bg-cyan.light {
|
||||
color: #1cbbb4;
|
||||
background-color: #d2f1f0;
|
||||
}
|
||||
|
||||
.bg-blue.light {
|
||||
color: #0081ff;
|
||||
background-color: #cce6ff;
|
||||
}
|
||||
|
||||
.bg-purple.light {
|
||||
color: #6739b6;
|
||||
background-color: #e1d7f0;
|
||||
}
|
||||
|
||||
.bg-mauve.light {
|
||||
color: #9c26b0;
|
||||
background-color: #ebd4ef;
|
||||
}
|
||||
|
||||
.bg-pink.light {
|
||||
color: #e03997;
|
||||
background-color: #f9d7ea;
|
||||
}
|
||||
|
||||
.bg-brown.light {
|
||||
color: #a5673f;
|
||||
background-color: #ede1d9;
|
||||
}
|
||||
|
||||
.bg-grey.light {
|
||||
color: #8799a3;
|
||||
background-color: #e7ebed;
|
||||
}
|
||||
|
||||
.bg-gradual-red {
|
||||
background-image: linear-gradient(45deg, #f43f3b, #ec008c);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.bg-gradual-orange {
|
||||
background-image: linear-gradient(45deg, #ff9700, #ed1c24);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.bg-gradual-green {
|
||||
background-image: linear-gradient(45deg, #39b54a, #8dc63f);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.bg-gradual-purple {
|
||||
background-image: linear-gradient(45deg, #9000ff, #5e00ff);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.bg-gradual-pink {
|
||||
background-image: linear-gradient(45deg, #ec008c, #6739b6);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.bg-gradual-blue {
|
||||
background-image: linear-gradient(45deg, #0081ff, #1cbbb4);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
/* ==================
|
||||
文本
|
||||
==================== */
|
||||
|
||||
.text-xs {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.text-sm {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.text-df {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.text-lg {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.text-xl {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.text-xxl {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.text-sl {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.text-xsl {
|
||||
font-size: 60px;
|
||||
}
|
||||
|
||||
.text-Abc {
|
||||
text-transform: Capitalize;
|
||||
}
|
||||
|
||||
.text-ABC {
|
||||
text-transform: Uppercase;
|
||||
}
|
||||
|
||||
.text-abc {
|
||||
text-transform: Lowercase;
|
||||
}
|
||||
|
||||
|
||||
.text-cut {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.text-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-content {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.text-red,
|
||||
.line-red,
|
||||
.lines-red {
|
||||
color: #e54d42;
|
||||
}
|
||||
|
||||
.text-orange,
|
||||
.line-orange,
|
||||
.lines-orange {
|
||||
color: #f37b1d;
|
||||
}
|
||||
|
||||
.text-yellow,
|
||||
.line-yellow,
|
||||
.lines-yellow {
|
||||
color: #fbbd08;
|
||||
}
|
||||
|
||||
.text-olive,
|
||||
.line-olive,
|
||||
.lines-olive {
|
||||
color: #8dc63f;
|
||||
}
|
||||
|
||||
.text-green,
|
||||
.line-green,
|
||||
.lines-green {
|
||||
color: #39b54a;
|
||||
}
|
||||
|
||||
.text-cyan,
|
||||
.line-cyan,
|
||||
.lines-cyan {
|
||||
color: #1cbbb4;
|
||||
}
|
||||
|
||||
.text-blue,
|
||||
.line-blue,
|
||||
.lines-blue {
|
||||
color: #0081ff;
|
||||
}
|
||||
|
||||
.text-purple,
|
||||
.line-purple,
|
||||
.lines-purple {
|
||||
color: #6739b6;
|
||||
}
|
||||
|
||||
.text-mauve,
|
||||
.line-mauve,
|
||||
.lines-mauve {
|
||||
color: #9c26b0;
|
||||
}
|
||||
|
||||
.text-pink,
|
||||
.line-pink,
|
||||
.lines-pink {
|
||||
color: #e03997;
|
||||
}
|
||||
|
||||
.text-brown,
|
||||
.line-brown,
|
||||
.lines-brown {
|
||||
color: #a5673f;
|
||||
}
|
||||
|
||||
.text-grey,
|
||||
.line-grey,
|
||||
.lines-grey {
|
||||
color: #8799a3;
|
||||
}
|
||||
|
||||
.text-gray,
|
||||
.line-gray,
|
||||
.lines-gray {
|
||||
color: #aaaaaa;
|
||||
}
|
||||
|
||||
.text-black,
|
||||
.line-black,
|
||||
.lines-black {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.text-white,
|
||||
.line-white,
|
||||
.lines-white {
|
||||
color: #ffffff;
|
||||
}
|
|
@ -0,0 +1,365 @@
|
|||
@charset "utf-8";
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#app-menu ul {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#app-menu li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#app-menu {
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.weixin-preview {
|
||||
position: relative;
|
||||
width: 320px;
|
||||
height: 540px;
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
border: 1px solid #e7e7eb;
|
||||
}
|
||||
|
||||
.weixin-preview a {
|
||||
text-decoration: none;
|
||||
color: #616161;
|
||||
}
|
||||
|
||||
.weixin-preview .weixin-hd .weixin-title {
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
top: 33px;
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.weixin-preview .weixin-header{
|
||||
text-align: center;
|
||||
padding: 10px 0;
|
||||
background-color: #616161;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.weixin-preview .weixin-menu {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-top: 1px solid #e7e7e7;
|
||||
background-position: 0 0;
|
||||
background-repeat: no-repeat;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
/*一级*/
|
||||
.weixin-preview .weixin-menu .menu-item {
|
||||
position: relative;
|
||||
float: left;
|
||||
line-height: 50px;
|
||||
height: 50px;
|
||||
text-align: center;
|
||||
width: 33.33%;
|
||||
border-left: 1px solid #e7e7e7;
|
||||
cursor: pointer;
|
||||
color: #616161;
|
||||
}
|
||||
|
||||
/*二级*/
|
||||
.weixin-preview .weixin-sub-menu {
|
||||
position: absolute;
|
||||
bottom: 60px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-top: 1px solid #d0d0d0;
|
||||
margin-bottom: 0px;
|
||||
background: #fafafa;
|
||||
display: block;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.weixin-preview .weixin-sub-menu .menu-sub-item {
|
||||
line-height: 50px;
|
||||
height: 50px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
border: 1px solid #d0d0d0;
|
||||
border-top-width: 0px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
color: #616161;
|
||||
}
|
||||
|
||||
.weixin-preview .weixin-sub-menu .menu-sub-item.on-drag-over{
|
||||
border-top: 2px solid #44b549;
|
||||
}
|
||||
|
||||
.weixin-preview .menu-arrow {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
margin-left: -6px;
|
||||
}
|
||||
|
||||
.weixin-preview .arrow_in {
|
||||
bottom: -4px;
|
||||
display: inline-block;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
border-width: 6px 6px 0px;
|
||||
border-style: solid dashed dashed;
|
||||
border-color: #fafafa transparent transparent;
|
||||
}
|
||||
|
||||
.weixin-preview .arrow_out {
|
||||
bottom: -5px;
|
||||
display: inline-block;
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
border-width: 6px 6px 0px;
|
||||
border-style: solid dashed dashed;
|
||||
border-color: #d0d0d0 transparent transparent;
|
||||
}
|
||||
|
||||
.weixin-preview .menu-item .menu-item-title, .weixin-preview .menu-sub-item .menu-item-title {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
.weixin-preview .menu-item.current, .weixin-preview .menu-sub-item.current {
|
||||
border: 1px solid #44b549;
|
||||
background: #fff;
|
||||
color: #44b549;
|
||||
}
|
||||
|
||||
.weixin-preview .weixin-sub-menu.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.weixin-preview .icon_menu_dot {
|
||||
/* background: url(../images/index_z354723.png) 0px -36px no-repeat; */
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
margin-right: 2px;
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
.weixin-preview .icon14_menu_add {
|
||||
/* background: url(../images/index_z354723.png) 0px 0px no-repeat; */
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
.weixin-preview li:hover .icon14_menu_add {
|
||||
/* background: url(../images/index_z354723.png) 0px -18px no-repeat; */
|
||||
}
|
||||
|
||||
.weixin-preview .menu-item:hover {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.weixin-preview .menu-sub-item:hover {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.weixin-preview li.current:hover {
|
||||
background: #fff;
|
||||
color: #44b549;
|
||||
}
|
||||
|
||||
/*菜单内容*/
|
||||
.weixin-menu-detail {
|
||||
width: 600px;
|
||||
padding: 0px 20px 5px;
|
||||
background-color: #f4f5f9;
|
||||
border: 1px solid #e7e7eb;
|
||||
float: left;
|
||||
min-height: 540px;
|
||||
}
|
||||
|
||||
.weixin-menu-detail .menu-name {
|
||||
float: left;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.weixin-menu-detail .menu-del {
|
||||
float: right;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
color: #459ae9;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.weixin-menu-detail .menu-input-group {
|
||||
width: 540px;
|
||||
margin: 10px 0 30px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.weixin-menu-detail .menu-label {
|
||||
float: left;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
width: 80px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.weixin-menu-detail .menu-input {
|
||||
float: left;
|
||||
width: 380px
|
||||
}
|
||||
|
||||
.weixin-menu-detail .menu-input-text {
|
||||
border: 0px;
|
||||
outline: 0px;
|
||||
background: #fff;
|
||||
width: 300px;
|
||||
padding: 5px 0px 5px 0px;
|
||||
margin-left: 10px;
|
||||
text-indent: 10px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.weixin-menu-detail .menu-tips {
|
||||
color: #8d8d8d;
|
||||
padding-top: 4px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.weixin-menu-detail .menu-tips.cursor {
|
||||
color: #459ae9;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.weixin-menu-detail .menu-input .menu-tips {
|
||||
margin: 0 0 0 10px;
|
||||
}
|
||||
|
||||
.weixin-menu-detail .menu-content {
|
||||
padding: 16px 20px;
|
||||
border: 1px solid #e7e7eb;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.weixin-menu-detail .menu-content .menu-input-group {
|
||||
margin: 0px 0 10px 0;
|
||||
}
|
||||
|
||||
.weixin-menu-detail .menu-content .menu-label {
|
||||
text-align: left;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.weixin-menu-detail .menu-content .menu-input-text {
|
||||
border: 1px solid #e7e7eb;
|
||||
}
|
||||
|
||||
.weixin-menu-detail .menu-content .menu-tips {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.weixin-menu-detail .menu-msg-content {
|
||||
padding: 0;
|
||||
border: 1px solid #e7e7eb;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.weixin-menu-detail .menu-msg-content .menu-msg-head {
|
||||
overflow: hidden;
|
||||
border-bottom: 1px solid #e7e7eb;
|
||||
line-height: 38px;
|
||||
height: 38px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.weixin-menu-detail .menu-msg-content .menu-msg-panel {
|
||||
padding: 30px 50px;
|
||||
}
|
||||
|
||||
.weixin-menu-detail .menu-msg-content .menu-msg-select {
|
||||
padding: 40px 20px;
|
||||
border: 2px dotted #d9dadc;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.weixin-menu-detail .menu-msg-content .menu-msg-select:hover {
|
||||
border-color: #b3b3b3;
|
||||
}
|
||||
|
||||
.weixin-menu-detail .menu-msg-content strong {
|
||||
display: block;
|
||||
padding-top: 3px;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.weixin-menu-detail .menu-msg-content .menu-msg-title {
|
||||
float: left;
|
||||
width: 310px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.icon36_common {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.icon_msg_sender {
|
||||
margin-right: 3px;
|
||||
margin-top: -2px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
/* background: url(../images/msg_tab_z25df2d.png) 0 -270px no-repeat; */
|
||||
}
|
||||
|
||||
.weixin-btn-group {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
margin: 30px 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.weixin-btn-group .btn {
|
||||
width: 100px;
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
#material-list {
|
||||
padding: 20px;
|
||||
overflow-y: scroll;
|
||||
height: 558px;
|
||||
}
|
||||
|
||||
#news-list {
|
||||
padding: 20px;
|
||||
overflow-y: scroll;
|
||||
height: 558px;
|
||||
}
|
||||
|
||||
#material-list table {
|
||||
width: 100%;
|
||||
}
|
|
@ -0,0 +1,412 @@
|
|||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.15;
|
||||
color: #303133;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
a {
|
||||
color: mix(#fff, $--color-primary, 20%);
|
||||
text-decoration: none;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
color: $--color-primary;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
||||
/* Utils
|
||||
------------------------------ */
|
||||
.clearfix:before,
|
||||
.clearfix:after {
|
||||
content: " ";
|
||||
display: table;
|
||||
}
|
||||
|
||||
.clearfix:after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
|
||||
/* Animation
|
||||
------------------------------ */
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity .5s;
|
||||
}
|
||||
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
/* Reset element-ui
|
||||
------------------------------ */
|
||||
.site-wrapper {
|
||||
.el-pagination {
|
||||
margin-top: 15px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Layout
|
||||
------------------------------ */
|
||||
.site-wrapper {
|
||||
position: relative;
|
||||
min-width: 1180px;
|
||||
}
|
||||
|
||||
|
||||
/* Sidebar fold
|
||||
------------------------------ */
|
||||
.site-sidebar--fold {
|
||||
.site-navbar__header,
|
||||
.site-navbar__brand,
|
||||
.site-sidebar,
|
||||
.site-sidebar__inner,
|
||||
.el-menu.site-sidebar__menu {
|
||||
width: 64px;
|
||||
}
|
||||
|
||||
.site-navbar__body,
|
||||
.site-content__wrapper {
|
||||
margin-left: 64px;
|
||||
}
|
||||
|
||||
.site-navbar__brand {
|
||||
&-lg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&-mini {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.site-sidebar,
|
||||
.site-sidebar__inner {
|
||||
overflow: initial;
|
||||
}
|
||||
|
||||
.site-sidebar__menu-icon {
|
||||
margin-right: 0;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.site-content--tabs > .el-tabs > .el-tabs__header {
|
||||
left: 64px;
|
||||
}
|
||||
}
|
||||
|
||||
// animation
|
||||
.site-navbar__header,
|
||||
.site-navbar__brand,
|
||||
.site-navbar__body,
|
||||
.site-sidebar,
|
||||
.site-sidebar__inner,
|
||||
.site-sidebar__menu.el-menu,
|
||||
.site-sidebar__menu-icon,
|
||||
.site-content__wrapper,
|
||||
.site-content--tabs > .el-tabs .el-tabs__header {
|
||||
transition: inline-block .3s, left .3s, width .3s, margin-left .3s, font-size .3s;
|
||||
}
|
||||
|
||||
|
||||
/* Navbar
|
||||
------------------------------ */
|
||||
.site-navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 1030;
|
||||
height: 50px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, .08);
|
||||
background-color: $navbar--background-color;
|
||||
|
||||
&--inverse {
|
||||
.site-navbar__body {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.el-menu {
|
||||
> .el-menu-item,
|
||||
> .el-submenu > .el-submenu__title {
|
||||
color: #fff;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background-color: mix(#000, $navbar--background-color, 15%);
|
||||
}
|
||||
}
|
||||
|
||||
> .el-menu-item.is-active,
|
||||
> .el-submenu.is-active > .el-submenu__title {
|
||||
border-bottom-color: mix(#fff, $navbar--background-color, 85%);
|
||||
}
|
||||
|
||||
.el-menu-item i,
|
||||
.el-submenu__title i,
|
||||
.el-dropdown {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu--popup-bottom-start {
|
||||
background-color: $navbar--background-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__header {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 230px;
|
||||
height: 50px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__brand {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
width: 230px;
|
||||
height: 50px;
|
||||
margin: 0;
|
||||
line-height: 50px;
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
color: #fff;
|
||||
|
||||
&-lg,
|
||||
&-mini {
|
||||
margin: 0 5px;
|
||||
color: #fff;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-mini {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__switch {
|
||||
font-size: 18px;
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
&__avatar {
|
||||
border-bottom: none !important;
|
||||
|
||||
* {
|
||||
vertical-align: inherit;
|
||||
}
|
||||
|
||||
.el-dropdown-link {
|
||||
> img {
|
||||
width: 36px;
|
||||
height: auto;
|
||||
margin-right: 5px;
|
||||
border-radius: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
position: relative;
|
||||
margin-left: 230px;
|
||||
padding-right: 15px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
&__menu {
|
||||
float: left;
|
||||
background-color: transparent;
|
||||
border-bottom: 0;
|
||||
|
||||
&--right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
a:focus,
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.el-menu-item,
|
||||
.el-submenu > .el-submenu__title {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
.el-submenu > .el-menu {
|
||||
top: 55px;
|
||||
}
|
||||
|
||||
.el-badge {
|
||||
display: inline;
|
||||
z-index: 2;
|
||||
|
||||
&__content {
|
||||
line-height: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Sidebar
|
||||
------------------------------ */
|
||||
.site-sidebar {
|
||||
position: fixed;
|
||||
top: 50px;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 1020;
|
||||
width: 230px;
|
||||
overflow: hidden;
|
||||
|
||||
&--dark,
|
||||
&--dark-popper {
|
||||
background-color: $sidebar--background-color-dark;
|
||||
|
||||
.site-sidebar__menu.el-menu,
|
||||
> .el-menu--popup {
|
||||
background-color: $sidebar--background-color-dark;
|
||||
|
||||
.el-menu-item,
|
||||
.el-submenu > .el-submenu__title {
|
||||
color: $sidebar--color-text-dark;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
color: mix(#fff, $sidebar--color-text-dark, 50%);
|
||||
background-color: mix(#fff, $sidebar--background-color-dark, 2.5%);
|
||||
}
|
||||
}
|
||||
|
||||
.el-menu,
|
||||
.el-submenu.is-opened {
|
||||
background-color: mix(#000, $sidebar--background-color-dark, 15%);
|
||||
}
|
||||
|
||||
.el-menu-item.is-active,
|
||||
.el-submenu.is-active > .el-submenu__title {
|
||||
color: mix(#fff, $sidebar--color-text-dark, 80%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__inner {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 250px;
|
||||
height: 100%;
|
||||
padding-bottom: 15px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
&__menu.el-menu {
|
||||
width: 230px;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
&__menu-icon {
|
||||
width: 24px;
|
||||
margin-right: 5px;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
color: inherit !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Content
|
||||
------------------------------ */
|
||||
.site-content {
|
||||
position: relative;
|
||||
padding: 15px;
|
||||
|
||||
&__wrapper {
|
||||
position: relative;
|
||||
padding-top: 50px;
|
||||
margin-left: 230px;
|
||||
min-height: 100%;
|
||||
background: $content--background-color;
|
||||
}
|
||||
|
||||
&--tabs {
|
||||
padding: 55px 0 0;
|
||||
}
|
||||
|
||||
> .el-tabs {
|
||||
> .el-tabs__header {
|
||||
position: fixed;
|
||||
top: 50px;
|
||||
left: 230px;
|
||||
right: 0;
|
||||
z-index: 930;
|
||||
padding: 0 55px 0 15px;
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .12), 0 0 6px 0 rgba(0, 0, 0, .04);
|
||||
background-color: #fff;
|
||||
|
||||
> .el-tabs__nav-wrap {
|
||||
margin-bottom: 0;
|
||||
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .el-tabs__content {
|
||||
padding: 0 15px 15px;
|
||||
|
||||
> .site-tabs__tools {
|
||||
position: fixed;
|
||||
top: 50px;
|
||||
right: 0;
|
||||
z-index: 931;
|
||||
height: 40px;
|
||||
padding: 0 12px;
|
||||
font-size: 16px;
|
||||
line-height: 40px;
|
||||
background-color: $content--background-color;
|
||||
cursor: pointer;
|
||||
|
||||
.el-icon--right {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.el-table__expand-icon {
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
vertical-align: middle;
|
||||
margin-right: 5px;
|
||||
}
|
|
@ -0,0 +1,447 @@
|
|||
/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */
|
||||
|
||||
/* Document
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Correct the line height in all browsers.
|
||||
* 2. Prevent adjustments of font size after orientation changes in
|
||||
* IE on Windows Phone and in iOS.
|
||||
*/
|
||||
|
||||
html {
|
||||
line-height: 1.15; /* 1 */
|
||||
-ms-text-size-adjust: 100%; /* 2 */
|
||||
-webkit-text-size-adjust: 100%; /* 2 */
|
||||
}
|
||||
|
||||
/* Sections
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Remove the margin in all browsers (opinionated).
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 9-.
|
||||
*/
|
||||
|
||||
article,
|
||||
aside,
|
||||
footer,
|
||||
header,
|
||||
nav,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the font size and margin on `h1` elements within `section` and
|
||||
* `article` contexts in Chrome, Firefox, and Safari.
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
/* Grouping content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 9-.
|
||||
* 1. Add the correct display in IE.
|
||||
*/
|
||||
|
||||
figcaption,
|
||||
figure,
|
||||
main { /* 1 */
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct margin in IE 8.
|
||||
*/
|
||||
|
||||
figure {
|
||||
margin: 1em 40px;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in Firefox.
|
||||
* 2. Show the overflow in Edge and IE.
|
||||
*/
|
||||
|
||||
hr {
|
||||
box-sizing: content-box; /* 1 */
|
||||
height: 0; /* 1 */
|
||||
overflow: visible; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/* Text-level semantics
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Remove the gray background on active links in IE 10.
|
||||
* 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
|
||||
*/
|
||||
|
||||
a {
|
||||
background-color: transparent; /* 1 */
|
||||
-webkit-text-decoration-skip: objects; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Remove the bottom border in Chrome 57- and Firefox 39-.
|
||||
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||
*/
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none; /* 1 */
|
||||
text-decoration: underline; /* 2 */
|
||||
text-decoration: underline dotted; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent the duplicate application of `bolder` by the next rule in Safari 6.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||
*/
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||
* 2. Correct the odd `em` font sizing in all browsers.
|
||||
*/
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace; /* 1 */
|
||||
font-size: 1em; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font style in Android 4.3-.
|
||||
*/
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct background and color in IE 9-.
|
||||
*/
|
||||
|
||||
mark {
|
||||
background-color: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct font size in all browsers.
|
||||
*/
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||
* all browsers.
|
||||
*/
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
/* Embedded content
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 9-.
|
||||
*/
|
||||
|
||||
audio,
|
||||
video {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in iOS 4-7.
|
||||
*/
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the border on images inside links in IE 10-.
|
||||
*/
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the overflow in IE.
|
||||
*/
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Forms
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* 1. Change the font styles in all browsers (opinionated).
|
||||
* 2. Remove the margin in Firefox and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: sans-serif; /* 1 */
|
||||
font-size: 100%; /* 1 */
|
||||
line-height: 1.15; /* 1 */
|
||||
margin: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the overflow in IE.
|
||||
* 1. Show the overflow in Edge.
|
||||
*/
|
||||
|
||||
button,
|
||||
input { /* 1 */
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||
* 1. Remove the inheritance of text transform in Firefox.
|
||||
*/
|
||||
|
||||
button,
|
||||
select { /* 1 */
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
|
||||
* controls in Android 4.
|
||||
* 2. Correct the inability to style clickable types in iOS and Safari.
|
||||
*/
|
||||
|
||||
button,
|
||||
html [type="button"], /* 1 */
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner border and padding in Firefox.
|
||||
*/
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the focus styles unset by the previous rule.
|
||||
*/
|
||||
|
||||
button:-moz-focusring,
|
||||
[type="button"]:-moz-focusring,
|
||||
[type="reset"]:-moz-focusring,
|
||||
[type="submit"]:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the padding in Firefox.
|
||||
*/
|
||||
|
||||
fieldset {
|
||||
padding: 0.35em 0.75em 0.625em;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the text wrapping in Edge and IE.
|
||||
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||
* 3. Remove the padding so developers are not caught out when they zero out
|
||||
* `fieldset` elements in all browsers.
|
||||
*/
|
||||
|
||||
legend {
|
||||
box-sizing: border-box; /* 1 */
|
||||
color: inherit; /* 2 */
|
||||
display: table; /* 1 */
|
||||
max-width: 100%; /* 1 */
|
||||
padding: 0; /* 3 */
|
||||
white-space: normal; /* 1 */
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct display in IE 9-.
|
||||
* 2. Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||
*/
|
||||
|
||||
progress {
|
||||
display: inline-block; /* 1 */
|
||||
vertical-align: baseline; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the default vertical scrollbar in IE.
|
||||
*/
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Add the correct box sizing in IE 10-.
|
||||
* 2. Remove the padding in IE 10-.
|
||||
*/
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
box-sizing: border-box; /* 1 */
|
||||
padding: 0; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||
*/
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the odd appearance in Chrome and Safari.
|
||||
* 2. Correct the outline style in Safari.
|
||||
*/
|
||||
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield; /* 1 */
|
||||
outline-offset: -2px; /* 2 */
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the inner padding and cancel buttons in Chrome and Safari on macOS.
|
||||
*/
|
||||
|
||||
[type="search"]::-webkit-search-cancel-button,
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||
* 2. Change font properties to `inherit` in Safari.
|
||||
*/
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
font: inherit; /* 2 */
|
||||
}
|
||||
|
||||
/* Interactive
|
||||
========================================================================== */
|
||||
|
||||
/*
|
||||
* Add the correct display in IE 9-.
|
||||
* 1. Add the correct display in Edge, IE, and Firefox.
|
||||
*/
|
||||
|
||||
details, /* 1 */
|
||||
menu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add the correct display in all browsers.
|
||||
*/
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
/* Scripting
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 9-.
|
||||
*/
|
||||
|
||||
canvas {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the correct display in IE.
|
||||
*/
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Hidden
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* Add the correct display in IE 10-.
|
||||
*/
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// 站点主色
|
||||
// tips: 要达到整站主题修改效果, 请确保[$--color-primary]站点主色与[/src/element-ui-theme/index.js]文件中[import './element-[#17B3A3]/index.css']当前主题色一致
|
||||
$--color-primary: #409EFF;
|
||||
|
||||
// Navbar
|
||||
$navbar--background-color: $--color-primary;
|
||||
|
||||
// Sidebar
|
||||
$sidebar--background-color-dark: #263238;
|
||||
$sidebar--color-text-dark: #8a979e;
|
||||
|
||||
// Content
|
||||
$content--background-color: #f1f4f5;
|
|
@ -0,0 +1,5 @@
|
|||
@import "normalize";
|
||||
// api: https://github.com/necolas/normalize.css/
|
||||
@import "variables";
|
||||
// 站点变量
|
||||
@import "base";
|
|
@ -0,0 +1,47 @@
|
|||
<template>
|
||||
<svg :class="getClassName" :width="width" :height="height" aria-hidden="true">
|
||||
<use :xlink:href="getName"></use>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'icon-svg',
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
className: {
|
||||
type: String
|
||||
},
|
||||
width: {
|
||||
type: String
|
||||
},
|
||||
height: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getName() {
|
||||
return `#icon-${this.name}`
|
||||
},
|
||||
getClassName() {
|
||||
return [
|
||||
'icon-svg',
|
||||
`icon-svg__${this.name}`,
|
||||
this.className && /\S/.test(this.className) ? `${this.className}` : ''
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.icon-svg {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
fill: currentColor;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,84 @@
|
|||
<template>
|
||||
<el-table-column :prop="prop" v-bind="$attrs">
|
||||
<template slot-scope="scope">
|
||||
<span @click.prevent="toggleHandle(scope.$index, scope.row)" :style="childStyles(scope.row)">
|
||||
<i :class="iconClasses(scope.row)" :style="iconStyles(scope.row)"></i>
|
||||
{{ scope.row[prop] }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import isArray from 'lodash/isArray'
|
||||
export default {
|
||||
name: 'table-tree-column',
|
||||
props: {
|
||||
prop: {
|
||||
type: String
|
||||
},
|
||||
treeKey: {
|
||||
type: String,
|
||||
default: 'id'
|
||||
},
|
||||
parentKey: {
|
||||
type: String,
|
||||
default: 'parentId'
|
||||
},
|
||||
levelKey: {
|
||||
type: String,
|
||||
default: '_level'
|
||||
},
|
||||
childKey: {
|
||||
type: String,
|
||||
default: 'children'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
childStyles(row) {
|
||||
return { 'padding-left': (row[this.levelKey] > 1 ? row[this.levelKey] * 7 : 0) + 'px' }
|
||||
},
|
||||
iconClasses(row) {
|
||||
return [!row._expanded ? 'el-icon-caret-right' : 'el-icon-caret-bottom']
|
||||
},
|
||||
iconStyles(row) {
|
||||
return { 'visibility': this.hasChild(row) ? 'visible' : 'hidden' }
|
||||
},
|
||||
hasChild(row) {
|
||||
return (isArray(row[this.childKey]) && row[this.childKey].length >= 1) || false
|
||||
},
|
||||
// 切换处理
|
||||
toggleHandle(index, row) {
|
||||
if (this.hasChild(row)) {
|
||||
var data = this.$parent.store.states.data.slice(0)
|
||||
data[index]._expanded = !data[index]._expanded
|
||||
if (data[index]._expanded) {
|
||||
data = data.splice(0, index + 1).concat(row[this.childKey]).concat(data)
|
||||
} else {
|
||||
data = this.removeChildNode(data, row[this.treeKey])
|
||||
}
|
||||
this.$parent.store.commit('setData', data)
|
||||
this.$nextTick(() => {
|
||||
this.$parent.doLayout()
|
||||
})
|
||||
}
|
||||
},
|
||||
// 移除子节点
|
||||
removeChildNode(data, parentId) {
|
||||
var parentIds = isArray(parentId) ? parentId : [parentId]
|
||||
if (parentId.length <= 0) {
|
||||
return data
|
||||
}
|
||||
var ids = []
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
if (parentIds.indexOf(data[i][this.parentKey]) !== -1 && parentIds.indexOf(data[i][this.treeKey]) === -1) {
|
||||
data[i]._expanded = false
|
||||
ids.push(data.splice(i, 1)[0][this.treeKey])
|
||||
i--
|
||||
}
|
||||
}
|
||||
return this.removeChildNode(data, ids)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,77 @@
|
|||
<template>
|
||||
<div class="panel flex flex-wrap">
|
||||
<el-tag v-for="tag in dynamicTags" closable @close="handleClose(tag)" :disable-transitions="false" :key="tag">
|
||||
{{tag}}
|
||||
</el-tag>
|
||||
<el-input class="input-new-tag" v-if="inputVisible" v-model="inputValue" ref="saveTagInput" size="small" @keyup.enter.native="handleInputConfirm" @blur="handleInputConfirm">
|
||||
</el-input>
|
||||
<el-button v-else class="button-new-tag" size="small" @click="showInput">+ 添加</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
/**
|
||||
* 标签编辑器
|
||||
*/
|
||||
let touchMoved = false;
|
||||
export default {
|
||||
name: 'tags-editor',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: true,
|
||||
default: ""
|
||||
},
|
||||
size: {//标签大小:[small:小,large:大]
|
||||
type: String,
|
||||
default: 'small'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
inputVisible: false,
|
||||
inputValue: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dynamicTags() {
|
||||
if (this.value != "") return this.value.split(',')
|
||||
return []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClose(tag) {
|
||||
let newTags = this.dynamicTags;
|
||||
newTags.splice(newTags.indexOf(tag), 1);
|
||||
this.$emit('input', newTags.join(","));
|
||||
},
|
||||
showInput() {
|
||||
this.inputVisible = true;
|
||||
this.$nextTick(_ => {
|
||||
this.$refs.saveTagInput.$refs.input.focus();
|
||||
});
|
||||
},
|
||||
handleInputConfirm() {
|
||||
let inputValue = this.inputValue;
|
||||
let newTags = this.dynamicTags;
|
||||
if (inputValue && newTags.indexOf(inputValue) < 0) {
|
||||
newTags.push(inputValue);
|
||||
}
|
||||
this.inputVisible = false;
|
||||
this.inputValue = '';
|
||||
this.$emit('input', newTags.join(","));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.panel {
|
||||
flex: 1;
|
||||
}
|
||||
.el-tag,.button-new-tag{
|
||||
margin: 5px;
|
||||
}
|
||||
.input-new-tag {
|
||||
width: inherit;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,189 @@
|
|||
<template>
|
||||
<el-dialog title="筛选模板消息目标用户" :close-on-click-modal="false" :visible.sync="visible">
|
||||
<el-form :inline="true" :model="dataForm" ref="dataForm" clearable @keyup.enter.native="getWxUsers()">
|
||||
<el-form-item>
|
||||
<el-select v-model="dataForm.tagid" filterable placeholder="用户标签" @change="getWxUsers()">
|
||||
<el-option v-for="item in wxUserTags" :key="item.id" :label="item.name" :value="item.id+''"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="dataForm.nickname" placeholder="昵称" @change="getWxUsers()" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="dataForm.province" placeholder="省份" @change="getWxUsers()" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="dataForm.city" placeholder="城市" @change="getWxUsers()" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="dataForm.remark" placeholder="备注" @change="getWxUsers()" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="dataForm.qrScene" placeholder="扫码场景值" @change="getWxUsers()" clearable></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="text-bold">本消息将发送给:</div>
|
||||
<div class="user-list" v-loading="wxUsersLoading">
|
||||
<div class="user-card" v-for="item in wxUserList" :key="item.openid">
|
||||
<el-avatar :src="item.headimgurl"></el-avatar>
|
||||
<div class="nickname">{{item.nickname}}</div>
|
||||
</div>
|
||||
<div class="text-bold">
|
||||
<span v-show="totalCount>10">...</span>
|
||||
等共<span class="text-success">{{totalCount}}</span>个用户
|
||||
</div>
|
||||
</div>
|
||||
<div class="margin-top text-bold">消息预览:</div>
|
||||
<div class="margin-top-xs">
|
||||
<el-input type="textarea" disabled autosize v-model="msgReview" placeholder="模版"></el-input>
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="send" type="success" :disabled="totalCount<=0 || sending">{{sending?'发送中...':'发送'}}</el-button>
|
||||
<el-button @click="visible=false">关闭</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
export default {
|
||||
name:'template-msg-task',
|
||||
props:{
|
||||
wxUserTagName:{
|
||||
type:String,
|
||||
required:false
|
||||
}
|
||||
},
|
||||
data(){
|
||||
return{
|
||||
visible:false,
|
||||
wxUsersLoading:false,
|
||||
sending:false,
|
||||
msgTemplate:{},
|
||||
dataForm: {
|
||||
page:1,
|
||||
sidx: 'subscribe_time',
|
||||
order: 'desc',
|
||||
tagid:'',
|
||||
nickname: '',
|
||||
city:'',
|
||||
province:'',
|
||||
remark:'',
|
||||
qrScene:''
|
||||
},
|
||||
wxUserList:[],
|
||||
totalCount:0
|
||||
}
|
||||
},
|
||||
computed: mapState({
|
||||
wxUserTags:state=>state.wxUserTags.tags,
|
||||
msgReview(){
|
||||
if(!this.msgTemplate.data) return ""
|
||||
let content = this.msgTemplate.content
|
||||
this.msgTemplate.data.forEach(item=>{
|
||||
content = content.replace("{{"+item.name+".DATA}}",item.value)
|
||||
})
|
||||
return content
|
||||
}
|
||||
}),
|
||||
mounted() {
|
||||
this.getWxUserTags().then((taglist)=>{
|
||||
if(this.wxUserTagName){
|
||||
let tagItem = taglist.find(tag=>tag.name==this.wxUserTagName)
|
||||
console.log(tagItem)
|
||||
if(tagItem) {
|
||||
this.dataForm.tagid=tagItem.id+''
|
||||
}
|
||||
}
|
||||
this.getWxUsers()
|
||||
});
|
||||
},
|
||||
methods:{
|
||||
init(msgTemplate){
|
||||
if(!msgTemplate || !msgTemplate.templateId){
|
||||
this.$message.error('消息模板无效')
|
||||
return
|
||||
}
|
||||
if(!msgTemplate.data || !(msgTemplate.data instanceof Array)){
|
||||
this.$message.error('请现配置此模板填充数据')
|
||||
return
|
||||
}
|
||||
this.msgTemplate=msgTemplate
|
||||
this.visible=true;
|
||||
},
|
||||
getWxUserTags() {
|
||||
return new Promise((resolve,reject)=>{
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/wxUserTags/list'),
|
||||
method: 'get',
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$store.commit('wxUserTags/updateTags', data.list)
|
||||
resolve(data.list)
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
reject(data.msg)
|
||||
}
|
||||
}).catch(err=>reject(err))
|
||||
})
|
||||
},
|
||||
getWxUsers() {
|
||||
this.wxUsersLoading = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/wxUser/list'),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams(this.dataForm)
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.wxUserList = data.page.list
|
||||
this.totalCount = data.page.totalCount
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
this.wxUsersLoading = false
|
||||
})
|
||||
},
|
||||
send(){
|
||||
if(this.sending)return
|
||||
this.sending=true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/msgTemplate/sendMsgBatch'),
|
||||
method: 'post',
|
||||
data:this.$http.adornData({
|
||||
wxUserFilterParams : this.dataForm,
|
||||
templateId : this.msgTemplate.templateId,
|
||||
url : this.msgTemplate.url,
|
||||
miniprogram : this.msgTemplate.miniprogram,
|
||||
data : this.msgTemplate.data,
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
this.sending = false
|
||||
if (data && data.code === 200) {
|
||||
this.$message.success("消息将在后台发送")
|
||||
this.visible=false
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.user-list{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
.user-card{
|
||||
overflow: hidden;
|
||||
max-width: 60px;
|
||||
margin: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
.nickname{
|
||||
color: #999999;
|
||||
overflow: hidden;
|
||||
text-overflow:ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,99 @@
|
|||
<template>
|
||||
<div class="tinymce-editor">
|
||||
<editor v-model="myValue" :init="init" @onExecCommand="onExecCommand"></editor>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Editor from "@tinymce/tinymce-vue";
|
||||
|
||||
var cos;
|
||||
export default {
|
||||
name: "tinymce-editor",
|
||||
components: {
|
||||
Editor
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
init: {
|
||||
language_url: "./tinymce/zh_CN.js", //public目录下
|
||||
language: "zh_CN",
|
||||
height: 500,
|
||||
plugins: "lists image media table paste link searchreplace anchor code preview pagebreak importcss",
|
||||
toolbar: "undo redo searchreplace | formatselect pagebreak | bold italic forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | lists link anchor image media table | removeformat code preview", //工具栏展示项
|
||||
toolbar_drawer: false,
|
||||
image_advtab: true,
|
||||
object_resizing: false,
|
||||
paste_data_images: true,
|
||||
content_css: "./tinymce/article.css",
|
||||
images_upload_handler: (blobInfo, success, failure) => {
|
||||
this.uploadFile(blobInfo.blob()).then(fileUrl => success(fileUrl)).catch(err => failure(err))
|
||||
}
|
||||
},
|
||||
myValue: this.value,
|
||||
uploading: false,
|
||||
cosConfig: []
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
// console.log('tinymce-editor mounted:',this.value)
|
||||
tinymce.init({});
|
||||
this.cosInit();
|
||||
},
|
||||
methods: {
|
||||
cosInit() {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl("/sys/oss/config"),
|
||||
method: "get",
|
||||
params: this.$http.adornParams()
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.cosConfig = data.config;
|
||||
} else {
|
||||
this.$message.error("请先配置云存储相关信息!");
|
||||
}
|
||||
});
|
||||
},
|
||||
onExecCommand(e) {
|
||||
//console.log(e)
|
||||
},
|
||||
uploadFile(file) {
|
||||
this.uploading = true;
|
||||
return new Promise((resolve, reject) => {
|
||||
let formData = new FormData();
|
||||
formData.append("file", file);
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/oss/upload'),
|
||||
method: 'post',
|
||||
data: formData
|
||||
}).then(({ data }) => {
|
||||
console.log(data)
|
||||
if (data && data.code === 200) {
|
||||
this.$emit('uploaded', data.url)
|
||||
resolve(data.url)
|
||||
} else {
|
||||
this.$message.error("文件上传失败:" + data.msg)
|
||||
reject(data.msg)
|
||||
}
|
||||
this.uploading = false;
|
||||
}).catch(err=>reject(err))
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newValue) {
|
||||
this.myValue = newValue;
|
||||
},
|
||||
myValue(newValue) {
|
||||
this.$emit("input", newValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,45 @@
|
|||
<template>
|
||||
<el-select v-model="selectedAppid" size="small" v-loading="dataListLoading" @change="selectAccount" filterable>
|
||||
<el-option v-for="item in accountList" :key="item.appid" :label="item.name+'('+ACCOUNT_TYPES[item.type]+')'" :value="item.appid"></el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dataListLoading: false
|
||||
}
|
||||
},
|
||||
computed: mapState({
|
||||
accountList: state=>state.wxAccount.accountList,
|
||||
ACCOUNT_TYPES: state=>state.wxAccount.ACCOUNT_TYPES,
|
||||
selectedAppid:state=>state.wxAccount.selectedAppid
|
||||
}),
|
||||
mounted(){
|
||||
this.getDataList()
|
||||
},
|
||||
methods:{
|
||||
getDataList() {
|
||||
this.dataListLoading = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/wxAccount/list'),
|
||||
method: 'get'
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$store.commit('wxAccount/updateAccountList', data.list)
|
||||
if(!data.list.length){
|
||||
this.$message.info("公众号列表为空,请先添加")
|
||||
}
|
||||
}
|
||||
this.dataListLoading = false
|
||||
})
|
||||
},
|
||||
selectAccount(appid){
|
||||
if(this.selectedAppid!=appid){
|
||||
this.$store.commit('wxAccount/selectAccount', appid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<div class="panel">
|
||||
<el-tooltip class="item" effect="dark" :content="msg.inOut?'公众号发出的消息':'来自用户的消息'" placement="right">
|
||||
<el-tag size="mini" v-if="msg.inOut" class="margin-right el-icon-upload2" type="info"></el-tag>
|
||||
<el-tag size="mini" v-else class="margin-right el-icon-download"></el-tag>
|
||||
</el-tooltip>
|
||||
<span class="panel-content">
|
||||
<span v-if="msg.msgType=='text'" v-html="msg.detail.content"></span>
|
||||
<span v-else-if="msg.msgType=='event'" >
|
||||
<el-tag size="mini" type="warning" effect="plain">事件</el-tag>
|
||||
<el-tag size="mini" type="info" effect="plain">{{msg.detail.event}}</el-tag>
|
||||
{{msg.detail.eventKey}}
|
||||
</span>
|
||||
<span v-else-if="msg.msgType=='transfer_customer_service'">
|
||||
<el-tag size="mini" type="warning" effect="plain">事件</el-tag>
|
||||
<el-tag size="mini" type="info" effect="plain">消息转客服</el-tag>
|
||||
</span>
|
||||
<span v-else>
|
||||
<el-tag size="mini" effect="plain">{{XmlMsgType[msg.msgType]}}</el-tag>
|
||||
后台不支持预览
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
export default {
|
||||
name:'wx-msg-preview',
|
||||
props:{
|
||||
msg:Object
|
||||
},
|
||||
computed:mapState({
|
||||
XmlMsgType:state=>state.message.XmlMsgType,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.panel,.panel a{
|
||||
color: #999;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,139 @@
|
|||
<template>
|
||||
<el-dialog title="公众号用户标签管理" :close-on-click-modal="false" :visible.sync="dialogVisible">
|
||||
<div class="panel flex flex-wrap" v-loading="submitting">
|
||||
<el-tag v-for="tag in wxUserTags" closable @click="editTag(tag.id,tag.name)" @close="deleteTag(tag.id)" :disable-transitions="false" :key="tag.id">
|
||||
{{tag.id}} {{tag.name}}
|
||||
</el-tag>
|
||||
<el-input class="input-new-tag" v-if="inputVisible" placeholder="回车确认" v-model="inputValue" ref="saveTagInput" size="small" @keyup.enter.native="addTag">
|
||||
</el-input>
|
||||
<el-button v-else class="button-new-tag" size="small" @click="showInput">+ 添加</el-button>
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible=false">关闭</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
export default {
|
||||
name: 'wx-user-tags-manager',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialogVisible:false,
|
||||
inputVisible: false,
|
||||
inputValue: '',
|
||||
submitting:false,
|
||||
}
|
||||
},
|
||||
computed: mapState({
|
||||
wxUserTags:state=>state.wxUserTags.tags
|
||||
}),
|
||||
mounted() {
|
||||
this.getWxUserTags();
|
||||
},
|
||||
methods: {
|
||||
show(){
|
||||
this.dialogVisible=true;
|
||||
},
|
||||
getWxUserTags() {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/wxUserTags/list'),
|
||||
method: 'get',
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$store.commit('wxUserTags/updateTags', data.list)
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
deleteTag(tagid) {
|
||||
if(this.submitting){
|
||||
return
|
||||
}
|
||||
this.$confirm(`确定删除标签?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.submitting=true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/wxUserTags/delete/'+tagid),
|
||||
method: 'post',
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.getWxUserTags();
|
||||
this.$emit('change');
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
this.submitting=false;
|
||||
})
|
||||
})
|
||||
},
|
||||
showInput() {
|
||||
this.inputVisible = true;
|
||||
this.$nextTick(_ => {
|
||||
this.$refs.saveTagInput.$refs.input.focus();
|
||||
});
|
||||
},
|
||||
addTag() {
|
||||
let newTagName = this.inputValue;
|
||||
this.saveTag(newTagName)
|
||||
this.inputVisible = false;
|
||||
this.inputValue = '';
|
||||
},
|
||||
editTag(tagid,orignName=''){
|
||||
this.$prompt('请输入新标签名称', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputValue:orignName,
|
||||
inputPattern: /^.{1,30}$/,
|
||||
inputErrorMessage: '名称1-30字符'
|
||||
}).then(({ value }) => {
|
||||
console.log(value)
|
||||
this.saveTag(value,tagid)
|
||||
})
|
||||
},
|
||||
saveTag(name,tagid){
|
||||
if(this.submitting){
|
||||
return
|
||||
}
|
||||
this.submitting=true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/wxUserTags/save'),
|
||||
method: 'post',
|
||||
data:this.$http.adornData({
|
||||
id : tagid?tagid:undefined,
|
||||
name : name
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.getWxUserTags();
|
||||
this.$emit('change');
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
this.submitting=false;
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.panel {
|
||||
flex: 1;
|
||||
}
|
||||
.el-tag,.button-new-tag {
|
||||
margin: 5px;
|
||||
}
|
||||
.input-new-tag {
|
||||
width: inherit;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,32 @@
|
|||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
import VueCookie from 'vue-cookie'
|
||||
import ElementUI from 'element-ui';
|
||||
import moment from 'moment'
|
||||
|
||||
import 'element-ui/lib/theme-chalk/index.css';
|
||||
import './assets/css/common.css'
|
||||
import './assets/scss/index.scss'
|
||||
import httpRequest from '@/utils/httpRequest' // api: https://github.com/axios/axios
|
||||
import { isAuth } from '@/utils'
|
||||
import VueClipboard from 'vue-clipboard2'
|
||||
|
||||
Vue.use(ElementUI);
|
||||
Vue.use(VueClipboard)
|
||||
Vue.use(VueCookie)
|
||||
Vue.config.productionTip = false
|
||||
|
||||
// 挂载全局
|
||||
Vue.prototype.$http = httpRequest // ajax请求方法
|
||||
Vue.prototype.isAuth = isAuth // 权限方法
|
||||
|
||||
moment.locale('zh-cn');
|
||||
Vue.prototype.$moment = moment; //时间处理
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
|
@ -0,0 +1 @@
|
|||
module.exports = file => () => import('@/views/' + file + '.vue')
|
|
@ -0,0 +1,154 @@
|
|||
/**
|
||||
* 全站路由配置
|
||||
*
|
||||
* 建议:
|
||||
* 1. 代码中路由统一使用name属性跳转(不使用path属性)
|
||||
*/
|
||||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
import http from '@/utils/httpRequest'
|
||||
import { isURL } from '@/utils/validate'
|
||||
import { clearLoginInfo } from '@/utils'
|
||||
|
||||
Vue.use(VueRouter)
|
||||
|
||||
const _import = require('./import-views')
|
||||
// 全局路由(无需嵌套上左右整体布局)
|
||||
const globalRoutes = [
|
||||
{ path: '/404', component: () => import('@/views/common/404'), name: '404', meta: { title: '404未找到' } },
|
||||
{ path: '/login', component: () => import('@/views/common/login'), name: 'login', meta: { title: '登录' } }
|
||||
]
|
||||
|
||||
// 主入口路由(需嵌套上左右整体布局)
|
||||
const mainRoutes = {
|
||||
path: '/',
|
||||
component: () => import('@/views/main'),
|
||||
name: 'main',
|
||||
redirect: { name: 'home' },
|
||||
meta: { title: '主入口整体布局' },
|
||||
children: [
|
||||
// 通过meta对象设置路由展示方式
|
||||
// 1. isTab: 是否通过tab展示内容, true: 是, false: 否
|
||||
// 2. iframeUrl: 是否通过iframe嵌套展示内容, '以http[s]://开头': 是, '': 否
|
||||
// 提示: 如需要通过iframe嵌套展示内容, 但不通过tab打开, 请自行创建组件使用iframe处理!
|
||||
{ path: '/home', component: () => import('@/views/common/home'), name: 'home', meta: { title: '首页' } },
|
||||
{ path: '/theme', component: () => import('@/views/common/theme'), name: 'theme', meta: { title: '主题' } },
|
||||
],
|
||||
beforeEnter(to, from, next) {
|
||||
let token = Vue.cookie.get('token')
|
||||
if (!token || !/\S/.test(token)) {
|
||||
clearLoginInfo()
|
||||
next({ name: 'login' })
|
||||
}
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: 'hash',
|
||||
scrollBehavior: () => ({ y: 0 }),
|
||||
isAddDynamicMenuRoutes: false, // 是否已经添加动态(菜单)路由
|
||||
routes: globalRoutes.concat(mainRoutes)
|
||||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
// 添加动态(菜单)路由
|
||||
// 1. 已经添加 or 全局路由, 直接访问
|
||||
// 2. 获取菜单列表, 添加并保存本地存储
|
||||
if (router.options.isAddDynamicMenuRoutes || fnCurrentRouteType(to, globalRoutes) === 'global') {
|
||||
next()
|
||||
} else {
|
||||
http({
|
||||
url: http.adornUrl('/sys/menu/nav'),
|
||||
method: 'get',
|
||||
params: http.adornParams()
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
fnAddDynamicMenuRoutes(data.menuList)
|
||||
router.options.isAddDynamicMenuRoutes = true
|
||||
sessionStorage.setItem('menuList', JSON.stringify(data.menuList || '[]'))
|
||||
sessionStorage.setItem('permissions', JSON.stringify(data.permissions || '[]'))
|
||||
next({ ...to, replace: true })
|
||||
} else {
|
||||
sessionStorage.setItem('menuList', '[]')
|
||||
sessionStorage.setItem('permissions', '[]')
|
||||
next()
|
||||
}
|
||||
}).catch((e) => {
|
||||
console.log(`%c${e} 请求菜单列表和权限失败,跳转至登录页!!`, 'color:blue')
|
||||
router.push({ name: 'login' })
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 判断当前路由类型, global: 全局路由, main: 主入口路由
|
||||
* @param {*} route 当前路由
|
||||
*/
|
||||
function fnCurrentRouteType(route, globalRoutes = []) {
|
||||
var temp = []
|
||||
for (var i = 0; i < globalRoutes.length; i++) {
|
||||
if (route.path === globalRoutes[i].path) {
|
||||
return 'global'
|
||||
} else if (globalRoutes[i].children && globalRoutes[i].children.length >= 1) {
|
||||
temp = temp.concat(globalRoutes[i].children)
|
||||
}
|
||||
}
|
||||
return temp.length >= 1 ? fnCurrentRouteType(route, temp) : 'main'
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加动态(菜单)路由
|
||||
* @param {*} menuList 菜单列表
|
||||
* @param {*} routes 递归创建的动态(菜单)路由
|
||||
*/
|
||||
function fnAddDynamicMenuRoutes(menuList = [], routes = []) {
|
||||
var temp = []
|
||||
for (var i = 0; i < menuList.length; i++) {
|
||||
if (menuList[i].list && menuList[i].list.length >= 1) {
|
||||
temp = temp.concat(menuList[i].list)
|
||||
} else if (menuList[i].url && /\S/.test(menuList[i].url)) {
|
||||
menuList[i].url = menuList[i].url.replace(/^\//, '')
|
||||
var route = {
|
||||
path: menuList[i].url.replace('/', '-'),
|
||||
component: null,
|
||||
name: menuList[i].url.replace('/', '-'),
|
||||
meta: {
|
||||
menuId: menuList[i].menuId,
|
||||
title: menuList[i].name,
|
||||
isDynamic: true,
|
||||
isTab: true,
|
||||
iframeUrl: ''
|
||||
}
|
||||
}
|
||||
// url以http[s]://开头, 通过iframe展示
|
||||
if (isURL(menuList[i].url)) {
|
||||
route['path'] = `i-${menuList[i].menuId}`
|
||||
route['name'] = `i-${menuList[i].menuId}`
|
||||
route['meta']['iframeUrl'] = menuList[i].url
|
||||
} else {
|
||||
try {
|
||||
route['component'] = _import(`modules/${menuList[i].url}`) || null
|
||||
// route['component'] = ()=>import(`@/views/modules/${menuList[i].url}.vue`) || null
|
||||
} catch (e) { }
|
||||
}
|
||||
routes.push(route)
|
||||
}
|
||||
}
|
||||
if (temp.length >= 1) {
|
||||
fnAddDynamicMenuRoutes(temp, routes)
|
||||
} else {
|
||||
mainRoutes.name = 'main-dynamic'
|
||||
mainRoutes.children = routes
|
||||
router.addRoutes([
|
||||
mainRoutes,
|
||||
{ path: '*', redirect: { name: '404' } }
|
||||
])
|
||||
sessionStorage.setItem('dynamicMenuRoutes', JSON.stringify(mainRoutes.children || '[]'))
|
||||
console.log('\n')
|
||||
console.log('%c!<-------------------- 动态(菜单)路由 s -------------------->', 'color:blue')
|
||||
console.log(mainRoutes.children)
|
||||
console.log('%c!<-------------------- 动态(菜单)路由 e -------------------->', 'color:blue')
|
||||
}
|
||||
}
|
||||
export default router
|
|
@ -0,0 +1,24 @@
|
|||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import common from './modules/common'
|
||||
import user from './modules/user'
|
||||
import article from './modules/article'
|
||||
import message from './modules/message'
|
||||
import wxUserTags from './modules/wxUserTags'
|
||||
import wxAccount from './modules/wxAccount'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
export default new Vuex.Store({
|
||||
modules: {
|
||||
common,
|
||||
user,
|
||||
article,
|
||||
message,
|
||||
wxUserTags,
|
||||
wxAccount
|
||||
},
|
||||
mutations: {
|
||||
},
|
||||
strict: true
|
||||
})
|
|
@ -0,0 +1,12 @@
|
|||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
ARTICLE_TYPES: {
|
||||
1: '普通文章',
|
||||
5: '帮助中心',
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
import router from '@/router'
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
// 页面文档可视高度(随窗口改变大小)
|
||||
documentClientHeight: 0,
|
||||
// 导航条, 布局风格, defalut(默认) / inverse(反向)
|
||||
navbarLayoutType: 'default',
|
||||
// 侧边栏, 布局皮肤, light(浅色) / dark(黑色)
|
||||
sidebarLayoutSkin: 'dark',
|
||||
// 侧边栏, 折叠状态
|
||||
sidebarFold: false,
|
||||
// 侧边栏, 菜单
|
||||
menuList: [],
|
||||
menuActiveName: '',
|
||||
// 内容, 是否需要刷新
|
||||
contentIsNeedRefresh: false,
|
||||
// 主入口标签页
|
||||
mainTabs: [],
|
||||
mainTabsActiveName: ''
|
||||
},
|
||||
mutations: {
|
||||
updateDocumentClientHeight(state, height) {
|
||||
state.documentClientHeight = height
|
||||
},
|
||||
updateNavbarLayoutType(state, type) {
|
||||
state.navbarLayoutType = type
|
||||
},
|
||||
updateSidebarLayoutSkin(state, skin) {
|
||||
state.sidebarLayoutSkin = skin
|
||||
},
|
||||
updateSidebarFold(state, fold) {
|
||||
state.sidebarFold = fold
|
||||
},
|
||||
updateMenuList(state, list) {
|
||||
state.menuList = list
|
||||
},
|
||||
updateMenuActiveName(state, name) {
|
||||
state.menuActiveName = name
|
||||
},
|
||||
updateContentIsNeedRefresh(state, status) {
|
||||
state.contentIsNeedRefresh = status
|
||||
},
|
||||
updateMainTabs(state, tabs) {
|
||||
state.mainTabs = tabs
|
||||
},
|
||||
updateMainTabsActiveName(state, name) {
|
||||
state.mainTabsActiveName = name
|
||||
},
|
||||
removeTab(state, tabName) {
|
||||
state.mainTabs = state.mainTabs.filter(item => item.name !== tabName)
|
||||
if (state.mainTabs.length >= 1) {
|
||||
// 当前选中tab被删除
|
||||
if (tabName === state.mainTabsActiveName) {
|
||||
var tab = state.mainTabs[state.mainTabs.length - 1]
|
||||
router.push({ name: tab.name, query: tab.query, params: tab.params }, () => {
|
||||
state.mainTabsActiveName = tab.name
|
||||
})
|
||||
}
|
||||
} else {
|
||||
state.menuActiveName = ''
|
||||
router.push({ name: 'home' })
|
||||
}
|
||||
},
|
||||
closeCurrentTab(state) {
|
||||
this.commit('common/removeTab', state.mainTabsActiveName)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
XmlMsgType:{
|
||||
"text":"文字",
|
||||
"image":"图片",
|
||||
"voice":"语音",
|
||||
"shortvideo":"短视频",
|
||||
"video":"视频",
|
||||
"news":"图文",
|
||||
"music":"音乐",
|
||||
"location":"位置",
|
||||
"link":"链接",
|
||||
"event":"事件",
|
||||
"transfer_customer_service":"转客服"
|
||||
},
|
||||
KefuMsgType: {
|
||||
"text": "文本消息",
|
||||
"image": "图片消息",
|
||||
"voice": "语音消息",
|
||||
"video": "视频消息",
|
||||
"music": "音乐消息",
|
||||
"news": "文章链接",
|
||||
"mpnews": "公众号图文消息",
|
||||
"wxcard": "卡券消息",
|
||||
"miniprogrampage": "小程序消息",
|
||||
"msgmenu": "菜单消息"
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
id: 0,
|
||||
name: ''
|
||||
},
|
||||
mutations: {
|
||||
updateId(state, id) {
|
||||
state.id = id
|
||||
},
|
||||
updateName(state, name) {
|
||||
state.name = name
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import Vue from 'vue'
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
ACCOUNT_TYPES:{
|
||||
1:'订阅号',
|
||||
2:'服务号'
|
||||
},
|
||||
accountList:[],
|
||||
selectedAppid:''
|
||||
},
|
||||
mutations: {
|
||||
updateAccountList (state, list) {
|
||||
state.accountList = list
|
||||
if(!list.length)return
|
||||
if(!state.selectedAppid){
|
||||
let appidCookie = Vue.cookie.get('appid')
|
||||
let selectedAppid = appidCookie?appidCookie:list[0].appid
|
||||
this.commit('wxAccount/selectAccount',selectedAppid)
|
||||
}
|
||||
},
|
||||
selectAccount (state, appid) {
|
||||
Vue.cookie.set('appid',appid)
|
||||
let oldAppid = state.selectedAppid
|
||||
state.selectedAppid = appid
|
||||
if(oldAppid){//切换账号时刷新网页
|
||||
location.reload();
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
tags:[]
|
||||
},
|
||||
mutations: {
|
||||
updateTags (state, tags) {
|
||||
state.tags = tags
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
import Vue from 'vue'
|
||||
import axios from 'axios'
|
||||
import router from '@/router'
|
||||
import qs from 'qs'
|
||||
import merge from 'lodash/merge'
|
||||
import { clearLoginInfo } from '@/utils'
|
||||
const baseUrl = '/wx'
|
||||
|
||||
const http = axios.create({
|
||||
timeout: 1000 * 30,
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8'
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* 请求拦截
|
||||
*/
|
||||
http.interceptors.request.use(config => {
|
||||
config.headers['token'] = Vue.cookie.get('token') // 请求头带上token
|
||||
return config
|
||||
}, error => {
|
||||
return Promise.reject(error)
|
||||
})
|
||||
|
||||
/**
|
||||
* 响应拦截
|
||||
*/
|
||||
http.interceptors.response.use(response => {
|
||||
if (response.data && response.data.code === 401) { // 401, token失效
|
||||
clearLoginInfo()
|
||||
router.push({ name: 'login' })
|
||||
}
|
||||
return response
|
||||
}, error => {
|
||||
return Promise.reject(error)
|
||||
})
|
||||
|
||||
/**
|
||||
* 请求地址处理
|
||||
* @param {*} actionName action方法名称
|
||||
*/
|
||||
http.adornUrl = (actionName) => {
|
||||
// 非生产环境 && 开启代理, 接口前缀统一使用[/proxyApi/]前缀做代理拦截!
|
||||
return baseUrl + actionName
|
||||
}
|
||||
|
||||
/**
|
||||
* get请求参数处理
|
||||
* @param {*} params 参数对象
|
||||
* @param {*} openDefultParams 是否开启默认参数?
|
||||
*/
|
||||
http.adornParams = (params = {}, openDefultParams = true) => {
|
||||
var defaults = {
|
||||
't': new Date().getTime()
|
||||
}
|
||||
return openDefultParams ? merge(defaults, params) : params
|
||||
}
|
||||
|
||||
/**
|
||||
* post请求数据处理
|
||||
* @param {*} data 数据对象
|
||||
* @param {*} openDefultdata 是否开启默认数据?
|
||||
* @param {*} contentType 数据格式
|
||||
* json: 'application/json; charset=utf-8'
|
||||
* form: 'application/x-www-form-urlencoded; charset=utf-8'
|
||||
*/
|
||||
http.adornData = (data = {}, openDefultdata = true, contentType = 'json') => {
|
||||
var defaults = {
|
||||
't': new Date().getTime()
|
||||
}
|
||||
data = openDefultdata ? merge(defaults, data) : data
|
||||
return contentType === 'json' ? JSON.stringify(data) : qs.stringify(data)
|
||||
}
|
||||
|
||||
export default http
|
|
@ -0,0 +1,58 @@
|
|||
import Vue from 'vue'
|
||||
import router from '@/router'
|
||||
import store from '@/store'
|
||||
|
||||
/**
|
||||
* 获取uuid
|
||||
*/
|
||||
export function getUUID() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
|
||||
return (c === 'x' ? (Math.random() * 16 | 0) : ('r&0x3' | '0x8')).toString(16)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有权限
|
||||
* @param {*} key
|
||||
*/
|
||||
export function isAuth(key) {
|
||||
return JSON.parse(sessionStorage.getItem('permissions') || '[]').indexOf(key) !== -1 || false
|
||||
}
|
||||
|
||||
/**
|
||||
* 树形数据转换
|
||||
* @param {*} data
|
||||
* @param {*} id
|
||||
* @param {*} pid
|
||||
*/
|
||||
export function treeDataTranslate(data, id = 'id', pid = 'parentId') {
|
||||
var res = []
|
||||
var temp = {}
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
temp[data[i][id]] = data[i]
|
||||
}
|
||||
for (var k = 0; k < data.length; k++) {
|
||||
if (temp[data[k][pid]] && data[k][id] !== data[k][pid]) {
|
||||
if (!temp[data[k][pid]]['children']) {
|
||||
temp[data[k][pid]]['children'] = []
|
||||
}
|
||||
if (!temp[data[k][pid]]['_level']) {
|
||||
temp[data[k][pid]]['_level'] = 1
|
||||
}
|
||||
data[k]['_level'] = temp[data[k][pid]]._level + 1
|
||||
temp[data[k][pid]]['children'].push(data[k])
|
||||
} else {
|
||||
res.push(data[k])
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除登录信息
|
||||
*/
|
||||
export function clearLoginInfo() {
|
||||
Vue.cookie.delete('token')
|
||||
//store.commit('resetStore')
|
||||
router.options.isAddDynamicMenuRoutes = false
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* 邮箱
|
||||
* @param {*} s
|
||||
*/
|
||||
export function isEmail(s) {
|
||||
return /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})$/.test(s)
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机号码
|
||||
* @param {*} s
|
||||
*/
|
||||
export function isMobile(s) {
|
||||
return /^1[0-9]{10}$/.test(s)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否八位的数字
|
||||
*/
|
||||
export function is8Number(s) {
|
||||
return /^[0-9]{8}$/.test(s)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 电话号码
|
||||
* @param {*} s
|
||||
*/
|
||||
export function isPhone(s) {
|
||||
return /^([0-9]{3,4}-)?[0-9]{7,8}$/.test(s)
|
||||
}
|
||||
|
||||
/**
|
||||
* URL地址
|
||||
* @param {*} s
|
||||
*/
|
||||
export function isURL(s) {
|
||||
return /^http[s]?:\/\/.*/.test(s)
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
<template>
|
||||
<div class="site-wrapper site-page--not-found">
|
||||
<div class="site-content__wrapper">
|
||||
<div class="site-content">
|
||||
<h2 class="not-found-title">400</h2>
|
||||
<p class="not-found-desc">抱歉!您访问的页面<em>失联</em>啦 ...</p>
|
||||
<el-button @click="$router.go(-1)">返回上一页</el-button>
|
||||
<el-button type="primary" class="not-found-btn-gohome" @click="$router.push({ name: 'home' })">进入首页</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.site-wrapper.site-page--not-found {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
.site-content__wrapper {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background-color: #fff;
|
||||
}
|
||||
.site-content {
|
||||
position: fixed;
|
||||
top: 15%;
|
||||
left: 50%;
|
||||
z-index: 2;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
.not-found-title {
|
||||
margin: 20px 0 15px;
|
||||
font-size: 10em;
|
||||
font-weight: 400;
|
||||
color: rgb(55, 71, 79);
|
||||
}
|
||||
.not-found-desc {
|
||||
margin: 0 0 30px;
|
||||
font-size: 26px;
|
||||
text-transform: uppercase;
|
||||
color: rgb(118, 131, 143);
|
||||
> em {
|
||||
font-style: normal;
|
||||
color: #ee8145;
|
||||
}
|
||||
}
|
||||
.not-found-btn-gohome {
|
||||
margin-left: 30px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,12 @@
|
|||
<template>
|
||||
<div class="mod-home">
|
||||
<h3>欢迎使用微信管理系统</h3>
|
||||
</div>
|
||||
</template>
|
||||
<style>
|
||||
.mod-home {
|
||||
line-height: 2.5;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,184 @@
|
|||
<template>
|
||||
<div class="site-wrapper site-page--login">
|
||||
<div class="site-content__wrapper">
|
||||
<div class="site-content">
|
||||
<div class="brand-info">
|
||||
<h2 class="brand-info__text">微信后台管理系统</h2>
|
||||
<p class="brand-info__intro">微信公众号后台管理系统。</p>
|
||||
</div>
|
||||
<div class="login-main">
|
||||
<h3 class="login-title">管理员登录</h3>
|
||||
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" status-icon>
|
||||
<el-form-item prop="userName">
|
||||
<el-input v-model="dataForm.userName" placeholder="帐号"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input v-model="dataForm.password" type="password" placeholder="密码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="captcha">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="14">
|
||||
<el-input v-model="dataForm.captcha" placeholder="验证码">
|
||||
</el-input>
|
||||
</el-col>
|
||||
<el-col :span="10" class="login-captcha">
|
||||
<img :src="captchaPath" @click="getCaptcha()" alt="">
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button class="login-btn-submit" type="primary" @click="dataFormSubmit()">登录</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getUUID } from '@/utils'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dataForm: {
|
||||
userName: '',
|
||||
password: '',
|
||||
uuid: '',
|
||||
captcha: ''
|
||||
},
|
||||
dataRule: {
|
||||
userName: [
|
||||
{ required: true, message: '帐号不能为空', trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '密码不能为空', trigger: 'blur' }
|
||||
],
|
||||
captcha: [
|
||||
{ required: true, message: '验证码不能为空', trigger: 'blur' }
|
||||
]
|
||||
},
|
||||
captchaPath: ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getCaptcha()
|
||||
},
|
||||
methods: {
|
||||
// 提交表单
|
||||
dataFormSubmit() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/login'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData({
|
||||
'username': this.dataForm.userName,
|
||||
'password': this.dataForm.password,
|
||||
'uuid': this.dataForm.uuid,
|
||||
'captcha': this.dataForm.captcha
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$cookie.set('token', data.token)
|
||||
this.$router.replace({ name: 'home' })
|
||||
} else {
|
||||
this.getCaptcha()
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
// 获取验证码
|
||||
getCaptcha() {
|
||||
this.dataForm.uuid = getUUID()
|
||||
this.captchaPath = this.$http.adornUrl(`/captcha?uuid=${this.dataForm.uuid}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.site-wrapper.site-page--login {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background-color: rgba(38, 50, 56, 0.5);
|
||||
overflow: hidden;
|
||||
&:before {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
content: "";
|
||||
background-color: #fa8bff;
|
||||
background-image: linear-gradient(
|
||||
45deg,
|
||||
#fa8bff 0%,
|
||||
#2bd2ff 52%,
|
||||
#2bff88 90%
|
||||
);
|
||||
background-size: cover;
|
||||
}
|
||||
.site-content__wrapper {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
background-color: transparent;
|
||||
}
|
||||
.site-content {
|
||||
min-height: 100%;
|
||||
padding: 30px 500px 30px 30px;
|
||||
}
|
||||
.brand-info {
|
||||
margin: 220px 100px 0 90px;
|
||||
color: #fff;
|
||||
}
|
||||
.brand-info__text {
|
||||
margin: 0 0 22px 0;
|
||||
font-size: 48px;
|
||||
font-weight: 400;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.brand-info__intro {
|
||||
margin: 10px 0;
|
||||
font-size: 16px;
|
||||
line-height: 1.58;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.login-main {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 150px 60px 180px;
|
||||
width: 470px;
|
||||
min-height: 100%;
|
||||
background-color: #fff;
|
||||
}
|
||||
.login-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
.login-captcha {
|
||||
overflow: hidden;
|
||||
> img {
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.login-btn-submit {
|
||||
width: 100%;
|
||||
margin-top: 38px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,33 @@
|
|||
<template>
|
||||
<el-form>
|
||||
<h2>布局设置</h2>
|
||||
<el-form-item label="导航条类型">
|
||||
<el-radio-group v-model="navbarLayoutType">
|
||||
<el-radio label="default" border>default</el-radio>
|
||||
<el-radio label="inverse" border>inverse</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="侧边栏皮肤">
|
||||
<el-radio-group v-model="sidebarLayoutSkin">
|
||||
<el-radio label="light" border>light</el-radio>
|
||||
<el-radio label="dark" border>dark</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
computed: {
|
||||
navbarLayoutType: {
|
||||
get() { return this.$store.state.common.navbarLayoutType },
|
||||
set(val) { this.$store.commit('common/updateNavbarLayoutType', val) }
|
||||
},
|
||||
sidebarLayoutSkin: {
|
||||
get() { return this.$store.state.common.sidebarLayoutSkin },
|
||||
set(val) { this.$store.commit('common/updateSidebarLayoutSkin', val) }
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
<template>
|
||||
<main class="site-content" :class="{ 'site-content--tabs': $route.meta.isTab }">
|
||||
<!-- 主入口标签页 s -->
|
||||
<el-tabs v-if="$route.meta.isTab" v-model="mainTabsActiveName" :closable="true" @tab-click="selectedTabHandle" @tab-remove="removeTabHandle">
|
||||
<el-dropdown class="site-tabs__tools" :show-timeout="0">
|
||||
<i class="el-icon-arrow-down el-icon--right"></i>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item @click.native="tabsCloseCurrentHandle">关闭当前标签页</el-dropdown-item>
|
||||
<el-dropdown-item @click.native="tabsCloseOtherHandle">关闭其它标签页</el-dropdown-item>
|
||||
<el-dropdown-item @click.native="tabsCloseAllHandle">关闭全部标签页</el-dropdown-item>
|
||||
<el-dropdown-item @click.native="refresh()">刷新当前标签页</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
<el-tab-pane v-for="item in mainTabs" :key="item.name" :label="item.title" :name="item.name">
|
||||
<el-card :body-style="siteContentViewHeight">
|
||||
<iframe v-if="item.type === 'iframe'" :src="item.iframeUrl" width="100%" height="100%" frameborder="0" scrolling="yes">
|
||||
</iframe>
|
||||
<keep-alive v-else>
|
||||
<router-view v-if="item.name === mainTabsActiveName" />
|
||||
</keep-alive>
|
||||
</el-card>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<!-- 主入口标签页 e -->
|
||||
<el-card v-else :body-style="siteContentViewHeight">
|
||||
<keep-alive>
|
||||
<router-view />
|
||||
</keep-alive>
|
||||
</el-card>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isURL } from '@/utils/validate'
|
||||
export default {
|
||||
inject: ['refresh'],
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
documentClientHeight: {
|
||||
get() { return this.$store.state.common.documentClientHeight }
|
||||
},
|
||||
menuActiveName: {
|
||||
get() { return this.$store.state.common.menuActiveName },
|
||||
set(val) { this.$store.commit('common/updateMenuActiveName', val) }
|
||||
},
|
||||
mainTabs: {
|
||||
get() { return this.$store.state.common.mainTabs },
|
||||
set(val) { this.$store.commit('common/updateMainTabs', val) }
|
||||
},
|
||||
mainTabsActiveName: {
|
||||
get() { return this.$store.state.common.mainTabsActiveName },
|
||||
set(val) { this.$store.commit('common/updateMainTabsActiveName', val) }
|
||||
},
|
||||
siteContentViewHeight() {
|
||||
var height = this.documentClientHeight - 50 - 30 - 2
|
||||
if (this.$route.meta.isTab) {
|
||||
height -= 40
|
||||
return isURL(this.$route.meta.iframeUrl) ? { height: height + 'px' } : { minHeight: height + 'px' }
|
||||
}
|
||||
return { minHeight: height + 'px' }
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// tabs, 选中tab
|
||||
selectedTabHandle(tab) {
|
||||
tab = this.mainTabs.filter(item => item.name === tab.name)
|
||||
if (tab.length >= 1) {
|
||||
this.$router.push({ name: tab[0].name, query: tab[0].query, params: tab[0].params })
|
||||
}
|
||||
},
|
||||
// tabs, 删除tab
|
||||
removeTabHandle(tabName) {
|
||||
this.$store.commit('common/removeTab', tabName)
|
||||
},
|
||||
// tabs, 关闭当前
|
||||
tabsCloseCurrentHandle() {
|
||||
this.removeTabHandle(this.mainTabsActiveName)
|
||||
},
|
||||
// tabs, 关闭其它
|
||||
tabsCloseOtherHandle() {
|
||||
this.mainTabs = this.mainTabs.filter(item => item.name === this.mainTabsActiveName)
|
||||
},
|
||||
// tabs, 关闭全部
|
||||
tabsCloseAllHandle() {
|
||||
this.mainTabs = []
|
||||
this.menuActiveName = ''
|
||||
this.$router.push({ name: 'home' })
|
||||
},
|
||||
// tabs, 刷新当前
|
||||
tabsRefreshCurrentHandle() {
|
||||
var tab = this.$route
|
||||
this.removeTabHandle(tab.name)
|
||||
this.$nextTick(() => {
|
||||
this.$router.push({ name: tab.name, query: tab.query, params: tab.params })
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
<template>
|
||||
<el-dialog title="修改密码" :visible.sync="visible" :append-to-body="true">
|
||||
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="80px">
|
||||
<el-form-item label="账号">
|
||||
<span>{{ userName }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="原密码" prop="password">
|
||||
<el-input type="password" v-model="dataForm.password"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码" prop="newPassword">
|
||||
<el-input type="password" v-model="dataForm.newPassword"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="confirmPassword">
|
||||
<el-input type="password" v-model="dataForm.confirmPassword"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { clearLoginInfo } from '@/utils'
|
||||
export default {
|
||||
data() {
|
||||
var validateConfirmPassword = (rule, value, callback) => {
|
||||
if (this.dataForm.newPassword !== value) {
|
||||
callback(new Error('确认密码与新密码不一致'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
return {
|
||||
visible: false,
|
||||
dataForm: {
|
||||
password: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
},
|
||||
dataRule: {
|
||||
password: [
|
||||
{ required: true, message: '原密码不能为空', trigger: 'blur' }
|
||||
],
|
||||
newPassword: [
|
||||
{ required: true, message: '新密码不能为空', trigger: 'blur' }
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: '确认密码不能为空', trigger: 'blur' },
|
||||
{ validator: validateConfirmPassword, trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
userName: {
|
||||
get() { return this.$store.state.user.name }
|
||||
},
|
||||
mainTabs: {
|
||||
get() { return this.$store.state.common.mainTabs },
|
||||
set(val) { this.$store.commit('common/updateMainTabs', val) }
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 初始化
|
||||
init() {
|
||||
this.visible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].resetFields()
|
||||
})
|
||||
},
|
||||
// 表单提交
|
||||
dataFormSubmit() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/user/password'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData({
|
||||
'password': this.dataForm.password,
|
||||
'newPassword': this.dataForm.newPassword
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
this.visible = false
|
||||
this.$nextTick(() => {
|
||||
this.mainTabs = []
|
||||
clearLoginInfo()
|
||||
this.$router.replace({ name: 'login' })
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
<template>
|
||||
<nav class="site-navbar" :class="'site-navbar--' + navbarLayoutType">
|
||||
<div class="site-navbar__header">
|
||||
<h1 class="site-navbar__brand" @click="$router.push({ name: 'home' })">
|
||||
<a class="site-navbar__brand-lg" href="javascript:;">微信管理系统</a>
|
||||
<a class="site-navbar__brand-mini" href="javascript:;">W</a>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="site-navbar__body clearfix">
|
||||
<el-menu class="site-navbar__menu" mode="horizontal">
|
||||
<el-menu-item class="site-navbar__switch" index="0" @click="sidebarFold = !sidebarFold">
|
||||
<i :class="sidebarFold?'el-icon-s-unfold':'el-icon-s-fold'"></i>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
<el-menu class="site-navbar__menu site-navbar__menu--right" mode="horizontal">
|
||||
<el-menu-item index="1" @click="$router.push({ name: 'theme' })">
|
||||
<template slot="title">
|
||||
<i class="el-icon-setting"></i>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="2" v-if="isAuth('wx:wxaccount:list')">
|
||||
<template slot="title">
|
||||
<wx-account-selector></wx-account-selector>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item class="site-navbar__avatar" index="3">
|
||||
<el-dropdown :show-timeout="0" placement="bottom">
|
||||
<span class="el-dropdown-link">
|
||||
{{ userName }}
|
||||
</span>
|
||||
<el-dropdown-menu slot="dropdown">
|
||||
<el-dropdown-item @click.native="updatePasswordHandle()">修改密码</el-dropdown-item>
|
||||
<el-dropdown-item @click.native="logoutHandle()">退出</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</el-dropdown>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</div>
|
||||
<!-- 弹窗, 修改密码 -->
|
||||
<update-password v-if="updatePassowrdVisible" ref="updatePassowrd"></update-password>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UpdatePassword from './main-navbar-update-password'
|
||||
import WxAccountSelector from '@/components/wx-account-selector'
|
||||
import { clearLoginInfo } from '@/utils'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
updatePassowrdVisible: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
UpdatePassword,WxAccountSelector
|
||||
},
|
||||
computed: {
|
||||
navbarLayoutType: {
|
||||
get() { return this.$store.state.common.navbarLayoutType }
|
||||
},
|
||||
sidebarFold: {
|
||||
get() { return this.$store.state.common.sidebarFold },
|
||||
set(val) { this.$store.commit('common/updateSidebarFold', val) }
|
||||
},
|
||||
mainTabs: {
|
||||
get() { return this.$store.state.common.mainTabs },
|
||||
set(val) { this.$store.commit('common/updateMainTabs', val) }
|
||||
},
|
||||
userName: {
|
||||
get() { return this.$store.state.user.name }
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 修改密码
|
||||
updatePasswordHandle() {
|
||||
this.updatePassowrdVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.updatePassowrd.init()
|
||||
})
|
||||
},
|
||||
// 退出
|
||||
logoutHandle() {
|
||||
this.$confirm(`确定进行[退出]操作?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/logout'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData()
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
clearLoginInfo()
|
||||
this.$router.push({ name: 'login' })
|
||||
}
|
||||
})
|
||||
}).catch(() => { })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,50 @@
|
|||
<template>
|
||||
<el-submenu v-if="menu.list && menu.list.length >= 1" :index="menu.menuId + ''" :popper-class="'site-sidebar--' + sidebarLayoutSkin + '-popper'">
|
||||
<template slot="title">
|
||||
<i class="site-sidebar__menu-icon" :class="menu.icon"></i>
|
||||
<!-- <icon-svg :name="menu.icon || ''" class="site-sidebar__menu-icon"></icon-svg> -->
|
||||
<span>{{ menu.name }}</span>
|
||||
</template>
|
||||
<sub-menu v-for="item in menu.list" :key="item.menuId" :menu="item" :dynamicMenuRoutes="dynamicMenuRoutes">
|
||||
</sub-menu>
|
||||
</el-submenu>
|
||||
<el-menu-item v-else :index="menu.menuId + ''" @click="gotoRouteHandle(menu)">
|
||||
<!-- <icon-svg :name="menu.icon || ''" class="site-sidebar__menu-icon"></icon-svg> -->
|
||||
<i class="site-sidebar__menu-icon fa" :class="menu.icon"></i>
|
||||
<span>{{ menu.name }}</span>
|
||||
</el-menu-item>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SubMenu from './main-sidebar-sub-menu'
|
||||
export default {
|
||||
name: 'sub-menu',
|
||||
props: {
|
||||
menu: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
dynamicMenuRoutes: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
SubMenu
|
||||
},
|
||||
computed: {
|
||||
sidebarLayoutSkin: {
|
||||
get() { return this.$store.state.common.sidebarLayoutSkin }
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 通过menuId与动态(菜单)路由进行匹配跳转至指定路由
|
||||
gotoRouteHandle(menu) {
|
||||
var route = this.dynamicMenuRoutes.filter(item => item.meta.menuId === menu.menuId)
|
||||
if (route.length >= 1) {
|
||||
this.$router.push({ name: route[0].name })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,90 @@
|
|||
<template>
|
||||
<aside class="site-sidebar" :class="'site-sidebar--' + sidebarLayoutSkin">
|
||||
<div class="site-sidebar__inner">
|
||||
<el-menu :default-active="menuActiveName || 'home'" :collapse="sidebarFold" :collapseTransition="false" class="site-sidebar__menu">
|
||||
<el-menu-item index="home" @click="$router.push({ name: 'home' })">
|
||||
<i class="site-sidebar__menu-icon el-icon-s-home"></i>
|
||||
<span slot="title">首页</span>
|
||||
</el-menu-item>
|
||||
<sub-menu v-for="menu in menuList" :key="menu.menuId" :menu="menu" :dynamicMenuRoutes="dynamicMenuRoutes">
|
||||
</sub-menu>
|
||||
</el-menu>
|
||||
</div>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SubMenu from './main-sidebar-sub-menu'
|
||||
import { isURL } from '@/utils/validate'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dynamicMenuRoutes: []
|
||||
}
|
||||
},
|
||||
components: {
|
||||
SubMenu
|
||||
},
|
||||
computed: {
|
||||
sidebarLayoutSkin: {
|
||||
get() { return this.$store.state.common.sidebarLayoutSkin }
|
||||
},
|
||||
sidebarFold: {
|
||||
get() { return this.$store.state.common.sidebarFold }
|
||||
},
|
||||
menuList: {
|
||||
get() { return this.$store.state.common.menuList },
|
||||
set(val) { this.$store.commit('common/updateMenuList', val) }
|
||||
},
|
||||
menuActiveName: {
|
||||
get() { return this.$store.state.common.menuActiveName },
|
||||
set(val) { this.$store.commit('common/updateMenuActiveName', val) }
|
||||
},
|
||||
mainTabs: {
|
||||
get() { return this.$store.state.common.mainTabs },
|
||||
set(val) { this.$store.commit('common/updateMainTabs', val) }
|
||||
},
|
||||
mainTabsActiveName: {
|
||||
get() { return this.$store.state.common.mainTabsActiveName },
|
||||
set(val) { this.$store.commit('common/updateMainTabsActiveName', val) }
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
$route: 'routeHandle'
|
||||
},
|
||||
created() {
|
||||
this.menuList = JSON.parse(sessionStorage.getItem('menuList') || '[]')
|
||||
this.dynamicMenuRoutes = JSON.parse(sessionStorage.getItem('dynamicMenuRoutes') || '[]')
|
||||
this.routeHandle(this.$route)
|
||||
},
|
||||
methods: {
|
||||
// 路由操作
|
||||
routeHandle(route) {
|
||||
if (route.meta.isTab) {
|
||||
// tab选中, 不存在先添加
|
||||
var tab = this.mainTabs.filter(item => item.name === route.name)[0]
|
||||
if (!tab) {
|
||||
if (route.meta.isDynamic) {
|
||||
route = this.dynamicMenuRoutes.filter(item => item.name === route.name)[0]
|
||||
if (!route) {
|
||||
return console.error('未能找到可用标签页!')
|
||||
}
|
||||
}
|
||||
tab = {
|
||||
menuId: route.meta.menuId || route.name,
|
||||
name: route.name,
|
||||
title: route.meta.title,
|
||||
type: isURL(route.meta.iframeUrl) ? 'iframe' : 'module',
|
||||
iframeUrl: route.meta.iframeUrl || '',
|
||||
params: route.params,
|
||||
query: route.query
|
||||
}
|
||||
this.mainTabs = this.mainTabs.concat(tab)
|
||||
}
|
||||
this.menuActiveName = tab.menuId + ''
|
||||
this.mainTabsActiveName = tab.name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,86 @@
|
|||
<template>
|
||||
<div class="site-wrapper" :class="{ 'site-sidebar--fold': sidebarFold }" v-loading.fullscreen.lock="loading" element-loading-text="拼命加载中">
|
||||
<template v-if="!loading">
|
||||
<main-navbar />
|
||||
<main-sidebar />
|
||||
<div class="site-content__wrapper" :style="{ 'min-height': documentClientHeight + 'px' }">
|
||||
<main-content v-if="!$store.state.common.contentIsNeedRefresh" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MainNavbar from './main-navbar'
|
||||
import MainSidebar from './main-sidebar'
|
||||
import MainContent from './main-content'
|
||||
export default {
|
||||
provide() {
|
||||
return {
|
||||
// 刷新
|
||||
refresh() {
|
||||
this.$store.commit('common/updateContentIsNeedRefresh', true)
|
||||
this.$nextTick(() => {
|
||||
this.$store.commit('common/updateContentIsNeedRefresh', false)
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
MainNavbar,
|
||||
MainSidebar,
|
||||
MainContent
|
||||
},
|
||||
computed: {
|
||||
documentClientHeight: {
|
||||
get() { return this.$store.state.common.documentClientHeight },
|
||||
set(val) { this.$store.commit('common/updateDocumentClientHeight', val) }
|
||||
},
|
||||
sidebarFold: {
|
||||
get() { return this.$store.state.common.sidebarFold }
|
||||
},
|
||||
userId: {
|
||||
get() { return this.$store.state.user.id },
|
||||
set(val) { this.$store.commit('user/updateId', val) }
|
||||
},
|
||||
userName: {
|
||||
get() { return this.$store.state.user.name },
|
||||
set(val) { this.$store.commit('user/updateName', val) }
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getUserInfo()
|
||||
},
|
||||
mounted() {
|
||||
this.resetDocumentClientHeight()
|
||||
},
|
||||
methods: {
|
||||
// 重置窗口可视高度
|
||||
resetDocumentClientHeight() {
|
||||
this.documentClientHeight = document.documentElement['clientHeight']
|
||||
window.onresize = () => {
|
||||
this.documentClientHeight = document.documentElement['clientHeight']
|
||||
}
|
||||
},
|
||||
// 获取当前管理员信息
|
||||
getUserInfo() {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/user/info'),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams()
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.loading = false
|
||||
this.userId = data.user.userId
|
||||
this.userName = data.user.username
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,19 @@
|
|||
<script>
|
||||
export default {
|
||||
name: "admin"
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>管理员管理</h1>
|
||||
<h1>管理员管理</h1>
|
||||
<h1>管理员管理</h1>
|
||||
<h1>管理员管理</h1>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,15 @@
|
|||
<script>
|
||||
export default {
|
||||
name: "hostel"
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div><h1>
|
||||
宿舍管理
|
||||
</h1></div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,15 @@
|
|||
<script>
|
||||
export default {
|
||||
name: "localTeacher"
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>本校老师管理</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,13 @@
|
|||
<script>
|
||||
export default {
|
||||
name: "student"
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>学生管理</h1>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,152 @@
|
|||
<template>
|
||||
<el-dialog :title="!dataForm.id ? '新增' : '修改'" :close-on-click-modal="false" :visible.sync="visible">
|
||||
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()"
|
||||
label-width="80px">
|
||||
<el-form-item label="身份码" prop="userId">
|
||||
<el-input v-model="dataForm.userId" placeholder="登录帐号"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户名" prop="userName">
|
||||
<el-input v-model="dataForm.userName" placeholder="用户名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="身份">
|
||||
<el-select v-model="dataForm.identity" placeholder="身份">
|
||||
<el-option v-for="(name,key) in identityes" :key="key+1" :value="key+1" :label="name"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属组">
|
||||
<el-select v-model="dataForm.groupId" placeholder="所属组">
|
||||
<el-option v-for="(name,key) in groupIdes" :key="key+1" :value="key+1" :label="name"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" size="mini" prop="status">
|
||||
<el-radio-group v-model="dataForm.status">
|
||||
<el-radio :label="1">禁用</el-radio>
|
||||
<el-radio :label="0">正常</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {is8Number} from '@/utils/validate'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
var validateuserId = (rule, value, callback) => {
|
||||
if (!is8Number(value)) {
|
||||
callback(new Error('请输入8位身份码(数字)'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
var validatePassword = (rule, value, callback) => {
|
||||
if (!this.dataForm.id && !/\S/.test(value)) {
|
||||
callback(new Error('密码不能为空'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
visible: false,
|
||||
roleList: [],
|
||||
dataForm: {
|
||||
id: 0,
|
||||
userName: '',
|
||||
groupId: '',
|
||||
identity: '',
|
||||
userId: '',
|
||||
status: 0
|
||||
},
|
||||
identityes: ['管理员', '研究生', '本校老师', '外校老师'],
|
||||
groupIdes: ['第一组', '第二组', '第三组', '第四三组'],
|
||||
dataRule: {
|
||||
userName: [
|
||||
{required: true, message: '用户名不能为空', trigger: 'blur'}
|
||||
],
|
||||
password: [
|
||||
{validator: validatePassword, trigger: 'blur'}
|
||||
],
|
||||
userId: [
|
||||
{required: true, message: '身份码不能为空', trigger: 'blur'},
|
||||
{validator: validateuserId, trigger: 'blur'}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init(id) {
|
||||
this.dataForm.id = id || 0
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/role/select'),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams()
|
||||
}).then(({data}) => {
|
||||
this.roleList = data && data.code === 200 ? data.list : []
|
||||
}).then(() => {
|
||||
this.visible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].resetFields()
|
||||
})
|
||||
}).then(() => {
|
||||
if (this.dataForm.id) {
|
||||
console.log('this.dataForm.id', this.dataForm.id)
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/h5/user/info/${this.dataForm.id}`),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams()
|
||||
}).then(({data}) => {
|
||||
console.log(data)
|
||||
if (data && data.code === 200) {
|
||||
this.dataForm.userName = data.user.userName
|
||||
this.dataForm.groupId = data.user.groupId
|
||||
this.dataForm.identity = data.user.identity
|
||||
this.dataForm.userId = data.user.userId
|
||||
this.dataForm.status = data.user.status
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
// 表单提交
|
||||
dataFormSubmit() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/h5/user/${!this.dataForm.id ? 'save' : 'update'}`),
|
||||
method: 'post',
|
||||
data: this.$http.adornData({
|
||||
'id':this.dataForm.id,
|
||||
'userName': this.dataForm.userName,
|
||||
'groupId': this.dataForm.groupId,
|
||||
'identity': this.dataForm.identity,
|
||||
'userId': this.dataForm.userId,
|
||||
'status': this.dataForm.status,
|
||||
})
|
||||
}).then(({data}) => {
|
||||
console.log(data)
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
this.visible = false
|
||||
this.$emit('refreshDataList')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,211 @@
|
|||
<template>
|
||||
<div class="mod-user">
|
||||
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
|
||||
<el-form-item>
|
||||
<el-input v-model="dataForm.userName" placeholder="用户身份码" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList()">查询</el-button>
|
||||
<el-button v-if="isAuth('h5:user:save')" type="primary" @click="addOrUpdateHandle()">新增</el-button>
|
||||
<el-button v-if="isAuth('h5:user:delete')" type="danger" @click="deleteHandle()"
|
||||
:disabled="dataListSelections.length <= 0">批量删除
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="dataList" border v-loading="dataListLoading" @selection-change="selectionChangeHandle"
|
||||
style="width: 100%;">
|
||||
<el-table-column type="selection" header-align="center" align="center" width="50">
|
||||
</el-table-column>
|
||||
<el-table-column prop="userId" header-align="center" align="center" width="80" label="ID">
|
||||
<template slot-scope="scope">
|
||||
{{ (pageIndex - 1) * pageSize + scope.$index + 1 }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="userId" header-align="center" align="center" width="100" label="用户身份码">
|
||||
</el-table-column>
|
||||
<el-table-column prop="userName" header-align="center" align="center" label="用户名">
|
||||
</el-table-column>
|
||||
<!-- <el-table-column prop="mobile" header-align="center" align="center" label="手机号">-->
|
||||
<!-- </el-table-column>-->
|
||||
<el-table-column prop="identity" header-align="center" align="center" label="身份">
|
||||
<template slot-scope="scope">
|
||||
{{ isIdentity(scope.row.identity) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column
|
||||
header-align="center"
|
||||
align="center"
|
||||
prop="groupId"
|
||||
label="所属分组"
|
||||
:filters="groupIdes"
|
||||
:filter-method="filterTag"
|
||||
filter-placement="bottom-end">
|
||||
<template slot-scope="scope">
|
||||
{{ getgroups(scope.row.groupId) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" header-align="center" align="center" label="状态">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.status === 1" size="small" type="danger">禁用</el-tag>
|
||||
<el-tag v-else size="small">正常</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" header-align="center" align="center" width="150" label="操作">
|
||||
<template slot-scope="scope">
|
||||
<el-button v-if="isAuth('h5:user:update')" type="text" size="small"
|
||||
@click="addOrUpdateHandle(scope.row.id)">修改
|
||||
</el-button>
|
||||
<el-button v-if="isAuth('h5:user:delete')" type="text" size="small"
|
||||
@click="deleteHandle(scope.row.id)">删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" :current-page="pageIndex"
|
||||
:page-sizes="[5,10, 20, 50, 100]" :page-size="pageSize" :total="totalCount"
|
||||
layout="total, sizes, prev, pager, next, jumper">
|
||||
</el-pagination>
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AddOrUpdate from './user-add-or-update'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dataForm: {
|
||||
userName: ''
|
||||
},
|
||||
temp:0,
|
||||
dataList: [],
|
||||
pageIndex: 1,
|
||||
pageSize: 5,
|
||||
totalCount: 0,
|
||||
dataListLoading: false,
|
||||
dataListSelections: [],
|
||||
addOrUpdateVisible: false,
|
||||
groupIdes: [{
|
||||
text: '第一组',
|
||||
id: 1,
|
||||
value: 1
|
||||
}, {
|
||||
text: '第二组',
|
||||
id: 2,
|
||||
value: 2
|
||||
}, {
|
||||
text: '第三组',
|
||||
id: 3,
|
||||
value: 3,
|
||||
}, {
|
||||
text: '第四三组',
|
||||
id: 4,
|
||||
value: 4,
|
||||
}],
|
||||
}
|
||||
},
|
||||
components: {
|
||||
AddOrUpdate
|
||||
},
|
||||
activated() {
|
||||
this.getDataList()
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
// 分组过滤
|
||||
filterTag(value, row) {
|
||||
return row.groupId === value;
|
||||
},
|
||||
getgroups(id) {
|
||||
let groupIdes = this.groupIdes
|
||||
return groupIdes[id - 1].text
|
||||
},
|
||||
isIdentity(identity) {
|
||||
switch (identity) {
|
||||
case 1:
|
||||
return '管理员'
|
||||
case 2:
|
||||
return '研究生'
|
||||
case 3:
|
||||
return '本校老师'
|
||||
case 4:
|
||||
return '外校老师'
|
||||
}
|
||||
},
|
||||
// 获取数据列表
|
||||
getDataList() {
|
||||
this.dataListLoading = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/h5/user/list'),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams({
|
||||
'page': this.pageIndex,
|
||||
'limit': this.pageSize,
|
||||
'userId': this.dataForm.userName
|
||||
})
|
||||
}).then(({data}) => {
|
||||
if (data && data.code === 200) {
|
||||
this.dataList = data.page.list
|
||||
this.totalCount = data.page.totalCount
|
||||
} else {
|
||||
this.dataList = []
|
||||
this.totalCount = 0
|
||||
}
|
||||
this.dataListLoading = false
|
||||
})
|
||||
},
|
||||
// 每页数
|
||||
sizeChangeHandle(val) {
|
||||
this.pageSize = val
|
||||
this.pageIndex = 1
|
||||
this.getDataList()
|
||||
},
|
||||
// 当前页
|
||||
currentChangeHandle(val) {
|
||||
this.pageIndex = val
|
||||
this.getDataList()
|
||||
},
|
||||
// 多选
|
||||
selectionChangeHandle(val) {
|
||||
this.dataListSelections = val
|
||||
},
|
||||
// 新增 / 修改
|
||||
addOrUpdateHandle(id) {
|
||||
this.addOrUpdateVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.addOrUpdate.init(id)
|
||||
})
|
||||
},
|
||||
// 删除
|
||||
deleteHandle(userid) {
|
||||
var userIds = userid ? [userid] : this.dataListSelections.map(item => item.id)
|
||||
this.$confirm(`确定对[userid=${userIds.join(',')}]进行[${userid ? '删除' : '批量删除'}]操作?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/h5/user/delete'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData(userIds, false)
|
||||
}).then(({data}) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => this.getDataList()
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,15 @@
|
|||
<script>
|
||||
export default {
|
||||
name: "waiTeacher"
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h1>外校老师管理</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,127 @@
|
|||
<template>
|
||||
<el-dialog title="云存储配置" :close-on-click-modal="false" :visible.sync="visible">
|
||||
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="120px">
|
||||
<el-form-item size="mini" label="存储类型">
|
||||
<el-radio-group v-model="dataForm.type">
|
||||
<el-radio :label="1">七牛</el-radio>
|
||||
<el-radio :label="2">阿里云</el-radio>
|
||||
<el-radio :label="3">腾讯云</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<template v-if="dataForm.type === 1">
|
||||
<el-form-item label="域名">
|
||||
<el-input v-model="dataForm.qiniuDomain" placeholder="七牛绑定的域名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="路径前缀">
|
||||
<el-input v-model="dataForm.qiniuPrefix" placeholder="不设置默认为空"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="AccessKey">
|
||||
<el-input v-model="dataForm.qiniuAccessKey" placeholder="七牛AccessKey"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="SecretKey">
|
||||
<el-input v-model="dataForm.qiniuSecretKey" placeholder="七牛SecretKey"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="空间名">
|
||||
<el-input v-model="dataForm.qiniuBucketName" placeholder="七牛存储空间名"></el-input>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-else-if="dataForm.type === 2">
|
||||
<el-form-item label="域名">
|
||||
<el-input v-model="dataForm.aliyunDomain" placeholder="阿里云绑定的域名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="路径前缀">
|
||||
<el-input v-model="dataForm.aliyunPrefix" placeholder="不设置默认为空"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="EndPoint">
|
||||
<el-input v-model="dataForm.aliyunEndPoint" placeholder="阿里云EndPoint"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="AccessKeyId">
|
||||
<el-input v-model="dataForm.aliyunAccessKeyId" placeholder="阿里云AccessKeyId"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="AccessKeySecret">
|
||||
<el-input v-model="dataForm.aliyunAccessKeySecret" placeholder="阿里云AccessKeySecret"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="BucketName">
|
||||
<el-input v-model="dataForm.aliyunBucketName" placeholder="阿里云BucketName"></el-input>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<template v-else-if="dataForm.type === 3">
|
||||
<el-form-item label="域名">
|
||||
<el-input v-model="dataForm.qcloudDomain" placeholder="腾讯云绑定的域名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="路径前缀">
|
||||
<el-input v-model="dataForm.qcloudPrefix" placeholder="不设置默认为空"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="AppId">
|
||||
<el-input v-model="dataForm.qcloudAppId" placeholder="腾讯云AppId"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="SecretId">
|
||||
<el-input v-model="dataForm.qcloudSecretId" placeholder="腾讯云SecretId"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="SecretKey">
|
||||
<el-input v-model="dataForm.qcloudSecretKey" placeholder="腾讯云SecretKey"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="BucketName">
|
||||
<el-input v-model="dataForm.qcloudBucketName" placeholder="腾讯云BucketName"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Bucket所属地区">
|
||||
<el-input v-model="dataForm.qcloudRegion" placeholder="如:sh(可选值 ,华南:gz 华北:tj 华东:sh)"></el-input>
|
||||
</el-form-item>
|
||||
</template>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
dataForm: {},
|
||||
dataRule: {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init(id) {
|
||||
this.visible = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/oss/config'),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams()
|
||||
}).then(({ data }) => {
|
||||
this.dataForm = data && data.code === 200 ? data.config : []
|
||||
})
|
||||
},
|
||||
// 表单提交
|
||||
dataFormSubmit() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/oss/saveConfig'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData(this.dataForm)
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
this.visible = false
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
<template>
|
||||
<div @click="selectFile">
|
||||
<input type="file" ref="fileInput" v-show="false" @change="onFileChange" />
|
||||
<div>{{uploading?infoText:'上传文件'}}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 腾讯云对象存储组件,文件直接上传到腾讯云,可在后端服务无对应外网权限时使用
|
||||
// 使用本组件需引入腾讯云对象存储依赖 <script src="https://unpkg.com/cos-js-sdk-v5@0.5.23/dist/cos-js-sdk-v5.min.js" async></script>
|
||||
var cos;
|
||||
export default {
|
||||
name: "oss-uploader",
|
||||
data() {
|
||||
return {
|
||||
uploading: false,
|
||||
infoText:"上传中...",
|
||||
cosConfig:[]
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/oss/config'),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams()
|
||||
}).then(({data}) => {
|
||||
if(data && data.code === 200){
|
||||
this.cosConfig = data.config
|
||||
cos=new COS({
|
||||
SecretId: data.config.qcloudSecretId,
|
||||
SecretKey: data.config.qcloudSecretKey,
|
||||
});
|
||||
}else{
|
||||
this.$message.error('请先配置云存储相关信息!')
|
||||
}
|
||||
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
selectFile() {//选择文件
|
||||
if (!this.uploading) {
|
||||
this.$refs.fileInput.click();
|
||||
}
|
||||
},
|
||||
onFileChange() {
|
||||
let file = this.$refs.fileInput.files[0];
|
||||
this.uploading = true;
|
||||
let now = new Date();
|
||||
let path=now.toISOString().slice(0,10)+'/'+now.getTime()+file.name.substr(file.name.lastIndexOf('.'))
|
||||
cos.putObject({
|
||||
Bucket: this.cosConfig.qcloudBucketName, /* 必须 */
|
||||
Region: this.cosConfig.qcloudRegion, /* 必须 */
|
||||
Key: path, /* 必须 */
|
||||
Body: file, // 上传文件对象
|
||||
onProgress: (progressData)=> {
|
||||
this.infoText='上传中:'+progressData.percent*100+'%'
|
||||
}
|
||||
}, (err, data)=> {
|
||||
console.log(err || data);
|
||||
this.uploading = false;
|
||||
if(data){
|
||||
this.infoText='上传文件'
|
||||
let fileUrl='https://'+this.cosConfig.qcloudBucketName+'.cos.'+this.cosConfig.qcloudRegion+'.myqcloud.com/'+path;
|
||||
this.saveUploadResult(fileUrl)
|
||||
}else {
|
||||
this.$message.error('文件上传失败',err)
|
||||
}
|
||||
|
||||
});
|
||||
},
|
||||
saveUploadResult(url){
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/oss/upload'),
|
||||
method: 'post',
|
||||
data:{
|
||||
url:url
|
||||
}
|
||||
}).then(({data})=>{
|
||||
this.$emit('uploaded', url)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
|
@ -0,0 +1,59 @@
|
|||
<template>
|
||||
<div @click="selectFile">
|
||||
<input type="file" ref="fileInput" v-show="false" @change="onFileChange" />
|
||||
<div>{{uploading?infoText:'上传文件'}}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "oss-uploader",
|
||||
data() {
|
||||
return {
|
||||
uploading: false,
|
||||
infoText: "上传中...",
|
||||
cosConfig: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/oss/config'),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams()
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200 && data.config.type) {
|
||||
this.cosConfig = data.config
|
||||
} else {
|
||||
this.$message.error('请先配置云存储相关信息!')
|
||||
}
|
||||
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
selectFile() {//选择文件
|
||||
if (!this.uploading) {
|
||||
this.$refs.fileInput.click();
|
||||
}
|
||||
},
|
||||
onFileChange() {
|
||||
let file = this.$refs.fileInput.files[0];
|
||||
this.uploading = true;
|
||||
let formData = new FormData();
|
||||
formData.append("file", file)
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/oss/upload'),
|
||||
method: 'post',
|
||||
data: formData
|
||||
}).then(({ data }) => {
|
||||
console.log(data)
|
||||
if (data && data.code === 200) {
|
||||
this.$emit('uploaded', data.url)
|
||||
} else {
|
||||
this.$message.error("文件上传失败:" + data.msg)
|
||||
}
|
||||
this.uploading = false;
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,146 @@
|
|||
<template>
|
||||
<div class="mod-oss">
|
||||
<el-form :inline="true" :model="dataForm">
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="configHandle()">云存储配置</el-button>
|
||||
<el-button type="primary">
|
||||
<OssUploader @uploaded="getDataList"></OssUploader>
|
||||
</el-button>
|
||||
<el-button type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0">批量删除</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="dataList" border v-loading="dataListLoading" @selection-change="selectionChangeHandle" style="width: 100%;">
|
||||
<el-table-column type="selection" header-align="center" align="center" width="50">
|
||||
</el-table-column>
|
||||
<el-table-column prop="id" header-align="center" align="center" width="80" label="ID">
|
||||
</el-table-column>
|
||||
<el-table-column prop="url" header-align="center" align="center" label="URL地址">
|
||||
<div slot-scope="scope">
|
||||
<img class="image-sm" v-if="isImageUrl(scope.row.url)" :src="scope.row.url" />
|
||||
<a :href="scope.row.url" target="_blank" v-else>{{scope.row.url}}</a>
|
||||
</div>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createDate" header-align="center" align="center" width="180" label="创建时间">
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" header-align="center" align="center" width="150" label="操作">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="small" @click="deleteHandle(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" :current-page="pageIndex" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :total="totalCount" layout="total, sizes, prev, pager, next, jumper">
|
||||
</el-pagination>
|
||||
<!-- 弹窗, 云存储配置 -->
|
||||
<config v-show="configVisible" ref="config"></config>
|
||||
<!-- 弹窗, 上传文件 -->
|
||||
<upload v-show="uploadVisible" ref="upload" @refreshDataList="getDataList"></upload>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dataForm: {},
|
||||
dataList: [],
|
||||
pageIndex: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
dataListLoading: false,
|
||||
dataListSelections: [],
|
||||
configVisible: false,
|
||||
uploadVisible: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Config: () => import('./oss-config'),
|
||||
OssUploader: () => import('./oss-uploader')
|
||||
},
|
||||
activated() {
|
||||
this.getDataList()
|
||||
},
|
||||
methods: {
|
||||
// 获取数据列表
|
||||
getDataList() {
|
||||
this.dataListLoading = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/oss/list'),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams({
|
||||
'page': this.pageIndex,
|
||||
'limit': this.pageSize,
|
||||
'sidx': 'id',
|
||||
'order': 'desc'
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.dataList = data.page.list
|
||||
this.totalCount = data.page.totalCount
|
||||
} else {
|
||||
this.dataList = []
|
||||
this.totalCount = 0
|
||||
}
|
||||
this.dataListLoading = false
|
||||
})
|
||||
},
|
||||
// 每页数
|
||||
sizeChangeHandle(val) {
|
||||
this.pageSize = val
|
||||
this.pageIndex = 1
|
||||
this.getDataList()
|
||||
},
|
||||
// 当前页
|
||||
currentChangeHandle(val) {
|
||||
this.pageIndex = val
|
||||
this.getDataList()
|
||||
},
|
||||
// 多选
|
||||
selectionChangeHandle(val) {
|
||||
this.dataListSelections = val
|
||||
},
|
||||
// 云存储配置
|
||||
configHandle() {
|
||||
this.configVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.config.init()
|
||||
})
|
||||
},
|
||||
// 上传文件
|
||||
uploadHandle() {
|
||||
this.uploadVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.upload.init()
|
||||
})
|
||||
},
|
||||
// 删除
|
||||
deleteHandle(id) {
|
||||
var ids = id ? [id] : this.dataListSelections.map(item => item.id)
|
||||
this.$confirm(`确定对[id=${ids.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/oss/delete'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData(ids, false)
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => this.getDataList()
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
}).catch(() => { })
|
||||
},
|
||||
isImageUrl(url) {
|
||||
return url && /.*\.(gif|jpg|jpeg|png|GIF|JPEG|JPG|PNG)/.test(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,96 @@
|
|||
<template>
|
||||
<el-dialog :title="!dataForm.id ? '新增' : '修改'" :close-on-click-modal="false" :visible.sync="visible">
|
||||
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="80px">
|
||||
<el-form-item label="参数名" prop="paramKey">
|
||||
<el-input v-model="dataForm.paramKey" placeholder="参数名"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="参数值" prop="paramValue">
|
||||
<el-input v-model="dataForm.paramValue" placeholder="参数值"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="dataForm.remark" placeholder="备注"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
dataForm: {
|
||||
id: 0,
|
||||
paramKey: '',
|
||||
paramValue: '',
|
||||
remark: ''
|
||||
},
|
||||
dataRule: {
|
||||
paramKey: [
|
||||
{ required: true, message: '参数名不能为空', trigger: 'blur' }
|
||||
],
|
||||
paramValue: [
|
||||
{ required: true, message: '参数值不能为空', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init(id) {
|
||||
this.dataForm.id = id || 0
|
||||
this.visible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].resetFields()
|
||||
if (this.dataForm.id) {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/sys/config/info/${this.dataForm.id}`),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams()
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.dataForm.paramKey = data.config.paramKey
|
||||
this.dataForm.paramValue = data.config.paramValue
|
||||
this.dataForm.remark = data.config.remark
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
// 表单提交
|
||||
dataFormSubmit() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/sys/config/${!this.dataForm.id ? 'save' : 'update'}`),
|
||||
method: 'post',
|
||||
data: this.$http.adornData({
|
||||
'id': this.dataForm.id || undefined,
|
||||
'paramKey': this.dataForm.paramKey,
|
||||
'paramValue': this.dataForm.paramValue,
|
||||
'remark': this.dataForm.remark
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
this.visible = false
|
||||
this.$emit('refreshDataList')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,134 @@
|
|||
<template>
|
||||
<div class="mod-config">
|
||||
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
|
||||
<el-form-item>
|
||||
<el-input v-model="dataForm.paramKey" placeholder="参数名" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList()">查询</el-button>
|
||||
<el-button type="primary" @click="addOrUpdateHandle()">新增</el-button>
|
||||
<el-button type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0">批量删除</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="dataList" border v-loading="dataListLoading" @selection-change="selectionChangeHandle" style="width: 100%;">
|
||||
<el-table-column type="selection" header-align="center" align="center" width="50">
|
||||
</el-table-column>
|
||||
<el-table-column prop="id" header-align="center" align="center" width="80" label="ID">
|
||||
</el-table-column>
|
||||
<el-table-column prop="paramKey" header-align="center" align="center" label="参数名">
|
||||
</el-table-column>
|
||||
<el-table-column prop="paramValue" header-align="center" align="center" label="参数值">
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" header-align="center" align="center" label="备注">
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" header-align="center" align="center" width="150" label="操作">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="small" @click="addOrUpdateHandle(scope.row.id)">修改</el-button>
|
||||
<el-button type="text" size="small" @click="deleteHandle(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" :current-page="pageIndex" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :total="totalCount" layout="total, sizes, prev, pager, next, jumper">
|
||||
</el-pagination>
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AddOrUpdate from './config-add-or-update'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dataForm: {
|
||||
paramKey: ''
|
||||
},
|
||||
dataList: [],
|
||||
pageIndex: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
dataListLoading: false,
|
||||
dataListSelections: [],
|
||||
addOrUpdateVisible: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
AddOrUpdate
|
||||
},
|
||||
activated() {
|
||||
this.getDataList()
|
||||
},
|
||||
methods: {
|
||||
// 获取数据列表
|
||||
getDataList() {
|
||||
this.dataListLoading = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/config/list'),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams({
|
||||
'page': this.pageIndex,
|
||||
'limit': this.pageSize,
|
||||
'paramKey': this.dataForm.paramKey
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.dataList = data.page.list
|
||||
this.totalCount = data.page.totalCount
|
||||
} else {
|
||||
this.dataList = []
|
||||
this.totalCount = 0
|
||||
}
|
||||
this.dataListLoading = false
|
||||
})
|
||||
},
|
||||
// 每页数
|
||||
sizeChangeHandle(val) {
|
||||
this.pageSize = val
|
||||
this.pageIndex = 1
|
||||
this.getDataList()
|
||||
},
|
||||
// 当前页
|
||||
currentChangeHandle(val) {
|
||||
this.pageIndex = val
|
||||
this.getDataList()
|
||||
},
|
||||
// 多选
|
||||
selectionChangeHandle(val) {
|
||||
this.dataListSelections = val
|
||||
},
|
||||
// 新增 / 修改
|
||||
addOrUpdateHandle(id) {
|
||||
this.addOrUpdateVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.addOrUpdate.init(id)
|
||||
})
|
||||
},
|
||||
// 删除
|
||||
deleteHandle(id) {
|
||||
var ids = id ? [id] : this.dataListSelections.map(item => item.id)
|
||||
this.$confirm(`确定对[id=${ids.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/config/delete'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData(ids, false)
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => this.getDataList()
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
}).catch(() => { })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,90 @@
|
|||
<template>
|
||||
<div class="mod-log">
|
||||
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
|
||||
<el-form-item>
|
||||
<el-input v-model="dataForm.key" placeholder="用户名/用户操作" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList()">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="dataList" border v-loading="dataListLoading" style="width: 100%">
|
||||
<el-table-column prop="id" header-align="center" align="center" width="80" label="ID">
|
||||
</el-table-column>
|
||||
<el-table-column prop="username" header-align="center" align="center" label="用户名">
|
||||
</el-table-column>
|
||||
<el-table-column prop="operation" header-align="center" align="center" label="用户操作">
|
||||
</el-table-column>
|
||||
<el-table-column prop="method" header-align="center" align="center" width="150" :show-overflow-tooltip="true" label="请求方法">
|
||||
</el-table-column>
|
||||
<el-table-column prop="params" header-align="center" align="center" width="150" :show-overflow-tooltip="true" label="请求参数">
|
||||
</el-table-column>
|
||||
<el-table-column prop="time" header-align="center" align="center" label="执行时长(毫秒)">
|
||||
</el-table-column>
|
||||
<el-table-column prop="ip" header-align="center" align="center" width="150" label="IP地址">
|
||||
</el-table-column>
|
||||
<el-table-column prop="createDate" header-align="center" align="center" width="180" label="创建时间">
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" :current-page="pageIndex" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :total="totalCount" layout="total, sizes, prev, pager, next, jumper">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dataForm: {
|
||||
key: ''
|
||||
},
|
||||
dataList: [],
|
||||
pageIndex: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
dataListLoading: false,
|
||||
selectionDataList: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getDataList()
|
||||
},
|
||||
methods: {
|
||||
// 获取数据列表
|
||||
getDataList() {
|
||||
this.dataListLoading = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/log/list'),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams({
|
||||
'page': this.pageIndex,
|
||||
'limit': this.pageSize,
|
||||
'key': this.dataForm.key,
|
||||
'sidx': 'id',
|
||||
'order': 'desc'
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.dataList = data.page.list
|
||||
this.totalCount = data.page.totalCount
|
||||
} else {
|
||||
this.dataList = []
|
||||
this.totalCount = 0
|
||||
}
|
||||
this.dataListLoading = false
|
||||
})
|
||||
},
|
||||
// 每页数
|
||||
sizeChangeHandle(val) {
|
||||
this.pageSize = val
|
||||
this.pageIndex = 1
|
||||
this.getDataList()
|
||||
},
|
||||
// 当前页
|
||||
currentChangeHandle(val) {
|
||||
this.pageIndex = val
|
||||
this.getDataList()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,218 @@
|
|||
<template>
|
||||
<el-dialog :title="!dataForm.id ? '新增' : '修改'" :close-on-click-modal="false" :visible.sync="visible">
|
||||
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="80px">
|
||||
<el-form-item label="类型" prop="type">
|
||||
<el-radio-group v-model="dataForm.type">
|
||||
<el-radio v-for="(type, index) in dataForm.typeList" :label="index" :key="index">{{ type }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item :label="dataForm.typeList[dataForm.type] + '名称'" prop="name">
|
||||
<el-input v-model="dataForm.name" :placeholder="dataForm.typeList[dataForm.type] + '名称'"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="上级菜单" prop="parentName">
|
||||
<el-popover ref="menuListPopover" placement="bottom-start" trigger="click">
|
||||
<el-tree :data="menuList" :props="menuListTreeProps" node-key="menuId" ref="menuListTree" @current-change="menuListTreeCurrentChangeHandle" :default-expand-all="true" :highlight-current="true" :expand-on-click-node="false">
|
||||
</el-tree>
|
||||
</el-popover>
|
||||
<el-input v-model="dataForm.parentName" v-popover:menuListPopover :readonly="true" placeholder="点击选择上级菜单" class="menu-list__input"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="dataForm.type === 1" label="菜单路由" prop="url">
|
||||
<el-input v-model="dataForm.url" placeholder="菜单路由"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="dataForm.type !== 0" label="授权标识" prop="perms">
|
||||
<el-input v-model="dataForm.perms" placeholder="多个用逗号分隔, 如: user:list,user:create"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="dataForm.type !== 2" label="菜单图标" prop="icon">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-input v-model="dataForm.icon" placeholder="菜单图标名称" class="icon-list__input"></el-input>
|
||||
</el-col>
|
||||
<el-col :span="12" class="icon-list__tips">
|
||||
<el-form-item v-if="dataForm.type !== 2" label="排序号" prop="orderNum">
|
||||
<el-input-number v-model="dataForm.orderNum" controls-position="right" :min="0" label="排序号"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
<div>参考ElementUI图标库, <a href="https://element.eleme.cn/#/zh-CN/component/icon" target="_blank">找图标</a></div>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { treeDataTranslate } from '@/utils'
|
||||
export default {
|
||||
data() {
|
||||
var validateUrl = (rule, value, callback) => {
|
||||
if (this.dataForm.type === 1 && !/\S/.test(value)) {
|
||||
callback(new Error('菜单URL不能为空'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
return {
|
||||
visible: false,
|
||||
dataForm: {
|
||||
id: 0,
|
||||
type: 1,
|
||||
typeList: ['目录', '菜单', '按钮'],
|
||||
name: '',
|
||||
parentId: 0,
|
||||
parentName: '',
|
||||
url: '',
|
||||
perms: '',
|
||||
orderNum: 0,
|
||||
icon: '',
|
||||
},
|
||||
dataRule: {
|
||||
name: [
|
||||
{ required: true, message: '菜单名称不能为空', trigger: 'blur' }
|
||||
],
|
||||
parentName: [
|
||||
{ required: true, message: '上级菜单不能为空', trigger: 'change' }
|
||||
],
|
||||
url: [
|
||||
{ validator: validateUrl, trigger: 'blur' }
|
||||
]
|
||||
},
|
||||
menuList: [],
|
||||
menuListTreeProps: {
|
||||
label: 'name',
|
||||
children: 'children'
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init(id) {
|
||||
this.dataForm.id = id || 0
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/menu/select'),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams()
|
||||
}).then(({ data }) => {
|
||||
this.menuList = treeDataTranslate(data.menuList, 'menuId')
|
||||
}).then(() => {
|
||||
this.visible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].resetFields()
|
||||
})
|
||||
}).then(() => {
|
||||
if (!this.dataForm.id) {
|
||||
// 新增
|
||||
this.menuListTreeSetCurrentNode()
|
||||
} else {
|
||||
// 修改
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/sys/menu/info/${this.dataForm.id}`),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams()
|
||||
}).then(({ data }) => {
|
||||
this.dataForm.id = data.menu.menuId
|
||||
this.dataForm.type = data.menu.type
|
||||
this.dataForm.name = data.menu.name
|
||||
this.dataForm.parentId = data.menu.parentId
|
||||
this.dataForm.url = data.menu.url
|
||||
this.dataForm.perms = data.menu.perms
|
||||
this.dataForm.orderNum = data.menu.orderNum
|
||||
this.dataForm.icon = data.menu.icon
|
||||
this.menuListTreeSetCurrentNode()
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
// 菜单树选中
|
||||
menuListTreeCurrentChangeHandle(data, node) {
|
||||
this.dataForm.parentId = data.menuId
|
||||
this.dataForm.parentName = data.name
|
||||
},
|
||||
// 菜单树设置当前选中节点
|
||||
menuListTreeSetCurrentNode() {
|
||||
this.$refs.menuListTree.setCurrentKey(this.dataForm.parentId)
|
||||
this.dataForm.parentName = (this.$refs.menuListTree.getCurrentNode() || {})['name']
|
||||
},
|
||||
// 表单提交
|
||||
dataFormSubmit() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/sys/menu/${!this.dataForm.id ? 'save' : 'update'}`),
|
||||
method: 'post',
|
||||
data: this.$http.adornData({
|
||||
'menuId': this.dataForm.id || undefined,
|
||||
'type': this.dataForm.type,
|
||||
'name': this.dataForm.name,
|
||||
'parentId': this.dataForm.parentId,
|
||||
'url': this.dataForm.url,
|
||||
'perms': this.dataForm.perms,
|
||||
'orderNum': this.dataForm.orderNum,
|
||||
'icon': this.dataForm.icon
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
this.visible = false
|
||||
this.$emit('refreshDataList')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.mod-menu {
|
||||
.menu-list__input,
|
||||
.icon-list__input {
|
||||
> .el-input__inner {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
&__icon-popover {
|
||||
width: 458px;
|
||||
overflow: hidden;
|
||||
}
|
||||
&__icon-inner {
|
||||
width: 478px;
|
||||
max-height: 258px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
&__icon-list {
|
||||
width: 458px;
|
||||
padding: 0;
|
||||
margin: -8px 0 0 -8px;
|
||||
> .el-button {
|
||||
padding: 8px;
|
||||
margin: 8px 0 0 8px;
|
||||
> span {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.icon-list__tips {
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
color: #e6a23c;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,109 @@
|
|||
<template>
|
||||
<div class="mod-menu">
|
||||
<el-form :inline="true" :model="dataForm">
|
||||
<el-form-item>
|
||||
<el-button v-if="isAuth('sys:menu:save')" type="primary" @click="addOrUpdateHandle()">新增</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table :data="dataList" row-key="menuId" border style="width: 100%; ">
|
||||
<el-table-column prop="name" header-align="center" min-width="150" label="名称">
|
||||
</el-table-column>
|
||||
<el-table-column prop="parentName" header-align="center" align="center" width="120" label="上级菜单">
|
||||
</el-table-column>
|
||||
<el-table-column header-align="center" align="center" label="图标">
|
||||
<template slot-scope="scope">
|
||||
<i :class="scope.row.icon"></i>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="type" header-align="center" align="center" label="类型">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.type === 0" size="small">目录</el-tag>
|
||||
<el-tag v-else-if="scope.row.type === 1" size="small" type="success">菜单</el-tag>
|
||||
<el-tag v-else-if="scope.row.type === 2" size="small" type="info">按钮</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="orderNum" header-align="center" align="center" label="排序号">
|
||||
</el-table-column>
|
||||
<el-table-column prop="url" header-align="center" align="center" width="150" :show-overflow-tooltip="true" label="菜单URL">
|
||||
</el-table-column>
|
||||
<el-table-column prop="perms" header-align="center" align="center" width="150" :show-overflow-tooltip="true" label="授权标识">
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" header-align="center" align="center" width="150" label="操作">
|
||||
<template slot-scope="scope">
|
||||
<el-button v-if="isAuth('sys:menu:update')" type="text" size="small" @click="addOrUpdateHandle(scope.row.menuId)">修改</el-button>
|
||||
<el-button v-if="isAuth('sys:menu:delete')" type="text" size="small" @click="deleteHandle(scope.row.menuId)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AddOrUpdate from './menu-add-or-update'
|
||||
import { treeDataTranslate } from '@/utils'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dataForm: {},
|
||||
dataList: [],
|
||||
dataListLoading: false,
|
||||
addOrUpdateVisible: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
AddOrUpdate
|
||||
},
|
||||
activated() {
|
||||
this.getDataList()
|
||||
},
|
||||
methods: {
|
||||
// 获取数据列表
|
||||
getDataList() {
|
||||
this.dataListLoading = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/menu/list'),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams()
|
||||
}).then(({ data }) => {
|
||||
this.dataList = treeDataTranslate(data, 'menuId')
|
||||
this.dataListLoading = false
|
||||
})
|
||||
},
|
||||
// 新增 / 修改
|
||||
addOrUpdateHandle(id) {
|
||||
this.addOrUpdateVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.addOrUpdate.init(id)
|
||||
})
|
||||
},
|
||||
// 删除
|
||||
deleteHandle(id) {
|
||||
this.$confirm(`确定对[id=${id}]进行[删除]操作?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/sys/menu/delete/${id}`),
|
||||
method: 'post',
|
||||
data: this.$http.adornData()
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => this.getDataList()
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
}).catch(() => { })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,111 @@
|
|||
<template>
|
||||
<el-dialog :title="!dataForm.id ? '新增' : '修改'" :close-on-click-modal="false" :visible.sync="visible">
|
||||
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="80px">
|
||||
<el-form-item label="角色名称" prop="roleName">
|
||||
<el-input v-model="dataForm.roleName" placeholder="角色名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="dataForm.remark" placeholder="备注"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item size="mini" label="授权">
|
||||
<el-tree :data="menuList" :props="menuListTreeProps" node-key="menuId" ref="menuListTree" :default-expand-all="true" show-checkbox>
|
||||
</el-tree>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { treeDataTranslate } from '@/utils'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
menuList: [],
|
||||
menuListTreeProps: {
|
||||
label: 'name',
|
||||
children: 'children'
|
||||
},
|
||||
dataForm: {
|
||||
id: 0,
|
||||
roleName: '',
|
||||
remark: ''
|
||||
},
|
||||
dataRule: {
|
||||
roleName: [
|
||||
{ required: true, message: '角色名称不能为空', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init(id) {
|
||||
this.dataForm.id = id || 0
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/menu/list'),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams()
|
||||
}).then(({ data }) => {
|
||||
this.menuList = treeDataTranslate(data, 'menuId')
|
||||
}).then(() => {
|
||||
this.visible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].resetFields()
|
||||
this.$refs.menuListTree.setCheckedKeys([])
|
||||
})
|
||||
}).then(() => {
|
||||
if (this.dataForm.id) {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/sys/role/info/${this.dataForm.id}`),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams()
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.dataForm.roleName = data.role.roleName
|
||||
this.dataForm.remark = data.role.remark
|
||||
data.role.menuIdList.forEach(item => {
|
||||
this.$refs.menuListTree.setChecked(item, true);
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
// 表单提交
|
||||
dataFormSubmit() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/sys/role/${!this.dataForm.id ? 'save' : 'update'}`),
|
||||
method: 'post',
|
||||
data: this.$http.adornData({
|
||||
'roleId': this.dataForm.id || undefined,
|
||||
'roleName': this.dataForm.roleName,
|
||||
'remark': this.dataForm.remark,
|
||||
'menuIdList': [].concat(this.$refs.menuListTree.getCheckedKeys(), this.$refs.menuListTree.getHalfCheckedKeys())
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
this.visible = false
|
||||
this.$emit('refreshDataList')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,132 @@
|
|||
<template>
|
||||
<div class="mod-role">
|
||||
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
|
||||
<el-form-item>
|
||||
<el-input v-model="dataForm.roleName" placeholder="角色名称" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList()">查询</el-button>
|
||||
<el-button v-if="isAuth('sys:role:save')" type="primary" @click="addOrUpdateHandle()">新增</el-button>
|
||||
<el-button v-if="isAuth('sys:role:delete')" type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0">批量删除</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="dataList" border v-loading="dataListLoading" @selection-change="selectionChangeHandle" style="width: 100%;">
|
||||
<el-table-column type="selection" header-align="center" align="center" width="50">
|
||||
</el-table-column>
|
||||
<el-table-column prop="roleId" header-align="center" align="center" width="80" label="ID">
|
||||
</el-table-column>
|
||||
<el-table-column prop="roleName" header-align="center" align="center" label="角色名称">
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" header-align="center" align="center" label="备注">
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" header-align="center" align="center" width="150" label="操作">
|
||||
<template slot-scope="scope">
|
||||
<el-button v-if="isAuth('sys:role:update')" type="text" size="small" @click="addOrUpdateHandle(scope.row.roleId)">修改</el-button>
|
||||
<el-button v-if="isAuth('sys:role:delete')" type="text" size="small" @click="deleteHandle(scope.row.roleId)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" :current-page="pageIndex" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :total="totalCount" layout="total, sizes, prev, pager, next, jumper">
|
||||
</el-pagination>
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AddOrUpdate from './role-add-or-update'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dataForm: {
|
||||
roleName: ''
|
||||
},
|
||||
dataList: [],
|
||||
pageIndex: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
dataListLoading: false,
|
||||
dataListSelections: [],
|
||||
addOrUpdateVisible: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
AddOrUpdate
|
||||
},
|
||||
activated() {
|
||||
this.getDataList()
|
||||
},
|
||||
methods: {
|
||||
// 获取数据列表
|
||||
getDataList() {
|
||||
this.dataListLoading = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/role/list'),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams({
|
||||
'page': this.pageIndex,
|
||||
'limit': this.pageSize,
|
||||
'roleName': this.dataForm.roleName
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.dataList = data.page.list
|
||||
this.totalCount = data.page.totalCount
|
||||
} else {
|
||||
this.dataList = []
|
||||
this.totalCount = 0
|
||||
}
|
||||
this.dataListLoading = false
|
||||
})
|
||||
},
|
||||
// 每页数
|
||||
sizeChangeHandle(val) {
|
||||
this.pageSize = val
|
||||
this.pageIndex = 1
|
||||
this.getDataList()
|
||||
},
|
||||
// 当前页
|
||||
currentChangeHandle(val) {
|
||||
this.pageIndex = val
|
||||
this.getDataList()
|
||||
},
|
||||
// 多选
|
||||
selectionChangeHandle(val) {
|
||||
this.dataListSelections = val
|
||||
},
|
||||
// 新增 / 修改
|
||||
addOrUpdateHandle(id) {
|
||||
this.addOrUpdateVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.addOrUpdate.init(id)
|
||||
})
|
||||
},
|
||||
// 删除
|
||||
deleteHandle(id) {
|
||||
var ids = id ? [id] : this.dataListSelections.map(item => item.roleId)
|
||||
this.$confirm(`确定对[id=${ids.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/role/delete'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData(ids, false)
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => this.getDataList()
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
}).catch(() => { })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,177 @@
|
|||
<template>
|
||||
<el-dialog :title="!dataForm.id ? '新增' : '修改'" :close-on-click-modal="false" :visible.sync="visible">
|
||||
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="80px">
|
||||
<el-form-item label="用户名" prop="userName">
|
||||
<el-input v-model="dataForm.userName" placeholder="登录帐号"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="密码" prop="password" :class="{ 'is-required': !dataForm.id }">
|
||||
<el-input v-model="dataForm.password" type="password" placeholder="密码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="comfirmPassword" :class="{ 'is-required': !dataForm.id }">
|
||||
<el-input v-model="dataForm.comfirmPassword" type="password" placeholder="确认密码"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="dataForm.email" placeholder="邮箱"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号" prop="mobile">
|
||||
<el-input v-model="dataForm.mobile" placeholder="手机号"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="角色" size="mini" prop="roleIdList">
|
||||
<el-checkbox-group v-model="dataForm.roleIdList">
|
||||
<el-checkbox v-for="role in roleList" :key="role.roleId" :label="role.roleId">{{ role.roleName }}</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" size="mini" prop="status">
|
||||
<el-radio-group v-model="dataForm.status">
|
||||
<el-radio :label="0">禁用</el-radio>
|
||||
<el-radio :label="1">正常</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isEmail, isMobile } from '@/utils/validate'
|
||||
export default {
|
||||
data() {
|
||||
var validatePassword = (rule, value, callback) => {
|
||||
if (!this.dataForm.id && !/\S/.test(value)) {
|
||||
callback(new Error('密码不能为空'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
var validateComfirmPassword = (rule, value, callback) => {
|
||||
if (!this.dataForm.id && !/\S/.test(value)) {
|
||||
callback(new Error('确认密码不能为空'))
|
||||
} else if (this.dataForm.password !== value) {
|
||||
callback(new Error('确认密码与密码输入不一致'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
var validateEmail = (rule, value, callback) => {
|
||||
if (!isEmail(value)) {
|
||||
callback(new Error('邮箱格式错误'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
var validateMobile = (rule, value, callback) => {
|
||||
if (!isMobile(value)) {
|
||||
callback(new Error('手机号格式错误'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
return {
|
||||
visible: false,
|
||||
roleList: [],
|
||||
dataForm: {
|
||||
id: 0,
|
||||
userName: '',
|
||||
password: '',
|
||||
comfirmPassword: '',
|
||||
salt: '',
|
||||
email: '',
|
||||
mobile: '',
|
||||
roleIdList: [],
|
||||
status: 1
|
||||
},
|
||||
dataRule: {
|
||||
userName: [
|
||||
{ required: true, message: '用户名不能为空', trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
{ validator: validatePassword, trigger: 'blur' }
|
||||
],
|
||||
comfirmPassword: [
|
||||
{ validator: validateComfirmPassword, trigger: 'blur' }
|
||||
],
|
||||
email: [
|
||||
{ required: true, message: '邮箱不能为空', trigger: 'blur' },
|
||||
{ validator: validateEmail, trigger: 'blur' }
|
||||
],
|
||||
mobile: [
|
||||
{ required: true, message: '手机号不能为空', trigger: 'blur' },
|
||||
{ validator: validateMobile, trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init(id) {
|
||||
this.dataForm.id = id || 0
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/role/select'),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams()
|
||||
}).then(({ data }) => {
|
||||
this.roleList = data && data.code === 200 ? data.list : []
|
||||
}).then(() => {
|
||||
this.visible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].resetFields()
|
||||
})
|
||||
}).then(() => {
|
||||
if (this.dataForm.id) {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/sys/user/info/${this.dataForm.id}`),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams()
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.dataForm.userName = data.user.username
|
||||
this.dataForm.salt = data.user.salt
|
||||
this.dataForm.email = data.user.email
|
||||
this.dataForm.mobile = data.user.mobile
|
||||
this.dataForm.roleIdList = data.user.roleIdList
|
||||
this.dataForm.status = data.user.status
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
// 表单提交
|
||||
dataFormSubmit() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/sys/user/${!this.dataForm.id ? 'save' : 'update'}`),
|
||||
method: 'post',
|
||||
data: this.$http.adornData({
|
||||
'userId': this.dataForm.id || undefined,
|
||||
'username': this.dataForm.userName,
|
||||
'password': this.dataForm.password,
|
||||
'salt': this.dataForm.salt,
|
||||
'email': this.dataForm.email,
|
||||
'mobile': this.dataForm.mobile,
|
||||
'status': this.dataForm.status,
|
||||
'roleIdList': this.dataForm.roleIdList
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
this.visible = false
|
||||
this.$emit('refreshDataList')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,140 @@
|
|||
<template>
|
||||
<div class="mod-user">
|
||||
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
|
||||
<el-form-item>
|
||||
<el-input v-model="dataForm.userName" placeholder="用户名" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList()">查询</el-button>
|
||||
<el-button v-if="isAuth('sys:user:save')" type="primary" @click="addOrUpdateHandle()">新增</el-button>
|
||||
<el-button v-if="isAuth('sys:user:delete')" type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0">批量删除</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="dataList" border v-loading="dataListLoading" @selection-change="selectionChangeHandle" style="width: 100%;">
|
||||
<el-table-column type="selection" header-align="center" align="center" width="50">
|
||||
</el-table-column>
|
||||
<el-table-column prop="userId" header-align="center" align="center" width="80" label="ID">
|
||||
</el-table-column>
|
||||
<el-table-column prop="username" header-align="center" align="center" label="用户名">
|
||||
</el-table-column>
|
||||
<el-table-column prop="email" header-align="center" align="center" label="邮箱">
|
||||
</el-table-column>
|
||||
<el-table-column prop="mobile" header-align="center" align="center" label="手机号">
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" header-align="center" align="center" label="状态">
|
||||
<template slot-scope="scope">
|
||||
<el-tag v-if="scope.row.status === 0" size="small" type="danger">禁用</el-tag>
|
||||
<el-tag v-else size="small">正常</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" header-align="center" align="center" width="150" label="操作">
|
||||
<template slot-scope="scope">
|
||||
<el-button v-if="isAuth('sys:user:update')" type="text" size="small" @click="addOrUpdateHandle(scope.row.userId)">修改</el-button>
|
||||
<el-button v-if="isAuth('sys:user:delete')" type="text" size="small" @click="deleteHandle(scope.row.userId)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" :current-page="pageIndex" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :total="totalCount" layout="total, sizes, prev, pager, next, jumper">
|
||||
</el-pagination>
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AddOrUpdate from './user-add-or-update'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dataForm: {
|
||||
userName: ''
|
||||
},
|
||||
dataList: [],
|
||||
pageIndex: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
dataListLoading: false,
|
||||
dataListSelections: [],
|
||||
addOrUpdateVisible: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
AddOrUpdate
|
||||
},
|
||||
activated() {
|
||||
this.getDataList()
|
||||
},
|
||||
methods: {
|
||||
// 获取数据列表
|
||||
getDataList() {
|
||||
this.dataListLoading = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/user/list'),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams({
|
||||
'page': this.pageIndex,
|
||||
'limit': this.pageSize,
|
||||
'username': this.dataForm.userName
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.dataList = data.page.list
|
||||
this.totalCount = data.page.totalCount
|
||||
} else {
|
||||
this.dataList = []
|
||||
this.totalCount = 0
|
||||
}
|
||||
this.dataListLoading = false
|
||||
})
|
||||
},
|
||||
// 每页数
|
||||
sizeChangeHandle(val) {
|
||||
this.pageSize = val
|
||||
this.pageIndex = 1
|
||||
this.getDataList()
|
||||
},
|
||||
// 当前页
|
||||
currentChangeHandle(val) {
|
||||
this.pageIndex = val
|
||||
this.getDataList()
|
||||
},
|
||||
// 多选
|
||||
selectionChangeHandle(val) {
|
||||
this.dataListSelections = val
|
||||
},
|
||||
// 新增 / 修改
|
||||
addOrUpdateHandle(id) {
|
||||
this.addOrUpdateVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.addOrUpdate.init(id)
|
||||
})
|
||||
},
|
||||
// 删除
|
||||
deleteHandle(id) {
|
||||
var userIds = id ? [id] : this.dataListSelections.map(item => item.userId)
|
||||
this.$confirm(`确定对[id=${userIds.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/sys/user/delete'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData(userIds, false)
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => this.getDataList()
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
}).catch(() => { })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,54 @@
|
|||
<template>
|
||||
<el-dialog title="开发接入信息" :close-on-click-modal="false" :visible.sync="visible">
|
||||
<div>
|
||||
<div class="list-item"><span class="label">公众号:</span>{{account.name}}</div>
|
||||
<div class="list-item"><span class="label">token:</span>{{account.token}}</div>
|
||||
<div class="list-item"><span class="label">aesKey:</span>{{account.aesKey}}</div>
|
||||
<div class="list-item">
|
||||
<span class="label">接入链接:</span>
|
||||
<span v-html="accessUrl"></span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
account: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
accessUrl() {
|
||||
let host = location.host;
|
||||
if(/^(\d(.\d){3})|localhost/.test(host)){
|
||||
host='<span class="text-red">正式域名</span>'
|
||||
}
|
||||
return location.protocol + '//' + host + '/wx/wx/msg/' + this.account.appid
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init(item) {
|
||||
this.visible = true
|
||||
if (item && item.appid) {
|
||||
this.account = item
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.list-item{
|
||||
line-height: 30px;
|
||||
}
|
||||
.label{
|
||||
width: 100px;
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,118 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
title="新增/修改"
|
||||
:close-on-click-modal="false"
|
||||
:visible.sync="visible">
|
||||
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="100px">
|
||||
<el-form-item label="公众号名称" prop="name">
|
||||
<el-input v-model="dataForm.name" placeholder="公众号名称"></el-input>
|
||||
</el-form-item>
|
||||
<div class="padding text-gray">测试号可选择服务号,不同类型账号、是否认证可使用功能权限不同,<a href="https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Explanation_of_interface_privileges.html">参考文档</a></div>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="公众号类型" prop="type">
|
||||
<el-select v-model="dataForm.type" placeholder="公众号类型">
|
||||
<el-option v-for="(name,key) in ACCOUNT_TYPES" :key="name" :label="name" :value="key"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="是否认证" prop="verified">
|
||||
<el-switch v-model="dataForm.verified" placeholder="是否认证"></el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="appid" prop="appid">
|
||||
<el-input v-model="dataForm.appid" placeholder="appid"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="appsecret" prop="secret">
|
||||
<el-input v-model="dataForm.secret" placeholder="appsecret"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="token" prop="token">
|
||||
<el-input v-model="dataForm.token" placeholder="token"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="aesKey" prop="aesKey">
|
||||
<el-input v-model="dataForm.aesKey" placeholder="aesKey,可为空"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
visible: false,
|
||||
dataForm: {
|
||||
appid: '',
|
||||
name: '',
|
||||
type:'2',
|
||||
verified:true,
|
||||
secret: '',
|
||||
token: 'my_weixin_token_',
|
||||
aesKey: ''
|
||||
},
|
||||
dataRule: {
|
||||
name: [
|
||||
{ required: true, message: '公众号名称不能为空', trigger: 'blur' }
|
||||
],
|
||||
appid: [
|
||||
{ required: true, message: 'appid不能为空', trigger: 'blur' }
|
||||
],
|
||||
secret: [
|
||||
{ required: true, message: 'appsecret不能为空', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: mapState({
|
||||
ACCOUNT_TYPES: state=>state.wxAccount.ACCOUNT_TYPES
|
||||
}),
|
||||
methods: {
|
||||
init (item) {
|
||||
this.visible = true
|
||||
if(item && item.appid){
|
||||
this.dataForm = item
|
||||
this.dataForm.type = item.type+''
|
||||
}else{
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].resetFields()
|
||||
})
|
||||
}
|
||||
|
||||
},
|
||||
// 表单提交
|
||||
dataFormSubmit () {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/manage/wxAccount/save`),
|
||||
method: 'post',
|
||||
data: this.$http.adornData(this.dataForm)
|
||||
}).then(({data}) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
this.visible = false
|
||||
this.$emit('refreshDataList')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,155 @@
|
|||
<template>
|
||||
<div v-show="visible">
|
||||
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" size="mini" label-width="80px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="文章标题" prop="title" required>
|
||||
<el-input v-model="dataForm.title" :maxlength="1024" placeholder="标题"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="文章类型" prop="type" required>
|
||||
<el-select v-model="dataForm.type" placeholder="选择文章类型">
|
||||
<el-option v-for="(name,key) in ARTICLE_TYPES" :key="name" :label="name" :value="key" allow-create></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="一级目录" prop="category">
|
||||
<el-input :maxlength="50" v-model="dataForm.category" placeholder="一级目录"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="二级分类" prop="subCategory">
|
||||
<el-input :maxlength="50" v-model="dataForm.subCategory" placeholder="二级目录"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="指向外链" prop="targetLink">
|
||||
<el-input v-model="dataForm.targetLink" placeholder="指向外链"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="摘要" prop="summary">
|
||||
<el-input v-model="dataForm.summary" placeholder="摘要" type="textarea" rows="3" maxlength="512" show-word-limit></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="标签" prop="tags">
|
||||
<tags-editor v-model="dataForm.tags"></tags-editor>
|
||||
</el-form-item>
|
||||
<el-form-item label="封面图" prop="image">
|
||||
<el-input v-model="dataForm.image" placeholder="图片链接">
|
||||
<OssUploader slot="append" @uploaded="dataForm.image=$event"></OssUploader>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<tinymce-editor ref="editor" v-model="dataForm.content"></tinymce-editor>
|
||||
</el-form>
|
||||
<div class="margin-top text-right">
|
||||
<el-button @click="$emit('hide')">取消</el-button>
|
||||
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
export default {
|
||||
name:'article-add-or-update',
|
||||
components: {
|
||||
TinymceEditor: () => import("@/components/tinymce-editor"),
|
||||
tagsEditor: () => import("@/components/tags-editor"),
|
||||
OssUploader: () => import('../oss/oss-uploader')
|
||||
},
|
||||
props:{
|
||||
visible:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dataForm: {
|
||||
id: "",
|
||||
type: '1',
|
||||
title: "",
|
||||
content: "",
|
||||
category: "",
|
||||
subCategory: "",
|
||||
summary: "",
|
||||
tags: "",
|
||||
openCount: 0,
|
||||
targetLink: location.origin + "/client/#/article/${articleId}",
|
||||
image: ""
|
||||
},
|
||||
dataRule: {
|
||||
type: [
|
||||
{ required: true, message: "文章类型不能为空", trigger: "blur" }
|
||||
],
|
||||
title: [
|
||||
{ required: true, message: "标题不能为空", trigger: "blur" }
|
||||
],
|
||||
category: [
|
||||
{ required: true, message: "分类不能为空", trigger: "blur" }
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: mapState({
|
||||
ARTICLE_TYPES: state=>state.article.ARTICLE_TYPES
|
||||
}),
|
||||
methods: {
|
||||
init(id) {
|
||||
this.dataForm.id = id || "";
|
||||
this.$nextTick(() => {
|
||||
this.$refs["dataForm"].resetFields();
|
||||
if (id > 0) {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/manage/article/info/${this.dataForm.id}`),
|
||||
method: "get",
|
||||
params: this.$http.adornParams()
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.dataForm=data.article;
|
||||
this.dataForm.type = data.article.type + "";
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
// 表单提交
|
||||
dataFormSubmit() {
|
||||
this.$refs["dataForm"].validate(valid => {
|
||||
if (valid) {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/manage/article/save`),
|
||||
method: "post",
|
||||
data: this.$http.adornData(this.dataForm)
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: "操作成功",
|
||||
type: "success",
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
this.$emit("refreshDataList");
|
||||
this.$emit('hide')
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.$message.error(data.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
imgUploadSuccess(response, file, fileList) {
|
||||
console.log(response);
|
||||
if (response.code == 200) {
|
||||
this.dataForm.image = response.data;
|
||||
console.log("this.article", this.article);
|
||||
} else {
|
||||
this.$message.warning(response.msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,157 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-show="!addOrUpdateVisible">
|
||||
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
|
||||
<el-form-item>
|
||||
<el-select v-model="dataForm.type" placeholder="选择文章类型">
|
||||
<el-option v-for="(name,key) in ARTICLE_TYPES" :key="key" :label="name" :value="key" allow-create></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="dataForm.title" placeholder="标题" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="pageIndex=1;getDataList()">查询</el-button>
|
||||
<el-button v-if="isAuth('wx:article:save')" type="primary" @click="addOrUpdateHandle()">新增</el-button>
|
||||
<el-button v-if="isAuth('wx:article:delete')" type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0">批量删除</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="dataList" border v-loading="dataListLoading" @selection-change="selectionChangeHandle" style="width: 100%;">
|
||||
<el-table-column type="selection" header-align="center" align="center" width="50">
|
||||
</el-table-column>
|
||||
<el-table-column prop="id" header-align="center" align="center" label="ID">
|
||||
</el-table-column>
|
||||
<el-table-column prop="type" header-align="center" align="center" :formatter="articleTypeFormat" label="文章类型">
|
||||
</el-table-column>
|
||||
<el-table-column prop="title" header-align="center" align="center" show-overflow-tooltip label="标题">
|
||||
<a :href="scope.row.targetLink" slot-scope="scope">{{scope.row.title}}</a>
|
||||
</el-table-column>
|
||||
<el-table-column prop="category" header-align="center" align="center" label="一级分类">
|
||||
</el-table-column>
|
||||
<el-table-column prop="subCategory" header-align="center" align="center" label="二级分类">
|
||||
</el-table-column>
|
||||
<el-table-column prop="openCount" header-align="center" align="center" label="打开次数">
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" header-align="center" align="center" width="150" label="操作">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="small" @click="addOrUpdateHandle(scope.row.id)">修改</el-button>
|
||||
<el-button type="text" size="small" @click="deleteHandle(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" :current-page="pageIndex" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :total="totalCount" layout="total, sizes, prev, pager, next, jumper">
|
||||
</el-pagination>
|
||||
</div>
|
||||
<!-- 新增 / 修改 -->
|
||||
<add-or-update :visible="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList" @hide="addOrUpdateVisible=false"></add-or-update>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AddOrUpdate from './article-add-or-update'
|
||||
import { mapState } from 'vuex'
|
||||
export default {
|
||||
components: {
|
||||
AddOrUpdate
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dataForm: {
|
||||
title: '',
|
||||
type: ''
|
||||
},
|
||||
dataList: [],
|
||||
pageIndex: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
dataListLoading: false,
|
||||
dataListSelections: [],
|
||||
addOrUpdateVisible: false
|
||||
}
|
||||
},
|
||||
computed: mapState({
|
||||
ARTICLE_TYPES: state=>state.article.ARTICLE_TYPES
|
||||
}),
|
||||
mounted() {
|
||||
this.getDataList()
|
||||
},
|
||||
methods: {
|
||||
// 获取数据列表
|
||||
getDataList() {
|
||||
this.dataListLoading = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/article/list'),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams({
|
||||
'page': this.pageIndex,
|
||||
'limit': this.pageSize,
|
||||
'title': this.dataForm.title,
|
||||
'type': this.dataForm.type,
|
||||
'sidx': 'id',
|
||||
'order': 'desc'
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.dataList = data.page.list
|
||||
this.totalCount = data.page.totalCount
|
||||
} else {
|
||||
this.dataList = []
|
||||
this.totalCount = 0
|
||||
}
|
||||
this.dataListLoading = false
|
||||
})
|
||||
},
|
||||
// 每页数
|
||||
sizeChangeHandle(val) {
|
||||
this.pageSize = val
|
||||
this.pageIndex = 1
|
||||
this.getDataList()
|
||||
},
|
||||
// 当前页
|
||||
currentChangeHandle(val) {
|
||||
this.pageIndex = val
|
||||
this.getDataList()
|
||||
},
|
||||
// 多选
|
||||
selectionChangeHandle(val) {
|
||||
this.dataListSelections = val
|
||||
},
|
||||
// 新增 / 修改
|
||||
addOrUpdateHandle(id) {
|
||||
this.addOrUpdateVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.addOrUpdate.init(id)
|
||||
})
|
||||
},
|
||||
// 删除
|
||||
deleteHandle(id) {
|
||||
var ids = id ? [id] : this.dataListSelections.map(item => item.id)
|
||||
this.$confirm(`确定对[id=${ids.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/article/delete'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData(ids, false)
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => this.getDataList()
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
articleTypeFormat(row, column, cellValue) {
|
||||
return this.ARTICLE_TYPES[cellValue];
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,38 @@
|
|||
<template>
|
||||
<el-dialog title="选择素材" :visible.sync="dataVisible" :modal="true" append-to-body @close="onClose">
|
||||
<material-news v-if="selectType=='news'" @selected="onSelect" selectMode></material-news>
|
||||
<material-file v-else :fileType="selectType" @selected="onSelect" selectMode></material-file>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name:"assets-selector",
|
||||
data:function (){
|
||||
return {
|
||||
dataVisible : this.visible
|
||||
}
|
||||
},
|
||||
components:{
|
||||
MaterialFile:()=>import('./material-file'),
|
||||
MaterialNews:()=>import('./material-news')
|
||||
},
|
||||
props:{
|
||||
selectType:{// image、voice、video、news
|
||||
type:String,
|
||||
default:'image'
|
||||
},
|
||||
visible:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
onSelect(itemInfo){
|
||||
this.$emit('selected', itemInfo)
|
||||
},
|
||||
onClose(){
|
||||
this.$emit('onClose')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,103 @@
|
|||
<template>
|
||||
<el-dialog :title="!dataForm.id ? '新增' : '修改'" :close-on-click-modal="false" :visible.sync="visible">
|
||||
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="80px">
|
||||
<el-form-item label="媒体文件">
|
||||
<el-button type="primary">
|
||||
选择文件
|
||||
<input type="file" style="opacity: 0;height: 100%;position: absolute;left: 0;top: 0;" @change="onFileChange" />
|
||||
</el-button>
|
||||
<div>{{dataForm.file.name}}</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="媒体类型" prop="mediaType">
|
||||
<el-select v-model="dataForm.mediaType" placeholder="媒体类型" style="width:100%">
|
||||
<el-option label="图片(2M以内,支持PNG\JPEG\JPG\GIF)" value="image"></el-option>
|
||||
<el-option label="视频(10M以内,只支持MP4)" value="video"></el-option>
|
||||
<el-option label="语音(2M、60s以内,支持AMR\MP3)" value="voice"></el-option>
|
||||
<el-option label="缩略图(64K以内JPG)" value="thumb"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="素材名称" prop="fileName">
|
||||
<el-input v-model="dataForm.fileName" placeholder="为便于管理建议按用途分类+素材内容命名"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dataFormSubmit()" :disabled="uploading">{{uploading?'提交中...':'提交'}}</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
uploading:false,
|
||||
dataForm: {
|
||||
mediaId: '',
|
||||
file: '',
|
||||
fileName: '',
|
||||
mediaType: 'image'
|
||||
},
|
||||
dataRule: {
|
||||
fileName: [
|
||||
{ required: true, message: '素材名称不能为空', trigger: 'blur' }
|
||||
],
|
||||
mediaType: [
|
||||
{ required: true, message: '素材类型不能为空', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init(fileType) {
|
||||
if(fileType)this.dataForm.mediaType=fileType
|
||||
this.visible = true
|
||||
},
|
||||
// 表单提交
|
||||
dataFormSubmit() {
|
||||
if(this.uploading)return
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.uploading=true
|
||||
console.log(this.dataForm)
|
||||
let form = new FormData();
|
||||
form.append('mediaId', this.dataForm.mediaId || '')
|
||||
form.append('file', this.dataForm.file)
|
||||
form.append('fileName', this.dataForm.fileName)
|
||||
form.append('mediaType', this.dataForm.mediaType)
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/manage/wxAssets/materialFileUpload`),
|
||||
method: 'post',
|
||||
data: form,
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
this.visible = false
|
||||
this.$emit('refreshDataList')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
this.uploading=false
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
onFileChange(e) {
|
||||
let file = event.currentTarget.files[0]
|
||||
this.dataForm.file = file;
|
||||
this.dataForm.fileName = file.name.substring(0, file.name.lastIndexOf('.'))
|
||||
let mediaType = file.type.substring(0, file.type.lastIndexOf('/'))
|
||||
if (mediaType == 'audio') mediaType = 'voice'
|
||||
this.dataForm.mediaType = mediaType
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,185 @@
|
|||
<template>
|
||||
<div class="mod-menu">
|
||||
<el-form :inline="true" :model="dataForm">
|
||||
<el-form-item v-show="!selectMode">
|
||||
<el-button size="mini" v-if="isAuth('wx:wxassets:save')" type="primary" @click="addOrUpdateHandle()">新增</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div v-loading="dataListLoading">
|
||||
<div class="card" v-for="item in dataList" :key="item.mediaId" @click="onSelect(item)">
|
||||
<el-image v-if="fileType=='image'" class="card-image" :src="item.url" fit="contain" lazy></el-image>
|
||||
<div v-else class="card-preview">
|
||||
<div v-if="fileType=='voice'" class="card-preview-icon el-icon-microphone"></div>
|
||||
<div v-if="fileType=='video'" class="card-preview-icon el-icon-video-camera-solid"></div>
|
||||
<div class="card-preview-text">管理后台不支持预览<br/>微信中可正常播放</div>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="text-cut-name">{{item.name}}</div>
|
||||
<div>{{$moment(item.updateTime).calendar()}}</div>
|
||||
<div class="flex justify-between align-center" v-show="!selectMode">
|
||||
<el-button size="mini" type="text" icon="el-icon-copy-document" v-clipboard:copy="item.mediaId" v-clipboard:success="onCopySuccess" v-clipboard:error="onCopyError">复制media_id</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-delete" @click="deleteHandle(item.mediaId)" >删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-pagination @current-change="currentChangeHandle" :current-page="pageIndex" :page-sizes="[20]" :page-size="20" :total="totalCount" layout="total, prev,pager, next, jumper">
|
||||
</el-pagination>
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="onChange"></add-or-update>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import AddOrUpdate from './material-file-add-or-update'
|
||||
export default {
|
||||
name:'material-file',
|
||||
props:{
|
||||
fileType:{// image、voice、video
|
||||
type:String,
|
||||
default:'image'
|
||||
},
|
||||
selectMode:{// 是否选择模式,选择模式下点击素材选中,不可新增和删除
|
||||
type:Boolean,
|
||||
default:false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
AddOrUpdate
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dataForm: {},
|
||||
addOrUpdateVisible: false,
|
||||
dataList: [],
|
||||
pageIndex: 1,
|
||||
pageSize: 20,
|
||||
totalCount: 0,
|
||||
dataListLoading: false,
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
init(){
|
||||
if(!this.dataList.length){
|
||||
this.getDataList()
|
||||
}
|
||||
},
|
||||
getDataList() {
|
||||
if(this.dataListLoading) return
|
||||
this.dataListLoading = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/wxAssets/materialFileBatchGet'),
|
||||
params: this.$http.adornParams({
|
||||
'page': this.pageIndex,
|
||||
'type': this.fileType
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code == 200) {
|
||||
this.dataList = data.data.items
|
||||
this.totalCount = data.data.totalCount
|
||||
this.pageIndex++;
|
||||
} else {
|
||||
this.$message.error(data.msg);
|
||||
}
|
||||
this.dataListLoading = false
|
||||
})
|
||||
},
|
||||
// 新增 / 修改
|
||||
addOrUpdateHandle() {
|
||||
this.addOrUpdateVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.addOrUpdate.init(this.fileType)
|
||||
})
|
||||
},
|
||||
onSelect(itemInfo){
|
||||
if(!this.selectMode)return
|
||||
this.$emit('selected',itemInfo)
|
||||
},
|
||||
//删除
|
||||
deleteHandle(id) {
|
||||
this.$confirm(`确定对[mediaId=${id}]进行删除操作?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/wxAssets/materialDelete'),
|
||||
method: 'post',
|
||||
data: { mediaId: id }
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => this.onChange()
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
// 当前页
|
||||
currentChangeHandle(val) {
|
||||
this.pageIndex = val
|
||||
this.getDataList()
|
||||
},
|
||||
onCopySuccess(){
|
||||
this.$message.success('已复制')
|
||||
},
|
||||
onCopyError(err){
|
||||
this.$message.error('复制失败,可能是此浏览器不支持复制')
|
||||
},
|
||||
onChange(){
|
||||
this.pageIndex=1
|
||||
this.getDataList()
|
||||
this.$emit('change')
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.card{
|
||||
width: 170px;
|
||||
display: inline-block;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #EBEEF5;
|
||||
box-shadow: 1px 1px 20px 0 rgba(0, 0, 0, 0.1);
|
||||
margin: 0 10px 10px 0;
|
||||
vertical-align: top;
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.card:hover{
|
||||
border: 2px solid #66b1ff;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.card-image{
|
||||
line-height: 170px;
|
||||
max-height: 170px;
|
||||
width: 100%;
|
||||
}
|
||||
.card-preview{
|
||||
padding: 20px 0;
|
||||
color: #d9d9d9;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.card-preview-icon{
|
||||
font-size: 30px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
.card-preview-text{
|
||||
font-size: 12px;
|
||||
}
|
||||
.card-footer{
|
||||
color: #ccc;
|
||||
font-size: 12px;
|
||||
padding: 15px 10px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,221 @@
|
|||
<template>
|
||||
<div v-show="visible">
|
||||
<div class="flex">
|
||||
<div class="card-list">
|
||||
<div class="text-center margin-bottom">图文列表</div>
|
||||
<div class="card-item" :class="{'selected':selectedIndex==index}" v-for="(item,index) in articles" :key="index" @click="selectedIndex=index">
|
||||
<div class="text-cut-name">{{item.title}}</div>
|
||||
</div>
|
||||
<div v-show="articles.length<8 && !mediaId" class="card-add el-icon-plus" @click="addArticle()"></div>
|
||||
</div>
|
||||
<el-form size="mini" v-if="articles.length" :model="articles[selectedIndex]" :rules="dataRule" ref="dataForm" label-width="100px">
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input v-model="articles[selectedIndex].title" placeholder="标题"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="封面图" prop="thumbMediaId">
|
||||
<el-input v-model="articles[selectedIndex].thumbMediaId" placeholder="封面图media_id">
|
||||
<div slot="append" @click="assetsSelectorVisible=true">从素材库中选择</div>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="摘要" prop="digest">
|
||||
<el-input v-model="articles[selectedIndex].digest" placeholder="摘要"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="原文地址" prop="contentSourceUrl">
|
||||
<el-input v-model="articles[selectedIndex].contentSourceUrl" placeholder="阅读原文链接"></el-input>
|
||||
</el-form-item>
|
||||
<el-row>
|
||||
<el-col :span="9">
|
||||
<el-form-item label="作者" prop="author">
|
||||
<el-input v-model="articles[selectedIndex].author" placeholder="作者"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-form-item label="显示封面" prop="showCoverPic">
|
||||
<el-switch v-model="articles[selectedIndex].showCoverPic"></el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-form-item label="允许评论" prop="needOpenComment">
|
||||
<el-switch v-model="articles[selectedIndex].needOpenComment"></el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="5">
|
||||
<el-form-item label="仅粉丝可评论" prop="onlyFansCanComment">
|
||||
<el-switch v-model="articles[selectedIndex].onlyFansCanComment"></el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="内容" prop="content">
|
||||
<tinymce-editor ref="editor" v-model="articles[selectedIndex].content"> </tinymce-editor>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="$emit('hide')">取消</el-button>
|
||||
<el-button type="primary" @click="dataFormSubmit()" :disabled="uploading">{{this.mediaId?'修改此篇':'全部提交(共'+articles.length+'篇)'}}</el-button>
|
||||
</div>
|
||||
<assets-selector v-if="assetsSelectorVisible" :visible="assetsSelectorVisible" selectType="image" @selected="onAssetsSelect"></assets-selector>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const articleTemplate={
|
||||
templateId: 0,
|
||||
title: '',
|
||||
content: '',
|
||||
author: '',
|
||||
showCoverPic: true,
|
||||
contentSourceUrl: '',
|
||||
digest: '',
|
||||
thumbMediaId: '',
|
||||
needOpenComment: false,
|
||||
onlyFansCanComment: false
|
||||
}
|
||||
export default {
|
||||
components: {
|
||||
TinymceEditor: () => import('@/components/tinymce-editor'),
|
||||
AssetsSelector:()=>import('./assets-selector')
|
||||
},
|
||||
props:{
|
||||
visible:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
assetsSelectorVisible:false,
|
||||
mediaId:'',
|
||||
selectedIndex:0,
|
||||
articles:[],
|
||||
uploading:false,
|
||||
dataRule: {
|
||||
title: [
|
||||
{ required: true, message: '标题不能为空', trigger: 'blur' }
|
||||
],
|
||||
content: [
|
||||
{ required: true, message: '内容不能为空', trigger: 'blur' }
|
||||
],
|
||||
thumbMediaId: [
|
||||
{ required: true, message: '封面图media_id不能为空', trigger: 'blur' }
|
||||
],
|
||||
contentSourceUrl: [
|
||||
{ required: true, message: '原文地址不得为空', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init(news){
|
||||
if(news && news.mediaId){
|
||||
this.mediaId=news.mediaId
|
||||
this.articles = news.content.articles
|
||||
}else{
|
||||
this.mediaId=''
|
||||
this.articles=[{...articleTemplate}]
|
||||
}
|
||||
},
|
||||
// 表单提交
|
||||
dataFormSubmit() {
|
||||
if(this.uploading)return
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
if(this.mediaId){// 编辑,只能一次修改一篇
|
||||
this.materialArticleUpdate();
|
||||
}else{ // 新增,全部文章一起保存
|
||||
this.materialNewsUpload();
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
materialNewsUpload(){
|
||||
this.uploading=true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/manage/wxAssets/materialNewsUpload`),
|
||||
method: 'post',
|
||||
data: this.$http.adornData(this.articles,false)
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: "操作成功",
|
||||
type: "success",
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
this.$emit("refreshDataList");
|
||||
this.emit('hide')
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
this.uploading=false
|
||||
})
|
||||
},
|
||||
materialArticleUpdate(){
|
||||
this.uploading=true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/manage/wxAssets/materialArticleUpdate`),
|
||||
method: 'post',
|
||||
data: this.$http.adornData({
|
||||
'mediaId':this.mediaId,
|
||||
'index':this.selectedIndex,
|
||||
'articles':this.articles[this.selectedIndex]
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message.success('操作成功')
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
this.uploading=false
|
||||
})
|
||||
},
|
||||
addArticle(){
|
||||
this.articles.push({...articleTemplate})
|
||||
this.selectedIndex=this.articles.length-1
|
||||
},
|
||||
onAssetsSelect(assetsInfo){
|
||||
Vue.set(this.articles[this.selectedIndex], 'thumbMediaId', assetsInfo.mediaId)
|
||||
this.assetsSelectorVisible=false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.card-list{
|
||||
width: 300px;
|
||||
padding-right: 10px;
|
||||
border-right: 1px solid #eeeeee;
|
||||
}
|
||||
.card-item{
|
||||
margin-top: 2px;
|
||||
padding: 20px 5px;
|
||||
border: 1px solid #ddd;
|
||||
font-size: 12px;
|
||||
line-height: 15px;
|
||||
}
|
||||
.card-item.selected{
|
||||
border: 2px solid #409EFF;
|
||||
}
|
||||
.text-cut-name{
|
||||
display: -webkit-box;
|
||||
word-wrap:break-word;
|
||||
word-break:break-all;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
.card-add{
|
||||
margin-top: 2px;
|
||||
display: block;
|
||||
border: 1px dotted #ddd;
|
||||
color: #ddd;
|
||||
text-align: center;
|
||||
font-size: 30px;
|
||||
line-height: 50px;
|
||||
}
|
||||
.dialog-footer {
|
||||
margin-top: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,206 @@
|
|||
<template>
|
||||
<div class="panel">
|
||||
<div v-show="!addOrUpdateVisible">
|
||||
<el-form :inline="true" :model="dataForm">
|
||||
<el-form-item v-show="!selectMode">
|
||||
<el-button size="mini" v-if="isAuth('wx:wxassets:save')" type="primary" @click="addOrUpdateHandle()">新增</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="flex justify-start" v-loading="dataListLoading">
|
||||
<div v-for="n in rows" :key="n">
|
||||
<template v-for="(item,i) in dataList">
|
||||
<div class="card" :key="item.mediaId" v-if="i%rows==n-1" @click="onSelect(item)">
|
||||
<div class="card-preview">
|
||||
<a v-for="(article,k) in item.content.articles" :key="k" :href="article.url" class="article-item" target="_blank">
|
||||
<div class="article-title">{{article.title}}</div>
|
||||
<el-image class="article-thumb" :src="article.thumbUrl"></el-image>
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div>{{$moment(item.updateTime).calendar()}}</div>
|
||||
<div class="flex justify-between align-center" v-show="!selectMode">
|
||||
<el-button size="mini" type="text" icon="el-icon-copy-document" v-clipboard:copy="item.mediaId" v-clipboard:success="onCopySuccess" v-clipboard:error="onCopyError">复制media_id</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-edit" @click="addOrUpdateHandle(item)">编辑</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-delete" @click="deleteHandle(item.mediaId)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<el-pagination @current-change="currentChangeHandle" :current-page="pageIndex" :page-size="pageSize" :total="totalCount" layout="total, prev,pager, next, jumper">
|
||||
</el-pagination>
|
||||
</div>
|
||||
<!-- 新增 / 修改 -->
|
||||
<add-or-update :visible="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="onChange" @hide="addOrUpdateVisible=false"></add-or-update>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import AddOrUpdate from './material-news-add-or-update'
|
||||
export default {
|
||||
name: 'material-news',
|
||||
components: {
|
||||
AddOrUpdate
|
||||
},
|
||||
props: {
|
||||
selectMode: {// 是否选择模式,选择模式下点击素材选中,不可新增和删除
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
rows: {
|
||||
type: Number,
|
||||
default: 4
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dataForm: {},
|
||||
addOrUpdateVisible: false,
|
||||
dataList: [],
|
||||
pageIndex: 1,
|
||||
pageSize: 20,
|
||||
totalCount: 0,
|
||||
dataListLoading: false
|
||||
}
|
||||
},
|
||||
mounted(){
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
if (!this.dataList.length) {
|
||||
this.getDataList()
|
||||
}
|
||||
},
|
||||
getDataList() {
|
||||
if (this.dataListLoading) return
|
||||
this.dataListLoading = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/wxAssets/materialNewsBatchGet'),
|
||||
params: this.$http.adornParams({
|
||||
'page': this.pageIndex
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
|
||||
if (data.code == 200) {
|
||||
this.dataList = data.data.items
|
||||
this.totalCount = data.data.totalCount
|
||||
} else {
|
||||
this.$message.error(data.msg);
|
||||
}
|
||||
this.dataListLoading = false
|
||||
})
|
||||
},
|
||||
onSelect(itemInfo) {
|
||||
if (!this.selectMode) return
|
||||
this.$emit('selected', itemInfo)
|
||||
},
|
||||
//删除
|
||||
deleteHandle(id) {
|
||||
this.$confirm(`确定对[mediaId=${id}]进行删除操作?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/wxAssets/materialDelete'),
|
||||
method: 'post',
|
||||
data: { mediaId: id }
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => this.onChange()
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
// 当前页
|
||||
currentChangeHandle(val) {
|
||||
this.pageIndex = val
|
||||
this.getDataList()
|
||||
},
|
||||
// 新增 / 修改
|
||||
addOrUpdateHandle(news) {
|
||||
this.addOrUpdateVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.addOrUpdate.init(news || '')
|
||||
})
|
||||
},
|
||||
onCopySuccess() {
|
||||
this.$message.success('已复制')
|
||||
},
|
||||
onCopyError(err) {
|
||||
this.$message.error('复制失败,可能是此浏览器不支持复制')
|
||||
},
|
||||
onChange() {
|
||||
this.pageIndex=1
|
||||
this.getDataList()
|
||||
this.$emit('change')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.card {
|
||||
width: 240px;
|
||||
min-height: 120px;
|
||||
display: inline-block;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #EBEEF5;
|
||||
box-shadow: 1px 1px 20px 0 rgba(0, 0, 0, 0.1);
|
||||
margin: 0 10px 10px 0;
|
||||
border-radius: 5px;
|
||||
vertical-align: top;
|
||||
height: fit-content;
|
||||
}
|
||||
.card:hover {
|
||||
border: 2px solid #66b1ff;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.card-preview {
|
||||
color: #d9d9d9;
|
||||
padding-left: 10px;
|
||||
padding-top: 15px;
|
||||
}
|
||||
.article-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
padding: 10px 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.article-item::after{
|
||||
width: 168px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
.article-title {
|
||||
display: -webkit-box;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
color: #333333;
|
||||
padding-right: 10px;
|
||||
line-height: 20px;
|
||||
font-size: 13px;
|
||||
|
||||
}
|
||||
.article-thumb {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: inline-block;
|
||||
}
|
||||
.card-footer {
|
||||
font-size: 12px;
|
||||
color: #ccc;
|
||||
padding: 15px 10px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,211 @@
|
|||
<template>
|
||||
<el-dialog :title="!dataForm.id ? '新增' : '修改'" :close-on-click-modal="false" :visible.sync="visible" >
|
||||
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" label-width="80px">
|
||||
<el-form-item label="规则名称" prop="ruleName">
|
||||
<el-input v-model="dataForm.ruleName" placeholder="规则名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="匹配词" prop="matchValue">
|
||||
<tags-editor v-model="dataForm.matchValue"></tags-editor>
|
||||
</el-form-item>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="作用范围" prop="appid">
|
||||
<el-select v-model="dataForm.appid" placeholder="作用范围">
|
||||
<el-option label="全部公众号" value=""></el-option>
|
||||
<el-option label="当前公众号" :value="selectedAppid"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="精确匹配" prop="exactMatch">
|
||||
<el-switch v-model="dataForm.exactMatch" :active-value="true" :inactive-value="false"></el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="回复类型" prop="replyType">
|
||||
<el-select v-model="dataForm.replyType" @change="onReplyTypeChange">
|
||||
<el-option v-for="(name,key) in KefuMsgType" :key="key" :value="key" :label="name"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="是否启用" prop="status">
|
||||
<el-switch v-model="dataForm.status" :active-value="true" :inactive-value="false"></el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="生效时间" prop="effectTimeStart">
|
||||
<el-time-picker v-model="dataForm.effectTimeStart" value-format="HH:mm:ss"></el-time-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="失效时间" prop="effectTimeEnd">
|
||||
<el-time-picker v-model="dataForm.effectTimeEnd" value-format="HH:mm:ss"></el-time-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="回复内容" prop="replyContent">
|
||||
<el-input v-model="dataForm.replyContent" type="textarea" :rows="5" placeholder="文本、图文ID、media_id、json配置"></el-input>
|
||||
<el-button type="text" v-show="'text'==dataForm.replyType" @click="addLink">插入链接</el-button>
|
||||
<el-button type="text" v-show="assetsType" @click="assetsSelectorVisible=true">
|
||||
从素材库中选择<span v-if="'miniprogrampage'==dataForm.replyType || 'music'==dataForm.replyType">缩略图</span>
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注说明" prop="desc">
|
||||
<el-input v-model="dataForm.desc" placeholder="备注说明"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
|
||||
</span>
|
||||
<assets-selector v-if="assetsSelectorVisible && assetsType" :visible="assetsSelectorVisible" :selectType="assetsType" @selected="onAssetsSelect" @onClose="assetsSelectorVisible=false"></assets-selector>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
export default {
|
||||
components: {
|
||||
tagsEditor: () => import('@/components/tags-editor'),
|
||||
AssetsSelector:()=>import('./assets/assets-selector')
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
assetsSelectorVisible:false,
|
||||
dataForm: {
|
||||
ruleId: 0,
|
||||
appid:'',
|
||||
ruleName: "",
|
||||
exactMatch: false,
|
||||
matchValue: "",
|
||||
replyType: 'text',
|
||||
replyContent: "",
|
||||
status: true,
|
||||
desc: "",
|
||||
effectTimeStart: "00:00:00",
|
||||
effectTimeEnd: "23:59:59"
|
||||
},
|
||||
dataRule: {
|
||||
ruleName: [
|
||||
{ required: true, message: "规则名称不能为空", trigger: "blur" }
|
||||
],
|
||||
matchValue: [
|
||||
{ required: true, message: "匹配的关键词、事件等不能为空", trigger: "blur" }
|
||||
],
|
||||
replyType: [
|
||||
{ required: true, message: "回复类型(1:文本2:图文3媒体)不能为空", trigger: "blur" }
|
||||
],
|
||||
replyContent: [
|
||||
{ required: true, message: "回复内容不能为空", trigger: "blur" }
|
||||
],
|
||||
status: [
|
||||
{ required: true, message: "是否有效不能为空", trigger: "blur" }
|
||||
],
|
||||
effectTimeStart: [
|
||||
{ required: true, message: "生效起始时间不能为空", trigger: "blur" }
|
||||
],
|
||||
effectTimeEnd: [
|
||||
{ required: true, message: "生效结束时间不能为空", trigger: "blur" }
|
||||
]
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: mapState({
|
||||
KefuMsgType: state=>state.message.KefuMsgType,
|
||||
selectedAppid:state=>state.wxAccount.selectedAppid,
|
||||
assetsType(){
|
||||
const config={//消息类型与选择素材类型对应关系
|
||||
'image':'image',
|
||||
'voice':'voice',
|
||||
'video':'video',
|
||||
'mpnews':'news',
|
||||
'miniprogrampage':'image',//小程序需选择卡片图
|
||||
'music':'image'
|
||||
}
|
||||
return config[this.dataForm.replyType] || ''
|
||||
}
|
||||
}),
|
||||
methods: {
|
||||
init(id) {
|
||||
this.dataForm.ruleId = id || 0;
|
||||
this.visible = true;
|
||||
this.$nextTick(() => {
|
||||
this.$refs["dataForm"].resetFields();
|
||||
if (this.dataForm.ruleId) {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl( `/manage/msgReplyRule/info/${this.dataForm.ruleId}` ),
|
||||
method: "get",
|
||||
params: this.$http.adornParams()
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.dataForm = data.msgReplyRule;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
// 表单提交
|
||||
dataFormSubmit() {
|
||||
this.$refs["dataForm"].validate(valid => {
|
||||
if (valid) {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/manage/msgReplyRule/${!this.dataForm.ruleId ? "save" : "update"}`),
|
||||
method: "post",
|
||||
data: this.$http.adornData(this.dataForm)
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: "操作成功",
|
||||
type: "success",
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
this.visible = false;
|
||||
this.$emit("refreshDataList");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.$message.error(data.msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
addLink() {
|
||||
this.dataForm.replyContent += '<a href="链接地址">链接文字</a>'
|
||||
},
|
||||
onReplyTypeChange(value) {
|
||||
if ("miniprogrampage" == value) {
|
||||
let demo = { title: "标题", appid: "小程序APPID", pagepath: "页面地址", thumb_media_id: "缩略图media_id" };
|
||||
this.dataForm.replyContent = JSON.stringify(demo, null, 4)
|
||||
} else if ("music" == value) {
|
||||
let demo = { musicurl: "音乐链接", hqmusicurl: "高品质链接", title: "标题", description: "描述", thumb_media_id: "缩略图media_id" }
|
||||
this.dataForm.replyContent = JSON.stringify(demo, null, 4)
|
||||
} else if ("msgmenu" == value) {
|
||||
let demo = { head_content: "开头文字", list: [{ id: "菜单1ID", content: "菜单2内容" }, { id: "菜单2ID", content: "菜单2内容" }, { id: "菜单nID", content: "菜单n内容" }], tail_content: "结尾文字" }
|
||||
this.dataForm.replyContent = JSON.stringify(demo, null, 4)
|
||||
} else if ("news" == value) {
|
||||
let demo={title:"文章标题",description:"文章简介",url:"链接URL",picUrl:"缩略图URL"}
|
||||
this.dataForm.replyContent = JSON.stringify(demo, null, 4)
|
||||
} else {
|
||||
this.dataForm.replyContent = '媒体素材media_id'
|
||||
}
|
||||
},
|
||||
onAssetsSelect(assetsInfo){
|
||||
if(this.dataForm.replyType=='miniprogrampage' || this.dataForm.replyType=='music'){
|
||||
let data = JSON.parse(this.dataForm.replyContent)
|
||||
if(data && data.thumb_media_id)data.thumb_media_id=assetsInfo.mediaId
|
||||
this.dataForm.replyContent = JSON.stringify(data, null, 4)
|
||||
}else{
|
||||
this.dataForm.replyContent = assetsInfo.mediaId
|
||||
}
|
||||
this.assetsSelectorVisible=false
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,166 @@
|
|||
<template>
|
||||
<div class="mod-config">
|
||||
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
|
||||
<el-form-item>
|
||||
<el-input v-model="dataForm.matchValue" placeholder="匹配关键词" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList()">查询</el-button>
|
||||
<el-button v-if="isAuth('wx:msgreplyrule:save')" type="primary" @click="addOrUpdateHandle()">新增</el-button>
|
||||
<el-button v-if="isAuth('wx:msgreplyrule:delete')" type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0">批量删除</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="dataList" border type="expand" v-loading="dataListLoading" @selection-change="selectionChangeHandle" style="width: 100%;">
|
||||
<el-table-column type="expand">
|
||||
<template slot-scope="props">
|
||||
<el-form label-position="left" inline class="demo-table-expand">
|
||||
<el-form-item label="作用范围">
|
||||
<span>{{ props.row.appid?'当前公众号':'全部公众号' }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="精确匹配">
|
||||
<span>{{ props.row.exactMatch?'是':'否' }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否有效">
|
||||
<span>{{ props.row.status?'是':'否' }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注说明">
|
||||
<span>{{ props.row.desc }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="生效时间">
|
||||
<span>{{ props.row.effectTimeStart }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="失效时间">
|
||||
<span>{{ props.row.effectTimeEnd }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column type="selection" header-align="center" align="center" width="50">
|
||||
</el-table-column>
|
||||
<el-table-column prop="ruleName" header-align="center" align="center" show-overflow-tooltip label="规则名称">
|
||||
</el-table-column>
|
||||
<el-table-column prop="matchValue" header-align="center" align="center" show-overflow-tooltip label="匹配关键词">
|
||||
</el-table-column>
|
||||
<el-table-column prop="replyType" header-align="center" align="center" :formatter="replyTypeFormat" label="消息类型">
|
||||
</el-table-column>
|
||||
<el-table-column prop="replyContent" header-align="center" align="center" show-overflow-tooltip label="回复内容">
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" header-align="center" align="center" width="150" label="操作">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="small" @click="addOrUpdateHandle(scope.row.ruleId)">修改</el-button>
|
||||
<el-button type="text" size="small" @click="deleteHandle(scope.row.ruleId)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" :current-page="pageIndex" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :total="totalCount" layout="total, sizes, prev, pager, next, jumper">
|
||||
</el-pagination>
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AddOrUpdate from './msg-reply-rule-add-or-update'
|
||||
import { mapState } from 'vuex'
|
||||
export default {
|
||||
components: {
|
||||
AddOrUpdate
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dataForm: {
|
||||
matchValue: ''
|
||||
},
|
||||
dataList: [],
|
||||
pageIndex: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
dataListLoading: false,
|
||||
dataListSelections: [],
|
||||
addOrUpdateVisible: false
|
||||
}
|
||||
},
|
||||
computed: mapState({
|
||||
KefuMsgType: state=>state.message.KefuMsgType
|
||||
}),
|
||||
|
||||
activated() {
|
||||
this.getDataList()
|
||||
},
|
||||
methods: {
|
||||
// 获取数据列表
|
||||
getDataList() {
|
||||
this.dataListLoading = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/msgReplyRule/list'),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams({
|
||||
'page': this.pageIndex,
|
||||
'limit': this.pageSize,
|
||||
'matchValue': this.dataForm.matchValue
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.dataList = data.page.list
|
||||
this.totalCount = data.page.totalCount
|
||||
} else {
|
||||
this.dataList = []
|
||||
this.totalCount = 0
|
||||
}
|
||||
this.dataListLoading = false
|
||||
})
|
||||
},
|
||||
// 每页数
|
||||
sizeChangeHandle(val) {
|
||||
this.pageSize = val
|
||||
this.pageIndex = 1
|
||||
this.getDataList()
|
||||
},
|
||||
// 当前页
|
||||
currentChangeHandle(val) {
|
||||
this.pageIndex = val
|
||||
this.getDataList()
|
||||
},
|
||||
// 多选
|
||||
selectionChangeHandle(val) {
|
||||
this.dataListSelections = val
|
||||
},
|
||||
// 新增 / 修改
|
||||
addOrUpdateHandle(id) {
|
||||
this.addOrUpdateVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.addOrUpdate.init(id)
|
||||
})
|
||||
},
|
||||
// 删除
|
||||
deleteHandle(id) {
|
||||
var ids = id ? [id] : this.dataListSelections.map(item => item.ruleId)
|
||||
this.$confirm(`确定对[id=${ids.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/msgReplyRule/delete'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData(ids, false)
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => this.getDataList()
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
replyTypeFormat(row, column, cellValue) {
|
||||
return this.KefuMsgType[cellValue];
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,165 @@
|
|||
<template>
|
||||
<el-dialog title="模板配置" :close-on-click-modal="false" :visible.sync="visible">
|
||||
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" label-width="100px" size="mini">
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input v-model="dataForm.title" placeholder="标题"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="链接" prop="url">
|
||||
<el-input v-model="dataForm.url" placeholder="跳转链接"></el-input>
|
||||
</el-form-item>
|
||||
<div>
|
||||
<el-form-item label="小程序appid" prop="miniprogram.appid">
|
||||
<el-input v-model="dataForm.miniprogram.appid" placeholder="小程序appid"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="小程序路径" prop="miniprogram.pagePath">
|
||||
<el-input v-model="dataForm.miniprogram.pagePath" placeholder="小程序pagePath"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-row>
|
||||
<el-col :span="16">
|
||||
<el-form-item label="模版名称" prop="name">
|
||||
<el-input v-model="dataForm.name" placeholder="模版名称"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="有效" prop="status">
|
||||
<el-switch v-model="dataForm.status" placeholder="是否有效" :active-value="true" :inactive-value="false"></el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div class="form-group-area">
|
||||
<el-form-item class="form-group-title">消息填充数据,请对照模板内容填写</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input type="textarea" disabled autosize v-model="dataForm.content" placeholder="模版"></el-input>
|
||||
</el-form-item>
|
||||
<el-row v-for="(item,index) in dataForm.data" :key="item.name">
|
||||
<el-col :span="16">
|
||||
<el-form-item :label="item.name" :prop="'data.'+index+'.value'" :rules="[{required: true,message: '填充内容不得为空', trigger: 'blur' }]">
|
||||
<el-input type="textarea" autosize rows="1" v-model="item.value" placeholder="填充内容" ></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="颜色" >
|
||||
<el-input type="color" v-model="item.color" placeholder="颜色"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
dataForm: {
|
||||
id: 0,
|
||||
templateId: '',
|
||||
title: '',
|
||||
data: [],
|
||||
url: '',
|
||||
miniprogram:{appid:'',pagePath:''},
|
||||
content: '',
|
||||
status: true,
|
||||
name: ''
|
||||
},
|
||||
dataRule: {
|
||||
title: [
|
||||
{ required: true, message: '标题不能为空', trigger: 'blur' }
|
||||
],
|
||||
data: [
|
||||
{ required: true, message: '内容不能为空', trigger: 'blur' }
|
||||
],
|
||||
name: [
|
||||
{ required: true, message: '模版名称不能为空', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init(id) {
|
||||
console.log('init',id)
|
||||
this.dataForm.id = id || 0
|
||||
this.visible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].resetFields()
|
||||
if (this.dataForm.id) {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/manage/msgTemplate/info/${this.dataForm.id}`),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams()
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.transformTemplate(data.msgTemplate)
|
||||
}else{
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* 根据content信息展开data配置项(content为微信公众平台后台配置的模板)
|
||||
* 如content='{{first.DATA}} ↵商品名称:{{keyword1.DATA}} ↵购买时间:{{keyword2.DATA}} ↵{{remark.DATA}}'
|
||||
* 则生成data=[{name:'first',value:'',color:''},{name:'first',value:'',color:''},{name:'first',value:'',color:''}]
|
||||
* 展示表单让管理员给对应的字段填充内容
|
||||
*/
|
||||
transformTemplate(template){
|
||||
if(!template.miniprogram)template.miniprogram={appid:'',pagePath:''}
|
||||
if(template.data instanceof Array) {//已经配置过了,直接读取
|
||||
this.dataForm = template
|
||||
return
|
||||
}
|
||||
|
||||
template.data=[]
|
||||
let keysArray = template.content.match(/\{\{(\w*)\.DATA\}\}/g) || [] //示例: ["{{first.DATA}}", "{{keyword1.DATA}}", "{{keyword2.DATA}}", "{{remark.DATA}}"]
|
||||
keysArray.map(item=>{
|
||||
name=item.replace('{{','').replace('.DATA}}','')
|
||||
template.data.push({"name":name,"value":"",color:"#000000"})
|
||||
})
|
||||
this.dataForm = template
|
||||
},
|
||||
// 表单提交
|
||||
dataFormSubmit() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/manage/msgTemplate/${!this.dataForm.id ? 'save' : 'update'}`),
|
||||
method: 'post',
|
||||
data: this.$http.adornData(this.dataForm)
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
this.visible = false
|
||||
this.$emit('refreshDataList')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.form-group-area{
|
||||
border:1px dotted gray;
|
||||
}
|
||||
.form-group-title{
|
||||
color: gray;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,215 @@
|
|||
<template>
|
||||
<div class="mod-config">
|
||||
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
|
||||
<el-form-item>
|
||||
<el-input v-model="dataForm.title" placeholder="标题" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList()">查询</el-button>
|
||||
<el-button v-if="isAuth('wx:msgtemplate:save')" type="success" @click="copyHandle()" :disabled="dataListSelections.length <= 0">批量复制</el-button>
|
||||
<el-button v-if="isAuth('wx:msgtemplate:save')" type="success" @click="templateMsgTaskHandle()" :disabled="dataListSelections.length!=1">推送消息</el-button>
|
||||
<el-button v-if="isAuth('wx:msgtemplate:delete')" type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0">批量删除</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item class="fr">
|
||||
<el-button v-if="isAuth('wx:msgtemplate:save')" icon="el-icon-sort" type="success" @click="syncWxTemplate()" :disabled="synchonizingWxTemplate">{{synchonizingWxTemplate?'同步中...':'同步公众号模板'}}</el-button>
|
||||
<el-button><el-link type="primary" icon="el-icon-link" target="_blank" href="https://kf.qq.com/faq/170209E3InyI170209nIF7RJ.html">模板管理指引</el-link></el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table :data="dataList" border v-loading="dataListLoading" @selection-change="selectionChangeHandle" style="width: 100%;">
|
||||
<el-table-column type="selection" header-align="center" align="center" width="50">
|
||||
</el-table-column>
|
||||
<el-table-column prop="templateId" show-overflow-tooltip header-align="center" align="center" label="模板ID">
|
||||
</el-table-column>
|
||||
<el-table-column prop="title" header-align="center" align="center" label="标题">
|
||||
<a :href="scope.row.url" slot-scope="scope">{{scope.row.title}}</a>
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" header-align="center" align="center" label="模版名称">
|
||||
</el-table-column>
|
||||
<el-table-column prop="content" show-overflow-tooltip header-align="center" align="center" label="模版字段" width="200">
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" header-align="center" align="center" label="是否有效">
|
||||
<span slot-scope="scope">{{scope.row.status?"是":"否"}}</span>
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" header-align="center" align="center" width="150" label="操作">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="small" @click="addOrUpdateHandle(scope.row.id)">配置</el-button>
|
||||
<el-button type="text" size="small" @click="deleteHandle(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" :current-page="pageIndex" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :total="totalCount" layout="total, sizes, prev, pager, next, jumper">
|
||||
</el-pagination>
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update>
|
||||
<template-msg-task v-if="templateMsgTaskVisible" ref="templateMsgTask"></template-msg-task>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AddOrUpdate from './msg-template-add-or-update'
|
||||
import TemplateMsgTask from '@/components/template-msg-task'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dataForm: {
|
||||
title: ''
|
||||
},
|
||||
dataList: [],
|
||||
pageIndex: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
dataListLoading: false,
|
||||
dataListSelections: [],
|
||||
addOrUpdateVisible: false,
|
||||
templateMsgTaskVisible:false,
|
||||
synchonizingWxTemplate:false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
AddOrUpdate,TemplateMsgTask
|
||||
},
|
||||
activated() {
|
||||
this.getDataList()
|
||||
},
|
||||
methods: {
|
||||
// 获取数据列表
|
||||
getDataList() {
|
||||
this.dataListLoading = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/msgTemplate/list'),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams({
|
||||
'page': this.pageIndex,
|
||||
'limit': this.pageSize,
|
||||
'title': this.dataForm.title,
|
||||
'sidx': 'id',
|
||||
'order': 'desc'
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.dataList = data.page.list
|
||||
this.totalCount = data.page.totalCount
|
||||
} else {
|
||||
this.dataList = []
|
||||
this.totalCount = 0
|
||||
}
|
||||
this.dataListLoading = false
|
||||
})
|
||||
},
|
||||
// 每页数
|
||||
sizeChangeHandle(val) {
|
||||
this.pageSize = val
|
||||
this.pageIndex = 1
|
||||
this.getDataList()
|
||||
},
|
||||
// 当前页
|
||||
currentChangeHandle(val) {
|
||||
this.pageIndex = val
|
||||
this.getDataList()
|
||||
},
|
||||
// 多选
|
||||
selectionChangeHandle(val) {
|
||||
this.dataListSelections = val
|
||||
},
|
||||
// 新增 / 修改
|
||||
addOrUpdateHandle(id) {
|
||||
this.addOrUpdateVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.addOrUpdate.init(id)
|
||||
})
|
||||
},
|
||||
// 删除
|
||||
deleteHandle(id) {
|
||||
var ids = id ? [id] : this.dataListSelections.map(item => item.id)
|
||||
this.$confirm(`确定对[id=${ids.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/msgTemplate/delete'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData(ids, false)
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
this.getDataList()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
syncWxTemplate(){
|
||||
if(this.synchonizingWxTemplate)return
|
||||
this.synchonizingWxTemplate=true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/msgTemplate/syncWxTemplate'),
|
||||
method: 'post',
|
||||
}).then(({ data }) => {
|
||||
this.synchonizingWxTemplate=false
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '同步完成',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
this.getDataList()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
}).catch(()=>this.synchonizingWxTemplate=false)
|
||||
},
|
||||
templateMsgTaskHandle(){
|
||||
this.templateMsgTaskVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.templateMsgTask.init(this.dataListSelections[0])
|
||||
})
|
||||
},
|
||||
async copyHandle(){
|
||||
let loading;
|
||||
for (let i = 0; i < this.dataListSelections.length; i++) {
|
||||
let item = this.dataListSelections[i];
|
||||
loading=this.$loading({
|
||||
lock: true,
|
||||
text: "复制模板:"+item.title,
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
});
|
||||
item.id='';
|
||||
item.updateTime=new Date()
|
||||
item.name+='_COPY'
|
||||
await this.addMsgTemplate(item).catch(()=>loading.close())
|
||||
loading.close()
|
||||
}
|
||||
loading.close()
|
||||
this.getDataList()
|
||||
},
|
||||
addMsgTemplate(msgTemplate){
|
||||
return new Promise((resolve, reject) => {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/msgTemplate/save'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData(msgTemplate)
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
resolve()
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
reject(data.msg)
|
||||
}
|
||||
}).catch(err=>reject(err))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,135 @@
|
|||
<template>
|
||||
<div class="mod-config">
|
||||
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
|
||||
<el-form-item>
|
||||
<el-input v-model="dataForm.touser" placeholder="openid" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList()">查询</el-button>
|
||||
<el-button v-if="isAuth('wx:templatemsglog:delete')" type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0">批量删除</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="dataList" border v-loading="dataListLoading" @selection-change="selectionChangeHandle" style="width: 100%;">
|
||||
<el-table-column type="selection" header-align="center" align="center" width="50">
|
||||
</el-table-column>
|
||||
<el-table-column prop="touser" header-align="center" align="center" label="openid" width="100">
|
||||
</el-table-column>
|
||||
<el-table-column prop="data" header-align="center" align="center" :formatter="tableJsonFormat" label="内容" width="300">
|
||||
</el-table-column>
|
||||
<el-table-column prop="sendResult" header-align="center" align="center" show-overflow-tooltip label="发送结果" width="150">
|
||||
</el-table-column>
|
||||
<el-table-column prop="sendTime" header-align="center" align="center" width="100" label="发送时间">
|
||||
</el-table-column>
|
||||
<el-table-column prop="url" header-align="center" align="center" show-overflow-tooltip label="链接">
|
||||
</el-table-column>
|
||||
<el-table-column prop="miniprogram" header-align="center" align="center" :formatter="tableJsonFormat" show-overflow-tooltip label="小程序">
|
||||
</el-table-column>
|
||||
<el-table-column prop="templateId" header-align="center" align="center" label="模板ID" width="150">
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" header-align="center" align="center" width="150" label="操作">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="small" @click="deleteHandle(scope.row.logId)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" :current-page="pageIndex" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :total="totalCount" layout="total, sizes, prev, pager, next, jumper">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dataForm: {
|
||||
touser: ''
|
||||
},
|
||||
dataList: [],
|
||||
pageIndex: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
dataListLoading: false,
|
||||
dataListSelections: [],
|
||||
addOrUpdateVisible: false
|
||||
}
|
||||
},
|
||||
activated() {
|
||||
this.getDataList()
|
||||
},
|
||||
methods: {
|
||||
// 获取数据列表
|
||||
getDataList() {
|
||||
this.dataListLoading = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/templateMsgLog/list'),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams({
|
||||
'page': this.pageIndex,
|
||||
'limit': this.pageSize,
|
||||
'touser': this.dataForm.touser,
|
||||
'sidx': 'send_time',
|
||||
'order': 'desc'
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.dataList = data.page.list
|
||||
this.totalCount = data.page.totalCount
|
||||
} else {
|
||||
this.dataList = []
|
||||
this.totalCount = 0
|
||||
}
|
||||
this.dataListLoading = false
|
||||
})
|
||||
},
|
||||
// 每页数
|
||||
sizeChangeHandle(val) {
|
||||
this.pageSize = val
|
||||
this.pageIndex = 1
|
||||
this.getDataList()
|
||||
},
|
||||
// 当前页
|
||||
currentChangeHandle(val) {
|
||||
this.pageIndex = val
|
||||
this.getDataList()
|
||||
},
|
||||
// 多选
|
||||
selectionChangeHandle(val) {
|
||||
this.dataListSelections = val
|
||||
},
|
||||
// 删除
|
||||
deleteHandle(id) {
|
||||
var ids = id ? [id] : this.dataListSelections.map(item => item.logId)
|
||||
this.$confirm(`确定对[id=${ids.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/templateMsgLog/delete'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData(ids, false)
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
this.getDataList()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
tableJsonFormat(row, column, cellValue){
|
||||
if (!cellValue) {
|
||||
return '';
|
||||
}
|
||||
return JSON.stringify(cellValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,137 @@
|
|||
<template>
|
||||
<div class="mod-config">
|
||||
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
|
||||
<el-form-item>
|
||||
<el-input v-model="dataForm.key" placeholder="参数名" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList()">查询</el-button>
|
||||
<el-button v-if="isAuth('wx:wxaccount:save')" type="primary" @click="addOrUpdateHandle()">新增</el-button>
|
||||
<el-button v-if="isAuth('wx:wxaccount:delete')" type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0">批量删除</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="dataList" border v-loading="dataListLoading" @selection-change="selectionChangeHandle" style="width: 100%;">
|
||||
<el-table-column type="selection" header-align="center" align="center" width="50">
|
||||
</el-table-column>
|
||||
<el-table-column prop="appid" header-align="center" align="center" label="appid">
|
||||
</el-table-column>
|
||||
<el-table-column prop="name" header-align="center" align="center" label="公众号名称">
|
||||
</el-table-column>
|
||||
<el-table-column prop="type" header-align="center" align="center" label="类型" :formatter="accountTypeFormat">
|
||||
</el-table-column>
|
||||
<el-table-column prop="verified" header-align="center" align="center" label="是否认证">
|
||||
<span slot-scope="scope">{{scope.row.verified?"是":"否"}}</span>
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" header-align="center" align="center" width="150" label="操作">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="small" @click="accessInfo(scope.row)">接入</el-button>
|
||||
<el-button type="text" size="small" @click="addOrUpdateHandle(scope.row)">修改</el-button>
|
||||
<el-button type="text" size="small" @click="deleteHandle(scope.row.appid)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update>
|
||||
<account-access v-if="accountAccessVisible" ref="accountAccessDialog"></account-access>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AddOrUpdate from './account/wx-account-add-or-update'
|
||||
import AccountAccess from './account/wx-account-access-info'
|
||||
import { mapState } from 'vuex'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dataForm: {
|
||||
key: ''
|
||||
},
|
||||
dataList: [],
|
||||
dataListLoading: false,
|
||||
dataListSelections: [],
|
||||
addOrUpdateVisible: false,
|
||||
accountAccessVisible:false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
AddOrUpdate,AccountAccess
|
||||
},
|
||||
computed: mapState({
|
||||
ACCOUNT_TYPES: state=>state.wxAccount.ACCOUNT_TYPES
|
||||
}),
|
||||
activated() {
|
||||
this.getDataList()
|
||||
},
|
||||
methods: {
|
||||
// 获取数据列表
|
||||
getDataList() {
|
||||
this.dataListLoading = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/wxAccount/list'),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams({
|
||||
'key': this.dataForm.key
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.dataList = data.list
|
||||
this.$store.commit('wxAccount/updateAccountList', data.list)
|
||||
} else {
|
||||
this.dataList = []
|
||||
}
|
||||
this.dataListLoading = false
|
||||
})
|
||||
},
|
||||
// 多选
|
||||
selectionChangeHandle(val) {
|
||||
this.dataListSelections = val
|
||||
},
|
||||
// 新增 / 修改
|
||||
addOrUpdateHandle(item) {
|
||||
this.addOrUpdateVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.addOrUpdate.init(item)
|
||||
})
|
||||
},
|
||||
accessInfo(item){
|
||||
this.accountAccessVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.accountAccessDialog.init(item)
|
||||
})
|
||||
},
|
||||
// 删除
|
||||
deleteHandle(appid) {
|
||||
var ids = appid ? [appid] : this.dataListSelections.map(item => {
|
||||
return item.appid
|
||||
})
|
||||
this.$confirm(`确定对[appid=${ids.join(',')}]进行[${appid ? '删除' : '批量删除'}]操作?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/wxAccount/delete'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData(ids, false)
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
this.getDataList()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
accountTypeFormat(row, column, cellValue) {
|
||||
return this.ACCOUNT_TYPES[cellValue];
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,57 @@
|
|||
<template>
|
||||
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
||||
<el-tab-pane :label="'图片素材('+assetsCount.imageCount+')'" name="image" lazy>
|
||||
<material-file fileType="image" ref="imagePanel" @change="materialCount"></material-file>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="'语音素材('+assetsCount.voiceCount+')'" name="voice" lazy>
|
||||
<material-file fileType="voice" ref="voicePanel" @change="materialCount"></material-file>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="'视频素材('+assetsCount.videoCount+')'" name="video" lazy>
|
||||
<material-file fileType="video" ref="videoPanel" @change="materialCount"></material-file>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="'图文素材('+assetsCount.newsCount+')'" name="news" lazy>
|
||||
<material-news ref="newsPanel" @change="materialCount"></material-news>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'image',
|
||||
assetsCount:{
|
||||
imageCount:'..',
|
||||
videoCount:'..',
|
||||
voiceCount:'..',
|
||||
newsCount:'..'
|
||||
}
|
||||
};
|
||||
},
|
||||
components: {
|
||||
MaterialFile:()=>import('./assets/material-file'),
|
||||
MaterialNews:()=>import('./assets/material-news')
|
||||
},
|
||||
mounted(){
|
||||
this.materialCount();
|
||||
},
|
||||
methods: {
|
||||
handleTabClick(tab, event) {
|
||||
this.$nextTick(()=>{
|
||||
this.$refs[tab.name+'Panel'].init()
|
||||
})
|
||||
},
|
||||
materialCount(){
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/wxAssets/materialCount')
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code == 200) {
|
||||
this.assetsCount=data.data
|
||||
} else {
|
||||
this.$message.error(data.msg);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,125 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="menu-input-group" style="border-bottom: 2px #e8e8e8 solid;">
|
||||
<div class="menu-name">{{button.name}}</div>
|
||||
<div class="menu-del" @click="$emit('delMenu')">删除菜单</div>
|
||||
</div>
|
||||
<div class="menu-input-group">
|
||||
<div class="menu-label">菜单名称</div>
|
||||
<div class="menu-input">
|
||||
<input type="text" name="name" placeholder="请输入菜单名称" class="menu-input-text" v-model="button.name" @input="checkMenuName(button.name)">
|
||||
<p class="menu-tips" style="color:#e15f63" v-show="menuNameBounds">字数超过上限</p>
|
||||
<p class="menu-tips">字数不超过{{selectedMenuLevel==1?'5':'8'}}个汉字</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-show="!button.subButtons || button.subButtons.length==0">
|
||||
<div class="menu-input-group">
|
||||
<div class="menu-label">菜单内容</div>
|
||||
<div class="menu-input">
|
||||
<select v-model="button.type" name="type" class="menu-input-text">
|
||||
<option value="view">跳转网页(view)</option>
|
||||
<option value="media_id">发送消息(media_id)</option>
|
||||
<!--<option value="view_limited">跳转公众号图文消息链接(view_limited)</option>-->
|
||||
<option value="miniprogram">打开指定小程序(miniprogram)</option>
|
||||
<option value="click">自定义点击事件(click)</option>
|
||||
<option value="scancode_push">扫码上传消息(scancode_push)</option>
|
||||
<option value="scancode_waitmsg">扫码提示下发(scancode_waitmsg)</option>
|
||||
<option value="pic_sysphoto">系统相机拍照(pic_sysphoto)</option>
|
||||
<option value="pic_photo_or_album">弹出拍照或者相册(pic_photo_or_album)</option>
|
||||
<option value="pic_weixin">弹出微信相册(pic_weixin)</option>
|
||||
<option value="location_select">弹出地理位置选择器(location_select)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-content" v-if="button.type=='view'">
|
||||
<div class="menu-input-group">
|
||||
<p class="menu-tips">订阅者点击该子菜单会跳到以下链接</p>
|
||||
<div class="menu-label">页面地址</div>
|
||||
<div class="menu-input">
|
||||
<input type="text" placeholder="" class="menu-input-text" v-model="button.url">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-content" v-else-if="button.type=='media_id'">
|
||||
<div class="menu-input-group">
|
||||
<p class="menu-tips">订阅者点击该菜单会收到以下图文消息</p>
|
||||
<div class="menu-label">media_id</div>
|
||||
<div class="menu-input">
|
||||
<input type="text" placeholder="图文消息media_id" class="menu-input-text" v-model="button.mediaId">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-content" v-else-if="button.type=='miniprogram'">
|
||||
<div class="menu-input-group">
|
||||
<p class="menu-tips">订阅者点击该子菜单会跳到以下小程序</p>
|
||||
<div class="menu-label">小程序appId</div>
|
||||
<div class="menu-input">
|
||||
<input type="text" placeholder="小程序的appId(仅认证公众号可配置)" class="menu-input-text" v-model="button.appId">
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-input-group">
|
||||
<div class="menu-label">小程序路径</div>
|
||||
<div class="menu-input">
|
||||
<input type="text" placeholder="小程序的页面路径 pages/index/index" class="menu-input-text" v-model="button.pagePath">
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-input-group">
|
||||
<div class="menu-label">备用网页</div>
|
||||
<div class="menu-input">
|
||||
<input type="text" placeholder="" class="menu-input-text" v-model="button.url">
|
||||
<p class="menu-tips">旧版微信客户端无法支持小程序,用户点击菜单时将会打开备用网页。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="menu-content" v-else>
|
||||
<div class="menu-input-group">
|
||||
<p class="menu-tips">用于消息接口推送,不超过128字节</p>
|
||||
<div class="menu-label">菜单KEY值</div>
|
||||
<div class="menu-input">
|
||||
<input type="text" placeholder="" class="menu-input-text" v-model="button.key">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
selectedMenuLevel: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
button: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
menuNameBounds: false,//菜单长度是否过长
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
//检查菜单名称长度
|
||||
checkMenuName: function (val) {
|
||||
if (this.selectedMenuLevel == 1 && this.getMenuNameLen(val) <= 10) {
|
||||
this.menuNameBounds = false
|
||||
} else if (this.selectedMenuLevel == 2 && this.getMenuNameLen(val) <= 16) {
|
||||
this.menuNameBounds = false
|
||||
} else {
|
||||
this.menuNameBounds = true
|
||||
}
|
||||
},
|
||||
//获取菜单名称长度
|
||||
getMenuNameLen: function (val) {
|
||||
var len = 0;
|
||||
for (var i = 0; i < val.length; i++) {
|
||||
var a = val.charAt(i);
|
||||
a.match(/[^\x00-\xff]/ig) != null ? len += 2 : len += 1;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,159 @@
|
|||
<template>
|
||||
<div>
|
||||
<div id="app-menu">
|
||||
<!-- 预览窗 -->
|
||||
<div class="weixin-preview">
|
||||
<div class="weixin-bd">
|
||||
<div class="weixin-header">公众号菜单</div>
|
||||
<ul class="weixin-menu" id="weixin-menu">
|
||||
<li v-for="(btn,i) in menu.buttons" :key="i" class="menu-item" :class="{'current':selectedMenuIndex===i&&selectedMenuLevel==1}" @click="selectMenu(i)">
|
||||
<div class="menu-item-title">
|
||||
<span>{{ btn.name }}</span>
|
||||
</div>
|
||||
<ul class="weixin-sub-menu">
|
||||
<li v-for="(sub,i2) in btn.subButtons" :key="i2" class="menu-sub-item" :class="{'current':selectedMenuIndex===i&&selectedSubMenuIndex===i2&&selectedMenuLevel==2,'on-drag-over':onDragOverMenu==(i+'_'+i2)}" @click.stop="selectSubMenu(i,i2)" draggable="true" @dragstart="selectSubMenu(i,i2)" @dragover.prevent="onDragOverMenu=(i+'_'+i2)" @drop="onDrop(i,i2)">
|
||||
<div class="menu-item-title">
|
||||
<span>{{sub.name}}</span>
|
||||
</div>
|
||||
</li>
|
||||
<li v-if="btn.subButtons.length<5" class="menu-sub-item" :class="{'on-drag-over':onDragOverMenu==(i+'_'+btn.subButtons.length)}" @click.stop="addMenu(2,i)" @dragover.prevent="onDragOverMenu=(i+'_'+btn.subButtons.length)" @drop="onDrop(i,btn.subButtons.length)">
|
||||
<div class="menu-item-title">
|
||||
<i class="el-icon-plus"></i>
|
||||
</div>
|
||||
</li>
|
||||
<i class="menu-arrow arrow_out"></i>
|
||||
<i class="menu-arrow arrow_in"></i>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="menu-item" v-if="menu.buttons.length<3" @click="addMenu(1)"> <i class="el-icon-plus"></i></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 菜单编辑器 -->
|
||||
<div class="weixin-menu-detail" v-if="selectedMenuLevel>0">
|
||||
<wx-menu-button-editor :button="selectedButton" :selectedMenuLevel="selectedMenuLevel" @delMenu="delMenu"></wx-menu-button-editor>
|
||||
</div>
|
||||
</div>
|
||||
<div class="weixin-btn-group" v-if="isAuth('wx:menu:save')" @click="updateWxMenu">
|
||||
<el-button type="success" icon="el-icon-upload">发布</el-button>
|
||||
<el-button type="warning" icon="el-icon-delete" @click="delMenu">清空</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
components: {
|
||||
wxMenuButtonEditor: () => import('./wx-menu-button-editor')
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
menu: { 'buttons': [] },//当前菜单
|
||||
selectedMenuIndex: '',//当前选中菜单索引
|
||||
selectedSubMenuIndex: '',//当前选中子菜单索引
|
||||
selectedMenuLevel: 0,//选中菜单级别
|
||||
selectedButton: '',//选中的菜单按钮
|
||||
onDragOverMenu:'' //当前鼠标拖动到的位置
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getWxMenu();
|
||||
},
|
||||
methods: {
|
||||
getWxMenu() {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/wxMenu/getMenu')
|
||||
}).then(({ data }) => {
|
||||
if (data.code == 200) {
|
||||
this.menu = data.data.menu;
|
||||
} else {
|
||||
this.$message({
|
||||
type: 'error',
|
||||
message: data.msg
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
},
|
||||
//选中主菜单
|
||||
selectMenu(i) {
|
||||
this.selectedMenuLevel = 1
|
||||
this.selectedSubMenuIndex = ''
|
||||
this.selectedMenuIndex = i
|
||||
this.selectedButton = this.menu.buttons[i]
|
||||
},
|
||||
//选中子菜单
|
||||
selectSubMenu(i,i2) {
|
||||
this.selectedMenuLevel = 2
|
||||
this.selectedMenuIndex = i
|
||||
this.selectedSubMenuIndex = i2
|
||||
this.selectedButton = this.menu.buttons[i].subButtons[i2]
|
||||
},
|
||||
//添加菜单
|
||||
addMenu(level,i) {
|
||||
if (level == 1 && this.menu.buttons.length < 3) {
|
||||
this.menu.buttons.push({
|
||||
"type": "view",
|
||||
"name": "菜单名称",
|
||||
"subButtons": [],
|
||||
"url": ""
|
||||
})
|
||||
this.selectMenu(this.menu.buttons.length - 1)
|
||||
}
|
||||
if (level == 2 && this.menu.buttons[i].subButtons.length < 5) {
|
||||
this.menu.buttons[i].subButtons.push({
|
||||
"type": "view",
|
||||
"name": "子菜单名称",
|
||||
"url": ""
|
||||
})
|
||||
this.selectSubMenu(i,this.menu.buttons[i].subButtons.length - 1)
|
||||
}
|
||||
},
|
||||
//删除菜单
|
||||
delMenu() {
|
||||
if (this.selectedMenuLevel == 1 && confirm('删除后菜单下设置的内容将被删除')) {
|
||||
this.menu.buttons.splice(this.selectedMenuIndex, 1);
|
||||
this.unSelectMenu()
|
||||
} else if (this.selectedMenuLevel == 2) {
|
||||
this.menu.buttons[this.selectedMenuIndex].subButtons.splice(this.selectedSubMenuIndex, 1);
|
||||
this.unSelectMenu()
|
||||
}
|
||||
},
|
||||
unSelectMenu(){//不选中任何菜单
|
||||
this.selectedMenuLevel = 0
|
||||
this.selectedMenuIndex = ''
|
||||
this.selectedSubMenuIndex = ''
|
||||
this.selectedButton = ''
|
||||
},
|
||||
updateWxMenu() {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/wxMenu/updateMenu'),
|
||||
data: this.menu,
|
||||
method: 'post'
|
||||
}).then(({ data }) => {
|
||||
if (data.code == 200) {
|
||||
this.$message.success('操作成功')
|
||||
} else {
|
||||
this.$message.error(data.msg);
|
||||
}
|
||||
|
||||
});
|
||||
},
|
||||
onDrop(i,i2){//拖拽移动位置
|
||||
this.onDragOverMenu='';
|
||||
if(i==this.selectedMenuIndex && i2==this.selectedSubMenuIndex) //拖拽到了原位置
|
||||
return
|
||||
if(i!=this.selectedMenuIndex && this.menu.buttons[i].subButtons.length>=5){
|
||||
this.$message.error('目标组已满');
|
||||
return
|
||||
}
|
||||
this.menu.buttons[i].subButtons.splice(i2,0,this.selectedButton)
|
||||
let delSubIndex = this.selectedSubMenuIndex
|
||||
if(i==this.selectedMenuIndex && i2<this.selectedSubMenuIndex)
|
||||
delSubIndex++
|
||||
this.menu.buttons[this.selectedMenuIndex].subButtons.splice(delSubIndex, 1);
|
||||
this.unSelectMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style src="@/assets/css/wx-menu.css"></style>
|
|
@ -0,0 +1,84 @@
|
|||
<template>
|
||||
<el-dialog title="消息回复" :close-on-click-modal="false" :visible.sync="visible">
|
||||
<el-form :model="dataForm" :rules="dataRule" ref="dataForm">
|
||||
<el-form-item prop="replyContent">
|
||||
<el-input v-model="dataForm.replyContent" type="textarea" :rows="5" placeholder="回复内容" maxlength="600" show-word-limit :autosize="{ minRows: 5, maxRows: 30 }" autocomplete></el-input>
|
||||
<el-button type="text" v-show="'text'==dataForm.replyType" @click="addLink">插入链接</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="success" @click="dataFormSubmit()" :disabled="uploading">{{uploading?'发送中...':'发送'}}</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
uploading: false,
|
||||
dataForm: {
|
||||
openid:'',
|
||||
replyType:'text',
|
||||
replyContent:''
|
||||
},
|
||||
dataRule: {
|
||||
replyContent: [
|
||||
{ required: true, message: "回复内容不能为空", trigger: "blur" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
components:{
|
||||
WxMsgPreview:()=>import('@/components/wx-msg-preview')
|
||||
},
|
||||
methods: {
|
||||
init(openid) {
|
||||
if(!openid)throw '参数异常'
|
||||
this.dataForm.openid=openid
|
||||
this.visible = true
|
||||
},
|
||||
// 表单提交
|
||||
dataFormSubmit() {
|
||||
if(this.uploading)return
|
||||
this.uploading=true
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/manage/wxMsg/reply`),
|
||||
method: 'post',
|
||||
data: this.$http.adornData(this.dataForm)
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '回复成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
this.visible = false
|
||||
|
||||
}
|
||||
})
|
||||
this.$emit("success",{...this.dataForm});
|
||||
this.dataForm.replyContent=''
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
this.uploading=false
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
addLink() {
|
||||
this.dataForm.replyContent += '<a href="链接地址">链接文字</a>'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.msg-container{
|
||||
background: #eee;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,184 @@
|
|||
<template>
|
||||
<div class="mod-config">
|
||||
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
|
||||
<el-form-item>
|
||||
<el-select v-model="dataForm.startTime" placeholder="时间">
|
||||
<el-option v-for="(name,key) in timeSelections" :key="key" :value="name" :label="key"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-select v-model="dataForm.msgTypes" placeholder="消息类型">
|
||||
<el-option value="" label="不限类型"></el-option>
|
||||
<el-option value="text,image,voice,shortvideo,video,news,music,location,link" label="消息"></el-option>
|
||||
<el-option value="event,transfer_customer_service" label="事件"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList()">查询</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="text-gray">
|
||||
24小时内消息可回复。此后台展示消息有一分钟左右延迟,如需畅聊请使用
|
||||
<a href="https://mpkf.weixin.qq.com/" target="_blank">公众平台客服</a>
|
||||
</div>
|
||||
<div v-loading="dataListLoading">
|
||||
<div class="msg-item" v-for="(msg,index) in dataList" :key="index">
|
||||
<div class="avatar"><el-avatar shape="square" :size="60" :src="getUserInfo(msg.openid).headimgurl"></el-avatar></div>
|
||||
<div class="item-content">
|
||||
<div class="flex justify-between margin-bottom">
|
||||
<div class="text-cut">{{getUserInfo(msg.openid).nickname || '--'}}</div>
|
||||
<div>{{$moment(msg.createTime).calendar()}}</div>
|
||||
<div class="reply-btn">
|
||||
<div v-if="canReply(msg.createTime)" @click="replyHandle(msg.openid)" class="el-icon-s-promotion">回复</div>
|
||||
</div>
|
||||
</div>
|
||||
<wx-msg-preview :msg="msg" singleLine></wx-msg-preview>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" :current-page="pageIndex" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :total="totalCount" layout="total, sizes, prev, pager, next, jumper">
|
||||
</el-pagination>
|
||||
<!-- 弹窗, 消息回复 -->
|
||||
<wx-msg-reply ref="wxMsgReply" @success="onReplyed"></wx-msg-reply>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const TIME_FORMAT = 'YYYY/MM/DD hh:mm:ss'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
timeSelections:{
|
||||
'近24小时':this.$moment().subtract(1, 'days').format(TIME_FORMAT),
|
||||
'近3天': this.$moment().subtract(3, 'days').format(TIME_FORMAT),
|
||||
'近7天': this.$moment().subtract(7, 'days').format(TIME_FORMAT),
|
||||
'近30天': this.$moment().subtract(30, 'days').format(TIME_FORMAT),
|
||||
},
|
||||
dataForm: {
|
||||
startTime: this.$moment().subtract(1, 'days').format(TIME_FORMAT),
|
||||
msgTypes: ''
|
||||
},
|
||||
dataList: [],
|
||||
userDataList:[],
|
||||
pageIndex: 1,
|
||||
pageSize: 20,
|
||||
totalCount: 0,
|
||||
dataListLoading: false,
|
||||
dataListSelections: []
|
||||
}
|
||||
},
|
||||
components: {
|
||||
WxMsgReply:()=>import('./wx-msg-reply'),
|
||||
WxMsgPreview:()=>import('@/components/wx-msg-preview')
|
||||
},
|
||||
activated() {
|
||||
this.getDataList()
|
||||
},
|
||||
methods: {
|
||||
// 获取数据列表
|
||||
getDataList() {
|
||||
this.dataListLoading = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/wxMsg/list'),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams({
|
||||
'page': this.pageIndex,
|
||||
'limit': this.pageSize,
|
||||
'msgTypes': this.dataForm.msgTypes,
|
||||
'startTime':this.dataForm.startTime,
|
||||
'sidx': 'create_time',
|
||||
'order': 'desc'
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.dataList = data.page.list
|
||||
this.totalCount = data.page.totalCount
|
||||
this.refreshUserList(this.dataList)
|
||||
} else {
|
||||
this.dataList = []
|
||||
this.totalCount = 0
|
||||
}
|
||||
this.dataListLoading = false
|
||||
})
|
||||
},
|
||||
refreshUserList(msgList){
|
||||
let openidList=msgList.map(msg=>msg.openid).filter(openid=>!this.userDataList.some(u=>u.openid==openid))
|
||||
if(!openidList.length)return
|
||||
openidList = Array.from(new Set(openidList))//去重
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/wxUser/listByIds'),
|
||||
method: 'post',
|
||||
data: this.$http.adornParams(openidList,false)
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.userDataList = this.userDataList.concat(data.data)
|
||||
}
|
||||
})
|
||||
},
|
||||
getUserInfo(openid){
|
||||
return this.userDataList.find(u=>u.openid==openid) || {nickname:'--',headimgurl:''}
|
||||
},
|
||||
// 是否可回复,24小时内可回复
|
||||
canReply(time){
|
||||
return new Date(time).getTime()>new Date().getTime()-24*60*60*1000
|
||||
},
|
||||
// 每页数
|
||||
sizeChangeHandle(val) {
|
||||
this.pageSize = val
|
||||
this.pageIndex = 1
|
||||
this.getDataList()
|
||||
},
|
||||
// 当前页
|
||||
currentChangeHandle(val) {
|
||||
this.pageIndex = val
|
||||
this.getDataList()
|
||||
},
|
||||
// 多选
|
||||
selectionChangeHandle(val) {
|
||||
this.dataListSelections = val
|
||||
},
|
||||
// 回复消息
|
||||
replyHandle(openid) {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.wxMsgReply.init(openid)
|
||||
})
|
||||
},
|
||||
onReplyed(replyMsg){
|
||||
this.dataList.unshift({
|
||||
openid : replyMsg.openid,
|
||||
msgType : replyMsg.replyType,
|
||||
detail : {
|
||||
content : replyMsg.replyContent
|
||||
},
|
||||
inOut : 1,
|
||||
createTime : new Date()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.msg-item{
|
||||
border: 1px solid #DCDFE6;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: top;
|
||||
margin-top: 20px;
|
||||
padding: 10px 20px;
|
||||
}
|
||||
.avatar{
|
||||
flex: 0;
|
||||
display: inline-block;
|
||||
min-width: 60px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
.item-content{
|
||||
flex: 1;
|
||||
line-height: 20px;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.reply-btn{
|
||||
width: 50px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,86 @@
|
|||
<template>
|
||||
<el-dialog :title="!dataForm.id ? '新增' : '修改'" :close-on-click-modal="false" :visible.sync="visible">
|
||||
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="100px">
|
||||
<el-form-item label="二维码类型" prop="isTemp">
|
||||
<el-radio v-model="dataForm.isTemp" :label="true">临时</el-radio>
|
||||
<el-radio v-model="dataForm.isTemp" :label="false">永久</el-radio>
|
||||
<div>
|
||||
<a class="text-warning" v-show="!dataForm.isTemp" target="_blank" href="https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html">注意永久二维码上限10万个,且暂时无法删除旧的二维码</a>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="场景值" prop="sceneStr">
|
||||
<el-input v-model="dataForm.sceneStr" placeholder="任意字符串" maxlength="64"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="失效时间/秒" prop="expireSeconds" v-if="dataForm.isTemp">
|
||||
<el-input v-model="dataForm.expireSeconds" placeholder="单位:秒,最大2592000(30天)"></el-input>
|
||||
<div>最大30天,当前设置:<span class="text-warning">{{dataForm.expireSeconds/(24*3600)}}天</span></div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button type="primary" @click="dataFormSubmit()">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
dataForm: {
|
||||
isTemp: true,
|
||||
sceneStr: '',
|
||||
expireSeconds: 2592000
|
||||
},
|
||||
dataRule: {
|
||||
isTemp: [
|
||||
{ required: true, message: '二维码类型不能为空', trigger: 'blur' }
|
||||
],
|
||||
sceneStr: [
|
||||
{ required: true, message: '场景值ID不能为空', trigger: 'blur' }
|
||||
],
|
||||
expireSeconds: [
|
||||
{ required: true, message: '该二维码失效时间不能为空', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init(id) {
|
||||
this.dataForm.id = id || 0
|
||||
this.visible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs['dataForm'].resetFields()
|
||||
})
|
||||
},
|
||||
// 表单提交
|
||||
dataFormSubmit() {
|
||||
this.$refs['dataForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/manage/wxQrCode/createTicket`),
|
||||
method: 'post',
|
||||
data: this.$http.adornData(this.dataForm)
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
this.visible = false
|
||||
this.$emit('refreshDataList')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,142 @@
|
|||
<template>
|
||||
<div class="mod-config">
|
||||
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
|
||||
<el-form-item>
|
||||
<el-input v-model="dataForm.sceneStr" placeholder="场景值" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList()">查询</el-button>
|
||||
<el-button v-if="isAuth('wx:wxqrcode:save')" type="primary" @click="addOrUpdateHandle()">新增</el-button>
|
||||
<el-button v-if="isAuth('wx:wxqrcode:delete')" type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0">批量删除</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="dataList" border v-loading="dataListLoading" @selection-change="selectionChangeHandle" style="width: 100%;">
|
||||
<el-table-column type="selection" header-align="center" align="center" width="50">
|
||||
</el-table-column>
|
||||
<el-table-column prop="id" header-align="center" align="center" label="ID">
|
||||
</el-table-column>
|
||||
<el-table-column prop="isTemp" header-align="center" align="center" label="类型">
|
||||
<span slot-scope="scope">{{scope.row.isTemp?'临时':'永久'}}</span>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sceneStr" header-align="center" align="center" label="场景值">
|
||||
</el-table-column>
|
||||
<el-table-column prop="ticket" header-align="center" align="center" show-overflow-tooltip label="二维码图片">
|
||||
<a :href="'https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket='+scope.row.ticket" slot-scope="scope">{{scope.row.ticket}}</a>
|
||||
</el-table-column>
|
||||
<el-table-column prop="url" header-align="center" align="center" show-overflow-tooltip label="解析后的地址">
|
||||
<a :href="scope.row.url" slot-scope="scope">{{scope.row.url}}</a>
|
||||
</el-table-column>
|
||||
<el-table-column prop="expireTime" header-align="center" align="center" width="100" label="失效时间">
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" header-align="center" align="center" width="150" label="操作">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="small" @click="deleteHandle(scope.row.id)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" :current-page="pageIndex" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :total="totalPage" layout="total, sizes, prev, pager, next, jumper">
|
||||
</el-pagination>
|
||||
<!-- 弹窗, 新增 / 修改 -->
|
||||
<add-or-update v-if="addOrUpdateVisible" ref="addOrUpdate" @refreshDataList="getDataList"></add-or-update>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AddOrUpdate from './wx-qrcode-add-or-update'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dataForm: {
|
||||
sceneStr: ''
|
||||
},
|
||||
dataList: [],
|
||||
pageIndex: 1,
|
||||
pageSize: 10,
|
||||
totalPage: 0,
|
||||
dataListLoading: false,
|
||||
dataListSelections: [],
|
||||
addOrUpdateVisible: false
|
||||
}
|
||||
},
|
||||
components: {
|
||||
AddOrUpdate
|
||||
},
|
||||
activated() {
|
||||
this.getDataList()
|
||||
},
|
||||
methods: {
|
||||
// 获取数据列表
|
||||
getDataList() {
|
||||
this.dataListLoading = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/wxQrCode/list'),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams({
|
||||
'page': this.pageIndex,
|
||||
'limit': this.pageSize,
|
||||
'sceneStr': this.dataForm.sceneStr,
|
||||
'sidx': 'id',
|
||||
'order': 'desc'
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.dataList = data.page.list
|
||||
this.totalPage = data.page.totalCount
|
||||
} else {
|
||||
this.dataList = []
|
||||
this.totalPage = 0
|
||||
}
|
||||
this.dataListLoading = false
|
||||
})
|
||||
},
|
||||
// 每页数
|
||||
sizeChangeHandle(val) {
|
||||
this.pageSize = val
|
||||
this.pageIndex = 1
|
||||
this.getDataList()
|
||||
},
|
||||
// 当前页
|
||||
currentChangeHandle(val) {
|
||||
this.pageIndex = val
|
||||
this.getDataList()
|
||||
},
|
||||
// 多选
|
||||
selectionChangeHandle(val) {
|
||||
this.dataListSelections = val
|
||||
},
|
||||
// 新增 / 修改
|
||||
addOrUpdateHandle(id) {
|
||||
this.addOrUpdateVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.addOrUpdate.init(id)
|
||||
})
|
||||
},
|
||||
// 删除
|
||||
deleteHandle(id) {
|
||||
var ids = id ? [id] : this.dataListSelections.map(item => item.id)
|
||||
this.$confirm(`确定对[id=${ids.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?(仅删存档)`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/wxQrCode/delete'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData(ids, false)
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => this.getDataList()
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,102 @@
|
|||
<template>
|
||||
<el-dialog :title="modeDesc[mode]+'用户标签'" :close-on-click-modal="false" :visible.sync="dialogVisible">
|
||||
<div>
|
||||
<el-select v-model="selectedTagid" filterable placeholder="请选择标签" style="width:100%">
|
||||
<el-option v-for="tagid in tagidsInOption" :key="tagid" :label="getTagName(tagid)" :value="tagid"></el-option>
|
||||
</el-select>
|
||||
<div style="margin-top:20px;">已选择用户数:{{wxUsers.length}}</div>
|
||||
</div>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible=false">关闭</el-button>
|
||||
<el-button type="primary" @click="dataFormSubmit()" :disabled="submitting">{{submitting?'保存中...':'确定'}}</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
export default {
|
||||
name:'wx-user-tagging',
|
||||
props:{
|
||||
wxUsers:Array,
|
||||
},
|
||||
data(){
|
||||
return{
|
||||
mode:'tagging',//操作,tagging | untagging
|
||||
modeDesc:{
|
||||
'tagging':'绑定',
|
||||
'untagging':'解绑'
|
||||
},
|
||||
selectedTagid:'',
|
||||
dialogVisible:false,
|
||||
submitting:false
|
||||
}
|
||||
},
|
||||
computed: mapState({
|
||||
wxUserTags:state=>state.wxUserTags.tags,
|
||||
/**
|
||||
* 返回下拉选择框中的选项列表
|
||||
* 假设 all= 全部标签,intersection = 用户标签交集(即所有用户都有的) ,union=用户标签并集(即至少一个用户的)
|
||||
* 那么绑定时可选:all-intersection的差集,即所有用户都有的就不列出来了
|
||||
* 解绑时可选:,union ,即用户有的标签都列出来
|
||||
*/
|
||||
tagidsInOption(){
|
||||
let userTags=this.wxUsers.map(u=>u.tagidList || [])//示例:[[1,2],[],[1,3]]
|
||||
if(this.mode=='tagging'){//绑定标签时可选:所有标签 - 用户标签交集
|
||||
let all = this.wxUserTags.map(item=>item.id)
|
||||
return all.filter(tagid=>!userTags.every(tagsIdArray=>tagsIdArray.indexOf(tagid)>-1))
|
||||
}else if(this.mode=='untagging'){//解绑标签时可选:用户标签的并集
|
||||
let unionSet = new Set();
|
||||
userTags.forEach(tagsIdArray=>{
|
||||
tagsIdArray.forEach(tagid => unionSet.add(tagid))
|
||||
});//将用户的标签放到unionSet中去重
|
||||
return Array.from(unionSet);//unionSet转为数组
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
}),
|
||||
methods:{
|
||||
init(mode){
|
||||
if('tagging'==mode || 'untagging'==mode){
|
||||
this.mode=mode;
|
||||
this.dialogVisible=true
|
||||
}else{
|
||||
throw('mode参数有误')
|
||||
}
|
||||
},
|
||||
getTagName(tagid){
|
||||
let tag = this.wxUserTags.find(item=>item.id==tagid)
|
||||
return tag?tag.name : "?"
|
||||
},
|
||||
dataFormSubmit(){
|
||||
if(this.submitting)return
|
||||
if(!this.selectedTagid){
|
||||
this.$message.error('未选择标签')
|
||||
return
|
||||
}
|
||||
this.submitting=true
|
||||
let openidList=this.wxUsers.map(u=>u.openid)
|
||||
this.$http({
|
||||
url: this.$http.adornUrl(`/manage/wxUserTags/${this.mode=='tagging'?'batchTagging':'batchUnTagging'}`),
|
||||
method: 'post',
|
||||
data:this.$http.adornData({
|
||||
tagid : this.selectedTagid,
|
||||
openidList : openidList
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
this.submitting=false
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功,列表数据需稍后刷新查看',
|
||||
type: 'success',
|
||||
onClose: () =>this.dialogVisible=false
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,209 @@
|
|||
<template>
|
||||
<div class="mod-config">
|
||||
<el-form :inline="true" :model="dataForm" @keyup.enter.native="getDataList()">
|
||||
<el-form-item>
|
||||
<el-select v-model="dataForm.tagid" filterable clearable placeholder="用户标签">
|
||||
<el-option v-for="item in wxUserTags" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="dataForm.nickname" placeholder="昵称" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="dataForm.city" placeholder="城市" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-input v-model="dataForm.qrSceneStr" placeholder="关注场景值" clearable></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button @click="getDataList()">查询</el-button>
|
||||
<el-button v-if="isAuth('wx:wxuser:save')" type="primary" @click="$refs.wxUserTagging.init('tagging')" :disabled="dataListSelections.length <= 0">绑定标签</el-button>
|
||||
<el-button v-if="isAuth('wx:wxuser:save')" type="primary" @click="$refs.wxUserTagging.init('untagging')" :disabled="dataListSelections.length <= 0">解绑标签</el-button>
|
||||
<el-button v-if="isAuth('wx:wxuser:delete')" type="danger" @click="deleteHandle()" :disabled="dataListSelections.length <= 0">批量删除</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item class="fr">
|
||||
<el-button icon="el-icon-price-tag" type="success" @click="$refs.wxUserTagsEditor.show()">标签管理</el-button>
|
||||
<el-button icon="el-icon-sort" type="success" @click="syncWxUsers()">同步粉丝</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="dataList" border v-loading="dataListLoading" @selection-change="selectionChangeHandle" style="width: 100%;">
|
||||
<el-table-column type="selection" header-align="center" align="center" width="50">
|
||||
</el-table-column>
|
||||
<el-table-column prop="openid" header-align="center" align="center" label="openid">
|
||||
</el-table-column>
|
||||
<el-table-column prop="nickname" header-align="center" align="center" label="昵称">
|
||||
</el-table-column>
|
||||
<el-table-column prop="sex" header-align="center" align="center" label="性别" :formatter="sexFormat">
|
||||
</el-table-column>
|
||||
<el-table-column prop="city" header-align="center" align="center" label="城市">
|
||||
</el-table-column>
|
||||
<el-table-column prop="headimgurl" header-align="center" align="center" label="头像">
|
||||
<img class="headimg" slot-scope="scope" v-if="scope.row.headimgurl" :src="scope.row.headimgurl" />
|
||||
</el-table-column>
|
||||
<el-table-column prop="tagidList" header-align="center" align="center" label="标签" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<span v-for="tagid in scope.row.tagidList" :key="tagid">{{getTagName(tagid)}} </span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="subscribeTime" header-align="center" align="center" label="订阅时间">
|
||||
<template slot-scope="scope">{{$moment(scope.row.subscribeTime).calendar()}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="qrSceneStr" header-align="center" align="center" label="场景值">
|
||||
</el-table-column>
|
||||
<el-table-column prop="subscribe" header-align="center" align="center" label="是否关注">
|
||||
<span slot-scope="scope">{{scope.row.subscribe?"是":"否"}}</span>
|
||||
</el-table-column>
|
||||
<el-table-column fixed="right" header-align="center" align="center" width="150" label="操作">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="small" @click="deleteHandle(scope.row.openid)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination @size-change="sizeChangeHandle" @current-change="currentChangeHandle" :current-page="pageIndex" :page-sizes="[10, 20, 50, 100]" :page-size="pageSize" :total="totalPage" layout="total, sizes, prev, pager, next, jumper">
|
||||
</el-pagination>
|
||||
<wx-user-tags-manager ref="wxUserTagsEditor" :visible="showWxUserTagsEditor" @close="showWxUserTagsEditor=false"></wx-user-tags-manager>
|
||||
<wx-user-tagging ref="wxUserTagging" :wxUsers="dataListSelections"></wx-user-tagging>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WxUserTagsManager from '@/components/wx-user-tags-manager'
|
||||
import WxUserTagging from './wx-user-tagging'
|
||||
import { mapState } from 'vuex'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dataForm: {
|
||||
tagid:'',
|
||||
nickname: '',
|
||||
city:'',
|
||||
qrSceneStr:''
|
||||
},
|
||||
dataList: [],
|
||||
pageIndex: 1,
|
||||
pageSize: 10,
|
||||
totalPage: 0,
|
||||
showWxUserTagsEditor:false,
|
||||
dataListLoading: false,
|
||||
dataListSelections: [],
|
||||
}
|
||||
},
|
||||
components: {
|
||||
WxUserTagsManager,WxUserTagging
|
||||
},
|
||||
activated() {
|
||||
this.getDataList()
|
||||
},
|
||||
computed: mapState({
|
||||
wxUserTags:state=>state.wxUserTags.tags
|
||||
}),
|
||||
methods: {
|
||||
// 获取数据列表
|
||||
getDataList() {
|
||||
this.dataListLoading = true
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/wxUser/list'),
|
||||
method: 'get',
|
||||
params: this.$http.adornParams({
|
||||
'page': this.pageIndex,
|
||||
'limit': this.pageSize,
|
||||
'nickname': this.dataForm.nickname,
|
||||
'tagid': this.dataForm.tagid,
|
||||
'city': this.dataForm.city,
|
||||
'qrSceneStr': this.dataForm.qrSceneStr,
|
||||
'sidx': 'subscribe_time',
|
||||
'order': 'desc'
|
||||
})
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.dataList = data.page.list
|
||||
this.totalPage = data.page.totalCount
|
||||
} else {
|
||||
this.dataList = []
|
||||
this.totalPage = 0
|
||||
}
|
||||
this.dataListLoading = false
|
||||
})
|
||||
},
|
||||
// 每页数
|
||||
sizeChangeHandle(val) {
|
||||
this.pageSize = val
|
||||
this.pageIndex = 1
|
||||
this.getDataList()
|
||||
},
|
||||
// 当前页
|
||||
currentChangeHandle(val) {
|
||||
this.pageIndex = val
|
||||
this.getDataList()
|
||||
},
|
||||
// 多选
|
||||
selectionChangeHandle(val) {
|
||||
this.dataListSelections = val
|
||||
},
|
||||
// 删除
|
||||
deleteHandle(id) {
|
||||
var ids = id ? [id] : this.dataListSelections.map(item => item.openid)
|
||||
this.$confirm(`确定对[id=${ids.join(',')}]进行[${id ? '删除' : '批量删除'}]操作?`, '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/wxUser/delete'),
|
||||
method: 'post',
|
||||
data: this.$http.adornData(ids, false)
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '操作成功',
|
||||
type: 'success',
|
||||
duration: 1500,
|
||||
onClose: () => {
|
||||
this.getDataList()
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
syncWxUsers(){
|
||||
this.$http({
|
||||
url: this.$http.adornUrl('/manage/wxUser/syncWxUsers'),
|
||||
method: 'post',
|
||||
}).then(({ data }) => {
|
||||
if (data && data.code === 200) {
|
||||
this.$message({
|
||||
message: '同步任务已建立,请稍候刷新查看列表',
|
||||
type: 'success',
|
||||
duration: 1500
|
||||
})
|
||||
} else {
|
||||
this.$message.error(data.msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
sexFormat(row, column, cellValue) {
|
||||
let sexType = {
|
||||
0: '未知',
|
||||
1: '男',
|
||||
2: '女'
|
||||
}
|
||||
return sexType[cellValue];
|
||||
},
|
||||
getTagName(tagid){
|
||||
let tag = this.wxUserTags.find(item=>item.id==tagid)
|
||||
return tag?tag.name : "?"
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.headimg{
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,30 @@
|
|||
module.exports = {
|
||||
publicPath: "./",
|
||||
devServer: {
|
||||
// 后端请求转发,此配置仅开发环境有效,生产环境请参考生产环境部署文档配置nginx转发
|
||||
proxy: {
|
||||
'/wx': {
|
||||
target: 'http://localhost:8080/'
|
||||
// target: 'http://154.8.196.128:8080/'
|
||||
}
|
||||
},
|
||||
port:8001
|
||||
},
|
||||
configureWebpack:{
|
||||
devServer: {
|
||||
historyApiFallback: true,
|
||||
allowedHosts:"all",
|
||||
}
|
||||
},
|
||||
chainWebpack: config => {
|
||||
// 移除 prefetch 插件
|
||||
config.plugins.delete('prefetch')
|
||||
},
|
||||
|
||||
outputDir: undefined,
|
||||
assetsDir: undefined,
|
||||
runtimeCompiler: undefined,
|
||||
productionSourceMap: false,
|
||||
parallel: undefined,
|
||||
css: undefined
|
||||
}
|
Loading…
Reference in New Issue