| @@ -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,37 @@ | |||||
| # 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是一个轻量级的公众号开发种子项目,可快速接入微信公众号管理功能 | |||||
| ## [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/) | |||||
| ## 截图 | |||||
|  | |||||
|  | |||||
|  | |||||
|  | |||||
|  | |||||
|  | |||||
|  | |||||
|  | |||||
|  | |||||
|  | |||||
|  | |||||
|  | |||||
| @@ -0,0 +1,9 @@ | |||||
| module.exports = { | |||||
| presets: [ | |||||
| '@vue/cli-plugin-babel/preset' | |||||
| ], | |||||
| "plugins": [ | |||||
| "@babel/plugin-syntax-dynamic-import" | |||||
| ], | |||||
| sourceType: 'unambiguous' | |||||
| } | |||||
| @@ -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": "^0.19.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": "^4.5.9", | |||||
| "@vue/cli-service": "^4.5.9", | |||||
| "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,155 @@ | |||||
| /** | |||||
| * 全站路由配置 | |||||
| * | |||||
| * 建议: | |||||
| * 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: '登录' } }, | |||||
| { path: '/smsManage', component: () => import('@/views/smsManage'), name: 'smsManage', 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,31 @@ | |||||
| /** | |||||
| * 邮箱 | |||||
| * @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) | |||||
| } | |||||
| /** | |||||
| * 电话号码 | |||||
| * @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,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,243 @@ | |||||
| <template> | |||||
| <div class="app-container"> | |||||
| <div class="title"> | |||||
| <!-- <img style="display: block; | |||||
| margin: 0 auto;" src="../../static/images/onlineHome/yinnongLogo.jpg" alt=""> --> | |||||
| </div> | |||||
| <!-- <van-tabs v-model="active" :swipeable="true" style="margin-top:0.5rem;padding:0 10px;"> --> | |||||
| <van-form style="margin:50px 0;"> | |||||
| <van-field | |||||
| v-model="formData.memberName" | |||||
| name="请输入姓名" | |||||
| placeholder="请输入姓名" | |||||
| :rules="[{ required: true, message: '' }]" | |||||
| /> | |||||
| <van-field | |||||
| v-model="formData.idcard" | |||||
| name="请输入身份证号" | |||||
| style="margin-top: 20px" | |||||
| placeholder="请输入身份证号" | |||||
| :rules="[{ required: true, message: '' }]" | |||||
| /> | |||||
| <van-field | |||||
| v-model="formData.mobile" | |||||
| name="请输入手机号" | |||||
| style="margin-top: 20px" | |||||
| placeholder="请输入手机号" | |||||
| :rules="[{ required: true, message: '' }]" | |||||
| /> | |||||
| <van-field | |||||
| v-model="formData.code" | |||||
| center | |||||
| clearable | |||||
| label="验证码" | |||||
| placeholder="图形验证码" | |||||
| > | |||||
| <template #button> | |||||
| <img style="width: 100px" :src="codeUrl" @click="getCode" /> | |||||
| </template> | |||||
| </van-field> | |||||
| <van-field | |||||
| v-model="formData.smsCode" | |||||
| style="margin-top: 20px" | |||||
| placeholder="请输入验证码" | |||||
| :rules="[{ required: true, message: '' }]" | |||||
| > | |||||
| <template #button> | |||||
| <!-- <van-button size="mini" type="info" @click="getRegisterSmsCode" >获取验证码</van-button> --> | |||||
| <div class="registerSmsBtn" @click="getRegisterSmsCode">{{ | |||||
| computeTime > 0 ? `(${computeTime}s)已发送` : "获取短信码" | |||||
| }}</div> | |||||
| </template> | |||||
| </van-field> | |||||
| <div style="margin: 50px 16px 16px;"> | |||||
| <van-button block type="info" native-type="submit" @click="registerSubmit">绑定</van-button> | |||||
| </div> | |||||
| </van-form> | |||||
| </div> | |||||
| </template> | |||||
| <style scoped> | |||||
| .app-container{ | |||||
| background: #fff; | |||||
| height: 100vh; | |||||
| } | |||||
| .title{ | |||||
| padding-top: 20%; | |||||
| width: 88%; | |||||
| margin: 0 auto; | |||||
| } | |||||
| .van-tab--active{ | |||||
| font-size: .6rem; | |||||
| font-weight: bold; | |||||
| } | |||||
| .van-tabs__line{ | |||||
| background:#1D6FE9; | |||||
| width: 0.15rem; | |||||
| height: 0.15rem; | |||||
| border-radius: 0.07rem; | |||||
| bottom: 0.3rem; | |||||
| } | |||||
| .van-tabs__nav{ | |||||
| padding:0 | |||||
| } | |||||
| .van-tab{ | |||||
| display: inline-block; | |||||
| flex: inherit; | |||||
| margin-left: 30px; | |||||
| line-height: .8rem; | |||||
| } | |||||
| .van-tab__text--ellipsis { | |||||
| overflow: auto; | |||||
| } | |||||
| .van-password-input{ | |||||
| width: 50%; | |||||
| margin: 0 auto; | |||||
| } | |||||
| [class*=van-hairline]::after{ | |||||
| border:none; | |||||
| } | |||||
| .van-password-input__security li{ | |||||
| margin: 0 10px; | |||||
| border-bottom: 3px solid black; | |||||
| } | |||||
| .registerSmsBtn{ | |||||
| color: rgb(29, 111, 233); | |||||
| font-size: 0.34rem; | |||||
| } | |||||
| </style> | |||||
| <script> | |||||
| import { getUUID } from '@/utils' | |||||
| export default { | |||||
| data() { | |||||
| return { | |||||
| showMessage:false, | |||||
| smsCodeValue:"", | |||||
| showKeyboard:false, | |||||
| formData: { | |||||
| username: "", //账号 | |||||
| password: "", //密码 | |||||
| code: null, //图片验证码 | |||||
| uuid: null, //识别uuid | |||||
| mobile: null, //手机号 | |||||
| smsCode: null, //短信验证码 | |||||
| memberName:null, //身份信息 | |||||
| idcard:null, //身份号码 | |||||
| rememberMe:false | |||||
| }, | |||||
| loading: false, | |||||
| codeUrl: "", //验证码 | |||||
| isSmsLogin: false, //是否手机验证码 | |||||
| computeTime: 0, | |||||
| active:1 | |||||
| }; | |||||
| }, | |||||
| created() { | |||||
| this.getCode(); | |||||
| this.reset(); | |||||
| }, | |||||
| methods: { | |||||
| reset(){ | |||||
| }, | |||||
| showPopup(){ | |||||
| this.showKeyboard = !this.showKeyboard | |||||
| }, | |||||
| showMessagePop(){ | |||||
| this.showMessage = !this.showMessage | |||||
| }, | |||||
| getCode() { | |||||
| this.formData.uuid = getUUID() | |||||
| this.codeUrl = this.$http.adornUrl(`/captcha?uuid=${this.formData.uuid}`) | |||||
| }, | |||||
| getRegisterSmsCode(){ | |||||
| if (!this.computeTime) { | |||||
| let myreg = /^[1][3,4,5,7,8,9][0-9]{9}$/; | |||||
| if (!myreg.test(this.formData.mobile)) { | |||||
| this.$dialog.alert({ | |||||
| message: '手机号格式不正确', | |||||
| }); | |||||
| return false; | |||||
| }else if (this.formData.code == "") { | |||||
| this.$dialog.alert({ | |||||
| message: '图片验证码不能为空', | |||||
| }); | |||||
| return false; | |||||
| } | |||||
| if (this.active==2) { | |||||
| let formObj = { | |||||
| code :this.formData.code, | |||||
| mobile:this.formData.mobile, | |||||
| uuid:this.formData.uuid | |||||
| } | |||||
| getRegisterSmsCode(formObj).then((res) => { | |||||
| console.log(res) | |||||
| console.log(res.code == 200) | |||||
| if(res.code == 200) { | |||||
| this.$dialog.alert({ | |||||
| message: '验证码已发送', | |||||
| }); | |||||
| this.formData.uuid = res.uuid; | |||||
| this.computeTime = 60; | |||||
| this.timer = setInterval(() => { | |||||
| this.computeTime--; | |||||
| if (this.computeTime <= 0) { | |||||
| clearInterval(this.timer); | |||||
| } | |||||
| }, 1000); | |||||
| } | |||||
| }).catch((res)=>{ | |||||
| if(res=='Error: 验证码已失效'){ | |||||
| this.getCode() | |||||
| } | |||||
| }); | |||||
| } | |||||
| } | |||||
| }, | |||||
| registerSubmit(){ | |||||
| //注册 | |||||
| if (this.formData.memberName == "") { | |||||
| this.$dialog.alert({ | |||||
| message: '姓名不能为空', | |||||
| }); | |||||
| return false; | |||||
| } else if (this.formData.idcard == "") { | |||||
| this.$dialog.alert({ | |||||
| message: '身份证号不能为空', | |||||
| }); | |||||
| return false; | |||||
| } else if (this.formData.mobile == "") { | |||||
| this.$dialog.alert({ | |||||
| message: '手机号码不能为空', | |||||
| }); | |||||
| return false; | |||||
| }else if (this.formData.smsCode == "") { | |||||
| this.$dialog.alert({ | |||||
| message: '短信验证码不能为空', | |||||
| }); | |||||
| return false; | |||||
| } | |||||
| //registerCheck,registerOn | |||||
| console.log(this.formData) | |||||
| registerCheck(this.formData).then((res)=>{ | |||||
| if(res.code == 200){ | |||||
| registerOn(this.formData).then((res)=>{ | |||||
| if(res.code == 200){ | |||||
| // | |||||
| this.$dialog.alert({ | |||||
| message: '您的初始密码:'+res.password, | |||||
| }).then(() => { | |||||
| this.$router.push({ path: "/yinnong/workbench" }).catch(() => {}); | |||||
| }); | |||||
| } | |||||
| }) | |||||
| } | |||||
| }) | |||||
| } | |||||
| }, | |||||
| }; | |||||
| </script> | |||||
| @@ -0,0 +1,29 @@ | |||||
| module.exports = { | |||||
| publicPath: "./", | |||||
| devServer: { | |||||
| // 后端请求转发,此配置仅开发环境有效,生产环境请参考生产环境部署文档配置nginx转发 | |||||
| proxy: { | |||||
| '/wx': { | |||||
| target: 'http://localhost:8088/' | |||||
| } | |||||
| }, | |||||
| port:8001, | |||||
| inline:false //实时编译 | |||||
| }, | |||||
| configureWebpack:{ | |||||
| devServer: { | |||||
| disableHostCheck: true | |||||
| } | |||||
| }, | |||||
| chainWebpack: config => { | |||||
| // 移除 prefetch 插件 | |||||
| config.plugins.delete('prefetch') | |||||
| }, | |||||
| outputDir: undefined, | |||||
| assetsDir: undefined, | |||||
| runtimeCompiler: undefined, | |||||
| productionSourceMap: false, | |||||
| parallel: undefined, | |||||
| css: undefined | |||||
| } | |||||