Your Name vor 10 Monaten
Commit
2e7a963364
100 geänderte Dateien mit 11167 neuen und 0 gelöschten Zeilen
  1. 11
    0
      .env.sample
  2. 21
    0
      .gitee/ISSUE_TEMPLATE.zh-CN.md
  3. 0
    0
      .gitee/ISSUE_TEMPLATE/ISSUE_TEMPLATE.zh-CN.md
  4. 38
    0
      .gitee/ISSUE_TEMPLATE/bug.yml
  5. 5
    0
      .gitee/ISSUE_TEMPLATE/config.yml
  6. 19
    0
      .gitee/ISSUE_TEMPLATE/feature.yml
  7. 6
    0
      .gitignore
  8. 191
    0
      LICENSE.txt
  9. 122
    0
      README.md
  10. 1
    0
      addons/.gitkeep
  11. 1
    0
      application/.htaccess
  12. 26
    0
      application/admin/behavior/Adminlog.php
  13. 1649
    0
      application/admin/command/Crud.php
  14. 21
    0
      application/admin/command/Crud/stubs/add.stub
  15. 33
    0
      application/admin/command/Crud/stubs/controller.stub
  16. 34
    0
      application/admin/command/Crud/stubs/controllerindex.stub
  17. 21
    0
      application/admin/command/Crud/stubs/edit.stub
  18. 3
    0
      application/admin/command/Crud/stubs/html/checkbox.stub
  19. 21
    0
      application/admin/command/Crud/stubs/html/fieldlist-template.stub
  20. 18
    0
      application/admin/command/Crud/stubs/html/fieldlist.stub
  21. 3
    0
      application/admin/command/Crud/stubs/html/radio.stub
  22. 5
    0
      application/admin/command/Crud/stubs/html/select.stub
  23. 1
    0
      application/admin/command/Crud/stubs/html/selects.stub
  24. 2
    0
      application/admin/command/Crud/stubs/html/switch.stub
  25. 47
    0
      application/admin/command/Crud/stubs/index.stub
  26. 8
    0
      application/admin/command/Crud/stubs/mixins/checkbox.stub
  27. 6
    0
      application/admin/command/Crud/stubs/mixins/datetime.stub
  28. 5
    0
      application/admin/command/Crud/stubs/mixins/modelrelationmethod-hasmany.stub
  29. 5
    0
      application/admin/command/Crud/stubs/mixins/modelrelationmethod.stub
  30. 8
    0
      application/admin/command/Crud/stubs/mixins/multiple.stub
  31. 7
    0
      application/admin/command/Crud/stubs/mixins/radio.stub
  32. 7
    0
      application/admin/command/Crud/stubs/mixins/select.stub
  33. 39
    0
      application/admin/command/Crud/stubs/model.stub
  34. 46
    0
      application/admin/command/Crud/stubs/recyclebin.stub
  35. 29
    0
      application/admin/command/Crud/stubs/validate.stub
  36. 270
    0
      application/admin/command/Install.php
  37. 272
    0
      application/admin/command/Install/install.html
  38. 372
    0
      application/admin/command/Install/yzncms.sql
  39. 276
    0
      application/admin/command/Menu.php
  40. 14
    0
      application/admin/common.php
  41. 398
    0
      application/admin/controller/Addons.php
  42. 328
    0
      application/admin/controller/Ajax.php
  43. 127
    0
      application/admin/controller/Index.php
  44. 66
    0
      application/admin/controller/Main.php
  45. 76
    0
      application/admin/controller/auth/Adminlog.php
  46. 281
    0
      application/admin/controller/auth/Group.php
  47. 205
    0
      application/admin/controller/auth/Manager.php
  48. 166
    0
      application/admin/controller/auth/Rule.php
  49. 191
    0
      application/admin/controller/general/Attachments.php
  50. 233
    0
      application/admin/controller/general/Config.php
  51. 87
    0
      application/admin/controller/general/Profile.php
  52. 355
    0
      application/admin/library/traits/Curd.php
  53. 42
    0
      application/admin/model/AdminUser.php
  54. 120
    0
      application/admin/model/Adminlog.php
  55. 64
    0
      application/admin/model/AuthGroup.php
  56. 34
    0
      application/admin/model/AuthRule.php
  57. 98
    0
      application/admin/model/Config.php
  58. 49
    0
      application/admin/model/ModelField.php
  59. 60
    0
      application/admin/model/Models.php
  60. 407
    0
      application/admin/service/User.php
  61. 18
    0
      application/admin/tags.php
  62. 44
    0
      application/admin/validate/AdminUser.php
  63. 36
    0
      application/admin/validate/AuthGroup.php
  64. 27
    0
      application/admin/validate/AuthRule.php
  65. 29
    0
      application/admin/validate/Config.php
  66. 132
    0
      application/admin/view/addons/config.html
  67. 688
    0
      application/admin/view/addons/index.html
  68. 27
    0
      application/admin/view/auth/adminlog/detail.html
  69. 66
    0
      application/admin/view/auth/adminlog/index.html
  70. 93
    0
      application/admin/view/auth/group/access.html
  71. 46
    0
      application/admin/view/auth/group/add.html
  72. 46
    0
      application/admin/view/auth/group/edit.html
  73. 60
    0
      application/admin/view/auth/group/index.html
  74. 62
    0
      application/admin/view/auth/manager/add.html
  75. 64
    0
      application/admin/view/auth/manager/edit.html
  76. 57
    0
      application/admin/view/auth/manager/index.html
  77. 136
    0
      application/admin/view/auth/rule/add.html
  78. 136
    0
      application/admin/view/auth/rule/edit.html
  79. 83
    0
      application/admin/view/auth/rule/index.html
  80. 1
    0
      application/admin/view/custom/.gitkeep
  81. 387
    0
      application/admin/view/general/attachments/cropper.html
  82. 55
    0
      application/admin/view/general/attachments/index.html
  83. 90
    0
      application/admin/view/general/attachments/select.html
  84. 165
    0
      application/admin/view/general/config/add.html
  85. 207
    0
      application/admin/view/general/config/edit.html
  86. 73
    0
      application/admin/view/general/config/index.html
  87. 208
    0
      application/admin/view/general/config/setting.html
  88. 106
    0
      application/admin/view/general/profile/index.html
  89. 1
    0
      application/admin/view/index/index.html
  90. 122
    0
      application/admin/view/index/login.html
  91. 33
    0
      application/admin/view/index_layout.html
  92. 176
    0
      application/admin/view/inputItem.html
  93. 212
    0
      application/admin/view/layout.html
  94. 27
    0
      application/admin/view/layui.html
  95. 159
    0
      application/admin/view/main/index.html
  96. 17
    0
      application/api/config/app.php
  97. 92
    0
      application/api/controller/Ems.php
  98. 98
    0
      application/api/controller/Sms.php
  99. 38
    0
      application/api/library/ExceptionHandle.php
  100. 0
    0
      application/command.php

+ 11
- 0
.env.sample Datei anzeigen

@@ -0,0 +1,11 @@
1
+[app]
2
+debug = false
3
+trace = false
4
+
5
+[database]
6
+hostname = 127.0.0.1
7
+database = yzncms
8
+username = root
9
+password = root
10
+hostport = 3306
11
+prefix = yzn_

+ 21
- 0
.gitee/ISSUE_TEMPLATE.zh-CN.md Datei anzeigen

@@ -0,0 +1,21 @@
1
+## 使用环境
2
+* 系统版本:
3
+* PHP 版本:
4
+* MYSQL 版本:
5
+* 浏览器 版本:
6
+* 本地或线上:
7
+
8
+## 问题描述
9
+
10
+
11
+
12
+<!--
13
+【系统版本】
14
+请在config/version.php查看yzncms_version参数
15
+描述你的问题现象,报错**贴截图**粘贴或者贴具体信息,提供**必要的代码段**,感谢!
16
+
17
+【必看教程】
18
+开发遇到错误怎么办:https://blog.yzncms.com/shows/19/104.html
19
+伪静态(URL重写): https://www.kancloud.cn/ken678/yzncms/1003231
20
+虚拟主机不支持绑定public的方法: https://www.kancloud.cn/ken678/yzncms/1003254
21
+-->

+ 0
- 0
.gitee/ISSUE_TEMPLATE/ISSUE_TEMPLATE.zh-CN.md Datei anzeigen


+ 38
- 0
.gitee/ISSUE_TEMPLATE/bug.yml Datei anzeigen

@@ -0,0 +1,38 @@
1
+name: 😇 问题反馈
2
+description: 使用 Yzncms 过程中遇到的 Bug、异常或其他困惑。
3
+title: ""
4
+labels: ["bug"]
5
+body:
6
+  - type: checkboxes
7
+    attributes:
8
+        label: 议题条件
9
+        description: 在你开始之前,请花几分钟时间确保你已如实完成以下工作,以便让我们更高效地沟通。
10
+        options:
11
+          - label: 我确认已查看官方使用文档:[https://www.kancloud.cn/ken678/yzncms](https://www.kancloud.cn/ken678/yzncms ) ,但没有找到相关解决方案。
12
+            required: true
13
+          - label: 我确认已在 [Issues](https://gitee.com/ken678/YZNCMS/issues ) 中搜索过类似的问题,但没有找到相关解决方案。
14
+            required: true
15
+          - label: 我已点击左上角【[star](https://gitee.com/ken678/YZNCMS/issues/I4KXPX )】进行关注,请作者快马加鞭处理。
16
+            required: false
17
+  - type: input
18
+    attributes:
19
+        label: 版本号
20
+        placeholder: 请提供您所使用的 Yzncms 版本号
21
+    validations:
22
+      required: true
23
+  - type: textarea
24
+    attributes:
25
+      label: 问题描述
26
+      placeholder: 请提供尽可能详细的问题描述和具体操作步骤等信息,以便我们也能够更轻松地将问题复现。
27
+    validations:
28
+      required: true
29
+  - type: textarea
30
+    attributes:
31
+      label: 业务代码
32
+      placeholder: 提供与该问题对应的业务代码片段,以便我们更好地排查问题。
33
+    validations:
34
+      required: true
35
+  - type: textarea
36
+    attributes:
37
+      label: 其他补充
38
+      placeholder: 如截图等补充信息。

+ 5
- 0
.gitee/ISSUE_TEMPLATE/config.yml Datei anzeigen

@@ -0,0 +1,5 @@
1
+blank_issues_enabled: false
2
+contact_links:
3
+  - name: 📄 阅读文档
4
+    url: https://www.kancloud.cn/ken678/yzncms
5
+    about: 建议你在创建 Issue 之前,仔细查阅 YznCMS 开发文档,以便对其有更深入的了解,和更好地分析问题。

+ 19
- 0
.gitee/ISSUE_TEMPLATE/feature.yml Datei anzeigen

@@ -0,0 +1,19 @@
1
+name: 😄 功能建议
2
+description: 对 Yzncms 提出您的宝贵建议。
3
+title: ""
4
+labels: ["建议"]
5
+body:
6
+  - type: textarea
7
+    id: related-problem
8
+    attributes:
9
+      label: 建议内容
10
+      placeholder: 阐述提出该建议的出发点。
11
+    validations:
12
+      required: true
13
+  - type: textarea
14
+    id: desired-solution
15
+    attributes:
16
+      label: 解决方案
17
+      placeholder: 您希望看到什么样的解决方案?
18
+    validations:
19
+      required: true

+ 6
- 0
.gitignore Datei anzeigen

@@ -0,0 +1,6 @@
1
+.idea
2
+.vscode
3
+*.log
4
+composer.lock
5
+/runtime/*
6
+/templates/

+ 191
- 0
LICENSE.txt Datei anzeigen

@@ -0,0 +1,191 @@
1
+Apache License
2
+Version 2.0, January 2004
3
+http://www.apache.org/licenses/
4
+
5
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+1. Definitions.
8
+
9
+"License" shall mean the terms and conditions for use, reproduction, and
10
+distribution as defined by Sections 1 through 9 of this document.
11
+
12
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
13
+owner that is granting the License.
14
+
15
+"Legal Entity" shall mean the union of the acting entity and all other entities
16
+that control, are controlled by, or are under common control with that entity.
17
+For the purposes of this definition, "control" means (i) the power, direct or
18
+indirect, to cause the direction or management of such entity, whether by
19
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
20
+outstanding shares, or (iii) beneficial ownership of such entity.
21
+
22
+"You" (or "Your") shall mean an individual or Legal Entity exercising
23
+permissions granted by this License.
24
+
25
+"Source" form shall mean the preferred form for making modifications, including
26
+but not limited to software source code, documentation source, and configuration
27
+files.
28
+
29
+"Object" form shall mean any form resulting from mechanical transformation or
30
+translation of a Source form, including but not limited to compiled object code,
31
+generated documentation, and conversions to other media types.
32
+
33
+"Work" shall mean the work of authorship, whether in Source or Object form, made
34
+available under the License, as indicated by a copyright notice that is included
35
+in or attached to the work (an example is provided in the Appendix below).
36
+
37
+"Derivative Works" shall mean any work, whether in Source or Object form, that
38
+is based on (or derived from) the Work and for which the editorial revisions,
39
+annotations, elaborations, or other modifications represent, as a whole, an
40
+original work of authorship. For the purposes of this License, Derivative Works
41
+shall not include works that remain separable from, or merely link (or bind by
42
+name) to the interfaces of, the Work and Derivative Works thereof.
43
+
44
+"Contribution" shall mean any work of authorship, including the original version
45
+of the Work and any modifications or additions to that Work or Derivative Works
46
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
47
+by the copyright owner or by an individual or Legal Entity authorized to submit
48
+on behalf of the copyright owner. For the purposes of this definition,
49
+"submitted" means any form of electronic, verbal, or written communication sent
50
+to the Licensor or its representatives, including but not limited to
51
+communication on electronic mailing lists, source code control systems, and
52
+issue tracking systems that are managed by, or on behalf of, the Licensor for
53
+the purpose of discussing and improving the Work, but excluding communication
54
+that is conspicuously marked or otherwise designated in writing by the copyright
55
+owner as "Not a Contribution."
56
+
57
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
58
+of whom a Contribution has been received by Licensor and subsequently
59
+incorporated within the Work.
60
+
61
+2. Grant of Copyright License.
62
+
63
+Subject to the terms and conditions of this License, each Contributor hereby
64
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
65
+irrevocable copyright license to reproduce, prepare Derivative Works of,
66
+publicly display, publicly perform, sublicense, and distribute the Work and such
67
+Derivative Works in Source or Object form.
68
+
69
+3. Grant of Patent License.
70
+
71
+Subject to the terms and conditions of this License, each Contributor hereby
72
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
73
+irrevocable (except as stated in this section) patent license to make, have
74
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
75
+such license applies only to those patent claims licensable by such Contributor
76
+that are necessarily infringed by their Contribution(s) alone or by combination
77
+of their Contribution(s) with the Work to which such Contribution(s) was
78
+submitted. If You institute patent litigation against any entity (including a
79
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
80
+Contribution incorporated within the Work constitutes direct or contributory
81
+patent infringement, then any patent licenses granted to You under this License
82
+for that Work shall terminate as of the date such litigation is filed.
83
+
84
+4. Redistribution.
85
+
86
+You may reproduce and distribute copies of the Work or Derivative Works thereof
87
+in any medium, with or without modifications, and in Source or Object form,
88
+provided that You meet the following conditions:
89
+
90
+You must give any other recipients of the Work or Derivative Works a copy of
91
+this License; and
92
+You must cause any modified files to carry prominent notices stating that You
93
+changed the files; and
94
+You must retain, in the Source form of any Derivative Works that You distribute,
95
+all copyright, patent, trademark, and attribution notices from the Source form
96
+of the Work, excluding those notices that do not pertain to any part of the
97
+Derivative Works; and
98
+If the Work includes a "NOTICE" text file as part of its distribution, then any
99
+Derivative Works that You distribute must include a readable copy of the
100
+attribution notices contained within such NOTICE file, excluding those notices
101
+that do not pertain to any part of the Derivative Works, in at least one of the
102
+following places: within a NOTICE text file distributed as part of the
103
+Derivative Works; within the Source form or documentation, if provided along
104
+with the Derivative Works; or, within a display generated by the Derivative
105
+Works, if and wherever such third-party notices normally appear. The contents of
106
+the NOTICE file are for informational purposes only and do not modify the
107
+License. You may add Your own attribution notices within Derivative Works that
108
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
109
+provided that such additional attribution notices cannot be construed as
110
+modifying the License.
111
+You may add Your own copyright statement to Your modifications and may provide
112
+additional or different license terms and conditions for use, reproduction, or
113
+distribution of Your modifications, or for any such Derivative Works as a whole,
114
+provided Your use, reproduction, and distribution of the Work otherwise complies
115
+with the conditions stated in this License.
116
+
117
+5. Submission of Contributions.
118
+
119
+Unless You explicitly state otherwise, any Contribution intentionally submitted
120
+for inclusion in the Work by You to the Licensor shall be under the terms and
121
+conditions of this License, without any additional terms or conditions.
122
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
123
+any separate license agreement you may have executed with Licensor regarding
124
+such Contributions.
125
+
126
+6. Trademarks.
127
+
128
+This License does not grant permission to use the trade names, trademarks,
129
+service marks, or product names of the Licensor, except as required for
130
+reasonable and customary use in describing the origin of the Work and
131
+reproducing the content of the NOTICE file.
132
+
133
+7. Disclaimer of Warranty.
134
+
135
+Unless required by applicable law or agreed to in writing, Licensor provides the
136
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
137
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
138
+including, without limitation, any warranties or conditions of TITLE,
139
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
140
+solely responsible for determining the appropriateness of using or
141
+redistributing the Work and assume any risks associated with Your exercise of
142
+permissions under this License.
143
+
144
+8. Limitation of Liability.
145
+
146
+In no event and under no legal theory, whether in tort (including negligence),
147
+contract, or otherwise, unless required by applicable law (such as deliberate
148
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
149
+liable to You for damages, including any direct, indirect, special, incidental,
150
+or consequential damages of any character arising as a result of this License or
151
+out of the use or inability to use the Work (including but not limited to
152
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
153
+any and all other commercial damages or losses), even if such Contributor has
154
+been advised of the possibility of such damages.
155
+
156
+9. Accepting Warranty or Additional Liability.
157
+
158
+While redistributing the Work or Derivative Works thereof, You may choose to
159
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
160
+other liability obligations and/or rights consistent with this License. However,
161
+in accepting such obligations, You may act only on Your own behalf and on Your
162
+sole responsibility, not on behalf of any other Contributor, and only if You
163
+agree to indemnify, defend, and hold each Contributor harmless for any liability
164
+incurred by, or claims asserted against, such Contributor by reason of your
165
+accepting any such warranty or additional liability.
166
+
167
+END OF TERMS AND CONDITIONS
168
+
169
+APPENDIX: How to apply the Apache License to your work
170
+
171
+To apply the Apache License to your work, attach the following boilerplate
172
+notice, with the fields enclosed by brackets "{}" replaced with your own
173
+identifying information. (Don't include the brackets!) The text should be
174
+enclosed in the appropriate comment syntax for the file format. We also
175
+recommend that a file or class name and description of purpose be included on
176
+the same "printed page" as the copyright notice for easier identification within
177
+third-party archives.
178
+
179
+   Copyright 2018 御宅男
180
+
181
+   Licensed under the Apache License, Version 2.0 (the "License");
182
+   you may not use this file except in compliance with the License.
183
+   You may obtain a copy of the License at
184
+
185
+     http://www.apache.org/licenses/LICENSE-2.0
186
+
187
+   Unless required by applicable law or agreed to in writing, software
188
+   distributed under the License is distributed on an "AS IS" BASIS,
189
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190
+   See the License for the specific language governing permissions and
191
+   limitations under the License.

+ 122
- 0
README.md Datei anzeigen

@@ -0,0 +1,122 @@
1
+# YznCMS 后台开发框架
2
+### 系统基于ThinkPHP5.1的强大后端框架与Layui2.9.x轻量级的前端UI框架,承诺后台框架永久免费且商业授权无限制,更可移除前台版权信息,是个人和企业专享、高度自定义的理想后台管理系统选择。
3
+
4
+[![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://gitee.com/ken678/YZNCMS/blob/master/LICENSE.txt)
5
+[![PHP Version Require](https://img.shields.io/badge/PHP-%3E%3D7.2.5-blue)](https://packagist.org/packages/yzncms/framework)
6
+[![Version](https://img.shields.io/badge/YznCMS-1.4.2-brightgreen.svg)](https://gitee.com/ken678/YZNCMS/)
7
+[![star](https://gitee.com/ken678/YZNCMS/badge/star.svg?theme=dark)](https://gitee.com/ken678/YZNCMS/stargazers)
8
+[![fork](https://gitee.com/ken678/YZNCMS/badge/fork.svg?theme=dark)](https://gitee.com/ken678/YZNCMS/members)
9
+
10
+## [插件版权]
11
+
12
+- 插件可用于个人或企业自营网站或应用或为客户定制开发
13
+- 插件(包含免费和付费插件)禁止二次转售,复制和传播等形式分发插件源码
14
+- 插件不限制域名,不限授权期限,不限建站数量!
15
+
16
+## [插件清单]
17
+
18
+|  名称 | 简介  |价格  |
19
+|---|---|---|
20
+|在线命令|可在线执行一键生成CRUD、一键生成菜单等相关命令|免费|
21
+|内容管理系统|这是一个功能强大的内容管理!自定义模型,字段不在话下,支持近二十种类型字段|免费|
22
+|会员插件|系统必备的会员基础功能!含会员组,会员投稿等强大功能|免费|
23
+|自定义表单|这是一个cms必备的表单提交插件!字段都可以任意定义,同样支持近二十种类型字段|免费|
24
+|站内消息|会员与会员之间,会员与管理员之间消息发送,支持批量|免费|
25
+|支付插件|支持支付宝(支持个人)和微信支付的所有场景|免费|
26
+|接口文档|根据注释自动生成API文档|免费|
27
+|万能采集|简单的采集工具,可以自定义字段入库,网页数据采集和内容管理的神器!|免费|
28
+|友情链接|管理第三方网站的友情链接,很常用的必备插件|免费|
29
+|网站地图|sitemap网站地图让搜索引擎对您网站的更快、更完整地进行索引,为您进行网站推广带来极大的方便|免费|
30
+|网址聚合推送|百度站长+神马搜索推送|免费|
31
+|数据库备份|简单的数据库备份|免费|
32
+|多通道短信|发送支持目前市面20+家短信服务商,自动轮询选择可用的服务商|免费|
33
+|editormd编辑器|	editor.md编辑器|免费|
34
+|七牛云|附件上传到七牛云|免费|
35
+|阿里云OSS|附件上传到阿里云oss|免费|
36
+|腾讯云COS|附件上传到腾讯云cos|免费|
37
+|返回顶部|回到顶部美化,随机或指定显示,100款样式,每天一种换,天天都用新样式|免费|
38
+|邮箱插件|邮箱插件-by赛邮|免费|
39
+|会员签到|会员签到插件,含排行榜,补签,已日历方式呈现|免费|
40
+|第三方登录|QQ,微信和新浪等近十种第三方登录|免费|
41
+|中文分词|支持百度和讯飞分词|免费|
42
+|地图位置|支持百度、高德和天地图|免费|
43
+|智能人机验证|一款免费的智能无感手势验证码|免费|
44
+|数据转换|将phpcmsV9的数据结构转换为yzncms|免费|
45
+|数据转换|将dedecmsV5.7的数据结构转换为yzncms|免费|
46
+|后台登录背景|使用必应壁纸自定义后台登录背景图|免费|
47
+|邮箱发送|采用phpmailer的方式发送邮件|免费|
48
+|消息队列|基于think-queue的队列|免费|
49
+|快递查询插件|支持阿里云,聚合数据,快递100,快递鸟快递查询|免费|
50
+|地区插件|省市区三级联动|免费|
51
+|cms小程序|传统的cms展示类型小程序|付费|
52
+|即时通讯|类似百度商桥,快商通之类的客服多坐席即时聊天工具|付费|
53
+|评论插件|类似多说,一段JS即可调用的强大评论|付费|
54
+|会员推广|生成推广链接,后台生成邀请码注册|付费|
55
+|数据导出|支持任意表和关联表导出excel等格式|付费|
56
+|在线客服|近十种不同样式的QQ在线客服|付费|
57
+|安全检测|检测文件是否篡改或丢失|付费|
58
+|阿里云邮箱|阿里云的邮箱推送|付费|
59
+|百度收录查询|查询文章是否被百度收录|付费|
60
+|蜘蛛访问统计|查看主流搜索引擎的蜘蛛访问记录|付费|
61
+|敏感词检测|支持对CMS所有页面进行敏感词检测|付费|
62
+|内容收藏|用于会员对cms的内容进行收藏|付费|
63
+|迅搜全文检索|基于迅搜(Xunsearch),支持百万以上全文数据检索|付费|
64
+|H5设计|H5端一键DIY拖拽式布局插件(支持各种小程序)|付费|
65
+|行为验证码|基于AJ-Captcha行为验证码,包含滑动拼图、文字点选两种方式|付费|
66
+|礼品卡提货系统|适用于公司节日福利发放礼品卡,大闸蟹等凭卡提货系统|付费|
67
+|IP归属地查询|根据IP离线定位库和高德API查询所属地|付费|
68
+|百度统计插件|免去百度登录,将百度统计接口集成至后台|付费|
69
+|消息通知|集合小程序,公众号模版消息,手机和邮箱通知|付费|
70
+|微信管理|管理微信公众号插件|付费|
71
+|在线投票系统|可快速创建微信H5或小程序投票网络投票活动的系统|付费|
72
+
73
+## [项目介绍]
74
+YznCMS(又名御宅男CMS)是基于最新TP5.1x框架和layui2.5x的后台管理系统。创立于2017年初,是一款完全免费开源的项目,他将是您轻松建站的首选利器。框架易于功能扩展,代码维护,方便二次开发,帮助开发者简单高效降低二次开发成本,满足专注业务深度开发的需求。
75
+- 手册地址:https://www.kancloud.cn/ken678/yzncms
76
+- 官方博客:http://blog.yzncms.com/
77
+- 视频教程:https://www.bilibili.com/video/av417106995 (即将重新录制)
78
+- 软件著作权编号:2020SR0038539
79
+
80
+## [环境要求]
81
+- WEB服务器:IIS/Apache/Nginx
82
+- PHP版本:php >= 7.2 (支持8.0,推荐7.3)
83
+- 数据库:MySQL >= 5.6 (需支持 innodb 引擎)
84
+
85
+
86
+## [常规安装]
87
+- 第一步:下载源码压缩包解压到你的项目根目录
88
+- 第二步:绑定到项目中的public目录为运行目录,ps:如果您的服务器不支持绑定目录,请查看public目录下的index.php和install.php代码注释,有详细具体操作
89
+- 第三步:访问[http://您的域名/install.php](http://www.yoursite.com/install.php)进行安装
90
+
91
+## [命令行安装]
92
+- 第一步:下载源码压缩包解压到你的项目根目录
93
+- 第二步:绑定到项目中的public目录为运行目录,ps:如果您的服务器不支持绑定目录,请查看public目录下的index.php代码注释,有详细具体操作
94
+- 第三步:进入目录  `cd yzncms`
95
+- 第四步:一键创建数据库并导入数据   `php think install -u 数据库用户名 -p 数据库密码`
96
+
97
+## [必看教程]
98
+- 伪静态(URL重写): https://www.kancloud.cn/ken678/yzncms/1003231
99
+- 开发遇到错误怎么办?: https://www.kancloud.cn/ken678/yzncms/3090278
100
+
101
+> 更多的常见问题和教程见手册
102
+
103
+## [截图预览]
104
+![输入图片说明](https://foruda.gitee.com/images/1694497052659798394/3f1a677d_555541.gif "YZNCMS后台管理系统")
105
+![输入图片说明](https://foruda.gitee.com/images/1694497169122838821/13d1e4fd_555541.png "YZNCMS后台管理系统")
106
+
107
+## [友情捐赠]  
108
+![输入图片说明](https://images.gitee.com/uploads/images/2019/0110/175836_7cb23388_555541.jpeg "1547112799941_01.jpg")
109
+![输入图片说明](https://images.gitee.com/uploads/images/2019/0110/181152_57b5113e_555541.jpeg "mm_facetoface_collect_qrcode_1547113957376_01.jpg")
110
+
111
+## [联系我们]
112
+目前只支持以下条件才能添加QQ或者微信好友,其他理由如“我要拜师”,“我要学习,我有问题”等等一律谢绝添加
113
+- 有重大安全BUG漏洞(一般漏洞提交到issues)
114
+- 有建站等项目合作(需要发送详细需求)
115
+
116
+满足以上条件请发送邮箱至530765310@qq.com,备注理由,符合条件会主动添加你为好友
117
+
118
+## [版权信息]  
119
+YznCMS遵循[Apache2.0](https://www.apache.org/licenses/LICENSE-2.0.html)开源协议发布,并允许商业使用。  
120
+本项目包含的第三方源码和二进制文件之版权信息另行标注。  
121
+版权所有Copyright © 2017-2024 by Yzncms (https://www.yzncms.com)  
122
+All rights reserved。

+ 1
- 0
addons/.gitkeep Datei anzeigen

@@ -0,0 +1 @@
1
+

+ 1
- 0
application/.htaccess Datei anzeigen

@@ -0,0 +1 @@
1
+deny from all

+ 26
- 0
application/admin/behavior/Adminlog.php Datei anzeigen

@@ -0,0 +1,26 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// | 初始化配置
14
+// +----------------------------------------------------------------------
15
+namespace app\admin\behavior;
16
+
17
+class Adminlog
18
+{
19
+    public function run($params)
20
+    {
21
+        //只记录POST请求的日志
22
+        if (request()->isPost() && config('auto_record_admin_log')) {
23
+            \app\admin\model\Adminlog::record();
24
+        }
25
+    }
26
+}

+ 1649
- 0
application/admin/command/Crud.php
Datei-Diff unterdrückt, da er zu groß ist
Datei anzeigen


+ 21
- 0
application/admin/command/Crud/stubs/add.stub Datei anzeigen

@@ -0,0 +1,21 @@
1
+{extend name="index_layout"/}
2
+{block name="main"}
3
+<form class="layui-form" method="post">
4
+
5
+{%addList%}
6
+    <div class="layui-form-item layer-footer">
7
+        <div class="layui-input-block">
8
+            <button class="layui-btn" lay-submit="">立即提交</button>
9
+            <button type="reset" class="layui-btn layui-btn-primary">重置</button>
10
+        </div>
11
+    </div>
12
+</form>
13
+{/block}
14
+{block name="script"}
15
+<script type="text/javascript">
16
+layui.use('yznForm', function() {
17
+    var yznForm = layui.yznForm;
18
+    yznForm.bindevent($("form.layui-form"));
19
+});
20
+</script>
21
+{/block}

+ 33
- 0
application/admin/command/Crud/stubs/controller.stub Datei anzeigen

@@ -0,0 +1,33 @@
1
+<?php
2
+
3
+namespace {%controllerNamespace%};
4
+
5
+use app\common\controller\Adminbase;
6
+
7
+/**
8
+ * {%tableComment%}
9
+ */
10
+class {%controllerName%} extends Adminbase
11
+{
12
+
13
+    /**
14
+     * {%modelName%}模型对象
15
+     * @var \{%modelNamespace%}\{%modelName%}
16
+     */
17
+    protected $modelClass = null;
18
+
19
+    protected function initialize()
20
+    {
21
+        parent::initialize();
22
+        $this->modelClass = new \{%modelNamespace%}\{%modelName%};
23
+{%controllerAssignList%}
24
+    }
25
+
26
+    /**
27
+     * 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
28
+     * 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
29
+     * 需要将application/admin/library/traits/Curd.php中对应的方法复制到当前控制器,然后进行修改
30
+     */
31
+
32
+{%controllerIndex%}
33
+}

+ 34
- 0
application/admin/command/Crud/stubs/controllerindex.stub Datei anzeigen

@@ -0,0 +1,34 @@
1
+
2
+    /**
3
+     * 查看
4
+     */
5
+    public function index()
6
+    {
7
+        //当前是否为关联查询
8
+        $this->relationSearch = {%relationSearch%};
9
+        //设置过滤方法
10
+        $this->request->filter(['strip_tags', 'trim']);
11
+        if ($this->request->isAjax()) {
12
+            //如果发送的来源是Selectpage,则转发到Selectpage
13
+            if ($this->request->request('keyField')) {
14
+                return $this->selectpage();
15
+            }
16
+            [$page, $limit, $where, $sort, $order] = $this->buildTableParames();
17
+
18
+            $list = $this->modelClass
19
+                    {%relationWithList%}
20
+                    ->where($where)
21
+                    ->order($sort, $order)
22
+                    ->paginate($limit);
23
+
24
+            foreach ($list as $row) {
25
+                {%visibleFieldList%}
26
+                {%relationVisibleFieldList%}
27
+            }
28
+
29
+            $result = ["code" => 0, "count" => $list->total(), "data" => $list->items()];
30
+
31
+            return json($result);
32
+        }
33
+        return $this->fetch();
34
+    }

+ 21
- 0
application/admin/command/Crud/stubs/edit.stub Datei anzeigen

@@ -0,0 +1,21 @@
1
+{extend name="index_layout"/}
2
+{block name="main"}
3
+<form class="layui-form" method="post">
4
+
5
+{%editList%}
6
+    <div class="layui-form-item layer-footer">
7
+        <div class="layui-input-block">
8
+            <button class="layui-btn" lay-submit="">立即提交</button>
9
+            <button type="reset" class="layui-btn layui-btn-primary">重置</button>
10
+        </div>
11
+    </div>
12
+</form>
13
+{/block}
14
+{block name="script"}
15
+<script type="text/javascript">
16
+layui.use('yznForm', function() {
17
+    var yznForm = layui.yznForm;
18
+    yznForm.bindevent($("form.layui-form"));
19
+});
20
+</script>
21
+{/block}

+ 3
- 0
application/admin/command/Crud/stubs/html/checkbox.stub Datei anzeigen

@@ -0,0 +1,3 @@
1
+{foreach name="{%fieldList%}" item="vo"}
2
+            <input id="{%fieldName%}-{$key}" lay-skin="primary" name="{%fieldName%}" type="checkbox" title="{$vo}" value="{$key}" {in name="key" value="{%selectedValue%}"}checked{/in} />
3
+            {/foreach}

+ 21
- 0
application/admin/command/Crud/stubs/html/fieldlist-template.stub Datei anzeigen

@@ -0,0 +1,21 @@
1
+            <table class="layui-form-field-label layui-table fieldlist" data-name="{%fieldName%}" data-id="{%field%}" data-template="true" data-tag="tr">
2
+                <tr>
3
+                    {%theadList%}
4
+                    <td width="90">操作</td>
5
+                </tr>
6
+                <tr>
7
+                   <td colspan="{%colspan%}"><button type="button" class="layui-btn btn-append">追加</button></td>
8
+                </tr>
9
+            </table>
10
+            <textarea name="{%fieldName%}" class="layui-textarea layui-hide" cols="30" rows="5">{%fieldValue%}</textarea>
11
+            <script type="text/html" id="{%field%}Tpl">
12
+                <tr class="layui-form-item rules-item">
13
+                    {{# layui.each(d.lists, function(index, item) { }}
14
+                    {%tbodyList%}
15
+                    <td>
16
+                       <button type="button" class="layui-btn layui-btn-danger btn-remove layui-btn-xs"><i class="iconfont icon-close"></i></button>
17
+                       <button type="button" class="layui-btn btn-dragsort layui-btn-xs"><i class="iconfont icon-yidong"></i></button>
18
+                   </td>
19
+                   {{# }); }}
20
+                </tr>
21
+            </script>

+ 18
- 0
application/admin/command/Crud/stubs/html/fieldlist.stub Datei anzeigen

@@ -0,0 +1,18 @@
1
+<dl class="fieldlist" data-name="{%fieldName%}" data-id="{%field%}">
2
+                <dd>
3
+                    <ins>键名</ins>
4
+                    <ins>键值</ins>
5
+                </dd>
6
+                <dd><button type="button" class="layui-btn btn-append">追加</button></dd>
7
+                <textarea name="{%fieldName%}" class="layui-textarea layui-hide" cols="30" rows="5">{%fieldValue%}</textarea>
8
+            </dl>
9
+            <script type="text/html" id="{%field%}Tpl">
10
+                <dd class="layui-form-item rules-item">
11
+                    {{# layui.each(d.lists, function(index, item) { }}
12
+                    <input type="text" class="layui-input" name="{{item.name}}[{{item.index}}][key]" placeholder="键" value="{{item.key|| ''}}" />
13
+                    <input type="text" class="layui-input" name="{{item.name}}[{{item.index}}][value]" placeholder="值" value="{{item.value|| ''}}" />
14
+                    <button type="button" class="layui-btn layui-btn-danger btn-remove layui-btn-xs"><i class="iconfont icon-close"></i></button>
15
+                    <button type="button" class="layui-btn btn-dragsort layui-btn-xs"><i class="iconfont icon-yidong"></i></button>
16
+                    {{# }); }}
17
+                </dd>
18
+            </script>

+ 3
- 0
application/admin/command/Crud/stubs/html/radio.stub Datei anzeigen

@@ -0,0 +1,3 @@
1
+{foreach name="{%fieldList%}" item="vo"}
2
+            <input id="{%fieldName%}-{$key}" name="{%fieldName%}" type="radio" value="{$key}" title="{$vo}" {in name="key" value="{%selectedValue%}"}checked{/in} />
3
+            {/foreach}

+ 5
- 0
application/admin/command/Crud/stubs/html/select.stub Datei anzeigen

@@ -0,0 +1,5 @@
1
+<select {%attrStr%}>
2
+                {foreach name="{%fieldList%}" item="vo"}
3
+                    <option value="{$key}" {in name="key" value="{%selectedValue%}"}selected{/in}>{$vo}</option>
4
+                {/foreach}
5
+            </select>

+ 1
- 0
application/admin/command/Crud/stubs/html/selects.stub Datei anzeigen

@@ -0,0 +1 @@
1
+<div class="form-selects" data-name="{%fieldName%}" data-id="{%field%}" data-value="{%selectedValue%}" data-list="{%fieldList%}"></div>

+ 2
- 0
application/admin/command/Crud/stubs/html/switch.stub Datei anzeigen

@@ -0,0 +1,2 @@
1
+<input id="{%fieldName%}-0" name="{%fieldName%}" type="radio" value="1" title="ON" {eq name="{%fieldValue%}" value="1" }checked{/eq} />
2
+<input id="{%fieldName%}-1" name="{%fieldName%}" type="radio" value="0" title="OFF" {eq name="{%fieldValue%}" value="0" }checked{/eq} />

+ 47
- 0
application/admin/command/Crud/stubs/index.stub Datei anzeigen

@@ -0,0 +1,47 @@
1
+{extend name="index_layout"/}
2
+{block name="main"}
3
+<div class="layui-card">
4
+    <div class="layui-card-header">{%tableComment%}</div>
5
+    <div class="layui-card-body">
6
+        <div class="layui-form">
7
+            <table class="layui-hide" id="currentTable" lay-filter="currentTable"
8
+                data-auth-add="{:$auth->check('{%controllerUrl%}/add')}" 
9
+                data-auth-edit="{:$auth->check('{%controllerUrl%}/edit')}"
10
+                data-auth-delete="{:$auth->check('{%controllerUrl%}/del')}"
11
+                data-auth-recyclebin="{:$auth->check('{%controllerUrl%}/recyclebin')}"
12
+                ></table>
13
+        </div>
14
+    </div>
15
+</div>
16
+{/block}
17
+{block name="script"}
18
+<script type="text/javascript">
19
+layui.use('yznTable', function() {
20
+    var table = layui.yznTable;
21
+
22
+    var init = {
23
+        table_elem: '#currentTable',
24
+        table_render_id: 'currentTable',
25
+        add_url: '{:url("add")}',
26
+        edit_url: '{:url("edit")}',
27
+        delete_url: '{:url("del")}',
28
+        modify_url: "{:url('multi')}",
29
+        recyclebin_url: "{:url('recyclebin')}",
30
+    };
31
+
32
+    table.render({
33
+        init: init,
34
+        toolbar: ['refresh', 'add', 'delete'{%recyclebinHtml%}],
35
+        url: '{:url("index")}',
36
+        cols: [
37
+            [
38
+               {%javascriptList%}
39
+            ]
40
+        ],
41
+        page: {}
42
+    });
43
+
44
+    yznTable.bindevent();
45
+});
46
+</script>
47
+{/block}

+ 8
- 0
application/admin/command/Crud/stubs/mixins/checkbox.stub Datei anzeigen

@@ -0,0 +1,8 @@
1
+
2
+    public function {%methodName%}($value, $data)
3
+    {
4
+        $value = $value ?: ($data['{%field%}'] ?? '');
5
+        $valueArr = explode(',', $value);
6
+        $list = $this->{%listMethodName%}();
7
+        return implode(',', array_intersect_key($list, array_flip($valueArr)));
8
+    }

+ 6
- 0
application/admin/command/Crud/stubs/mixins/datetime.stub Datei anzeigen

@@ -0,0 +1,6 @@
1
+
2
+    public function {%methodName%}($value, $data)
3
+    {
4
+        $value = $value ?: ($data['{%field%}'] ?? '');
5
+        return is_numeric($value) ? date("Y-m-d H:i:s", $value) : $value;
6
+    }

+ 5
- 0
application/admin/command/Crud/stubs/mixins/modelrelationmethod-hasmany.stub Datei anzeigen

@@ -0,0 +1,5 @@
1
+
2
+    public function {%relationMethod%}s()
3
+    {
4
+        return $this->{%relationMode%}('{%relationClassName%}', '{%relationForeignKey%}', '{%relationPrimaryKey%}');
5
+    }

+ 5
- 0
application/admin/command/Crud/stubs/mixins/modelrelationmethod.stub Datei anzeigen

@@ -0,0 +1,5 @@
1
+
2
+    public function {%relationMethod%}()
3
+    {
4
+        return $this->{%relationMode%}('{%relationClassName%}', '{%relationForeignKey%}', '{%relationPrimaryKey%}', [], 'LEFT');
5
+    }

+ 8
- 0
application/admin/command/Crud/stubs/mixins/multiple.stub Datei anzeigen

@@ -0,0 +1,8 @@
1
+
2
+    public function {%methodName%}($value, $data)
3
+    {
4
+        $value = $value ?: ($data['{%field%}'] ?? '');
5
+        $valueArr = explode(',', $value);
6
+        $list = $this->{%listMethodName%}();
7
+        return implode(',', array_intersect_key($list, array_flip($valueArr)));
8
+    }

+ 7
- 0
application/admin/command/Crud/stubs/mixins/radio.stub Datei anzeigen

@@ -0,0 +1,7 @@
1
+
2
+    public function {%methodName%}($value, $data)
3
+    {
4
+        $value = $value ?: ($data['{%field%}'] ?? '');
5
+        $list = $this->{%listMethodName%}();
6
+        return $list[$value] ?? '';
7
+    }

+ 7
- 0
application/admin/command/Crud/stubs/mixins/select.stub Datei anzeigen

@@ -0,0 +1,7 @@
1
+
2
+    public function {%methodName%}($value, $data)
3
+    {
4
+        $value = $value ?: ($data['{%field%}'] ?? '');
5
+        $list = $this->{%listMethodName%}();
6
+        return $list[$value] ?? '';
7
+    }

+ 39
- 0
application/admin/command/Crud/stubs/model.stub Datei anzeigen

@@ -0,0 +1,39 @@
1
+<?php
2
+
3
+namespace {%modelNamespace%};
4
+
5
+use think\Model;
6
+{%softDeleteClassPath%}
7
+
8
+class {%modelName%} extends Model
9
+{
10
+
11
+    {%softDelete%}
12
+
13
+    {%modelConnection%}
14
+
15
+    // 表名
16
+    protected ${%modelTableType%} = '{%modelTableTypeName%}';
17
+    
18
+    // 自动写入时间戳字段
19
+    protected $autoWriteTimestamp = {%modelAutoWriteTimestamp%};
20
+
21
+    // 定义时间戳字段名
22
+    protected $createTime = {%createTime%};
23
+    protected $updateTime = {%updateTime%};
24
+    protected $deleteTime = {%deleteTime%};
25
+
26
+    // 追加属性
27
+    protected $append = [
28
+{%appendAttrList%}
29
+    ];
30
+
31
+{%getEnumList%}
32
+
33
+{%getAttrList%}
34
+
35
+{%setAttrList%}
36
+
37
+{%relationMethodList%}
38
+
39
+}

+ 46
- 0
application/admin/command/Crud/stubs/recyclebin.stub Datei anzeigen

@@ -0,0 +1,46 @@
1
+{extend name="index_layout"/}
2
+{block name="main"}
3
+<div class="layui-card">
4
+    <div class="layui-card-header">{%tableComment%}</div>
5
+    <div class="layui-card-body">
6
+        <div class="layui-form">
7
+            <table class="layui-hide" id="currentTable" lay-filter="currentTable"
8
+                data-auth-restore="{:$auth->check('{%controllerUrl%}/restore')}" 
9
+                data-auth-destroy="{:$auth->check('{%controllerUrl%}/destroy')}"
10
+                ></table>
11
+        </div>
12
+    </div>
13
+</div>
14
+{/block}
15
+{block name="script"}
16
+<script type="text/javascript">
17
+layui.use('yznTable', function() {
18
+    var table = layui.yznTable;
19
+
20
+    var init = {
21
+        table_elem: '#currentTable',
22
+        table_render_id: 'currentTable',
23
+        restore_url: '{:url("{%controllerUrl%}/restore")}',
24
+        destroy_url: '{:url("{%controllerUrl%}/destroy")}',
25
+        modify_url: "{:url('{%controllerUrl%}/multi')}",
26
+    };
27
+
28
+    table.render({
29
+        init: init,
30
+        toolbar: ['refresh', 'restore', 'destroy'],
31
+        url: '{:url("{%controllerUrl%}/recyclebin")}',
32
+        cols: [
33
+            [
34
+                { type: 'checkbox', fixed: 'left' },
35
+                { field: 'id', width: 70, title: 'ID' },{%recyclebinTitleJs%}
36
+                { field: '{%deleteTimeField%}', width: 160, title: '删除时间', search: 'range', templet: yznTable.formatter.datetime},
37
+                { fixed: 'right', width: 140, title: '操作', templet: yznTable.formatter.tool,operat: ['restore','destroy']}
38
+            ]
39
+        ],
40
+        page: {}
41
+    });
42
+
43
+    yznTable.bindevent();
44
+});
45
+</script>
46
+{/block}

+ 29
- 0
application/admin/command/Crud/stubs/validate.stub Datei anzeigen

@@ -0,0 +1,29 @@
1
+<?php
2
+
3
+namespace {%validateNamespace%};
4
+
5
+use think\Validate;
6
+
7
+class {%validateName%} extends Validate
8
+{
9
+    /**
10
+     * 验证规则
11
+     */
12
+    protected $rule = [
13
+    ];
14
+
15
+    /**
16
+     * 提示消息
17
+     */
18
+    protected $message = [
19
+    ];
20
+    
21
+    /**
22
+     * 验证场景
23
+     */
24
+    protected $scene = [
25
+        'add'  => [],
26
+        'edit' => [],
27
+    ];
28
+    
29
+}

+ 270
- 0
application/admin/command/Install.php Datei anzeigen

@@ -0,0 +1,270 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | fastadmin: https://www.fastadmin.net/
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// | 安装主程序
14
+// +----------------------------------------------------------------------
15
+
16
+namespace app\admin\command;
17
+
18
+use PDO;
19
+use think\console\Command;
20
+use think\console\Input;
21
+use think\console\input\Option;
22
+use think\console\Output;
23
+use think\Db;
24
+use think\Exception;
25
+use think\facade\Config;
26
+use think\facade\Request;
27
+use think\facade\View;
28
+use util\File;
29
+use util\Random;
30
+
31
+class Install extends Command
32
+{
33
+    /**
34
+     * @var \think\Request Request 实例
35
+     */
36
+    protected $request;
37
+
38
+    protected function configure()
39
+    {
40
+        $config = Config::get('database.');
41
+        $this->setName('install')
42
+            ->addOption('hostname', 'a', Option::VALUE_OPTIONAL, 'mysql hostname', $config['hostname'])
43
+            ->addOption('hostport', 'o', Option::VALUE_OPTIONAL, 'mysql hostport', $config['hostport'])
44
+            ->addOption('database', 'd', Option::VALUE_OPTIONAL, 'mysql database', $config['database'])
45
+            ->addOption('prefix', 'r', Option::VALUE_OPTIONAL, 'table prefix', $config['prefix'])
46
+            ->addOption('username', 'u', Option::VALUE_OPTIONAL, 'mysql username', $config['username'])
47
+            ->addOption('password', 'p', Option::VALUE_OPTIONAL, 'mysql password', $config['password'])
48
+            ->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override', false)
49
+            ->setDescription('New installation of YznCMS');
50
+    }
51
+
52
+    /**
53
+     * 命令行安装
54
+     */
55
+    protected function execute(Input $input, Output $output)
56
+    {
57
+        define('INSTALL_PATH', APP_PATH . 'admin' . DS . 'command' . DS . 'Install' . DS);
58
+        // 覆盖安装
59
+        $force    = $input->getOption('force');
60
+        $hostname = $input->getOption('hostname');
61
+        $hostport = $input->getOption('hostport');
62
+        $database = $input->getOption('database');
63
+        $prefix   = $input->getOption('prefix');
64
+        $username = $input->getOption('username');
65
+        $password = $input->getOption('password');
66
+
67
+        $installLockFile = INSTALL_PATH . "install.lock";
68
+        if (is_file($installLockFile) && !$force) {
69
+            $output->error("\nYznCMS already installed!\nIf you need to reinstall again, use the parameter --force=true");
70
+            return false;
71
+        }
72
+        $adminUsername = 'admin';
73
+        $adminPassword = Random::alnum(10);
74
+        $adminEmail    = 'admin@admin.com';
75
+        try {
76
+            $this->installation($hostname, $hostport, $database, $username, $password, $prefix, $adminUsername, $adminPassword, $adminEmail);
77
+        } catch (\Exception $e) {
78
+            $output->error($e->getMessage());
79
+            return false;
80
+        }
81
+        $output->highlight("Admin url: http://www.yoursite.com/admin");
82
+        $output->highlight("Admin username: {$adminUsername}");
83
+        $output->highlight("Admin password: {$adminPassword}");
84
+        $output->info("Install Successed!");
85
+    }
86
+
87
+    /**
88
+     * PC端安装
89
+     */
90
+    public function index()
91
+    {
92
+        $this->request = Request::instance();
93
+
94
+        define('INSTALL_PATH', APP_PATH . 'admin' . DS . 'command' . DS . 'Install' . DS);
95
+
96
+        $installLockFile = INSTALL_PATH . "install.lock";
97
+
98
+        if (is_file($installLockFile)) {
99
+            echo '当前已经安装成功,如果需要重新安装,请手动移除install.lock文件';
100
+            exit;
101
+        }
102
+        $output = function ($code, $msg, $url = null, $data = null) {
103
+            return json(['code' => $code, 'msg' => $msg, 'url' => $url, 'data' => $data]);
104
+        };
105
+
106
+        if ($this->request->isPost()) {
107
+            $mysqlHostname = $this->request->post('mysqlHostname', '127.0.0.1');
108
+            $mysqlHostport = $this->request->post('mysqlHostport', '3306');
109
+            $hostArr       = explode(':', $mysqlHostname);
110
+            if (count($hostArr) > 1) {
111
+                $mysqlHostname = $hostArr[0];
112
+                $mysqlHostport = $hostArr[1];
113
+            }
114
+            $mysqlUsername             = $this->request->post('mysqlUsername', 'root');
115
+            $mysqlPassword             = $this->request->post('mysqlPassword', '');
116
+            $mysqlDatabase             = $this->request->post('mysqlDatabase', '');
117
+            $mysqlPrefix               = $this->request->post('mysqlPrefix', 'yzn_');
118
+            $adminUsername             = $this->request->post('adminUsername', 'admin');
119
+            $adminPassword             = $this->request->post('adminPassword', '');
120
+            $adminPasswordConfirmation = $this->request->post('adminPasswordConfirmation', '');
121
+            $adminEmail                = $this->request->post('adminEmail', 'admin@admin.com');
122
+
123
+            if ($adminPassword !== $adminPasswordConfirmation) {
124
+                return $output(0, '两次输入的密码不一致');
125
+            }
126
+            try {
127
+                $this->installation($mysqlHostname, $mysqlHostport, $mysqlDatabase, $mysqlUsername, $mysqlPassword, $mysqlPrefix, $adminUsername, $adminPassword, $adminEmail);
128
+            } catch (\PDOException $e) {
129
+                throw new Exception($e->getMessage());
130
+            } catch (\Exception $e) {
131
+                return $output(0, $e->getMessage());
132
+            }
133
+            return $output(1, '安装成功!', null);
134
+        }
135
+        $errInfo = '';
136
+        try {
137
+            $this->checkenv();
138
+        } catch (\Exception $e) {
139
+            $errInfo = $e->getMessage();
140
+        }
141
+        return view::fetch(INSTALL_PATH . "install.html", ['errInfo' => $errInfo]);
142
+    }
143
+
144
+    /**
145
+     * 执行安装
146
+     */
147
+    protected function installation($mysqlHostname, $mysqlHostport, $mysqlDatabase, $mysqlUsername, $mysqlPassword, $mysqlPrefix, $adminUsername, $adminPassword, $adminEmail = null)
148
+    {
149
+        $this->checkenv();
150
+
151
+        if ($mysqlDatabase == '') {
152
+            throw new Exception('请输入正确的数据库名');
153
+        }
154
+        if (!preg_match("/^\w{3,12}$/", $adminUsername)) {
155
+            throw new Exception('用户名只能由3-30位数字、字母、下划线组合');
156
+        }
157
+        if (!preg_match("/^[\S]{6,16}$/", $adminPassword)) {
158
+            throw new Exception('密码长度必须在6-30位之间,不能包含空格');
159
+        }
160
+        $weakPasswordArr = ['123456', '12345678', '123456789', '654321', '111111', '000000', 'password', 'qwerty', 'abc123', '1qaz2wsx'];
161
+        if (in_array($adminPassword, $weakPasswordArr)) {
162
+            throw new Exception('密码太简单,请重新输入');
163
+        }
164
+        $sql = file_get_contents(INSTALL_PATH . 'yzncms.sql');
165
+
166
+        $sql = str_replace("`yzn_", "`{$mysqlPrefix}", $sql);
167
+
168
+        // 先尝试能否自动创建数据库
169
+        $config = Config::get('database.');
170
+        try {
171
+            $pdo = new PDO("{$config['type']}:host={$mysqlHostname}" . ($mysqlHostport ? ";port={$mysqlHostport}" : ''), $mysqlUsername, $mysqlPassword);
172
+            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
173
+            $pdo->query("CREATE DATABASE IF NOT EXISTS `{$mysqlDatabase}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;");
174
+
175
+            // 连接install命令中指定的数据库
176
+            $instance = Db::connect([
177
+                'type'     => "{$config['type']}",
178
+                'hostname' => "{$mysqlHostname}",
179
+                'hostport' => "{$mysqlHostport}",
180
+                'database' => "{$mysqlDatabase}",
181
+                'username' => "{$mysqlUsername}",
182
+                'password' => "{$mysqlPassword}",
183
+                'prefix'   => "{$mysqlPrefix}",
184
+            ]);
185
+
186
+            // 查询一次SQL,判断连接是否正常
187
+            $instance->execute("SELECT 1");
188
+
189
+            $db = new PDO("{$config['type']}:dbname={$mysqlDatabase};host={$mysqlHostname}", $mysqlUsername, $mysqlPassword);
190
+            $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
191
+            // 调用原生PDO对象进行批量查询
192
+            $db->exec($sql);
193
+        } catch (\PDOException $e) {
194
+            throw new Exception($e->getMessage());
195
+        }
196
+
197
+        // 数据库配置文件
198
+        $dbConfigFile = ROOT_PATH . 'config' . DS . 'database.php';
199
+        $dbConfigText = @file_get_contents($dbConfigFile);
200
+        $callback     = function ($matches) use ($mysqlHostname, $mysqlHostport, $mysqlUsername, $mysqlPassword, $mysqlDatabase, $mysqlPrefix) {
201
+            $field   = "mysql" . ucfirst($matches[1]);
202
+            $replace = $$field;
203
+            if ($matches[1] == 'hostport' && $mysqlHostport == 3306) {
204
+                $replace = '';
205
+            }
206
+            return "'{$matches[1]}'{$matches[2]}=>{$matches[3]}Env::get('database.{$matches[1]}', '{$replace}'),";
207
+        };
208
+        $dbConfigText = preg_replace_callback("/'(hostname|database|username|password|hostport|prefix)'(\s+)=>(\s+)Env::get\((.*)\)\,/", $callback, $dbConfigText);
209
+
210
+        // 检测能否成功写入数据库配置
211
+        $result = @file_put_contents($dbConfigFile, $dbConfigText);
212
+        if (!$result) {
213
+            throw new Exception('当前权限不足,无法写入文件 config/database.php');
214
+        }
215
+
216
+        // 设置新的Token随机密钥key
217
+        $oldTokenKey    = config('token.key');
218
+        $newTokenKey    = Random::alnum(32);
219
+        $coreConfigFile = ROOT_PATH . 'config' . DS . 'token.php';
220
+        $coreConfigText = @file_get_contents($coreConfigFile);
221
+        $coreConfigText = preg_replace("/'key'(\s+)=>(\s+)'{$oldTokenKey}'/", "'key'\$1=>\$2'{$newTokenKey}'", $coreConfigText);
222
+
223
+        $result = @file_put_contents($coreConfigFile, $coreConfigText);
224
+        if (!$result) {
225
+            throw new Exception('当前权限不足,无法写入文件 config/token.php');
226
+        }
227
+
228
+        // 变更默认管理员密码
229
+        $adminPassword = $adminPassword ? $adminPassword : Random::alnum(8);
230
+        $adminEmail    = $adminEmail ? $adminEmail : "admin@admin.com";
231
+        $newSalt       = substr(md5(uniqid(true)), 0, 6);
232
+        $newPassword   = md5(trim($adminPassword) . $newSalt);
233
+        $data          = ['username' => $adminUsername, 'email' => $adminEmail, 'password' => $newPassword, 'encrypt' => $newSalt];
234
+        $instance->name('admin')->where('username', 'admin')->update($data);
235
+
236
+        $installLockFile = INSTALL_PATH . "install.lock";
237
+        //检测能否成功写入lock文件
238
+        $result = @file_put_contents($installLockFile, 1);
239
+        if (!$result) {
240
+            throw new Exception('当前权限不足,无法写入文件 application/admin/command/Install/install.lock');
241
+        }
242
+        try {
243
+            //删除安装脚本
244
+            @unlink(ROOT_PATH . 'public' . DS . 'install.php');
245
+        } catch (\Exception $e) {
246
+
247
+        }
248
+        return true;
249
+    }
250
+
251
+    /**
252
+     * 检测环境
253
+     */
254
+    protected function checkenv()
255
+    {
256
+        //数据库配置文件
257
+        $dbConfigFile = ROOT_PATH . 'config' . DS . 'database.php';
258
+
259
+        if (version_compare(PHP_VERSION, '7.2.5', '<')) {
260
+            throw new Exception("当前版本" . PHP_VERSION . "过低,请使用PHP7.2.5以上版本");
261
+        }
262
+        if (!extension_loaded("PDO")) {
263
+            throw new Exception("当前未开启PDO,无法进行安装");
264
+        }
265
+        if (!File::is_really_writable($dbConfigFile)) {
266
+            throw new Exception("当前权限不足,无法写入文件 config/database.php");
267
+        }
268
+        return true;
269
+    }
270
+}

+ 272
- 0
application/admin/command/Install/install.html Datei anzeigen

@@ -0,0 +1,272 @@
1
+<!DOCTYPE html>
2
+<html>
3
+
4
+<head>
5
+    <meta charset="utf-8">
6
+    <title>安装YznCMS后台程序</title>
7
+    <meta name="renderer" content="webkit">
8
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
9
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
10
+    <link rel="stylesheet" href="__STATIC__/libs/layui/css/layui.css" media="all">
11
+    <style type="text/css">
12
+    body {
13
+        text-align: center;
14
+        background-repeat: no-repeat;
15
+        background-color: #f1f4fd;
16
+        background-size: cover;
17
+        background-image: url(__STATIC__/admin/img/background01.svg);
18
+        margin: 0;
19
+        padding: 0;
20
+        line-height: 1.5;
21
+        -webkit-font-smoothing: antialiased;
22
+        -moz-osx-font-smoothing: grayscale;
23
+    }
24
+    body, input, button {
25
+        font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, 'Microsoft Yahei', Arial, sans-serif;
26
+        font-size: 14px;
27
+        color: #7E96B3;
28
+    }
29
+    h2 {
30
+        margin-top: 20px;
31
+        font-size: 28px;
32
+        font-weight: normal;
33
+        color: #3C5675;
34
+        margin-bottom: 0
35
+    }
36
+    .content {
37
+        margin-top: 20px;
38
+    }
39
+    .content p {
40
+        margin: 20px;
41
+    }
42
+    .content form {
43
+        margin: 0 auto;
44
+        width: 500px;
45
+        margin-bottom: 20px;
46
+    }
47
+    .content form .bg {
48
+        margin-top: 20px;
49
+        background-color: #fff;
50
+        border: 1px solid #fff;
51
+        border-radius: 3px;
52
+        padding: 14px 14px;
53
+        box-shadow: 0 0 8px #cccccc;
54
+    }
55
+    .content form .layui-form-item:last-child {
56
+        margin-bottom: 0 !important;
57
+    }
58
+    .content form .layui-btn {
59
+        margin-top: 20px;
60
+    }
61
+    .content .layui-elem-field legend {
62
+        font-size: 16px;
63
+        color: #8a8a8a;
64
+    }
65
+    .content .tips {
66
+        float: left;
67
+        margin-top: 5px;
68
+        margin-bottom: 5px;
69
+        font-size: 12px;
70
+        color: #eca6a6
71
+    }
72
+    .content .admin-tips {
73
+        float: left;
74
+        margin-top: 5px;
75
+        margin-bottom: 5px;
76
+        font-size: 12px;
77
+        color: #eca6a6
78
+    }
79
+    .content .desc {
80
+        font-size: 16px;
81
+        color: #4E5465;
82
+        margin-bottom: 30px;
83
+    }
84
+    .content .desc a {
85
+        color: #07adeb;
86
+        margin-right: 10px;
87
+    }
88
+    .content .desc a:first-child {
89
+        margin-left: 10px;
90
+    }
91
+    #error, .error, #success, .success,  #warmtips, .warmtips{
92
+        color: #fff;
93
+        padding: 15px 20px;
94
+        border-radius: 4px;
95
+        margin-bottom: 20px;
96
+    }
97
+    #error a, .error a {
98
+        color: white;
99
+        text-decoration: underline;
100
+    }
101
+    #warmtips {
102
+        background: #ffcdcd;
103
+        font-size: 14px;
104
+        color: #e74c3c;
105
+    }
106
+    #warmtips a {
107
+        background: #ffffff7a;
108
+        display: block;
109
+        height: 30px;
110
+        line-height: 30px;
111
+        margin-top: 10px;
112
+        color: #e21a1a;
113
+        border-radius: 3px;
114
+    }
115
+    @media screen and (max-width:768px) {
116
+        .content form {
117
+            width: 95%;
118
+        }
119
+    }
120
+    </style>
121
+</head>
122
+
123
+<body>
124
+    <h2>安装YznCMS后台系统</h2>
125
+    <div class="content">
126
+        <p class="desc">
127
+            使用过程中遇到任何问题可参考
128
+            <a href="https://www.kancloud.cn/ken678/yzncms" target="_blank">文档教程</a>
129
+            <a href="https://www.bilibili.com/video/av417106995" target="_blank">视频教程</a>
130
+        </p>
131
+        <form class="layui-form layui-form-pane" action="">
132
+            {if $errInfo}
133
+            <div class="error layui-bg-red">
134
+                {$errInfo}
135
+            </div>
136
+            {/if}
137
+            <div id="error" class="layui-bg-red" style="display:none"></div>
138
+            <div id="success" class="layui-bg-cyan" style="display:none"></div>
139
+            <div id="warmtips" style="display:none"></div>
140
+            <div class="bg">
141
+                <div class="layui-form-item">
142
+                    <label class="layui-form-label">数据库地址</label>
143
+                    <div class="layui-input-block">
144
+                        <input class="layui-input" type="text" name="mysqlHostname" value="127.0.0.1" autocomplete="off" lay-verify="required" placeholder="请输入数据库地址">
145
+                    </div>
146
+                </div>
147
+                <div class="layui-form-item">
148
+                    <label class="layui-form-label">数据库名称</label>
149
+                    <div class="layui-input-block">
150
+                        <input class="layui-input" type="text" name="mysqlDatabase" value="" autocomplete="off" lay-verify="required" placeholder="请输入数据库名称">
151
+                    </div>
152
+                </div>
153
+                <div class="layui-form-item">
154
+                    <label class="layui-form-label">数据表前缀</label>
155
+                    <div class="layui-input-block">
156
+                        <input class="layui-input" type="text" name="mysqlPrefix" autocomplete="off" lay-verify="required" value="yzn_" placeholder="请输入数据表前缀">
157
+                    </div>
158
+                </div>
159
+                <div class="layui-form-item">
160
+                    <label class="layui-form-label">数据库账号</label>
161
+                    <div class="layui-input-block">
162
+                        <input class="layui-input" type="text" name="mysqlUsername" autocomplete="off" lay-verify="required" value="root" placeholder="请输入数据库账号">
163
+                    </div>
164
+                </div>
165
+                <div class="layui-form-item">
166
+                    <label class="layui-form-label">数据库密码</label>
167
+                    <div class="layui-input-block">
168
+                        <input class="layui-input" type="password" autocomplete="off" lay-verify="required" name="mysqlPassword" placeholder="请输入数据库密码">
169
+                    </div>
170
+                </div>
171
+                <div class="layui-form-item">
172
+                    <label class="layui-form-label">数据库端口</label>
173
+                    <div class="layui-input-block">
174
+                        <input class="layui-input" type="number" name="mysqlHostport" value="3306" autocomplete="off" lay-verify="required" placeholder="请输入数据库端口">
175
+                    </div>
176
+                </div>
177
+            </div>
178
+            <div class="bg">
179
+                <div class="layui-form-item">
180
+                    <label class="layui-form-label">管理员账号</label>
181
+                    <div class="layui-input-block">
182
+                        <input class="layui-input" name="adminUsername" autocomplete="off" lay-verify="required" value="admin" placeholder="请输入管理员账号"/>
183
+                    </div>
184
+                </div>
185
+                <div class="layui-form-item">
186
+                    <label class="layui-form-label">管理员邮箱</label>
187
+                    <div class="layui-input-block">
188
+                        <input class="layui-input" name="adminEmail" autocomplete="off" lay-verify="required" value="admin@admin.com" placeholder="请输入管理员邮箱"/>
189
+                    </div>
190
+                </div>
191
+                <div class="layui-form-item">
192
+                    <label class="layui-form-label">管理员密码</label>
193
+                    <div class="layui-input-block">
194
+                        <input class="layui-input" type="password" autocomplete="off" lay-verify="required" name="adminPassword" placeholder="请输入管理员密码">
195
+                    </div>
196
+                </div>
197
+                <div class="layui-form-item">
198
+                    <label class="layui-form-label">密码确认</label>
199
+                    <div class="layui-input-block">
200
+                        <input class="layui-input" type="password" autocomplete="off" lay-verify="required" name="adminPasswordConfirmation" placeholder="请输入密码确认">
201
+                    </div>
202
+                </div>
203
+            </div>
204
+            <div class="layui-form-item form-buttons">
205
+                <button class="layui-btn layui-btn-normal {:$errInfo?'layui-btn-disabled':''}" {:$errInfo?'disabled':''} lay-submit lay-filter="install">确定安装</button>
206
+            </div>
207
+        </form>
208
+    </div>
209
+    <script src="__STATIC__/libs/layui/layui.js" charset="utf-8"></script>
210
+    <script>
211
+    layui.use(['form', 'layer'], function() {
212
+        var $ = layui.jquery,
213
+            form = layui.form,
214
+            layer = layui.layer;
215
+
216
+            $(function () {
217
+                $('form :input:first').select();
218
+            })
219
+            form.on('submit(install)', function(data) {
220
+                if ($(this).hasClass('layui-btn-disabled')) {
221
+                    return false;
222
+                }
223
+                var $error = $("#error");
224
+                var $success = $("#success");
225
+                var $button = $('.layui-btn')
226
+                    .text("安装中...")
227
+                    .prop('disabled', true);
228
+                var _data = data.field;
229
+
230
+                $.ajax({
231
+                    url: "",
232
+                    type: "POST",
233
+                    dataType: "json",
234
+                    data: _data,
235
+                    success: function (ret) {
236
+                        if (ret.code == 1) {
237
+                            var data = ret.data;
238
+                            $error.hide();
239
+                            $("form .bg").remove();
240
+                            $button.remove();
241
+                            $success.text(ret.msg).show();
242
+
243
+                            $buttons = $("form .form-buttons");
244
+                            $("<a class='layui-btn layui-btn-normal' href='./'>访问首页</a>").appendTo($buttons);
245
+                            $("#warmtips").html("温馨提示:为了安全,请删除/public/install.php文件").show();
246
+                            var url = location.href.replace(/install\.php/, 'admin');
247
+                            $('<a class="layui-btn" href="' + url + '" id="btn-admin" style="background:#4e73df">' + "进入后台" + '</a>').appendTo($buttons);
248
+
249
+                            localStorage.setItem("fastep", "installed");
250
+                        } else {
251
+                            $error.show().text(ret.msg);
252
+                            $button.prop('disabled', false).text("点击安装");
253
+                            $("html,body").animate({
254
+                                scrollTop: 0
255
+                            }, 500);
256
+                        }
257
+                    },
258
+                    error: function (xhr) {
259
+                        $error.show().text(xhr.responseText);
260
+                        $button.prop('disabled', false).text("点击安装");
261
+                        $("html,body").animate({
262
+                            scrollTop: 0
263
+                        }, 500);
264
+                    }
265
+                });
266
+                return false;
267
+            });
268
+    });
269
+    </script>
270
+</body>
271
+
272
+</html>

+ 372
- 0
application/admin/command/Install/yzncms.sql Datei anzeigen

@@ -0,0 +1,372 @@
1
+/*
2
+ YznCMS Install SQL
3
+ Date: 2024-02-05 09:17:57
4
+*/
5
+
6
+SET FOREIGN_KEY_CHECKS = 0;
7
+
8
+-- ----------------------------
9
+-- Table structure for `yzn_admin`
10
+-- ----------------------------
11
+DROP TABLE IF EXISTS `yzn_admin`;
12
+CREATE TABLE `yzn_admin` (
13
+  `id` smallint(3) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户ID',
14
+  `username` varchar(20) DEFAULT NULL COMMENT '管理账号',
15
+  `password` varchar(32) DEFAULT NULL COMMENT '管理密码',
16
+  `roleid` tinyint(4) unsigned DEFAULT '0' COMMENT '规则ID',
17
+  `encrypt` varchar(6) DEFAULT NULL COMMENT '加密因子',
18
+  `nickname` varchar(16) NOT NULL COMMENT '昵称',
19
+  `last_login_time` int(10) unsigned DEFAULT NULL COMMENT '最后登录时间',
20
+  `login_failure` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '失败次数',
21
+  `create_time` int(10) unsigned DEFAULT NULL COMMENT '创建时间',
22
+  `update_time` int(10) unsigned DEFAULT NULL COMMENT '更新时间',
23
+  `last_login_ip` varchar(50) NOT NULL DEFAULT '' COMMENT '最后登录IP',
24
+  `email` varchar(40) DEFAULT NULL COMMENT '电子邮箱',
25
+  `mobile` varchar(11) DEFAULT '' COMMENT '手机号码',
26
+  `token` varchar(60) NOT NULL DEFAULT '' COMMENT 'Session标识',
27
+  `status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '状态',
28
+  PRIMARY KEY (`id`),
29
+  KEY `username` (`username`)
30
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='管理员表';
31
+
32
+-- ----------------------------
33
+-- Records of yzn_admin
34
+-- ----------------------------
35
+INSERT INTO `yzn_admin` VALUES (1, 'admin', '9724b5e6c56b95f5723009ef81961bfe', 1, 'Wo0bAa', '御宅男', 1546940765,0,1546940765,1546940765, '127.0.0.1', '530765310@qq.com', '', '',1);
36
+
37
+-- ----------------------------
38
+-- Table structure for `yzn_adminlog`
39
+-- ----------------------------
40
+DROP TABLE IF EXISTS `yzn_adminlog`;
41
+CREATE TABLE `yzn_adminlog` (
42
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
43
+  `admin_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '管理员ID',
44
+  `username` varchar(30) DEFAULT '' COMMENT '管理员名字',
45
+  `url` varchar(1500) DEFAULT '' COMMENT '操作页面',
46
+  `title` varchar(100) DEFAULT '' COMMENT '日志标题',
47
+  `content` longtext NOT NULL COMMENT '内容',
48
+  `ip` varchar(50) DEFAULT '' COMMENT 'IP',
49
+  `useragent` varchar(255) DEFAULT '' COMMENT 'User-Agent',
50
+  `create_time` int(10) unsigned DEFAULT NULL COMMENT '操作时间',
51
+  PRIMARY KEY (`id`),
52
+  KEY `name` (`username`)
53
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='操作日志';
54
+
55
+-- ----------------------------
56
+-- Table structure for `yzn_attachment`
57
+-- ----------------------------
58
+DROP TABLE IF EXISTS `yzn_attachment`;
59
+CREATE TABLE `yzn_attachment` (
60
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
61
+  `category` varchar(50) DEFAULT '' COMMENT '类别',
62
+  `admin_id` smallint(3) unsigned NOT NULL DEFAULT '0' COMMENT '管理员id',
63
+  `user_id` mediumint(8) unsigned NOT NULL DEFAULT '0' COMMENT '用户id',
64
+  `name` varchar(100) NOT NULL DEFAULT '' COMMENT '文件名',
65
+  `path` varchar(255) NOT NULL DEFAULT '' COMMENT '文件路径',
66
+  `thumb` varchar(255) NOT NULL DEFAULT '' COMMENT '缩略图路径',
67
+  `url` varchar(255) NOT NULL DEFAULT '' COMMENT '文件链接',
68
+  `mime` varchar(100) NOT NULL DEFAULT '' COMMENT '文件mime类型',
69
+  `ext` varchar(4) NOT NULL DEFAULT '' COMMENT '文件类型',
70
+  `size` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '文件大小',
71
+  `md5` varchar(32) NOT NULL DEFAULT '' COMMENT '文件md5',
72
+  `sha1` varchar(40) NOT NULL DEFAULT '' COMMENT 'sha1 散列值',
73
+  `driver` varchar(16) NOT NULL DEFAULT 'local' COMMENT '上传驱动',
74
+  `create_time` int(10) unsigned DEFAULT NULL COMMENT '上传时间',
75
+  `update_time` int(10) unsigned DEFAULT NULL COMMENT '更新时间',
76
+  `listorders` int(5) NOT NULL DEFAULT '100' COMMENT '排序',
77
+  `status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '状态',
78
+  PRIMARY KEY (`id`)
79
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='附件表';
80
+
81
+-- ----------------------------
82
+-- Table structure for `yzn_auth_group`
83
+-- ----------------------------
84
+DROP TABLE IF EXISTS `yzn_auth_group`;
85
+CREATE TABLE `yzn_auth_group` (
86
+  `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户组id,自增主键',
87
+  `parentid` mediumint(8) unsigned NOT NULL DEFAULT '0' COMMENT '父组别',
88
+  `title` varchar(20) NOT NULL DEFAULT '' COMMENT '用户组中文名称',
89
+  `description` varchar(80) NOT NULL DEFAULT '' COMMENT '描述信息',
90
+  `rules` text NOT NULL COMMENT '用户组拥有的规则id,多个规则 , 隔开',
91
+  `status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '状态',
92
+  PRIMARY KEY (`id`)
93
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='权限组表';
94
+
95
+-- ----------------------------
96
+-- Records of yzn_auth_group
97
+-- ----------------------------
98
+INSERT INTO `yzn_auth_group` VALUES (1, 0, '超级管理员', '拥有所有权限', '*', 1);
99
+INSERT INTO `yzn_auth_group` VALUES (2, 1, '编辑', '编辑', '', 1);
100
+
101
+-- ----------------------------
102
+-- Table structure for yzn_auth_rule
103
+-- ----------------------------
104
+DROP TABLE IF EXISTS `yzn_auth_rule`;
105
+CREATE TABLE `yzn_auth_rule` (
106
+  `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT COMMENT '规则id,自增主键',
107
+  `parentid` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '父ID',
108
+  `name` varchar(100) DEFAULT '' COMMENT '规则名称',
109
+  `title` varchar(50) DEFAULT '' COMMENT '规则名称',
110
+  `icon` varchar(50) DEFAULT '' COMMENT '图标',
111
+  `url` varchar(255) DEFAULT '' COMMENT '规则URL',
112
+  `condition` varchar(255) DEFAULT '' COMMENT '条件',
113
+  `remark` varchar(255) DEFAULT '' COMMENT '备注',
114
+  `ismenu` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否为菜单',
115
+  `menutype` enum('_iframe','_blank','_layer') DEFAULT NULL COMMENT '菜单类型',
116
+  `extend` varchar(255) DEFAULT '' COMMENT '扩展属性',
117
+  `create_time` int(10) unsigned DEFAULT NULL COMMENT '创建时间',
118
+  `update_time` int(10) unsigned DEFAULT NULL COMMENT '更新时间',
119
+  `listorder` smallint(3) unsigned NOT NULL DEFAULT '0' COMMENT '排序',
120
+  `status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '状态',
121
+  PRIMARY KEY (`id`),
122
+  UNIQUE KEY `name` (`name`) USING BTREE,
123
+  KEY `parentid` (`parentid`),
124
+  KEY `listorder` (`listorder`)
125
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='节点表';
126
+
127
+-- ----------------------------
128
+-- Records of yzn_auth_rule
129
+-- ----------------------------
130
+INSERT INTO `yzn_auth_rule` VALUES (1, 0, 'general', '常规管理', 'iconfont icon-setup', '', '', '', 1, NULL, '', 1691377129, 1691377129, 999, 1);
131
+INSERT INTO `yzn_auth_rule` VALUES (2, 0, 'addons', '插件管理', 'iconfont icon-equalizer-line', '', '', '', 1, NULL, '', 1691377129, 1691377129, 777, 1);
132
+INSERT INTO `yzn_auth_rule` VALUES (4, 1, 'general.profile', '个人资料', 'iconfont icon-user-line', '', '', '', 1, NULL, '', 1691377129, 1691377129, 0, 1);
133
+INSERT INTO `yzn_auth_rule` VALUES (5, 4, 'general.profile/index', '查看', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
134
+INSERT INTO `yzn_auth_rule` VALUES (6, 4, 'general.profile/update', '资料更新', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
135
+INSERT INTO `yzn_auth_rule` VALUES (10, 1, 'general.config', '配置管理', 'iconfont icon-apartment', '', '', '', 1, NULL, '', 1691377129, 1691377129, 999, 1);
136
+INSERT INTO `yzn_auth_rule` VALUES (11, 1, 'general.config/setting', '网站设置', 'iconfont icon-setup', '', '', '', 1, NULL, '', 1691377129, 1691377129, 888, 1);
137
+INSERT INTO `yzn_auth_rule` VALUES (12, 28, 'auth.rule', '菜单管理', 'iconfont icon-other', '', '', '', 1, NULL, '', 1691377129, 1691377129, 666, 1);
138
+INSERT INTO `yzn_auth_rule` VALUES (13, 1, 'general.attachments', '附件管理', 'iconfont icon-accessory', '', '', '', 1, NULL, '', 1691377129, 1691377129, 666, 1);
139
+INSERT INTO `yzn_auth_rule` VALUES (14, 13, 'general.attachments/index', '查看', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
140
+INSERT INTO `yzn_auth_rule` VALUES (15, 13, 'general.attachments/del', '删除', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
141
+INSERT INTO `yzn_auth_rule` VALUES (16, 13, 'general.attachments/getUrlFile', '图片本地化', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
142
+INSERT INTO `yzn_auth_rule` VALUES (17, 13, 'general.attachments/select', '图片选择', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
143
+INSERT INTO `yzn_auth_rule` VALUES (18, 12, 'auth.rule/index', '查看', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
144
+INSERT INTO `yzn_auth_rule` VALUES (19, 12, 'auth.rule/add', '新增', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
145
+INSERT INTO `yzn_auth_rule` VALUES (20, 12, 'auth.rule/edit', '编辑', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
146
+INSERT INTO `yzn_auth_rule` VALUES (21, 12, 'auth.rule/del', '删除', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
147
+INSERT INTO `yzn_auth_rule` VALUES (22, 12, 'auth.rule/multi', '批量更新', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
148
+INSERT INTO `yzn_auth_rule` VALUES (23, 10, 'general.config/add', '新增', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
149
+INSERT INTO `yzn_auth_rule` VALUES (24, 10, 'general.config/edit', '编辑', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
150
+INSERT INTO `yzn_auth_rule` VALUES (25, 10, 'general.config/del', '删除', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
151
+INSERT INTO `yzn_auth_rule` VALUES (26, 10, 'general.config/multi', '批量更新', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
152
+INSERT INTO `yzn_auth_rule` VALUES (27, 10, 'general.config/index', '查看', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
153
+INSERT INTO `yzn_auth_rule` VALUES (28, 0, 'auth', '权限管理', 'iconfont icon-user-settings-line', '', '', '', 1, NULL, '', 1691377129, 1691377129, 888, 1);
154
+INSERT INTO `yzn_auth_rule` VALUES (29, 28, 'auth.manager', '管理员管理', 'iconfont icon-user-settings-line', '', '', '', 1, NULL, '', 1691377129, 1691377129, 999, 1);
155
+INSERT INTO `yzn_auth_rule` VALUES (30, 29, 'auth.manager/index', '查看', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
156
+INSERT INTO `yzn_auth_rule` VALUES (31, 29, 'auth.manager/edit', '编辑', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
157
+INSERT INTO `yzn_auth_rule` VALUES (32, 29, 'auth.manager/del', '删除', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
158
+INSERT INTO `yzn_auth_rule` VALUES (33, 29, 'auth.manager/add', '新增', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
159
+INSERT INTO `yzn_auth_rule` VALUES (34, 28, 'auth.adminlog', '管理日志', 'iconfont icon-history', '', '', '', 1, NULL, '', 1691377129, 1691377129, 888, 1);
160
+INSERT INTO `yzn_auth_rule` VALUES (35, 34, 'auth.adminlog/index', '查看', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
161
+INSERT INTO `yzn_auth_rule` VALUES (36, 34, 'auth.adminlog/deletelog', '删除', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
162
+INSERT INTO `yzn_auth_rule` VALUES (37, 28, 'auth.group', '角色管理', 'iconfont icon-user-shared-2-line', '', '', '', 1, NULL, '', 1691377129, 1691377129, 777, 1);
163
+INSERT INTO `yzn_auth_rule` VALUES (38, 37, 'auth.group/index', '查看', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
164
+INSERT INTO `yzn_auth_rule` VALUES (39, 37, 'auth.group/add', '新增', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
165
+INSERT INTO `yzn_auth_rule` VALUES (40, 37, 'auth.group/edit', '编辑', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
166
+INSERT INTO `yzn_auth_rule` VALUES (41, 37, 'auth.group/del', '删除', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
167
+INSERT INTO `yzn_auth_rule` VALUES (42, 37, 'auth.group/access', '访问授权', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
168
+INSERT INTO `yzn_auth_rule` VALUES (45, 2, 'addons/index', '查看', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
169
+INSERT INTO `yzn_auth_rule` VALUES (46, 2, 'addons/config', '配置', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
170
+INSERT INTO `yzn_auth_rule` VALUES (49, 2, 'addons/state', '禁用启用', '', '', '', '', 0, NULL, '', 1691377129, 1691377129, 0, 1);
171
+
172
+-- ----------------------------
173
+-- Table structure for `yzn_cache`
174
+-- ----------------------------
175
+DROP TABLE IF EXISTS `yzn_cache`;
176
+CREATE TABLE `yzn_cache` (
177
+  `id` int(10) NOT NULL AUTO_INCREMENT,
178
+  `key` varchar(100) NOT NULL DEFAULT '' COMMENT '缓存KEY值',
179
+  `name` varchar(100) NOT NULL DEFAULT '' COMMENT '名称',
180
+  `module` varchar(20) NOT NULL DEFAULT '' COMMENT '模块名称',
181
+  `model` varchar(30) NOT NULL DEFAULT '' COMMENT '模型名称',
182
+  `action` varchar(30) NOT NULL DEFAULT '' COMMENT '方法名',
183
+  `system` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否系统',
184
+  PRIMARY KEY (`id`),
185
+  KEY `ckey` (`key`)
186
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='缓存列队表';
187
+
188
+-- ----------------------------
189
+-- Records of yzn_cache
190
+-- ----------------------------
191
+INSERT INTO `yzn_cache` VALUES (1, 'Model', '模型列表', 'admin', 'Models', 'model_cache', 1);
192
+INSERT INTO `yzn_cache` VALUES (2, 'ModelField', '模型字段', 'admin', 'ModelField', 'model_field_cache', 1);
193
+
194
+-- ----------------------------
195
+-- Table structure for `yzn_config`
196
+-- ----------------------------
197
+DROP TABLE IF EXISTS `yzn_config`;
198
+CREATE TABLE `yzn_config` (
199
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '配置ID',
200
+  `name` varchar(30) NOT NULL DEFAULT '' COMMENT '配置名称',
201
+  `type` varchar(32) NOT NULL DEFAULT '' COMMENT '配置类型',
202
+  `title` varchar(50) NOT NULL DEFAULT '' COMMENT '配置说明',
203
+  `group` varchar(32) NOT NULL DEFAULT '' COMMENT '配置分组',
204
+  `options` varchar(255) NOT NULL DEFAULT '' COMMENT '配置项',
205
+  `remark` varchar(100) NOT NULL DEFAULT '' COMMENT '配置说明',
206
+  `create_time` int(10) unsigned DEFAULT NULL COMMENT '创建时间',
207
+  `update_time` int(10) unsigned DEFAULT NULL COMMENT '更新时间',
208
+  `status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '状态',
209
+  `value` text NULL COMMENT '配置值',
210
+  `visible` varchar(255) DEFAULT '' COMMENT '可见条件',
211
+  `extend` varchar(255) DEFAULT '' COMMENT '扩展属性',
212
+  `listorder` smallint(3) unsigned NOT NULL DEFAULT '0' COMMENT '排序',
213
+  PRIMARY KEY (`id`),
214
+  UNIQUE KEY `uk_name` (`name`),
215
+  KEY `type` (`type`),
216
+  KEY `group` (`group`)
217
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='网站配置';
218
+
219
+-- ----------------------------
220
+-- Records of yzn_config
221
+-- ----------------------------
222
+INSERT INTO `yzn_config` VALUES (1, 'web_site_icp', 'text', '备案信息', 'base', '', '', 1551244923, 1551244971, 1, '', '', '',1);
223
+INSERT INTO `yzn_config` VALUES (2, 'web_site_statistics', 'textarea', '站点代码', 'base', '', '', 1551244957, 1551244957, 1, '','', '', 100);
224
+INSERT INTO `yzn_config` VALUES (3, 'config_group', 'array', '配置分组', 'system', '', '', 1494408414, 1494408414, 1, '{\"base\":\"基础\",\"system\":\"系统\",\"upload\":\"上传\",\"develop\":\"开发\"}','', '', 0);
225
+INSERT INTO `yzn_config` VALUES (4, 'theme', 'text', '主题风格', 'system', '', '', 1541752781, 1541756888, 1, 'default', '', '',1);
226
+INSERT INTO `yzn_config` VALUES (5, 'admin_forbid_ip', 'textarea', '后台禁止访问IP', 'system', '', '匹配IP段用\"*\"占位,如192.168.*.*,多个IP地址请用英文逗号\",\"分割', 1551244957, 1551244957, 1, '', '', '',2);
227
+INSERT INTO `yzn_config` VALUES (6, 'upload_image_size', 'text', '图片上传大小限制', 'upload', '', '0为不限制大小,单位:kb', 1540457656, 1552436075, 1, '0', '', '',2);
228
+INSERT INTO `yzn_config` VALUES (7, 'upload_image_ext', 'text', '允许上传图片后缀', 'upload', '', '多个后缀用逗号隔开,不填写则不限制类型', 1540457657, 1552436074, 1, 'gif,jpg,jpeg,bmp,png', '', '',1);
229
+INSERT INTO `yzn_config` VALUES (8, 'upload_file_size', 'text', '文件上传大小限制', 'upload', '', '0为不限制大小,单位:kb', 1540457658, 1552436078, 1, '0', '', '',3);
230
+INSERT INTO `yzn_config` VALUES (9, 'upload_file_ext', 'text', '允许上传文件后缀', 'upload', '', '多个后缀用逗号隔开,不填写则不限制类型', 1540457659, 1552436080, 1, 'doc,docx,xls,xlsx,ppt,pptx,pdf,wps,txt,rar,zip,gz,bz2,7z', '', '',4);
231
+INSERT INTO `yzn_config` VALUES (10, 'upload_driver', 'radio', '上传驱动', 'upload', 'local:本地', '图片或文件上传驱动', 1541752781, 1552436085, 1, 'local', '', '',9);
232
+INSERT INTO `yzn_config` VALUES (11, 'upload_thumb_water', 'switch', '添加水印', 'upload', '', '', 1552435063, 1552436080, 1, '0', '', '',5);
233
+INSERT INTO `yzn_config` VALUES (12, 'upload_thumb_water_pic', 'image', '水印图片', 'upload', '', '只有开启水印功能才生效', 1552435183, 1552436081, 1, '', '', '',6);
234
+INSERT INTO `yzn_config` VALUES (13, 'upload_thumb_water_position', 'radio', '水印位置', 'upload', '1:左上角\r\n2:上居中\r\n3:右上角\r\n4:左居中\r\n5:居中\r\n6:右居中\r\n7:左下角\r\n8:下居中\r\n9:右下角', '只有开启水印功能才生效', 1552435257, 1552436082, 1, '9', '', '',7);
235
+INSERT INTO `yzn_config` VALUES (14, 'upload_thumb_water_alpha', 'text', '水印透明度', 'upload', '', '请输入0~100之间的数字,数字越小,透明度越高', 1552435299, 1552436083, 1, '50', '', '',8);
236
+
237
+-- ----------------------------
238
+-- Table structure for `yzn_field_type`
239
+-- ----------------------------
240
+DROP TABLE IF EXISTS `yzn_field_type`;
241
+CREATE TABLE `yzn_field_type` (
242
+  `name` varchar(32) NOT NULL COMMENT '字段类型',
243
+  `title` varchar(64) NOT NULL DEFAULT '' COMMENT '中文类型名',
244
+  `listorder` int(4) NOT NULL DEFAULT '0' COMMENT '排序',
245
+  `default_define` varchar(128) NOT NULL DEFAULT '' COMMENT '默认定义',
246
+  `ifoption` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否需要设置选项',
247
+  `ifstring` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否自由字符',
248
+  PRIMARY KEY (`name`),
249
+  UNIQUE KEY `name` (`name`)
250
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='字段类型表';
251
+
252
+-- ----------------------------
253
+-- Records of yzn_field_type
254
+-- ----------------------------
255
+INSERT INTO `yzn_field_type` VALUES ('text', '输入框', 1, 'varchar(255) NOT NULL', 0, 1);
256
+INSERT INTO `yzn_field_type` VALUES ('checkbox', '复选框', 2, 'varchar(32) NOT NULL', 1, 0);
257
+INSERT INTO `yzn_field_type` VALUES ('textarea', '多行文本', 3, 'varchar(255) NOT NULL', 0, 1);
258
+INSERT INTO `yzn_field_type` VALUES ('password', '密码', 4, 'varchar(255) NOT NULL', 0, 1);
259
+INSERT INTO `yzn_field_type` VALUES ('radio', '单选按钮', 5, 'char(10) NOT NULL', 1, 0);
260
+INSERT INTO `yzn_field_type` VALUES ('switch', '开关', 6, 'tinyint(2) UNSIGNED NOT NULL', 0, 0);
261
+INSERT INTO `yzn_field_type` VALUES ('array', '数组', 7, 'varchar(512) NOT NULL', 0, 0);
262
+INSERT INTO `yzn_field_type` VALUES ('select', '下拉框', 8, 'char(10) NOT NULL', 1, 0);
263
+INSERT INTO `yzn_field_type` VALUES ('selects', '下拉框(多选)', 9, 'varchar(32) NOT NULL', 1, 0);
264
+INSERT INTO `yzn_field_type` VALUES ('selectpage', '高级下拉框', 10, 'varchar(32) NOT NULL', 1, 0);
265
+INSERT INTO `yzn_field_type` VALUES ('image', '单张图', 11, 'varchar(255) NOT NULL', 0, 0);
266
+INSERT INTO `yzn_field_type` VALUES ('images', '多张图', 12, 'text NOT NULL', 0, 0);
267
+INSERT INTO `yzn_field_type` VALUES ('tags', '标签', 13, 'varchar(255) NOT NULL', 0, 1);
268
+INSERT INTO `yzn_field_type` VALUES ('number', '数字', 14, 'int(10) UNSIGNED NOT NULL', 0, 0);
269
+INSERT INTO `yzn_field_type` VALUES ('datetime', '日期和时间', 15, 'int(10) UNSIGNED NOT NULL', 0, 0);
270
+INSERT INTO `yzn_field_type` VALUES ('datetimerange', '日期和时间区间', 16, 'varchar(100) NOT NULL', 0, 0);
271
+INSERT INTO `yzn_field_type` VALUES ('Ueditor', '百度编辑器', 17, 'mediumtext NOT NULL', 0, 1);
272
+INSERT INTO `yzn_field_type` VALUES ('markdown', 'markdown编辑器', 18, 'mediumtext NOT NULL', 0, 1);
273
+INSERT INTO `yzn_field_type` VALUES ('files', '多文件', 19, 'text NOT NULL', 0, 0);
274
+INSERT INTO `yzn_field_type` VALUES ('file', '单文件', 20, 'varchar(255) NOT NULL', 0, 0);
275
+INSERT INTO `yzn_field_type` VALUES ('color', '颜色值', 21, 'varchar(7) NOT NULL', 0, 0);
276
+INSERT INTO `yzn_field_type` VALUES ('city', '城市地区', 22, 'varchar(255) NOT NULL', 0, 0);
277
+INSERT INTO `yzn_field_type` VALUES ('custom', '自定义', 23, 'text NOT NULL', 1, 0);
278
+
279
+-- ----------------------------
280
+-- Table structure for `yzn_model`
281
+-- ----------------------------
282
+DROP TABLE IF EXISTS `yzn_model`;
283
+CREATE TABLE `yzn_model` (
284
+  `id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
285
+  `module` varchar(15) NOT NULL  DEFAULT '' COMMENT '所属模块',
286
+  `name` varchar(30) NOT NULL DEFAULT '' COMMENT '模型名称',
287
+  `tablename` varchar(20) NOT NULL DEFAULT '' COMMENT '表名',
288
+  `description` varchar(100) NOT NULL DEFAULT '' COMMENT '描述',
289
+  `setting` text NOT NULL COMMENT '配置信息',
290
+  `type` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '模型类别:1-独立表,2-主附表',
291
+  `create_time` int(10) unsigned DEFAULT NULL COMMENT '添加时间',
292
+  `update_time` int(10) unsigned DEFAULT NULL COMMENT '更新时间',
293
+  `listorders` tinyint(3) NOT NULL DEFAULT '0' COMMENT '排序',
294
+  `status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '状态',
295
+  PRIMARY KEY (`id`)
296
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='模型列表';
297
+
298
+-- ----------------------------
299
+-- Table structure for `yzn_model_field`
300
+-- ----------------------------
301
+DROP TABLE IF EXISTS `yzn_model_field`;
302
+CREATE TABLE `yzn_model_field` (
303
+  `id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
304
+  `modelid` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT '模型ID',
305
+  `name` varchar(20) NOT NULL DEFAULT '' COMMENT '字段名',
306
+  `title` varchar(30) NOT NULL DEFAULT '' COMMENT '别名',
307
+  `remark` tinytext NULL COMMENT '字段提示',
308
+  `pattern` varchar(255) NOT NULL DEFAULT '' COMMENT '数据校验正则',
309
+  `errortips` varchar(255) NOT NULL DEFAULT '' COMMENT '数据校验未通过的提示信息',
310
+  `type` varchar(20) NOT NULL DEFAULT '' COMMENT '字段类型',
311
+  `extend` varchar(255) DEFAULT '' COMMENT '扩展信息',
312
+  `setting` mediumtext NULL COMMENT '字段配置',
313
+  `ifsystem` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '是否主表字段 1 是',
314
+  `iscore` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否内部字段',
315
+  `iffixed` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否固定不可修改',
316
+  `ifrequire` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '是否必填',
317
+  `ifsearch` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '作为搜索条件',
318
+  `isadd` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '在投稿中显示',
319
+  `create_time` int(10) unsigned DEFAULT NULL COMMENT '创建时间',
320
+  `update_time` int(10) unsigned DEFAULT NULL COMMENT '更新时间',
321
+  `listorder` smallint(5) unsigned NOT NULL DEFAULT '0' COMMENT '排序',
322
+  `status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '状态',
323
+  PRIMARY KEY (`id`),
324
+  KEY `name` (`name`,`modelid`)
325
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='模型字段列表';
326
+
327
+-- ----------------------------
328
+-- Table structure for `yzn_terms`
329
+-- ----------------------------
330
+DROP TABLE IF EXISTS `yzn_terms`;
331
+CREATE TABLE `yzn_terms` (
332
+  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '分类ID',
333
+  `parentid` smallint(5) NOT NULL DEFAULT '0' COMMENT '父ID',
334
+  `name` varchar(20) NOT NULL DEFAULT '' COMMENT '分类名称',
335
+  `module` varchar(15) NOT NULL DEFAULT '' COMMENT '所属模块',
336
+  `setting` mediumtext NULL COMMENT '相关配置信息',
337
+  PRIMARY KEY (`id`),
338
+  KEY `name` (`name`),
339
+  KEY `module` (`module`)
340
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='分类表';
341
+
342
+-- ----------------------------
343
+-- Table structure for `yzn_sms`
344
+-- ----------------------------
345
+DROP TABLE IF EXISTS `yzn_sms`;
346
+CREATE TABLE `yzn_sms` (
347
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
348
+  `event` varchar(30) NOT NULL DEFAULT '' COMMENT '事件',
349
+  `mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '手机号',
350
+  `code` varchar(10) NOT NULL DEFAULT '' COMMENT '验证码',
351
+  `times` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '验证次数',
352
+  `ip` varchar(50) NOT NULL DEFAULT '' COMMENT '操作IP',
353
+  `create_time` int(10) unsigned DEFAULT NULL COMMENT '创建时间',
354
+  PRIMARY KEY (`id`)
355
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='短信验证码表';
356
+
357
+-- ----------------------------
358
+-- Table structure for `yzn_ems`
359
+-- ----------------------------
360
+DROP TABLE IF EXISTS `yzn_ems`;
361
+CREATE TABLE `yzn_ems` (
362
+  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
363
+  `event` varchar(30) NOT NULL DEFAULT '' COMMENT '事件',
364
+  `email` varchar(100) NOT NULL DEFAULT '' COMMENT '邮箱',
365
+  `code` varchar(10) NOT NULL DEFAULT '' COMMENT '验证码',
366
+  `times` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '验证次数',
367
+  `ip` varchar(50) NOT NULL DEFAULT '' COMMENT '操作IP',
368
+  `create_time` int(10) unsigned DEFAULT NULL COMMENT '创建时间',
369
+  PRIMARY KEY (`id`)
370
+) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT='邮箱验证码表';
371
+
372
+SET FOREIGN_KEY_CHECKS = 1;

+ 276
- 0
application/admin/command/Menu.php Datei anzeigen

@@ -0,0 +1,276 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | fastadmin: https://www.fastadmin.net/
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// | 菜单主程序
14
+// +----------------------------------------------------------------------
15
+
16
+namespace app\admin\command;
17
+
18
+use app\admin\model\AuthRule;
19
+use ReflectionClass;
20
+use ReflectionMethod;
21
+use think\console\Command;
22
+use think\console\Input;
23
+use think\console\input\Option;
24
+use think\console\Output;
25
+use think\Exception;
26
+use think\facade\Cache;
27
+use think\facade\Config;
28
+use think\Loader;
29
+
30
+class Menu extends Command
31
+{
32
+    protected $model = null;
33
+
34
+    protected function configure()
35
+    {
36
+        $this
37
+            ->setName('menu')
38
+            ->addOption('controller', 'c', Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, 'controller name,use \'all-controller\' when build all menu', null)
39
+            ->addOption('delete', 'd', Option::VALUE_OPTIONAL, 'delete the specified menu', '')
40
+            ->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force delete menu,without tips', null)
41
+            ->addOption('equal', 'e', Option::VALUE_OPTIONAL, 'the controller must be equal', null)
42
+            ->setDescription('Build auth menu from controller');
43
+        //要执行的controller必须一样,不适用模糊查询
44
+    }
45
+
46
+    protected function execute(Input $input, Output $output)
47
+    {
48
+        $this->model = new AuthRule();
49
+        $adminPath   = dirname(__DIR__) . DS;
50
+        //控制器名
51
+        $controller = $input->getOption('controller') ?: '';
52
+        if (!$controller) {
53
+            throw new Exception("please input controller name");
54
+        }
55
+        $force = $input->getOption('force');
56
+        //是否为删除模式
57
+        $delete = $input->getOption('delete');
58
+        //是否控制器完全匹配
59
+        $equal = $input->getOption('equal');
60
+
61
+        if ($delete) {
62
+            $ids  = [];
63
+            $list = $this->model->where(function ($query) use ($controller, $equal) {
64
+                foreach ($controller as $index => $item) {
65
+                    if (stripos($item, '_') !== false) {
66
+                        $item = Loader::parseName($item, 1);
67
+                    }
68
+                    if (stripos($item, '/') !== false) {
69
+                        $controllerArr = explode('/', $item);
70
+                        end($controllerArr);
71
+                        $key                 = key($controllerArr);
72
+                        $controllerArr[$key] = Loader::parseName($controllerArr[$key]);
73
+                    } else {
74
+                        $controllerArr = [Loader::parseName($item)];
75
+                    }
76
+                    $item = str_replace('_', '\_', implode('/', $controllerArr));
77
+                    if ($equal) {
78
+                        $query->whereOr('name', 'eq', $item);
79
+                    } else {
80
+                        $query->whereOr('name', 'like', strtolower($item) . "%");
81
+                    }
82
+                }
83
+            })->select();
84
+            foreach ($list as $k => $v) {
85
+                $output->warning($v->name);
86
+                $ids[] = $v->id;
87
+            }
88
+            if (!$ids) {
89
+                throw new Exception("There is no menu to delete");
90
+            }
91
+            if (!$force) {
92
+                $question = $output->confirm($input, "Are you sure you want to delete all those menu?  Type 'yes' to continue: ", false);
93
+                if (!$question) {
94
+                    throw new Exception("Operation is aborted!");
95
+                }
96
+            }
97
+            AuthRule::destroy($ids);
98
+
99
+            Cache::rm("__menu__");
100
+            $output->info("Delete Successed");
101
+            return;
102
+        }
103
+
104
+        foreach ($controller as $index => $item) {
105
+            if (stripos($item, '_') !== false) {
106
+                $item = Loader::parseName($item, 1);
107
+            }
108
+            if (stripos($item, '/') !== false) {
109
+                $controllerArr = explode('/', $item);
110
+                end($controllerArr);
111
+                $key                 = key($controllerArr);
112
+                $controllerArr[$key] = ucfirst($controllerArr[$key]);
113
+            } else {
114
+                $controllerArr = [ucfirst($item)];
115
+            }
116
+            $adminPath = dirname(__DIR__) . DS . 'controller' . DS . implode(DS, $controllerArr) . '.php';
117
+            if (!is_file($adminPath)) {
118
+                $output->error("controller not found");
119
+                return;
120
+            }
121
+            $this->importRule($item);
122
+        }
123
+
124
+        Cache::rm("__menu__");
125
+        $output->info("Build Successed!");
126
+    }
127
+
128
+    protected function importRule($controller)
129
+    {
130
+        $controller = str_replace('\\', '/', $controller);
131
+        if (stripos($controller, '/') !== false) {
132
+            $controllerArr = explode('/', $controller);
133
+            end($controllerArr);
134
+            $key                 = key($controllerArr);
135
+            $controllerArr[$key] = ucfirst($controllerArr[$key]);
136
+        } else {
137
+            $key           = 0;
138
+            $controllerArr = [ucfirst($controller)];
139
+        }
140
+        $classSuffix = Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : '';
141
+        $className   = "\\app\\admin\\controller\\" . implode("\\", $controllerArr) . $classSuffix;
142
+
143
+        $pathArr = $controllerArr;
144
+        array_unshift($pathArr, '', 'application', 'admin', 'controller');
145
+        $classFile    = ROOT_PATH . implode(DS, $pathArr) . $classSuffix . ".php";
146
+        $classContent = file_get_contents($classFile);
147
+        $uniqueName   = uniqid("YznCMS") . $classSuffix;
148
+        $classContent = str_replace("class " . $controllerArr[$key] . $classSuffix . " ", 'class ' . $uniqueName . ' ', $classContent);
149
+        $classContent = preg_replace("/namespace\s(.*);/", 'namespace ' . __NAMESPACE__ . ";", $classContent);
150
+
151
+        //临时的类文件
152
+        $tempClassFile = __DIR__ . DS . $uniqueName . ".php";
153
+        file_put_contents($tempClassFile, $classContent);
154
+
155
+        $className = "\\app\\admin\\command\\" . $uniqueName;
156
+
157
+        //删除临时文件
158
+        register_shutdown_function(function () use ($tempClassFile) {
159
+            if ($tempClassFile) {
160
+                //删除临时文件
161
+                @unlink($tempClassFile);
162
+            }
163
+        });
164
+
165
+        //反射机制调用类的注释和方法名
166
+        $reflector = new ReflectionClass($className);
167
+
168
+        //只匹配公共的方法
169
+        $methods = $reflector->getMethods(ReflectionMethod::IS_PUBLIC);
170
+
171
+        $classComment = $reflector->getDocComment();
172
+        //判断是否有启用软删除
173
+        $softDeleteMethods = ['destroy', 'restore', 'recyclebin'];
174
+        $withSofeDelete    = false;
175
+        $modelRegexArr     = ["/\\\$this\->model\s*=\s*model\(['|\"](\w+)['|\"]\);/", "/\\\$this\->model\s*=\s*new\s+([a-zA-Z\\\]+);/"];
176
+        $modelRegex        = preg_match($modelRegexArr[0], $classContent) ? $modelRegexArr[0] : $modelRegexArr[1];
177
+
178
+        preg_match_all($modelRegex, $classContent, $matches);
179
+        if (isset($matches[1]) && isset($matches[1][0]) && $matches[1][0]) {
180
+            \think\facade\Request::instance()->module('admin');
181
+            $model = model($matches[1][0]);
182
+            if (in_array('trashed', get_class_methods($model))) {
183
+                $withSofeDelete = true;
184
+            }
185
+        }
186
+        //忽略的类
187
+        if (stripos($classComment, "@internal") !== false) {
188
+            return;
189
+        }
190
+        preg_match_all('#(@.*?)\n#s', $classComment, $annotations);
191
+        $controllerIcon   = 'iconfont icon-other';
192
+        $controllerRemark = '';
193
+        //判断注释中是否设置了icon值
194
+        if (isset($annotations[1])) {
195
+            foreach ($annotations[1] as $tag) {
196
+                if (stripos($tag, '@icon') !== false) {
197
+                    $controllerIcon = substr($tag, stripos($tag, ' ') + 1);
198
+                }
199
+                if (stripos($tag, '@remark') !== false) {
200
+                    $controllerRemark = substr($tag, stripos($tag, ' ') + 1);
201
+                }
202
+            }
203
+        }
204
+        //过滤掉其它字符
205
+        $controllerTitle = trim(preg_replace(['/^\/\*\*(.*)[\n\r\t]/u', '/[\s]+\*\//u', '/\*\s@(.*)/u', '/[\s|\*]+/u'], '', $classComment));
206
+
207
+        //先导入菜单的数据
208
+        $pid = 0;
209
+        foreach ($controllerArr as $k => $v) {
210
+            $key = $k + 1;
211
+            //驼峰转下划线
212
+            $controllerNameArr = array_slice($controllerArr, 0, $key);
213
+            foreach ($controllerNameArr as &$val) {
214
+                $val = strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $val), "_"));
215
+            }
216
+            unset($val);
217
+            $name      = implode('.', $controllerNameArr);
218
+            $title     = (!isset($controllerArr[$key]) ? $controllerTitle : '');
219
+            $icon      = (!isset($controllerArr[$key]) ? $controllerIcon : 'iconfont icon-other');
220
+            $remark    = (!isset($controllerArr[$key]) ? $controllerRemark : '');
221
+            $title     = $title ? $title : $v;
222
+            $rulemodel = $this->model->get(['name' => $name]);
223
+            if (!$rulemodel) {
224
+                $this->model
225
+                    ->data(['parentid' => $pid, 'name' => $name, 'title' => $title, 'icon' => $icon, 'remark' => $remark, 'ismenu' => 1, 'status' => 1])
226
+                    ->isUpdate(false)
227
+                    ->save();
228
+                $pid = $this->model->id;
229
+            } else {
230
+                $pid = $rulemodel->id;
231
+            }
232
+        }
233
+        $ruleArr = [];
234
+        foreach ($methods as $m => $n) {
235
+            //过滤特殊的类
236
+            if (in_array($n->name, ['initialize', '_empty', 'registerMiddleware']) || substr($n->name, 0, 2) == '__') {
237
+                continue;
238
+            }
239
+            //未启用软删除时过滤相关方法
240
+            if (!$withSofeDelete && in_array($n->name, $softDeleteMethods)) {
241
+                continue;
242
+            }
243
+            //只匹配符合的方法
244
+            if (!preg_match('/^(\w+)' . Config::get('action_suffix') . '/', $n->name, $matchtwo)) {
245
+                unset($methods[$m]);
246
+                continue;
247
+            }
248
+            $comment = $reflector->getMethod($n->name)->getDocComment();
249
+            //忽略的方法
250
+            if (stripos($comment, "@internal") !== false) {
251
+                continue;
252
+            }
253
+            //过滤掉其它字符
254
+            $comment = preg_replace(['/^\/\*\*(.*)[\n\r\t]/u', '/[\s]+\*\//u', '/\*\s@(.*)/u', '/[\s|\*]+/u'], '', $comment);
255
+
256
+            $title = $comment ? $comment : ucfirst($n->name);
257
+
258
+            //获取主键,作为AuthRule更新依据
259
+            $id = $this->getAuthRulePK($name . "/" . strtolower($n->name));
260
+
261
+            $ruleArr[] = ['id' => $id, 'parentid' => $pid, 'name' => $name . "/" . strtolower($n->name), 'icon' => 'iconfont icon-circle-line', 'title' => $title, 'ismenu' => 0, 'status' => 1];
262
+        }
263
+        $this->model->isUpdate(false)->saveAll($ruleArr);
264
+    }
265
+
266
+    //获取主键
267
+    protected function getAuthRulePK($name)
268
+    {
269
+        if (!empty($name)) {
270
+            $id = $this->model
271
+                ->where('name', $name)
272
+                ->value('id');
273
+            return $id ? $id : null;
274
+        }
275
+    }
276
+}

+ 14
- 0
application/admin/common.php Datei anzeigen

@@ -0,0 +1,14 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// | 后台函数文件
14
+// +----------------------------------------------------------------------

+ 398
- 0
application/admin/controller/Addons.php Datei anzeigen

@@ -0,0 +1,398 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// | 插件管理
14
+// +----------------------------------------------------------------------
15
+namespace app\admin\controller;
16
+
17
+use app\admin\model\Adminlog;
18
+use app\common\controller\Adminbase;
19
+use think\addons\AddonException;
20
+use think\addons\Service;
21
+use think\Db;
22
+use think\Exception;
23
+use think\facade\Config;
24
+
25
+class Addons extends Adminbase
26
+{
27
+    //初始化
28
+    protected function initialize()
29
+    {
30
+        parent::initialize();
31
+        if (!$this->auth->isAdministrator() && in_array($this->request->action(), ['install', 'uninstall', 'local'])) {
32
+            $this->error('非超级管理员禁止操作!');
33
+        }
34
+    }
35
+
36
+    //显示插件列表
37
+    public function index()
38
+    {
39
+        $type   = $this->request->param("type", 'online');
40
+        $limit  = $this->request->param("limit/d");
41
+        $page   = $this->request->param("page/d", 1);
42
+        $search = $this->request->param("search", '', 'strip_tags,htmlspecialchars');
43
+
44
+        if ($this->request->isAjax()) {
45
+            if ($type == 'local') {
46
+                $addons = get_addon_list();
47
+                if ($search) {
48
+                    $addons = array_filter($addons, function ($v) use ($search) {
49
+                        return stripos($v['name'], $search) !== false ||
50
+                        stripos($v['title'], $search) !== false ||
51
+                        stripos($v['description'], $search) !== false;
52
+                    });
53
+                }
54
+                $list = [];
55
+                foreach ($addons as $k => $v) {
56
+                    $config              = get_addon_config($v['name']);
57
+                    $v['config']         = $config ? 1 : 0;
58
+                    $addons[$k]['addon'] = $v;
59
+                }
60
+                $count = count($addons);
61
+                if ($limit) {
62
+                    $addons = array_slice($addons, ($page - 1) * $limit, $limit);
63
+                }
64
+                $result = ["code" => 0, "data" => $addons, 'count' => $count];
65
+            } else {
66
+                //在线插件
67
+                $list         = $this->getAddonList($search, $page, $limit);
68
+                $onlineaddons = $list['list'] ?? [];
69
+                $category     = $list['category'] ?? [];
70
+                $count        = $list['count'] ?? -1;
71
+                //本地插件
72
+                $addons = get_addon_list();
73
+                foreach ($addons as &$v) {
74
+                    $config      = get_addon_config($v['name']);
75
+                    $v['config'] = $config ? 1 : 0;
76
+                }
77
+                foreach ($onlineaddons as &$item) {
78
+                    $item['addon'] = $addons[$item['name']] ?? '';
79
+                }
80
+                $result = ["code" => 0, "data" => $onlineaddons, "category" => $category, 'count' => $count];
81
+            }
82
+
83
+            return json($result);
84
+        }
85
+        $this->assign([
86
+            'api_url' => config('api_url'),
87
+            'type'    => $type,
88
+        ]);
89
+        return $this->fetch();
90
+    }
91
+
92
+    /**
93
+     * 设置插件页面
94
+     */
95
+    public function config($name = null)
96
+    {
97
+        $name = $name ?: $this->request->get("name");
98
+        if (!$name) {
99
+            $this->error('参数不得为空!');
100
+        }
101
+        if (!preg_match('/^[a-zA-Z0-9]+$/', $name)) {
102
+            $this->error('插件名称不正确!');
103
+        }
104
+        if (!is_dir(ADDON_PATH . $name)) {
105
+            $this->error('目录不存在!');
106
+        }
107
+        $info   = get_addon_info($name);
108
+        $config = get_addon_fullconfig($name);
109
+        if (!$info) {
110
+            $this->error('配置不存在!');
111
+        }
112
+        if ($this->request->isPost()) {
113
+            $params = $this->request->post("config/a", [], 'trim');
114
+            if ($params) {
115
+                foreach ($config as $k => &$v) {
116
+                    if (isset($params[$v['name']])) {
117
+                        if ($v['type'] == 'array') {
118
+                            $params[$v['name']] = is_array($params[$v['name']]) ? $params[$v['name']] : (array) json_decode($params[$v['name']],
119
+                                true);
120
+                            $value = $params[$v['name']];
121
+                        } else {
122
+                            $value = is_array($params[$v['name']]) ? implode(',',
123
+                                $params[$v['name']]) : $params[$v['name']];
124
+                        }
125
+                        $v['value'] = $value;
126
+                    } elseif ($v['type'] == 'checkbox' && !isset($params[$v['name']])) {
127
+                        //单独处理多选框为空不传参
128
+                        $v['value'] = '';
129
+                    }
130
+                }
131
+                try {
132
+                    $addon = get_addon_instance($name);
133
+                    //插件自定义配置实现逻辑
134
+                    if (method_exists($addon, 'config')) {
135
+                        $addon->config($name, $config);
136
+                    } else {
137
+                        //更新配置文件
138
+                        set_addon_fullconfig($name, $config);
139
+                        Service::refresh();
140
+                    }
141
+                } catch (\Exception $e) {
142
+                    $this->error($e->getMessage());
143
+                }
144
+            }
145
+            $this->success('插件配置成功!');
146
+        }
147
+        $tips = [];
148
+        foreach ($config as $index => &$item) {
149
+            $item['extend'] = $item['extend'] ?? '';
150
+            if ($item['name'] == '__tips__') {
151
+                $tips = $item;
152
+                unset($config[$index]);
153
+            }
154
+        }
155
+        $this->assign('data', ['info' => $info, 'config' => $config, 'tips' => $tips]);
156
+        $configFile = ADDON_PATH . $name . DS . 'config.html';
157
+        $viewFile   = is_file($configFile) ? $configFile : '';
158
+        return $this->fetch($viewFile);
159
+    }
160
+
161
+    /**
162
+     * 禁用启用.
163
+     */
164
+    public function state()
165
+    {
166
+        $name   = $this->request->param('name');
167
+        $action = $this->request->param('action');
168
+        $force  = $this->request->post("force/d");
169
+        if (!$name) {
170
+            $this->error('参数不得为空!');
171
+        }
172
+        if (!preg_match('/^[a-zA-Z0-9]+$/', $name)) {
173
+            $this->error('插件名称不正确');
174
+        }
175
+        try {
176
+            $action = $action == 'enable' ? $action : 'disable';
177
+            //调用启用、禁用的方法
178
+            Service::$action($name, $force);
179
+        } catch (AddonException $e) {
180
+            $this->result($e->getData(), $e->getCode(), $e->getMessage());
181
+        } catch (\Exception $e) {
182
+            $this->error($e->getMessage());
183
+        }
184
+        $this->success('操作成功');
185
+    }
186
+
187
+    /**
188
+     * 安装插件
189
+     */
190
+    public function install()
191
+    {
192
+        Adminlog::setTitle('插件安装');
193
+        $name  = $this->request->param('name');
194
+        $force = $this->request->param("force/d");
195
+        if (empty($name)) {
196
+            $this->error('请选择需要安装的插件!');
197
+        }
198
+        if (!preg_match('/^[a-zA-Z0-9]+$/', $name)) {
199
+            $this->error('插件标识错误!');
200
+        }
201
+        $info = [];
202
+        try {
203
+            $uid     = $this->request->post("uid");
204
+            $token   = $this->request->post("token");
205
+            $version = $this->request->post("version");
206
+            $extend  = [
207
+                'uid'            => $uid,
208
+                'token'          => $token,
209
+                'version'        => $version,
210
+                'yzncms_version' => Config::get('version.yzncms_version'),
211
+            ];
212
+            $info = Service::install($name, $force, $extend);
213
+        } catch (AddonException $e) {
214
+            $this->result($e->getData(), $e->getCode(), $e->getMessage());
215
+        } catch (\Exception $e) {
216
+            $this->error($e->getMessage());
217
+        }
218
+        $this->success('插件安装成功!清除浏览器缓存和框架缓存后生效!', '', ['addon' => $info]);
219
+    }
220
+
221
+    /**
222
+     * 卸载插件
223
+     */
224
+    public function uninstall()
225
+    {
226
+        Adminlog::setTitle('插件卸载');
227
+        $name       = $this->request->param('name');
228
+        $force      = $this->request->param("force/d");
229
+        $droptables = $this->request->param("droptables/d");
230
+        if (empty($name)) {
231
+            $this->error('请选择需要安装的插件!');
232
+        }
233
+        if (!preg_match('/^[a-zA-Z0-9]+$/', $name)) {
234
+            $this->error('插件标识错误!');
235
+        }
236
+        //只有开启调试且为超级管理员才允许删除相关数据库
237
+        $tables = [];
238
+        if ($droptables && Config::get("app_debug") && $this->auth->isAdministrator()) {
239
+            $tables = get_addon_tables($name);
240
+        }
241
+        try {
242
+            Service::uninstall($name, $force);
243
+            if ($tables) {
244
+                //$prefix = Config::get('database.prefix');
245
+                //删除插件关联表
246
+                foreach ($tables as $table) {
247
+                    Db::execute("DROP TABLE IF EXISTS `{$table}`");
248
+                }
249
+            }
250
+        } catch (AddonException $e) {
251
+            $this->result($e->getData(), $e->getCode(), $e->getMessage());
252
+        } catch (\Exception $e) {
253
+            $this->error($e->getMessage());
254
+        }
255
+        $this->success('插件卸载成功!清除浏览器缓存和框架缓存后生效!');
256
+    }
257
+
258
+    /**
259
+     * 本地上传
260
+     */
261
+    public function local()
262
+    {
263
+        Adminlog::setTitle('插件上传安装');
264
+        if (!Config::get("app_debug")) {
265
+            $this->error('本地上传安装需要开启调试模式!');
266
+        }
267
+        $file = $this->request->file('file');
268
+        try {
269
+            $info = Service::local($file);
270
+        } catch (AddonException $e) {
271
+            $this->result($e->getData(), $e->getCode(), $e->getMessage());
272
+        } catch (\Exception $e) {
273
+            $this->error($e->getMessage());
274
+        }
275
+        $this->success('插件安装成功!清除浏览器缓存和框架缓存后生效!', '', ['addon' => $info]);
276
+    }
277
+
278
+    /**
279
+     * 更新插件
280
+     */
281
+    public function upgrade()
282
+    {
283
+        $name        = $this->request->param('name');
284
+        $addonTmpDir = ROOT_PATH . 'runtime' . DS . 'addons' . DS;
285
+        if (empty($name)) {
286
+            $this->error('请选择需要安装的插件!');
287
+        }
288
+        if (!preg_match('/^[a-zA-Z0-9]+$/', $name)) {
289
+            $this->error('插件标识错误!');
290
+        }
291
+        if (!is_dir($addonTmpDir)) {
292
+            @mkdir($addonTmpDir, 0755, true);
293
+        }
294
+        $info = [];
295
+        try {
296
+            $info    = get_addon_info($name);
297
+            $uid     = $this->request->post("uid");
298
+            $token   = $this->request->post("token");
299
+            $version = $this->request->post("version");
300
+            $extend  = [
301
+                'uid'            => $uid,
302
+                'token'          => $token,
303
+                'version'        => $version,
304
+                'oldversion'     => $info['version'] ?? '',
305
+                'yzncms_version' => Config::get('version.yzncms_version'),
306
+            ];
307
+            //调用更新的方法
308
+            $info = Service::upgrade($name, $extend);
309
+        } catch (AddonException $e) {
310
+            $this->result($e->getData(), $e->getCode(), $e->getMessage());
311
+        } catch (Exception $e) {
312
+            $this->error($e->getMessage());
313
+        }
314
+        $this->success('升级成功', '', ['addon' => $info]);
315
+    }
316
+
317
+    /**
318
+     * 测试数据
319
+     */
320
+    public function testdata()
321
+    {
322
+        Adminlog::setTitle('插件导入测试数据');
323
+        $name = $this->request->post("name");
324
+        if (empty($name)) {
325
+            $this->error('请选择需要安装的插件!');
326
+        }
327
+        if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
328
+            $this->error('插件标识错误!');
329
+        }
330
+
331
+        try {
332
+            Service::runSQL($name, 'testdata');
333
+        } catch (AddonException $e) {
334
+            $this->result($e->getData(), $e->getCode(), $e->getMessage());
335
+        } catch (Exception $e) {
336
+            $this->error($e->getMessage(), $e->getCode());
337
+        }
338
+        $this->success('导入成功');
339
+    }
340
+
341
+    /**
342
+     * 检测
343
+     */
344
+    public function isbuy()
345
+    {
346
+        $name    = $this->request->post("name");
347
+        $uid     = $this->request->post("uid");
348
+        $token   = $this->request->post("token");
349
+        $version = $this->request->post("version");
350
+        $extend  = [
351
+            'uid'            => $uid,
352
+            'token'          => $token,
353
+            'version'        => $version,
354
+            'yzncms_version' => Config::get('version.yzncms_version'),
355
+        ];
356
+        try {
357
+            $result = Service::isBuy($name, $extend);
358
+        } catch (Exception $e) {
359
+            $this->error($e->getMessage());
360
+        }
361
+        return json($result);
362
+    }
363
+
364
+    /**
365
+     * 获取插件相关表
366
+     */
367
+    public function get_table_list()
368
+    {
369
+        $name = $this->request->post("name");
370
+        if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
371
+            $this->error('插件标识错误!');
372
+        }
373
+        $tables = get_addon_tables($name);
374
+        //$prefix = Config::get('database.prefix');
375
+        $tables = array_values($tables);
376
+        $this->success('', null, ['tables' => $tables]);
377
+    }
378
+
379
+    protected function getAddonList($search, $page, $limit)
380
+    {
381
+        $params = [
382
+            'uid'         => $this->request->param('uid/d'),
383
+            'token'       => $this->request->param('token'),
384
+            'category_id' => $this->request->param('category_id/d'),
385
+            'version'     => Config::get('version.yzncms_version'),
386
+            'page'        => $page,
387
+            'limit'       => $limit,
388
+            'search'      => $search,
389
+        ];
390
+        $json = [];
391
+        try {
392
+            $json = Service::addons($params);
393
+        } catch (\Exception $e) {
394
+
395
+        }
396
+        return $json;
397
+    }
398
+}

+ 328
- 0
application/admin/controller/Ajax.php Datei anzeigen

@@ -0,0 +1,328 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// | 后台常用ajax
14
+// +----------------------------------------------------------------------
15
+namespace app\admin\controller;
16
+
17
+use app\admin\model\Adminlog;
18
+use app\common\controller\Adminbase;
19
+use app\common\exception\UploadException;
20
+use app\common\library\Upload as UploadLib;
21
+use Exception;
22
+use think\Db;
23
+use think\Response;
24
+
25
+class Ajax extends Adminbase
26
+{
27
+    protected $noNeedRight = ['*'];
28
+
29
+    //编辑器初始配置
30
+    private $editorConfig = [
31
+        /* 上传图片配置项 */
32
+        "imageActionName"         => "uploadimage", /* 执行上传图片的action名称 */
33
+        "imageFieldName"       => "upfile", /* 提交的图片表单名称 */
34
+        "imageMaxSize"     => 2048000, /* 上传大小限制,单位B */
35
+        "imageAllowFiles" => [".png", ".jpg", ".jpeg", ".gif", ".bmp"], /* 上传图片格式显示 */
36
+        "imageCompressEnable" => true, /* 是否压缩图片,默认是true */
37
+        "imageCompressBorder" => 1600, /* 图片压缩最长边限制 */
38
+        "imageInsertAlign" => "none", /* 插入的图片浮动方式 */
39
+        "imageUrlPrefix" => "", /* 图片访问路径前缀 */
40
+        'imagePathFormat' => '',
41
+        /* 涂鸦图片上传配置项 */
42
+        "scrawlActionName"        => "uploadscrawl", /* 执行上传涂鸦的action名称 */
43
+        "scrawlFieldName"      => "upfile", /* 提交的图片表单名称 */
44
+        'scrawlPathFormat' => '',
45
+        "scrawlMaxSize"           => 2048000, /* 上传大小限制,单位B */
46
+        'scrawlUrlPrefix'      => '',
47
+        'scrawlInsertAlign'       => 'none',
48
+        /* 截图工具上传 */
49
+        "snapscreenActionName"    => "uploadimage", /* 执行上传截图的action名称 */
50
+        'snapscreenPathFormat' => '',
51
+        'snapscreenUrlPrefix'     => '',
52
+        'snapscreenInsertAlign'   => 'none',
53
+        /* 抓取远程图片配置 */
54
+        'catcherLocalDomain'      => ['127.0.0.1', 'localhost', 'img.baidu.com'],
55
+        "catcherActionName"       => "catchimage", /* 执行抓取远程图片的action名称 */
56
+        'catcherFieldName'     => 'source',
57
+        'catcherPathFormat'       => '',
58
+        'catcherUrlPrefix'        => '',
59
+        'catcherMaxSize'          => 0,
60
+        'catcherAllowFiles'       => ['.png', '.jpg', '.jpeg', '.gif', '.bmp'],
61
+        /* 上传视频配置 */
62
+        "videoActionName"         => "uploadvideo", /* 执行上传视频的action名称 */
63
+        "videoFieldName"       => "upfile", /* 提交的视频表单名称 */
64
+        'videoPathFormat'  => '',
65
+        'videoUrlPrefix'          => '',
66
+        'videoMaxSize'            => 0,
67
+        'videoAllowFiles'         => [".flv", ".swf", ".mkv", ".avi", ".rm", ".rmvb", ".mpeg", ".mpg", ".ogg", ".ogv", ".mov", ".wmv", ".mp4", ".webm", ".mp3", ".wav", ".mid"],
68
+        /* 上传文件配置 */
69
+        "fileActionName"          => "uploadfile", /* controller里,执行上传视频的action名称 */
70
+        'fileFieldName'        => 'upfile',
71
+        'filePathFormat'          => '',
72
+        'fileUrlPrefix'           => '',
73
+        'fileMaxSize'             => 0,
74
+        'fileAllowFiles'          => [".flv", ".swf"],
75
+        /* 列出指定目录下的图片 */
76
+        "imageManagerActionName"  => "listimage", /* 执行图片管理的action名称 */
77
+        'imageManagerListPath' => '',
78
+        'imageManagerListSize'    => 20,
79
+        'imageManagerUrlPrefix'   => '',
80
+        'imageManagerInsertAlign' => 'none',
81
+        'imageManagerAllowFiles'  => ['.png', '.jpg', '.jpeg', '.gif', '.bmp'],
82
+        /* 列出指定目录下的文件 */
83
+        "fileManagerActionName"   => "listfile", /* 执行文件管理的action名称 */
84
+        'fileManagerListPath'  => '',
85
+        'fileManagerUrlPrefix'    => '',
86
+        'fileManagerListSize'     => '',
87
+        'fileManagerAllowFiles'   => [".flv", ".swf"],
88
+    ];
89
+
90
+    protected function initialize()
91
+    {
92
+        parent::initialize();
93
+        //图片上传大小和类型
94
+        $this->editorConfig['imageMaxSize'] = $this->editorConfig['catcherMaxSize'] = 0 == config('site.upload_image_size') ? 1024 * 1024 * 1024 : config('site.upload_image_size') * 1024;
95
+        if (!empty(config('site.upload_image_ext'))) {
96
+            $imageallowext = parse_attr(config('site.upload_image_ext'));
97
+            foreach ($imageallowext as $k => $rs) {
98
+                $imageallowext[$k] = ".{$rs}";
99
+            }
100
+            $this->editorConfig['imageAllowFiles'] = $imageallowext;
101
+        }
102
+
103
+        //附件上传大小和类型
104
+        $this->editorConfig['fileMaxSize'] = $this->editorConfig['videoMaxSize'] = 0 == config('site.upload_file_size') ? 1024 * 1024 * 1024 : config('site.upload_file_size') * 1024;
105
+        if (!empty(config('site.upload_file_ext'))) {
106
+            $fileallowext = parse_attr(config('site.upload_file_ext'));
107
+            foreach ($fileallowext as $k => $rs) {
108
+                $fileallowext[$k] = ".{$rs}";
109
+            }
110
+            $this->editorConfig['fileAllowFiles'] = $fileallowext;
111
+        }
112
+    }
113
+
114
+    //过滤内容的敏感词
115
+    public function filterWord($content)
116
+    {
117
+        $content = $this->request->post('content');
118
+        // 获取感词库文件路径
119
+        $wordFilePath = ROOT_PATH . 'data/words.txt';
120
+        $handle       = \util\SensitiveHelper::init()->setTreeByFile($wordFilePath);
121
+        $word         = $handle->getBadWord($content);
122
+        if ($word) {
123
+            $this->error('内容包含违禁词!', null, $word);
124
+        } else {
125
+            $this->success('内容没有违禁词!');
126
+        }
127
+    }
128
+
129
+    /**
130
+     * 生成后缀图标
131
+     */
132
+    public function icon()
133
+    {
134
+        $suffix                  = $this->request->request("suffix", 'file');
135
+        $data                    = build_suffix_image($suffix);
136
+        $header                  = ['Content-Type' => 'image/svg+xml'];
137
+        $offset                  = 30 * 60 * 60 * 24; // 缓存一个月
138
+        $header['Cache-Control'] = 'public';
139
+        $header['Pragma']        = 'cache';
140
+        $header['Expires']       = gmdate("D, d M Y H:i:s", time() + $offset) . " GMT";
141
+        $response                = Response::create($data, '', 200, $header);
142
+        return $response;
143
+    }
144
+
145
+    /**
146
+     * 读取省市区数据,联动列表
147
+     */
148
+    public function area()
149
+    {
150
+        $params = $this->request->get("row/a");
151
+        if (!empty($params)) {
152
+            $province = $params['province'] ?? null;
153
+            $city     = $params['city'] ?? null;
154
+        } else {
155
+            $province = $this->request->get('province');
156
+            $city     = $this->request->get('city');
157
+        }
158
+        $where        = ['pid' => 0, 'level' => 1];
159
+        $provincelist = null;
160
+        if ($province !== null) {
161
+            $where['pid']   = $province;
162
+            $where['level'] = 2;
163
+            if ($city !== null) {
164
+                $where['pid']   = $city;
165
+                $where['level'] = 3;
166
+            }
167
+        }
168
+        $provincelist = Db::name('area')->where($where)->field('id as value,name')->select();
169
+        $this->success('', '', $provincelist);
170
+    }
171
+
172
+    public function upload($dir = '', $from = '')
173
+    {
174
+        Adminlog::setTitle('附件上传');
175
+        if ($dir == '') {
176
+            return json([
177
+                'code'    => 0,
178
+                'msg'     => '没有指定上传目录',
179
+                'state'   => '没有指定上传目录', //兼容百度
180
+                'message' => '没有指定上传目录', //兼容editormd
181
+            ]);
182
+        }
183
+        $chunkid = $this->request->post("chunkid");
184
+        if ($chunkid) {
185
+            if (!config('chunking')) {
186
+                return json([
187
+                    'code' => 0,
188
+                    'msg'  => '未开启分片上传功能',
189
+                ]);
190
+            }
191
+            //分片
192
+            $action     = $this->request->post("action");
193
+            $chunkindex = $this->request->post("chunkindex/d", 0);
194
+            $chunkcount = $this->request->post("chunkcount/d", 1);
195
+            $filename   = $this->request->post("filename");
196
+            $method     = $this->request->method(true);
197
+            if ($action == 'merge') {
198
+                $attachment = null;
199
+                //合并分片文件
200
+                try {
201
+                    $upload     = new UploadLib();
202
+                    $attachment = $upload->merge($chunkid, $chunkcount, $filename, $dir, $from);
203
+                } catch (UploadException $e) {
204
+                    return json([
205
+                        'code'    => 0,
206
+                        'msg'     => $e->getMessage(),
207
+                        'state'   => $e->getMessage(), //兼容百度
208
+                        'message' => $e->getMessage(), //兼容editormd
209
+                    ]);
210
+                }
211
+                return $attachment;
212
+            } elseif ($method == 'clean') {
213
+                //删除冗余的分片文件
214
+                try {
215
+                    $upload = new UploadLib();
216
+                    $upload->clean($chunkid);
217
+                } catch (UploadException $e) {
218
+                    return json([
219
+                        'code'    => 0,
220
+                        'msg'     => $e->getMessage(),
221
+                        'state'   => $e->getMessage(), //兼容百度
222
+                        'message' => $e->getMessage(), //兼容editormd
223
+                    ]);
224
+                }
225
+                return json(['code' => 1]);
226
+            } else {
227
+                //上传分片文件
228
+                $file = $this->request->file('file');
229
+                try {
230
+                    $upload = new UploadLib($file);
231
+                    $upload->chunk($chunkid, $chunkindex, $chunkcount);
232
+                } catch (UploadException $e) {
233
+                    return json([
234
+                        'code'    => 0,
235
+                        'msg'     => $e->getMessage(),
236
+                        'state'   => $e->getMessage(), //兼容百度
237
+                        'message' => $e->getMessage(), //兼容editormd
238
+                    ]);
239
+                }
240
+                return json(['code' => 1]);
241
+            }
242
+        }
243
+        // 获取附件数据
244
+        switch ($from) {
245
+            case 'editormd':
246
+                $file_input_name = 'editormd-image-file';
247
+                break;
248
+            case 'ueditor':
249
+                $file_input_name = 'upfile';
250
+                break;
251
+            default:
252
+                $file_input_name = 'file';
253
+        }
254
+        $attachment = null;
255
+
256
+        try {
257
+            //默认普通上传文件
258
+            $file = $this->request->file($file_input_name);
259
+            if ($from == 'ueditor') {
260
+                return $this->ueditor($file);
261
+            }
262
+            $upload     = new UploadLib($file);
263
+            $attachment = $upload->upload($dir);
264
+        } catch (UploadException | Exception $e) {
265
+            return json([
266
+                'code'    => 0,
267
+                'msg'     => $e->getMessage(),
268
+                'state'   => $e->getMessage(), //兼容百度
269
+                'message' => $e->getMessage(), //兼容editormd
270
+            ]);
271
+        }
272
+        return $attachment;
273
+    }
274
+
275
+    private function ueditor($file)
276
+    {
277
+        $action = $this->request->get('action');
278
+        switch ($action) {
279
+            /* 获取配置信息 */
280
+            case 'config':
281
+                $result = $this->editorConfig;
282
+                break;
283
+            /* 上传图片 */
284
+            case 'uploadimage':
285
+                $upload = new UploadLib($file);
286
+                return $upload->upload('images', 'ueditor');
287
+                break;
288
+            /* 上传涂鸦 */
289
+            case 'uploadscrawl':
290
+                $upload = new UploadLib($file);
291
+                return $upload->upload('images', 'ueditor_scrawl');
292
+                break;
293
+            /* 上传视频 */
294
+            case 'uploadvideo':
295
+                $upload = new UploadLib($file);
296
+                return $upload->upload('videos', 'ueditor');
297
+                break;
298
+            /* 上传附件 */
299
+            case 'uploadfile':
300
+                $upload = new UploadLib($file);
301
+                return $upload->upload('files', 'ueditor');
302
+                break;
303
+            /* 列出图片
304
+            /*case 'listimage':
305
+            return $this->showFileList('listimage');
306
+            break;
307
+            列出附件
308
+            case 'listfile':
309
+            return $this->showFileList('listfile');
310
+            break;*/
311
+            default:
312
+                $result = [
313
+                    'state' => '请求地址出错',
314
+                ];
315
+                break;
316
+        }
317
+        /* 输出结果 */
318
+        if (isset($_GET["callback"])) {
319
+            if (preg_match("/^[\w_]+$/", $_GET["callback"])) {
320
+                return htmlspecialchars($_GET["callback"]) . '(' . $result . ')';
321
+            } else {
322
+                return json(['state' => 'callback参数不合法']);
323
+            }
324
+        } else {
325
+            return json($result);
326
+        }
327
+    }
328
+}

+ 127
- 0
application/admin/controller/Index.php Datei anzeigen

@@ -0,0 +1,127 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// | 后台首页
14
+// +----------------------------------------------------------------------
15
+namespace app\admin\controller;
16
+
17
+use app\admin\model\Adminlog;
18
+use app\common\controller\Adminbase;
19
+use think\addons\Service;
20
+use think\facade\Cache;
21
+use think\facade\Hook;
22
+use util\File;
23
+
24
+class Index extends Adminbase
25
+{
26
+    protected $noNeedLogin = ['login'];
27
+    protected $noNeedRight = ['index', 'cache', 'logout'];
28
+
29
+    //初始化
30
+    protected function initialize()
31
+    {
32
+        parent::initialize();
33
+        //移除HTML标签
34
+        $this->request->filter('trim,strip_tags,htmlspecialchars');
35
+    }
36
+
37
+    //后台首页
38
+    public function index()
39
+    {
40
+        if ($this->request->isPost()) {
41
+            return json($this->auth->getSidebar());
42
+        }
43
+        return $this->fetch();
44
+    }
45
+
46
+    //登录判断
47
+    public function login()
48
+    {
49
+        $url = $this->request->get('url', 'admin/index/index', 'url_clean');
50
+        if ($this->auth->isLogin()) {
51
+            $this->success("你已经登录,无需重复登录", $url);
52
+        }
53
+        //保持会话有效时长,单位:小时
54
+        $keeyloginhours = 24;
55
+        if ($this->request->isPost()) {
56
+            $data      = $this->request->post();
57
+            $keeplogin = $this->request->post('keeplogin');
58
+            $rule      = [
59
+                'verify|验证码'   => 'require|captcha',
60
+                'username|用户名' => 'require|alphaDash|length:3,20',
61
+                'password|密码'  => 'require|length:3,20',
62
+                '__token__'    => 'require|token',
63
+            ];
64
+            $result = $this->validate($data, $rule);
65
+            Adminlog::setTitle('登录');
66
+            if (true !== $result) {
67
+                $this->error($result, $url, ['token' => $this->request->token()]);
68
+            }
69
+            if ($this->auth->login($data['username'], $data['password'], $keeplogin ? $keeyloginhours * 3600 : 0)) {
70
+                Hook::listen("admin_login_after", $this->request);
71
+                $this->success('恭喜您,登陆成功', $url);
72
+            } else {
73
+                $msg = $this->auth->getError();
74
+                $msg = $msg ?: '用户名或者密码错误!';
75
+                $this->error($msg, $url, ['token' => $this->request->token()]);
76
+            }
77
+        }
78
+        if ($this->auth->autologin()) {
79
+            $this->redirect($url);
80
+        }
81
+        Hook::listen("admin_login_init", $this->request);
82
+        $this->assign('keeyloginhours', $keeyloginhours);
83
+        return $this->fetch();
84
+    }
85
+
86
+    //手动退出登录
87
+    public function logout()
88
+    {
89
+        if ($this->auth->logout()) {
90
+            Hook::listen("admin_logout_after", $this->request);
91
+            $this->success('注销成功!', url("admin/index/login"));
92
+        }
93
+    }
94
+
95
+    //缓存更新
96
+    public function cache()
97
+    {
98
+        try {
99
+            $type = $this->request->request("type");
100
+            switch ($type) {
101
+                case 'all':
102
+                case 'data':
103
+                    File::del_dir(ROOT_PATH . 'runtime' . DS . 'cache');
104
+                    Cache::clear();
105
+                    if ($type == 'data') {
106
+                        break;
107
+                    }
108
+
109
+                case 'template':
110
+                    File::del_dir(ROOT_PATH . 'runtime' . DS . 'temp');
111
+                    if ($type == 'template') {
112
+                        break;
113
+                    }
114
+                case 'addons':
115
+                    // 插件缓存
116
+                    Service::refresh();
117
+                    if ($type == 'addons') {
118
+                        break;
119
+                    }
120
+            }
121
+        } catch (\Exception $e) {
122
+            $this->error($e->getMessage());
123
+        }
124
+        Hook::listen("wipecache_after");
125
+        $this->success('清理缓存');
126
+    }
127
+}

+ 66
- 0
application/admin/controller/Main.php Datei anzeigen

@@ -0,0 +1,66 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// | 后台欢迎页
14
+// +----------------------------------------------------------------------
15
+namespace app\admin\controller;
16
+
17
+use app\common\controller\Adminbase;
18
+use think\Db;
19
+
20
+class Main extends Adminbase
21
+{
22
+    protected $noNeedRight = ['index'];
23
+    //欢迎首页
24
+    public function index()
25
+    {
26
+        if (IS_ROOT && $this->auth->password == encrypt_password('admin', $this->auth->encrypt)) {
27
+            $this->assign('default_pass', 1);
28
+        }
29
+        $this->assign('sys_info', $this->get_sys_info());
30
+        return $this->fetch();
31
+    }
32
+
33
+    //phpinfo信息 按需显示在前台
34
+    protected function get_sys_info()
35
+    {
36
+        //$sys_info['os'] = PHP_OS; //操作系统
37
+        $sys_info['ip']           = GetHostByName($_SERVER['SERVER_NAME']); //服务器IP
38
+        $sys_info['web_server']   = $_SERVER['SERVER_SOFTWARE']; //服务器环境
39
+        $sys_info['phpv']         = phpversion(); //php版本
40
+        $sys_info['fileupload']   = @ini_get('file_uploads') ? ini_get('upload_max_filesize') : 'unknown'; //文件上传限制
41
+        $sys_info['memory_limit'] = ini_get('memory_limit'); //最大占用内存
42
+        //$sys_info['set_time_limit'] = function_exists("set_time_limit") ? true : false; //最大执行时间
43
+        //$sys_info['zlib'] = function_exists('gzclose') ? 'YES' : 'NO'; //Zlib支持
44
+        //$sys_info['safe_mode'] = (boolean) ini_get('safe_mode') ? 'YES' : 'NO'; //安全模式
45
+        //$sys_info['timezone'] = function_exists("date_default_timezone_get") ? date_default_timezone_get() : "no_timezone";
46
+        //$sys_info['curl'] = function_exists('curl_init') ? 'YES' : 'NO'; //Curl支持
47
+        //$sys_info['max_ex_time'] = @ini_get("max_execution_time") . 's';
48
+        $sys_info['domain']          = $_SERVER['HTTP_HOST']; //域名
49
+        $sys_info['remaining_space'] = function_exists('disk_free_space') ? round((disk_free_space(".") / (1024 * 1024)), 2) . 'M' : '未知'; //剩余空间
50
+        //$sys_info['user_ip'] = $_SERVER['REMOTE_ADDR']; //用户IP地址
51
+        $sys_info['beijing_time'] = gmdate("Y年n月j日 H:i:s", time() + 8 * 3600); //北京时间
52
+        $sys_info['time']         = date("Y年n月j日 H:i:s"); //服务器时间
53
+        //$sys_info['web_directory'] = $_SERVER["DOCUMENT_ROOT"]; //网站目录
54
+        $mysqlinfo                 = Db::query("SELECT VERSION() as version");
55
+        $sys_info['mysql_version'] = $mysqlinfo[0]['version'];
56
+        if (function_exists("gd_info")) {
57
+            //GD库版本
58
+            $gd                 = gd_info();
59
+            $sys_info['gdinfo'] = $gd['GD Version'];
60
+        } else {
61
+            $sys_info['gdinfo'] = "未知";
62
+        }
63
+        return $sys_info;
64
+    }
65
+
66
+}

+ 76
- 0
application/admin/controller/auth/Adminlog.php Datei anzeigen

@@ -0,0 +1,76 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// | 日志首页
14
+// +----------------------------------------------------------------------
15
+namespace app\admin\controller\auth;
16
+
17
+use app\admin\model\Adminlog as AdminlogModel;
18
+use app\common\controller\Adminbase;
19
+
20
+class Adminlog extends Adminbase
21
+{
22
+    protected $modelClass       = null;
23
+    protected $childrenAdminIds = [];
24
+
25
+    protected function initialize()
26
+    {
27
+        parent::initialize();
28
+        $this->modelClass       = new AdminlogModel;
29
+        $this->childrenAdminIds = $this->auth->getChildrenAdminIds(true);
30
+    }
31
+
32
+    //删除一个月前的操作日志
33
+    public function deletelog()
34
+    {
35
+        AdminlogModel::where('create_time', '<= time', time() - (86400 * 30))->delete();
36
+        $this->success("删除日志成功!");
37
+    }
38
+
39
+    /**
40
+     * 详情
41
+     */
42
+    public function detail($id)
43
+    {
44
+        $row = $this->modelClass->get($id);
45
+        if (!$row) {
46
+            $this->error('记录未找到');
47
+        }
48
+        if (!$this->auth->isAdministrator()) {
49
+            if (!$row['admin_id'] || !in_array($row['admin_id'], $this->childrenAdminIds)) {
50
+                $this->error('你没有权限访问');
51
+            }
52
+        }
53
+        $this->assign("row", $row->toArray());
54
+        return $this->fetch();
55
+    }
56
+
57
+    //添加
58
+    public function add()
59
+    {
60
+        $this->error();
61
+    }
62
+
63
+    //编辑
64
+    public function edit()
65
+    {
66
+        $this->error();
67
+    }
68
+
69
+    //批量更新
70
+    public function multi()
71
+    {
72
+        // 管理员禁止批量操作
73
+        $this->error();
74
+    }
75
+
76
+}

+ 281
- 0
application/admin/controller/auth/Group.php Datei anzeigen

@@ -0,0 +1,281 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2007 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: fastadmin: https://www.fastadmin.net/
10
+// +----------------------------------------------------------------------
11
+namespace app\admin\Controller\auth;
12
+
13
+use app\admin\model\AuthGroup as AuthGroupModel;
14
+use app\common\controller\Adminbase;
15
+use think\Db;
16
+use util\Tree;
17
+
18
+/**
19
+ * 权限管理控制器
20
+ */
21
+class Group extends Adminbase
22
+{
23
+    //当前登录管理员所有子组别
24
+    protected $childrenGroupIds = [];
25
+    //当前组别列表数据
26
+    protected $grouplist   = [];
27
+    protected $groupdata   = [];
28
+    protected $noNeedRight = [];
29
+
30
+    protected function initialize()
31
+    {
32
+        parent::initialize();
33
+        $this->modelClass = new AuthGroupModel;
34
+
35
+        $this->childrenGroupIds = $this->auth->getChildrenGroupIds(true);
36
+
37
+        $groupList = AuthGroupModel::where('id', 'in', $this->childrenGroupIds)->select()->toArray();
38
+
39
+        Tree::instance()->init($groupList);
40
+        $groupList = [];
41
+        if ($this->auth->isAdministrator()) {
42
+            $groupList = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0), 'title');
43
+        } else {
44
+            $groups   = $this->auth->getGroups();
45
+            $groupIds = [];
46
+            foreach ($groups as $m => $n) {
47
+                if (in_array($n['id'], $groupIds) || in_array($n['parentid'], $groupIds)) {
48
+                    continue;
49
+                }
50
+                $groupList = array_merge($groupList, Tree::instance()->getTreeList(Tree::instance()->getTreeArray($n['parentid']), 'title'));
51
+                foreach ($groupList as $index => $item) {
52
+                    $groupIds[] = $item['id'];
53
+                }
54
+            }
55
+        }
56
+
57
+        $groupName = [];
58
+        foreach ($groupList as $k => $v) {
59
+            $groupName[$v['id']] = $v['title'];
60
+        }
61
+        $this->grouplist = $groupList;
62
+        $this->groupdata = $groupName;
63
+        $this->assign('groupdata', $this->groupdata);
64
+    }
65
+
66
+    //权限管理首页
67
+    public function index()
68
+    {
69
+        if ($this->request->isAjax()) {
70
+            $list   = $this->grouplist;
71
+            $total  = count($list);
72
+            $result = ["code" => 0, "count" => $total, "data" => $list];
73
+            return json($result);
74
+        } else {
75
+            return $this->fetch();
76
+        }
77
+    }
78
+
79
+    //创建管理员用户组
80
+    public function add()
81
+    {
82
+        if ($this->request->isPost()) {
83
+            $this->token();
84
+            $params = $this->request->post("row/a", [], 'strip_tags');
85
+            $result = $this->validate($params, 'app\admin\validate\AuthGroup');
86
+            if (true !== $result) {
87
+                return $this->error($result);
88
+            }
89
+            if (!in_array($params['parentid'], $this->childrenGroupIds)) {
90
+                $this->error('父组别超出权限范围');
91
+            }
92
+            $parentmodel = AuthGroupModel::get($params['parentid']);
93
+            if (!$parentmodel) {
94
+                $this->error('父组别未找到');
95
+            }
96
+            if ($params) {
97
+                $this->modelClass->create($params);
98
+                $this->success('新增成功');
99
+            }
100
+            $this->error('参数不能为空');
101
+        }
102
+        return $this->fetch();
103
+
104
+    }
105
+
106
+    //编辑管理员用户组
107
+    public function edit()
108
+    {
109
+        $id = $this->request->param('id/d');
110
+        if (!in_array($id, $this->childrenGroupIds)) {
111
+            $this->error('你没有权限访问!');
112
+        }
113
+        $row = $this->modelClass->get($id);
114
+        if (!$row) {
115
+            $this->error('记录未找到');
116
+        }
117
+        if ($this->request->isPost()) {
118
+            $this->token();
119
+            $params = $this->request->post("row/a", [], 'strip_tags');
120
+            //父节点不能是非权限内节点
121
+            if (!in_array($params['parentid'], $this->childrenGroupIds)) {
122
+                $this->error('父组别超出权限范围');
123
+            }
124
+            // 父节点不能是它自身的子节点或自己本身
125
+            if (in_array($params['parentid'], Tree::instance()->getChildrenIds($row->id, true))) {
126
+                $this->error('父角色不能是自身!');
127
+            }
128
+            $params['rules'] = explode(',', $params['rules']);
129
+
130
+            $parentmodel = AuthGroupModel::get($params['parentid']);
131
+            if (!$parentmodel) {
132
+                $this->error('父组别未找到');
133
+            }
134
+
135
+            // 父级别的规则节点
136
+            $parentrules = explode(',', $parentmodel->rules);
137
+            // 当前组别的规则节点
138
+            $currentrules = $this->auth->getRuleIds();
139
+            $rules        = $params['rules'];
140
+            // 如果父组不是超级管理员则需要过滤规则节点,不能超过父组别的权限
141
+            $rules = in_array('*', $parentrules) ? $rules : array_intersect($parentrules, $rules);
142
+            // 如果当前组别不是超级管理员则需要过滤规则节点,不能超当前组别的权限
143
+            $rules           = in_array('*', $currentrules) ? $rules : array_intersect($currentrules, $rules);
144
+            $params['rules'] = implode(',', $rules);
145
+            if ($params) {
146
+                Db::startTrans();
147
+                try {
148
+                    $row->save($params);
149
+                    $children_auth_groups = model("AuthGroup")->all(['id' => ['in', implode(',', (Tree::instance()->getChildrenIds($row->id)))]]);
150
+                    $childparams          = [];
151
+                    foreach ($children_auth_groups as $key => $children_auth_group) {
152
+                        $childparams[$key]['id']    = $children_auth_group->id;
153
+                        $childparams[$key]['rules'] = implode(',', array_intersect(explode(',', $children_auth_group->rules), $rules));
154
+                    }
155
+                    model("AuthGroup")->saveAll($childparams);
156
+                    Db::commit();
157
+                } catch (Exception $e) {
158
+                    Db::rollback();
159
+                    $this->error($e->getMessage());
160
+                }
161
+                $this->success('编辑成功');
162
+            }
163
+            $this->error('参数不能为空');
164
+        }
165
+        $this->assign("data", $row);
166
+        return $this->fetch();
167
+    }
168
+
169
+    //访问授权页面
170
+    public function access()
171
+    {
172
+        $id  = $this->request->param('id/d');
173
+        $pid = $this->request->param('pid/d');
174
+        if (!in_array($id, $this->childrenGroupIds)) {
175
+            $this->error('你没有权限访问!');
176
+        }
177
+        $row = $this->modelClass->get($id);
178
+        if (!$row) {
179
+            $this->error('记录未找到');
180
+        }
181
+        if ($this->request->isPost()) {
182
+            $this->token();
183
+            $params          = $this->request->post("row/a", [], 'strip_tags');
184
+            $params['rules'] = explode(',', $params['rules']);
185
+
186
+            $parentmodel = $this->modelClass->get($params['parentid']);
187
+            if (!$parentmodel) {
188
+                $this->error('父组别未找到');
189
+            }
190
+            // 父级别的规则节点
191
+            $parentrules = explode(',', $parentmodel->rules);
192
+            // 当前组别的规则节点
193
+            $currentrules = $this->auth->getRuleIds();
194
+            $rules        = $params['rules'];
195
+            // 如果父组不是超级管理员则需要过滤规则节点,不能超过父组别的权限
196
+            $rules = in_array('*', $parentrules) ? $rules : array_intersect($parentrules, $rules);
197
+            // 如果当前组别不是超级管理员则需要过滤规则节点,不能超当前组别的权限
198
+            $rules = in_array('*', $currentrules) ? $rules : array_intersect($currentrules, $rules);
199
+            Db::startTrans();
200
+            try {
201
+                $row->rules = implode(',', $rules);
202
+                $row->save();
203
+                $children_auth_groups = model("AuthGroup")->all(['id' => ['in', implode(',', (Tree::instance()->getChildrenIds($row->id)))]]);
204
+                $childparams          = [];
205
+                foreach ($children_auth_groups as $key => $children_auth_group) {
206
+                    $childparams[$key]['id']    = $children_auth_group->id;
207
+                    $childparams[$key]['rules'] = implode(',', array_intersect(explode(',', $children_auth_group->rules), $rules));
208
+                }
209
+                model("AuthGroup")->saveAll($childparams);
210
+                Db::commit();
211
+            } catch (Exception $e) {
212
+                Db::rollback();
213
+                $this->error($e->getMessage());
214
+            }
215
+            $this->success('编辑成功');
216
+        }
217
+        $parentGroupModel = $this->modelClass->get($pid);
218
+
219
+        $ruleList = model('AuthRule')->order('listorder', 'desc')->order('id', 'asc')->select()->toArray();
220
+        //读取父类角色所有节点列表
221
+        $parentRuleList = [];
222
+        if (in_array('*', explode(',', $parentGroupModel->rules))) {
223
+            $parentRuleList = $ruleList;
224
+        } else {
225
+            $parentRuleIds = explode(',', $parentGroupModel->rules);
226
+            foreach ($ruleList as $k => $v) {
227
+                if (in_array($v['id'], $parentRuleIds)) {
228
+                    $parentRuleList[] = $v;
229
+                }
230
+            }
231
+        }
232
+
233
+        $ruleTree  = new Tree();
234
+        $groupTree = new Tree();
235
+        //当前所有正常规则列表
236
+        $ruleTree->init($parentRuleList);
237
+        //角色组列表
238
+        $groupTree->init(model('AuthGroup')->where('id', 'in', $this->childrenGroupIds)->select()->toArray());
239
+
240
+        //读取当前角色下规则ID集合
241
+        $adminRuleIds = $this->auth->getRuleIds();
242
+        //是否是超级管理员
243
+        $superadmin = $this->auth->isAdministrator();
244
+        //当前拥有的规则ID集合
245
+        $currentRuleIds = explode(',', $row->rules);
246
+        $parentRuleList = $ruleTree->getTreeList($ruleTree->getTreeArray(0), 'name');
247
+        /*$hasChildrens = [];
248
+        foreach ($parentRuleList as $k => $v) {
249
+        if ($v['haschild']) {
250
+        $hasChildrens[] = $v['id'];
251
+        }
252
+        }*/
253
+        $parentRuleIds = array_map(function ($item) {
254
+            return $item['id'];
255
+        }, $parentRuleList);
256
+        $nodeList = [];
257
+        foreach ($parentRuleList as $k => $v) {
258
+            if (!$superadmin && !in_array($v['id'], $adminRuleIds)) {
259
+                continue;
260
+            }
261
+            if ($v['parentid'] && !in_array($v['parentid'], $parentRuleIds)) {
262
+                continue;
263
+            }
264
+            //$ischeck = in_array($v['id'], $currentRuleIds) && !in_array($v['id'], $hasChildrens);
265
+            $ischeck    = in_array($v['id'], $currentRuleIds);
266
+            $nodeList[] = ['id' => $v['id'], 'parentid' => $v['parentid'], 'name' => $v['title'], 'checked' => $ischeck];
267
+        }
268
+        //dump($nodeList);
269
+        $this->assign('nodeList', json_encode($nodeList));
270
+        $this->assign("data", $row);
271
+        return $this->fetch();
272
+    }
273
+
274
+    //批量更新
275
+    public function multi()
276
+    {
277
+        // 管理员禁止批量操作
278
+        $this->error();
279
+    }
280
+
281
+}

+ 205
- 0
application/admin/controller/auth/Manager.php Datei anzeigen

@@ -0,0 +1,205 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2007 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+namespace app\admin\controller\auth;
12
+
13
+use app\admin\model\AdminUser as Admin_User;
14
+use app\admin\model\AuthGroup as AuthGroupModel;
15
+use app\common\controller\Adminbase;
16
+use util\Tree;
17
+
18
+/**
19
+ * 管理员管理
20
+ */
21
+class Manager extends Adminbase
22
+{
23
+    protected $searchFields     = 'id,username,nickname';
24
+    protected $childrenGroupIds = [];
25
+    protected $childrenAdminIds = [];
26
+    protected function initialize()
27
+    {
28
+        parent::initialize();
29
+        $this->modelClass = new Admin_User;
30
+
31
+        $this->childrenAdminIds = $this->auth->getChildrenAdminIds(true);
32
+        $this->childrenGroupIds = $this->auth->getChildrenGroupIds(true);
33
+
34
+        $groupList = AuthGroupModel::where('id', 'in', $this->childrenGroupIds)->select()->toArray();
35
+        Tree::instance()->init($groupList);
36
+        $groupdata = [];
37
+        if ($this->auth->isAdministrator()) {
38
+            $result = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0), 'title');
39
+            foreach ($result as $k => $v) {
40
+                $groupdata[$v['id']] = $v['title'];
41
+            }
42
+        } else {
43
+            $result = [];
44
+            $groups = $this->auth->getGroups();
45
+            foreach ($groups as $m => $n) {
46
+                $childlist = Tree::instance()->getTreeList(Tree::instance()->getTreeArray($n['id']), 'title');
47
+                //$temp = [];
48
+                foreach ($childlist as $k => $v) {
49
+                    $groupdata[$v['id']] = $v['title'];
50
+                }
51
+                //$result[$n['title']] = $temp;
52
+            }
53
+            //$groupdata = $result;
54
+        }
55
+        $this->assign('groupdata', $groupdata);
56
+    }
57
+
58
+    /**
59
+     * 管理员管理列表
60
+     */
61
+    public function index()
62
+    {
63
+        if ($this->request->isAjax()) {
64
+            //如果发送的来源是Selectpage,则转发到Selectpage
65
+            if ($this->request->request('keyField')) {
66
+                return $this->selectpage();
67
+            }
68
+            list($page, $limit, $where, $sort, $order) = $this->buildTableParames();
69
+
70
+            $childrenGroupIds = $this->childrenGroupIds;
71
+            $groupName        = AuthGroupModel::where('id', 'in', $childrenGroupIds)
72
+                ->column('id,title');
73
+
74
+            $list = $this->modelClass
75
+                ->where($where)
76
+                ->where('id', 'in', $this->childrenAdminIds)
77
+                ->field(['password', 'salt', 'token'], true)
78
+                ->order($sort, $order)
79
+                ->paginate($limit);
80
+
81
+            foreach ($list as $k => &$v) {
82
+                $v['groups']      = $groupName[$v['roleid']] ?? '未知';
83
+            }
84
+            unset($v);
85
+            $result = ["code" => 0, 'count' => $list->total(), "data" => $list->items()];
86
+            return json($result);
87
+        }
88
+        return $this->fetch();
89
+    }
90
+
91
+    /**
92
+     * 添加管理员
93
+     */
94
+    public function add()
95
+    {
96
+        if ($this->request->isPost()) {
97
+            $this->token();
98
+            $params             = $this->request->post('');
99
+            $result             = $this->validate($params, 'AdminUser.insert');
100
+            $passwordinfo       = encrypt_password($params['password']); //对密码进行处理
101
+            $params['password'] = $passwordinfo['password'];
102
+            $params['encrypt']  = $passwordinfo['encrypt'];
103
+            if (true !== $result) {
104
+                return $this->error($result);
105
+            }
106
+            if (!in_array($params['roleid'], $this->childrenGroupIds)) {
107
+                $this->error('没有权限操作!');
108
+            }
109
+            try {
110
+                $this->modelClass->save($params);
111
+            } catch (\Exception $e) {
112
+                $this->error($e->getMessage());
113
+            }
114
+            $this->success("添加成功!", url('index'));
115
+        }
116
+        return $this->fetch();
117
+    }
118
+
119
+    /**
120
+     * 管理员编辑
121
+     */
122
+    public function edit()
123
+    {
124
+        $id  = $this->request->param('id/d', 0);
125
+        $row = $this->modelClass->get($id);
126
+        if (!$row) {
127
+            $this->error('记录未找到');
128
+        }
129
+        if (!in_array($row->id, $this->childrenAdminIds)) {
130
+            $this->error('没有权限操作!');
131
+        }
132
+        if ($this->request->isPost()) {
133
+            $this->token();
134
+            $params = $this->request->post('');
135
+            $result = $this->validate($params, 'AdminUser.update');
136
+            if (true !== $result) {
137
+                return $this->error($result);
138
+            }
139
+            if (!in_array($params['roleid'], $this->childrenGroupIds)) {
140
+                $this->error('没有权限操作!');
141
+            }
142
+            //密码为空,表示不修改密码
143
+            if (isset($params['password']) && $params['password']) {
144
+                $passwordinfo       = encrypt_password($params['password']); //对密码进行处理
145
+                $params['encrypt']  = $passwordinfo['encrypt'];
146
+                $params['password'] = $passwordinfo['password'];
147
+
148
+            } else {
149
+                unset($params['password'], $params['encrypt']);
150
+            }
151
+            try {
152
+                $row->allowField(true)->save($params);
153
+            } catch (\Exception $e) {
154
+                $this->error($e->getMessage());
155
+            }
156
+            $this->success("修改成功!");
157
+        }
158
+        $this->assign("data", $row);
159
+        return $this->fetch();
160
+    }
161
+
162
+    /**
163
+     * 管理员删除
164
+     */
165
+    public function del()
166
+    {
167
+        if (false === $this->request->isPost()) {
168
+            $this->error('未知参数');
169
+        }
170
+        $id = $this->request->param('id/d');
171
+        if (empty($id)) {
172
+            $this->error('请指定需要删除的用户ID!');
173
+        }
174
+        if ($id == 1) {
175
+            $this->error('禁止对超级管理员执行该操作!');
176
+        }
177
+        $ids = array_intersect($this->childrenAdminIds, array_filter(explode(',', $id)));
178
+
179
+        $adminList = $this->modelClass->where('id', 'in', $ids)->where('roleid', 'in', $this->childrenGroupIds)->select();
180
+        if ($adminList) {
181
+            $deleteIds = [];
182
+            foreach ($adminList as $k => $v) {
183
+                $deleteIds[] = $v->id;
184
+            }
185
+            $deleteIds = array_values(array_diff($deleteIds, [$this->auth->id]));
186
+            if ($deleteIds) {
187
+                try {
188
+                    $this->modelClass->destroy($deleteIds);
189
+                } catch (\Exception $e) {
190
+                    $this->error($e->getMessage());
191
+                }
192
+                $this->success("删除成功!");
193
+            }
194
+        }
195
+        $this->error('没有权限删除!');
196
+    }
197
+
198
+    //批量更新.
199
+    public function multi()
200
+    {
201
+        // 管理员禁止批量操作
202
+        $this->error();
203
+    }
204
+
205
+}

+ 166
- 0
application/admin/controller/auth/Rule.php Datei anzeigen

@@ -0,0 +1,166 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// | 后台菜单管理
14
+// +----------------------------------------------------------------------
15
+namespace app\admin\controller\auth;
16
+
17
+use app\admin\model\AuthRule as AuthRuleModel;
18
+use app\common\controller\Adminbase;
19
+use think\Db;
20
+use think\facade\Cache;
21
+use util\Tree;
22
+
23
+class Rule extends Adminbase
24
+{
25
+    protected $modelClass = null;
26
+
27
+    protected function initialize()
28
+    {
29
+        parent::initialize();
30
+        $this->modelClass = new AuthRuleModel;
31
+    }
32
+
33
+    //后台菜单首页
34
+    public function index()
35
+    {
36
+        if ($this->request->isAjax()) {
37
+            $ruleList   = AuthRuleModel::order('listorder DESC,id ASC')->select()->toArray();
38
+            $tree       = new Tree();
39
+            $tree->icon = ['', '', ''];
40
+            $tree->nbsp = '';
41
+            $tree->init($ruleList);
42
+            $list   = $tree->getTreeArray(0);
43
+            $total  = count($list);
44
+            $result = ["code" => 0, "count" => $total, "data" => $list];
45
+            return json($result);
46
+        }
47
+        return $this->fetch();
48
+    }
49
+
50
+    //新增
51
+    public function add()
52
+    {
53
+        if ($this->request->isPost()) {
54
+            $this->token();
55
+            $params = $this->request->post("row/a", [], 'strip_tags');
56
+            if ($params) {
57
+                if (!$params['ismenu'] && !$params['parentid']) {
58
+                    $this->error('非菜单规则节点必须有父级');
59
+                }
60
+                $params['icon'] = $params['icon'] ? 'iconfont ' . $params['icon'] : '';
61
+                $result         = $this->validate($params, 'app\admin\validate\AuthRule');
62
+                if (true !== $result) {
63
+                    $this->error($result);
64
+                }
65
+                try {
66
+                    $result = $this->modelClass->save($params);
67
+                } catch (\Exception $e) {
68
+                    $this->error($e->getMessage());
69
+                }
70
+                $this->success('新增成功');
71
+            }
72
+            $this->error('参数不能为空');
73
+        }
74
+        $tree     = new Tree();
75
+        $parentid = $this->request->param('parentid/d', 0);
76
+        $ruleList = AuthRuleModel::where('ismenu', 1)->order('listorder DESC,id ASC')->select()->toArray();
77
+        $tree->init($ruleList);
78
+        $select_categorys = $tree->getTree(0, '', $parentid);
79
+        $this->assign("select_categorys", $select_categorys);
80
+        return $this->fetch();
81
+    }
82
+
83
+    //编辑
84
+    public function edit()
85
+    {
86
+        $id  = $this->request->param('id/d', 0);
87
+        $row = $this->modelClass->get($id);
88
+        if (!$row) {
89
+            $this->error('记录未找到');
90
+        }
91
+        if ($this->request->isPost()) {
92
+            $this->token();
93
+            $params = $this->request->post("row/a", [], 'strip_tags');
94
+            if ($params) {
95
+                if (!$params['ismenu'] && !$params['parentid']) {
96
+                    $this->error('非菜单规则节点必须有父级');
97
+                }
98
+                if ($params['parentid'] == $row['id']) {
99
+                    $this->error('父级不能是它自己');
100
+                }
101
+                if ($params['parentid'] != $row['parentid']) {
102
+                    $childrenIds = Tree::instance()->init(AuthRuleModel::select()->toArray())->getChildrenIds($row['id']);
103
+                    if (in_array($params['parentid'], $childrenIds)) {
104
+                        $this->error('父级不能是它的子级');
105
+                    }
106
+                }
107
+                $params['icon'] = $params['icon'] ? 'iconfont ' . $params['icon'] : '';
108
+                $result         = $this->validate($params, 'app\admin\validate\AuthRule');
109
+                if (true !== $result) {
110
+                    $this->error($result);
111
+                }
112
+                try {
113
+                    $result = $row->save($params);
114
+                } catch (\Exception $e) {
115
+                    $this->error($e->getMessage());
116
+                }
117
+                $this->success('编辑成功');
118
+            }
119
+            $this->error('参数不能为空');
120
+        }
121
+        $tree     = new Tree();
122
+        $result   = AuthRuleModel::where('ismenu', 1)->order('listorder DESC,id ASC')->select()->toArray();
123
+        $ruleList = [];
124
+        foreach ($result as $r) {
125
+            $r['selected'] = $r['id'] == $row->parentid ? 'selected' : '';
126
+            $ruleList[]    = $r;
127
+        }
128
+
129
+        $tree->init($ruleList);
130
+        $select_categorys = $tree->getTree(0, '', $row->parentid);
131
+        $this->assign("select_categorys", $select_categorys);
132
+        $this->view->assign("data", $row);
133
+        return $this->fetch();
134
+    }
135
+
136
+    //删除
137
+    public function del()
138
+    {
139
+        if (false === $this->request->isPost()) {
140
+            $this->error('未知参数');
141
+        }
142
+        $ids = $this->request->param('id/a', null);
143
+        if (empty($ids)) {
144
+            $this->error('参数错误!');
145
+        }
146
+        if (!is_array($ids)) {
147
+            $ids = [0 => $ids];
148
+        }
149
+        if ($ids) {
150
+            // 必须将结果集转换为数组
151
+            $ruleList = \think\Db::name("auth_rule")->field('id,parentid')->order('listorder DESC,id ASC')->select();
152
+            Tree::instance()->init($ruleList);
153
+            $delIds = [];
154
+            foreach ($ids as $k => $v) {
155
+                $delIds = array_merge($delIds, Tree::instance()->getChildrenIds($v, true));
156
+            }
157
+            $delIds = array_unique($delIds);
158
+            $count  = $this->modelClass->where('id', 'in', $delIds)->delete();
159
+            if ($count) {
160
+                Cache::rm('__menu__');
161
+                $this->success("操作成功!");
162
+            }
163
+        }
164
+        $this->error('没有数据删除!');
165
+    }
166
+}

+ 191
- 0
application/admin/controller/general/Attachments.php Datei anzeigen

@@ -0,0 +1,191 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// | 附件上传处理类
14
+// +----------------------------------------------------------------------
15
+namespace app\admin\controller\general;
16
+
17
+use app\common\controller\Adminbase;
18
+use app\common\model\Attachment as AttachmentModel;
19
+use think\facade\Hook;
20
+
21
+class Attachments extends Adminbase
22
+{
23
+    private $uploadUrl      = '';
24
+    protected $searchFields = 'id,name';
25
+
26
+    protected function initialize()
27
+    {
28
+        parent::initialize();
29
+        $this->modelClass = new AttachmentModel;
30
+        $this->uploadUrl  = config('public_url') . 'uploads/';
31
+    }
32
+
33
+    /**
34
+     * 查看
35
+     */
36
+    public function index()
37
+    {
38
+        if ($this->request->isAjax()) {
39
+            $mimetypeQuery = [];
40
+            $allGet        = $this->request->request();
41
+            $filterArr     = isset($allGet['filter']) ? (array) json_decode($allGet['filter'], true) : [];
42
+            if (isset($filterArr['mime']) && preg_match("/(\/|\,|\*)/", $filterArr['mime'])) {
43
+                $mimetype      = $filterArr['mime'];
44
+                $filterArr     = array_diff_key($filterArr, ['mime' => '']);
45
+                $mimetypeQuery = function ($query) use ($mimetype) {
46
+                    $mimetypeArr = array_filter(explode(',', $mimetype));
47
+                    foreach ($mimetypeArr as $index => $item) {
48
+                        $query->whereOr('mime', 'like', '%' . str_replace("/*", "/", $item) . '%');
49
+                    }
50
+                };
51
+            }
52
+            $allGet['filter'] = json_encode($filterArr);
53
+            $this->request->withGet($allGet);
54
+
55
+            [$page, $limit, $where, $sort, $order] = $this->buildTableParames();
56
+
57
+            $count = $this->modelClass
58
+                ->where($where)
59
+                ->where($mimetypeQuery)
60
+                ->order($sort, $order)
61
+                ->count();
62
+
63
+            $data = $this->modelClass
64
+                ->where($where)
65
+                ->where($mimetypeQuery)
66
+                ->order($sort, $order)
67
+                ->page($page, $limit)
68
+                ->select();
69
+            $result = ["code" => 0, 'count' => $count, 'data' => $data];
70
+            return json($result);
71
+        }
72
+        return $this->fetch();
73
+    }
74
+
75
+    //附件选择
76
+    public function select()
77
+    {
78
+        if ($this->request->isAjax()) {
79
+            return $this->index();
80
+        }
81
+        $mimetype = $this->request->get('mimetype/s', '');
82
+        $mimetype = substr($mimetype, -1) === '/' ? $mimetype . '*' : $mimetype;
83
+        $this->assign('mimetype', $mimetype);
84
+        return $this->fetch();
85
+    }
86
+
87
+    public function cropper()
88
+    {
89
+        return $this->fetch();
90
+    }
91
+
92
+    //附件删除
93
+    public function del()
94
+    {
95
+        if (false === $this->request->isPost()) {
96
+            $this->error('未知参数');
97
+        }
98
+        $ids = $this->request->param('id/a', null);
99
+        if (empty($ids)) {
100
+            $this->error('请选择需要删除的附件!');
101
+        }
102
+        if (!is_array($ids)) {
103
+            $ids = [0 => $ids];
104
+        }
105
+        $isAdministrator = $this->auth->isAdministrator();
106
+        Hook::add('upload_delete', function ($params) {
107
+            if ($params['driver'] == 'local') {
108
+                $attachmentFile = ROOT_PATH . '/public' . $params['path'];
109
+                if (is_file($attachmentFile)) {
110
+                    @unlink($attachmentFile);
111
+                }
112
+            }
113
+        });
114
+        $attachmentlist = AttachmentModel::where('id', 'in', $ids)->select();
115
+        foreach ($attachmentlist as $attachment) {
116
+            Hook::listen("upload_delete", $attachment);
117
+            $attachment->delete();
118
+        }
119
+        $this->success('文件删除成功~');
120
+    }
121
+
122
+    /**
123
+     * html代码远程图片本地化
124
+     * @param string $content html代码
125
+     * @param string $type 文件类型
126
+     */
127
+    public function getUrlFile()
128
+    {
129
+        $content = $this->request->post('content');
130
+        $type    = $this->request->post('type');
131
+        $urls    = [];
132
+        $urls    = \util\GetImgSrc::srcList($content);
133
+        $urls    = array_filter(array_map(function ($val) {
134
+            //http开头验证
135
+            if (strpos($val, "http") === 0) {
136
+                return $val;
137
+            }
138
+        }, $urls));
139
+
140
+        $file_info = [
141
+            'admin_id' => $this->auth->id,
142
+            'thumb'    => '',
143
+        ];
144
+        foreach ($urls as $vo) {
145
+            $vo   = trim(urldecode($vo));
146
+            $host = parse_url($vo, PHP_URL_HOST);
147
+            if ($host != $_SERVER['HTTP_HOST']) {
148
+                //当前域名下的文件不下载
149
+                $fileExt = strrchr($vo, '.'); //$fileExt = '.jpg';非正常后缀图片可以强制设置图片后缀进行抓取下载
150
+                if (!in_array($fileExt, ['.jpg', '.gif', '.png', '.bmp', '.jpeg', '.tiff'])) {
151
+                    exit($content);
152
+                }
153
+                //图片是否合法
154
+                $imgInfo = getimagesize($vo);
155
+                if (!$imgInfo || !isset($imgInfo[0]) || !isset($imgInfo[1])) {
156
+                    exit($content);
157
+                }
158
+                $filename = ROOT_PATH . 'public' . DS . 'uploads' . DS . 'temp' . DS . md5($vo) . $fileExt;
159
+                if (http_down($vo, $filename) !== false) {
160
+                    $file_info['md5'] = hash_file('md5', $filename);
161
+                    if ($file_exists = AttachmentModel::get(['md5' => $file_info['md5']])) {
162
+                        unlink($filename);
163
+                        $localpath = $file_exists['path'];
164
+                    } else {
165
+                        $file_info['sha1'] = hash_file('sha1', $filename);
166
+                        $file_info['size'] = filesize($filename);
167
+                        $file_info['mime'] = mime_content_type($filename);
168
+
169
+                        $fpath    = $type . DS . date('Ymd');
170
+                        $savePath = ROOT_PATH . 'public' . DS . 'uploads' . DS . $fpath;
171
+                        if (!is_dir($savePath)) {
172
+                            mkdir($savePath, 0755, true);
173
+                        }
174
+                        $fname             = DS . md5(microtime(true)) . $fileExt;
175
+                        $file_info['name'] = substr(htmlspecialchars(strip_tags($vo)), 0, 100);
176
+                        $file_info['path'] = $this->uploadUrl . str_replace(DS, '/', $fpath . $fname);
177
+                        $file_info['ext']  = ltrim($fileExt, ".");
178
+
179
+                        if (rename($filename, $savePath . $fname)) {
180
+                            AttachmentModel::create($file_info);
181
+                            $localpath = $file_info['path'];
182
+                        }
183
+                    }
184
+                    $content = str_replace($vo, $localpath, $content);
185
+                }
186
+            }
187
+        }
188
+        exit($content);
189
+    }
190
+
191
+}

+ 233
- 0
application/admin/controller/general/Config.php Datei anzeigen

@@ -0,0 +1,233 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// | 系统配置
14
+// +----------------------------------------------------------------------
15
+namespace app\admin\controller\general;
16
+
17
+use app\admin\model\Config as ConfigModel;
18
+use app\common\controller\Adminbase;
19
+use think\Db;
20
+
21
+class Config extends Adminbase
22
+{
23
+    protected function initialize()
24
+    {
25
+        parent::initialize();
26
+        $filepath = APP_PATH . 'admin' . DS . "view" . DS . 'custom' . DS;
27
+        $custom   = str_replace($filepath . DS, '', glob($filepath . DS . 'custom*'));
28
+
29
+        $this->assign('custom', $custom);
30
+        $this->assign('groupArray', config('site.config_group'));
31
+        $this->modelClass = new ConfigModel;
32
+    }
33
+
34
+    //配置首页
35
+    public function index($group = 'base')
36
+    {
37
+        if ($this->request->isAjax()) {
38
+            $list = $this->modelClass->view('config')
39
+                ->where('group', $group)
40
+                ->view('field_type', 'title as ftitle', 'field_type.name=config.type', 'LEFT')
41
+                ->order('listorder,id desc')
42
+                ->select();
43
+
44
+            return json(["code" => 0, "data" => $list]);
45
+        } else {
46
+            $this->assign('group', $group);
47
+            return $this->fetch();
48
+        }
49
+    }
50
+
51
+    //配置设置
52
+    public function setting($group = 'base')
53
+    {
54
+        if ($this->request->isPost()) {
55
+            $this->token();
56
+            $data = $this->request->post('row/a');
57
+            // 查询该分组下所有的配置项名和类型
58
+            $items = ConfigModel::where('group', $group)->where('status', 1)->column('name,type');
59
+            foreach ($items as $name => $type) {
60
+                //查看是否赋值
61
+                if (!isset($data[$name])) {
62
+                    switch ($type) {
63
+                        // 开关
64
+                        case 'switch':
65
+                            $data[$name] = 0;
66
+                            break;
67
+                        case 'checkbox':
68
+                            $data[$name] = '';
69
+                            break;
70
+                    }
71
+                } else {
72
+                    // 如果值是数组则转换成字符串,适用于复选框等类型
73
+                    if (is_array($data[$name])) {
74
+                        $data[$name] = implode(',', $data[$name]);
75
+                    }
76
+                    switch ($type) {
77
+                        // 开关
78
+                        case 'switch':
79
+                            $data[$name] = 1;
80
+                            break;
81
+                    }
82
+                }
83
+                //数据格式验证
84
+                if (!empty($data[$name]) && in_array($type, ['number']) && !\think\facade\Validate::isNumber($data[$name])) {
85
+                    return $this->error("'" . $name . "'格式错误~");
86
+                }
87
+                if (isset($data[$name])) {
88
+                    ConfigModel::where(['name' => $name])->setField('value', $data[$name]);
89
+                }
90
+            }
91
+            try {
92
+                ConfigModel::refreshFile();
93
+            } catch (Exception $e) {
94
+                $this->error($e->getMessage());
95
+            }
96
+            return $this->success('设置更新成功');
97
+        } else {
98
+            $configList = ConfigModel::where('group', $group)
99
+                ->where('status', 1)
100
+                ->order('listorder,id desc')
101
+                ->select();
102
+            foreach ($configList as &$value) {
103
+                $value['fieldArr'] = 'row';
104
+                if ($value['type'] == 'custom') {
105
+                    if ($value['options'] != '') {
106
+                        $tpar             = explode(".", $value['options'], 2);
107
+                        $value['options'] = $this->fetch('admin@custom/' . $tpar[0], ['vo' => $value])->getContent();
108
+                        unset($tpar);
109
+                    }
110
+                } elseif ($value['options'] != '') {
111
+                    $value['options'] = parse_attr($value['options']);
112
+                }
113
+                if ($value['type'] == 'checkbox') {
114
+                    $value['value'] = empty($value['value']) ? [] : explode(',', $value['value']);
115
+                }
116
+                if ($value['type'] == 'datetime') {
117
+                    $value['value'] = empty($value['value']) ? date('Y-m-d H:i:s') : $value['value'];
118
+                }
119
+                if ($value['type'] == 'Ueditor') {
120
+                    $value['value'] = htmlspecialchars_decode($value['value']);
121
+                }
122
+            }
123
+            $this->assign([
124
+                'fieldList' => $configList,
125
+                'group'     => $group,
126
+            ]);
127
+            return $this->fetch();
128
+        }
129
+    }
130
+
131
+    //新增配置
132
+    public function add()
133
+    {
134
+        $groupType = $this->request->param('groupType', 'base');
135
+        if ($this->request->isPost()) {
136
+            $this->token();
137
+            $params = $this->request->post("row/a");
138
+            if ($params) {
139
+                $result = $this->validate($params, 'app\admin\validate\Config');
140
+                if (true !== $result) {
141
+                    return $this->error($result);
142
+                }
143
+                try {
144
+                    $res = $this->modelClass->create($params);
145
+                } catch (Exception $e) {
146
+                    $this->error($e->getMessage());
147
+                }
148
+                if ($res !== false) {
149
+                    try {
150
+                        ConfigModel::refreshFile();
151
+                    } catch (Exception $e) {
152
+                        $this->error($e->getMessage());
153
+                    }
154
+                    $this->success('新增成功');
155
+                } else {
156
+                    $this->error($this->modelClass->getError());
157
+                }
158
+            }
159
+            $this->error('参数不能为空');
160
+        }
161
+        $fieldType = Db::name('field_type')->order('listorder')->column('name,title,ifstring');
162
+        $this->assign([
163
+            'fieldType' => $fieldType,
164
+            'groupType' => $groupType,
165
+        ]);
166
+        return $this->fetch();
167
+
168
+    }
169
+
170
+    //编辑配置
171
+    public function edit()
172
+    {
173
+        $id  = $this->request->param('id/d');
174
+        $row = $this->modelClass->get($id);
175
+        if (!$row) {
176
+            $this->error('记录未找到');
177
+        }
178
+        if ($this->request->isPost()) {
179
+            $this->token();
180
+            $params = $this->request->post("row/a");
181
+            if ($params) {
182
+                $result = $this->validate($params, 'app\admin\validate\Config');
183
+                if (true !== $result) {
184
+                    return $this->error($result);
185
+                }
186
+                try {
187
+                    $res = $row->save($params);
188
+                } catch (Exception $e) {
189
+                    $this->error($e->getMessage());
190
+                }
191
+                if ($res !== false) {
192
+                    try {
193
+                        ConfigModel::refreshFile();
194
+                    } catch (Exception $e) {
195
+                        $this->error($e->getMessage());
196
+                    }
197
+                    $this->success('编辑成功');
198
+                } else {
199
+                    $this->error($row->getError());
200
+                }
201
+            }
202
+            $this->error('参数不能为空');
203
+        }
204
+        $fieldType = Db::name('field_type')->order('listorder')->column('name,title,ifstring');
205
+        $this->assign([
206
+            'data'      => $row,
207
+            'id'        => $id,
208
+            'fieldType' => $fieldType,
209
+        ]);
210
+        return $this->fetch();
211
+    }
212
+
213
+    //删除配置
214
+    public function del()
215
+    {
216
+        if (false === $this->request->isPost()) {
217
+            $this->error('未知参数');
218
+        }
219
+        $id  = $this->request->param('id/d');
220
+        $row = ConfigModel::find($id);
221
+        if ($row) {
222
+            try {
223
+                $row->delete();
224
+                ConfigModel::refreshFile();
225
+            } catch (Exception $e) {
226
+                $this->error($e->getMessage());
227
+            }
228
+            $this->success("操作成功!");
229
+        } else {
230
+            $this->error('配置不存在');
231
+        }
232
+    }
233
+}

+ 87
- 0
application/admin/controller/general/Profile.php Datei anzeigen

@@ -0,0 +1,87 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// | 个人资料
14
+// +----------------------------------------------------------------------
15
+namespace app\admin\controller\general;
16
+
17
+use app\admin\model\Adminlog as AdminlogModel;
18
+use app\admin\model\AdminUser;
19
+use app\common\controller\Adminbase;
20
+use think\facade\Session;
21
+use think\facade\Validate;
22
+
23
+class Profile extends Adminbase
24
+{
25
+
26
+    public function index()
27
+    {
28
+        if ($this->request->isAjax()) {
29
+            $this->modelClass                      = new AdminlogModel;
30
+            [$page, $limit, $where, $sort, $order] = $this->buildTableParames();
31
+            $count                                 = $this->modelClass
32
+                ->where($where)
33
+                ->where('admin_id', (int) $this->auth->id)
34
+                ->order($sort, $order)
35
+                ->count();
36
+            $list = $this->modelClass
37
+                ->where($where)
38
+                ->where('admin_id', (int) $this->auth->id)
39
+                ->order($sort, $order)
40
+                ->page($page, $limit)
41
+                ->select();
42
+
43
+            $result = ["code" => 0, 'count' => $count, 'data' => $list];
44
+            return json($result);
45
+        }
46
+        return $this->fetch();
47
+    }
48
+
49
+    //更新个人信息
50
+    public function update()
51
+    {
52
+        if ($this->request->isPost()) {
53
+            $this->token();
54
+            $params       = $this->request->post();
55
+            $params['id'] = $this->auth->id;
56
+            $rule         = [
57
+                'email|邮箱'  => 'require|email|unique:admin',
58
+                'mobile|手机' => 'mobile|unique:admin',
59
+            ];
60
+
61
+            $result = $this->validate($params, $rule);
62
+            if (true !== $result) {
63
+                $this->error($result);
64
+            }
65
+            if (isset($params['password']) && $params['password']) {
66
+                if (!Validate::regex($params['password'], "/^[\S]{6,16}$/")) {
67
+                    $this->error('密码长度必须在6-16位之间,不能包含空格');
68
+                }
69
+                $data               = encrypt_password($params['password']);
70
+                $params['encrypt']  = $data['encrypt'];
71
+                $params['password'] = $data['password'];
72
+            } else {
73
+                unset($params['password'], $params['encrypt']);
74
+            }
75
+            if ($params) {
76
+                $admin = AdminUser::get($this->auth->id);
77
+                $admin->save($params);
78
+                //因为个人资料面板读取的Session显示,修改自己资料后同时更新Session
79
+                Session::set("admin", $admin->toArray());
80
+                Session::set("admin.safecode", $this->auth->getEncryptSafecode($admin));
81
+                $this->success('修改成功!');
82
+            }
83
+            $this->error();
84
+        }
85
+        return;
86
+    }
87
+}

+ 355
- 0
application/admin/library/traits/Curd.php Datei anzeigen

@@ -0,0 +1,355 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// | Trait Curd
14
+// +----------------------------------------------------------------------
15
+namespace app\admin\library\traits;
16
+
17
+use Exception;
18
+use think\Db;
19
+use think\exception\PDOException;
20
+use think\exception\ValidateException;
21
+
22
+trait Curd
23
+{
24
+    /**
25
+     * 排除前台提交过来的字段
26
+     * @param $params
27
+     * @return array
28
+     */
29
+    protected function preExcludeFields($params)
30
+    {
31
+        if (is_array($this->excludeFields)) {
32
+            foreach ($this->excludeFields as $field) {
33
+                if (key_exists($field, $params)) {
34
+                    unset($params[$field]);
35
+                }
36
+            }
37
+        } else {
38
+            if (key_exists($this->excludeFields, $params)) {
39
+                unset($params[$this->excludeFields]);
40
+            }
41
+        }
42
+        return $params;
43
+    }
44
+
45
+    /**
46
+     * 查看
47
+     */
48
+    public function index()
49
+    {
50
+        if ($this->request->isAjax()) {
51
+            //如果发送的来源是Selectpage,则转发到Selectpage
52
+            if ($this->request->request('keyField')) {
53
+                return $this->selectpage();
54
+            }
55
+            [$page, $limit, $where, $sort, $order] = $this->buildTableParames();
56
+
57
+            $count = $this->modelClass
58
+                ->where($where)
59
+                ->order($sort, $order)
60
+                ->count();
61
+
62
+            $data = $this->modelClass
63
+                ->where($where)
64
+                ->order($sort, $order)
65
+                ->page($page, $limit)
66
+                ->select();
67
+            $result = ["code" => 0, 'count' => $count, 'data' => $data];
68
+            return json($result);
69
+        }
70
+        return $this->fetch();
71
+    }
72
+
73
+    /**
74
+     * 回收站
75
+     */
76
+    public function recyclebin()
77
+    {
78
+        if ($this->request->isAjax()) {
79
+            [$page, $limit, $where, $sort, $order] = $this->buildTableParames();
80
+
81
+            $count = $this->modelClass
82
+                ->onlyTrashed()
83
+                ->where($where)
84
+                ->order($sort, $order)
85
+                ->count();
86
+
87
+            $data = $this->modelClass
88
+                ->onlyTrashed()
89
+                ->where($where)
90
+                ->order($sort, $order)
91
+                ->page($page, $limit)
92
+                ->select();
93
+            $result = ["code" => 0, 'count' => $count, 'data' => $data];
94
+            return json($result);
95
+        }
96
+        return $this->fetch();
97
+
98
+    }
99
+
100
+    /**
101
+     * 添加
102
+     */
103
+    public function add()
104
+    {
105
+        if ($this->request->isPost()) {
106
+            $params = $this->request->post("row/a");
107
+            if ($params) {
108
+                $params = $this->preExcludeFields($params);
109
+
110
+                if ($this->dataLimit && $this->dataLimitFieldAutoFill) {
111
+                    $params[$this->dataLimitField] = $this->auth->id;
112
+                }
113
+                $result = false;
114
+                Db::startTrans();
115
+                try {
116
+                    //是否采用模型验证
117
+                    if ($this->modelValidate) {
118
+                        $name     = str_replace("\\model\\", "\\validate\\", get_class($this->modelClass));
119
+                        $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.add' : $name) : $this->modelValidate;
120
+                        $this->validateFailException(true)->validate($params, $validate);
121
+                    }
122
+                    $result = $this->modelClass->allowField(true)->save($params);
123
+                    Db::commit();
124
+                } catch (ValidateException | PDOException | Exception $e) {
125
+                    Db::rollback();
126
+                    $this->error($e->getMessage());
127
+                }
128
+                if ($result !== false) {
129
+                    $this->success('新增成功');
130
+                } else {
131
+                    $this->error('未插入任何行');
132
+                }
133
+            }
134
+            $this->error('参数不能为空');
135
+        }
136
+        return $this->fetch();
137
+    }
138
+
139
+    /**
140
+     * 编辑
141
+     */
142
+    public function edit()
143
+    {
144
+        $id  = $this->request->param('id/d', 0);
145
+        $row = $this->modelClass->get($id);
146
+        if (!$row) {
147
+            $this->error('记录未找到');
148
+        }
149
+        $adminIds = $this->getDataLimitAdminIds();
150
+        if (is_array($adminIds)) {
151
+            if (!in_array($row[$this->dataLimitField], $adminIds)) {
152
+                $this->error('你没有权限访问');
153
+            }
154
+        }
155
+        if ($this->request->isPost()) {
156
+            $params = $this->request->post("row/a");
157
+            if ($params) {
158
+                $params = $this->preExcludeFields($params);
159
+                $result = false;
160
+                Db::startTrans();
161
+                try {
162
+                    //是否采用模型验证
163
+                    if ($this->modelValidate) {
164
+                        $name     = str_replace("\\model\\", "\\validate\\", get_class($this->modelClass));
165
+                        $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.edit' : $name) : $this->modelValidate;
166
+                        $this->validateFailException(true)->validate($params, $validate);
167
+                    }
168
+                    $result = $row->allowField(true)->save($params);
169
+                    Db::commit();
170
+                } catch (ValidateException | PDOException | Exception $e) {
171
+                    Db::rollback();
172
+                    $this->error($e->getMessage());
173
+                }
174
+                if ($result !== false) {
175
+                    $this->success('修改成功');
176
+                } else {
177
+                    $this->error('未更新任何行');
178
+                }
179
+            }
180
+            $this->error('参数不能为空');
181
+        }
182
+        $this->view->assign("data", $row);
183
+        return $this->fetch();
184
+    }
185
+
186
+    /**
187
+     * 删除
188
+     */
189
+    public function del()
190
+    {
191
+        if (false === $this->request->isPost()) {
192
+            $this->error('未知参数');
193
+        }
194
+        $ids = $this->request->param('id/a', null);
195
+        if (empty($ids)) {
196
+            $this->error('参数错误!');
197
+        }
198
+        if (!is_array($ids)) {
199
+            $ids = [0 => $ids];
200
+        }
201
+        $pk       = $this->modelClass->getPk();
202
+        $adminIds = $this->getDataLimitAdminIds();
203
+        $where    = [];
204
+        if (is_array($adminIds)) {
205
+            $where[] = [$this->dataLimitField, 'in', $adminIds];
206
+        }
207
+        $where[] = [$pk, 'in', $ids];
208
+        $list    = $this->modelClass->where($where)->select();
209
+        $count   = 0;
210
+        Db::startTrans();
211
+        try {
212
+            foreach ($list as $k => $v) {
213
+                $count += $v->delete();
214
+            }
215
+            Db::commit();
216
+        } catch (PDOException | Exception $e) {
217
+            Db::rollback();
218
+            $this->error($e->getMessage());
219
+        }
220
+        if ($count) {
221
+            $this->success("操作成功!");
222
+        }
223
+        $this->error('没有数据删除!');
224
+    }
225
+
226
+    /**
227
+     * 真实删除
228
+     */
229
+    public function destroy()
230
+    {
231
+        if (false === $this->request->isPost()) {
232
+            $this->error('未知参数');
233
+        }
234
+        $ids = $this->request->param('id/a', null);
235
+        if ($ids && !is_array($ids)) {
236
+            $ids = [0 => $ids];
237
+        }
238
+        $pk       = $this->modelClass->getPk();
239
+        $adminIds = $this->getDataLimitAdminIds();
240
+        $where    = [];
241
+        if (is_array($adminIds)) {
242
+            $where[] = [$this->dataLimitField, 'in', $adminIds];
243
+        }
244
+        if ($ids) {
245
+            $where[] = [$pk, 'in', $ids];
246
+        }
247
+        $count = 0;
248
+        Db::startTrans();
249
+        try {
250
+            $list = $this->modelClass->onlyTrashed()->where($where)->select();
251
+            foreach ($list as $item) {
252
+                $count += $item->delete(true);
253
+            }
254
+            Db::commit();
255
+        } catch (PDOException | Exception $e) {
256
+            Db::rollback();
257
+            $this->error($e->getMessage());
258
+        }
259
+        if ($count) {
260
+            $this->success("操作成功!");
261
+        }
262
+        $this->error('没有数据删除!');
263
+    }
264
+
265
+    /**
266
+     * 还原
267
+     */
268
+    public function restore()
269
+    {
270
+        if (false === $this->request->isPost()) {
271
+            $this->error('未知参数');
272
+        }
273
+        $ids = $this->request->param('id/a', null);
274
+        if ($ids && !is_array($ids)) {
275
+            $ids = [0 => $ids];
276
+        }
277
+        $pk       = $this->modelClass->getPk();
278
+        $adminIds = $this->getDataLimitAdminIds();
279
+        $where    = [];
280
+        if (is_array($adminIds)) {
281
+            $where[] = [$this->dataLimitField, 'in', $adminIds];
282
+        }
283
+        if ($ids) {
284
+            $where[] = [$pk, 'in', $ids];
285
+        }
286
+        $count = 0;
287
+        Db::startTrans();
288
+        try {
289
+            $list = $this->modelClass->onlyTrashed()->where($where)->select();
290
+            foreach ($list as $item) {
291
+                $count += $item->restore();
292
+            }
293
+            Db::commit();
294
+        } catch (PDOException | Exception $e) {
295
+            Db::rollback();
296
+            $this->error($e->getMessage());
297
+        }
298
+        if ($count) {
299
+            $this->success("操作成功!");
300
+        }
301
+        $this->error('未更新任何行');
302
+    }
303
+
304
+    /**
305
+     * 批量更新
306
+     */
307
+    public function multi()
308
+    {
309
+        if (false === $this->request->isPost()) {
310
+            $this->error('未知参数');
311
+        }
312
+        $ids = $this->request->param('id/a', null);
313
+        if (empty($ids)) {
314
+            $this->error('参数错误!');
315
+        }
316
+        if (!is_array($ids)) {
317
+            $ids = [0 => $ids];
318
+        }
319
+        $value = $this->request->param('value/d', 0);
320
+        if ($this->request->has('param')) {
321
+            $param = $this->request->param('param/s');
322
+            $param = $this->auth->isAdministrator() ? $param : (in_array($param, (is_array($this->multiFields) ? $this->multiFields : explode(',', $this->multiFields))) ? $param : '');
323
+            if ($param) {
324
+                $pk       = $this->modelClass->getPk();
325
+                $adminIds = $this->getDataLimitAdminIds();
326
+                $where    = [];
327
+                if (is_array($adminIds)) {
328
+                    $where[] = [$this->dataLimitField, 'in', $adminIds];
329
+                }
330
+                $where[] = [$pk, 'in', $ids];
331
+                $count   = 0;
332
+                Db::startTrans();
333
+                try {
334
+                    $list = $this->modelClass->where($where)->select();
335
+                    foreach ($list as $item) {
336
+                        $item->{$param} = $value;
337
+                        $count += $item->save();
338
+                    }
339
+                    Db::commit();
340
+                } catch (Exception $e) {
341
+                    Db::rollback();
342
+                    $this->error($e->getMessage());
343
+                }
344
+                if ($count) {
345
+                    $this->success("操作成功!");
346
+                }
347
+                $this->error('未更新任何行');
348
+            } else {
349
+                $this->error('你没有权限操作!');
350
+            }
351
+        }
352
+        $this->error('Param参数不能为空');
353
+    }
354
+
355
+}

+ 42
- 0
application/admin/model/AdminUser.php Datei anzeigen

@@ -0,0 +1,42 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// | 后台用户管理
14
+// +----------------------------------------------------------------------
15
+namespace app\admin\model;
16
+
17
+use think\Model;
18
+
19
+class AdminUser extends Model
20
+{
21
+    // 自动写入时间戳
22
+    protected $autoWriteTimestamp = true;
23
+    // 设置当前模型对应的完整数据表名称
24
+    protected $name   = 'admin';
25
+    protected $insert = ['status' => 1];
26
+
27
+    public function getLastLoginTimeAttr($value)
28
+    {
29
+        return date('Y-m-d H:i:s', $value);
30
+    }
31
+
32
+    public static function init()
33
+    {
34
+        self::beforeWrite(function ($row) {
35
+            $changed = $row->getChangedData();
36
+            //如果修改了用户或或密码则需要重新登录
37
+            if (isset($changed['username']) || isset($changed['password']) || isset($changed['salt'])) {
38
+                $row->token = '';
39
+            }
40
+        });
41
+    }
42
+}

+ 120
- 0
application/admin/model/Adminlog.php Datei anzeigen

@@ -0,0 +1,120 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// | 后台用户管理
14
+// +----------------------------------------------------------------------
15
+namespace app\admin\model;
16
+
17
+use app\admin\service\User;
18
+use think\Loader;
19
+use think\Model;
20
+
21
+class Adminlog extends Model
22
+{
23
+    protected $autoWriteTimestamp = true;
24
+    protected $updateTime         = false;
25
+
26
+    //自定义日志标题
27
+    protected static $title = '';
28
+    //自定义日志内容
29
+    protected static $content = '';
30
+    //忽略的链接正则列表
31
+    protected static $ignoreRegex = [
32
+        '/^(.*)\/(selectpage|index|logout)$/i',
33
+    ];
34
+
35
+    public static function setTitle($title)
36
+    {
37
+        self::$title = $title;
38
+    }
39
+
40
+    public static function setContent($content)
41
+    {
42
+        self::$content = $content;
43
+    }
44
+
45
+    public static function setIgnoreRegex($regex = [])
46
+    {
47
+        $regex             = is_array($regex) ? $regex : [$regex];
48
+        self::$ignoreRegex = array_merge(self::$ignoreRegex, $regex);
49
+    }
50
+
51
+    /**
52
+     * 记录日志
53
+     * @param string $title
54
+     * @param string $content
55
+     */
56
+    public static function record($title = '', $content = '')
57
+    {
58
+        $auth     = User::instance();
59
+        $admin_id = $auth->isLogin() ? $auth->id : 0;
60
+        $username = $auth->isLogin() ? $auth->username : '未知';
61
+
62
+        // 设置过滤函数
63
+        request()->filter('trim,strip_tags,htmlspecialchars');
64
+
65
+        $controllername = Loader::parseName(request()->controller());
66
+        $actionname     = strtolower(request()->action());
67
+        $path           = $controllername . '/' . $actionname;
68
+        if (self::$ignoreRegex) {
69
+            foreach (self::$ignoreRegex as $index => $item) {
70
+                if (preg_match($item, $path)) {
71
+                    return;
72
+                }
73
+            }
74
+        }
75
+        $content = $content ?: self::$content;
76
+        if (!$content) {
77
+            $content = request()->param('', null);
78
+            $content = self::getPureContent($content);
79
+        }
80
+        $title = $title ?: self::$title;
81
+        if (!$title) {
82
+            $controllerTitle = AuthRule::where('name', $controllername)->value('title');
83
+            $title           = AuthRule::where('name', $path)->value('title');
84
+            $title           = $title ?: '未知' . '(' . $actionname . ')';
85
+            $title           = $controllerTitle ? ($controllerTitle . '-' . $title) : $title;
86
+        }
87
+        self::create([
88
+            'title'     => $title,
89
+            'content'   => !is_scalar($content) ? json_encode($content, JSON_UNESCAPED_UNICODE) : $content,
90
+            'url'       => substr(xss_clean(strip_tags(request()->url())), 0, 1500),
91
+            'admin_id'  => $admin_id,
92
+            'username'  => $username,
93
+            'useragent' => substr(request()->server('HTTP_USER_AGENT'), 0, 255),
94
+            'ip'        => xss_clean(strip_tags(request()->ip())),
95
+        ]);
96
+    }
97
+
98
+    /**
99
+     * 获取已屏蔽关键信息的数据
100
+     * @param $content
101
+     * @return false|string
102
+     */
103
+    protected static function getPureContent($content)
104
+    {
105
+        if (!is_array($content)) {
106
+            return $content;
107
+        }
108
+        foreach ($content as $index => &$item) {
109
+            if (preg_match("/(password|salt|token)/i", $index)) {
110
+                $item = "***";
111
+            } else {
112
+                if (is_array($item)) {
113
+                    $item = self::getPureContent($item);
114
+                }
115
+            }
116
+        }
117
+        return $content;
118
+    }
119
+
120
+}

+ 64
- 0
application/admin/model/AuthGroup.php Datei anzeigen

@@ -0,0 +1,64 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2007 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+
12
+namespace app\admin\model;
13
+
14
+use think\Db;
15
+use think\Exception;
16
+use think\Model;
17
+
18
+/**
19
+ * 用户组模型类
20
+ * Class AuthGroupModel
21
+ */
22
+class AuthGroup extends Model
23
+{
24
+    protected static $roleList = [];
25
+
26
+    public static function init()
27
+    {
28
+        self::beforeDelete(function ($row) {
29
+            if ($row->id == 1) {
30
+                throw new Exception("超级管理员角色不能被删除!");
31
+            }
32
+            $admin = Db::name('Admin')->where('roleid', $row->id)->find();
33
+            if ($admin) {
34
+                throw new Exception("该角色下有管理员!");
35
+            }
36
+            //子角色列表
37
+            $child = explode(',', self::getArrchildid($row->id));
38
+            if (count($child) > 1) {
39
+                throw new Exception("该角色下有子角色,请删除子角色才可以删除!");
40
+            }
41
+        });
42
+    }
43
+
44
+    /**
45
+     * 通过递归的方式获取该角色下的全部子角色
46
+     * @param type $id
47
+     * @return string
48
+     */
49
+    public static function getArrchildid($id)
50
+    {
51
+        if (empty(self::$roleList)) {
52
+            self::$roleList = self::order(["id" => "desc"])->column('*', 'id');
53
+        }
54
+        $arrchildid = $id;
55
+        if (is_array(self::$roleList)) {
56
+            foreach (self::$roleList as $k => $cat) {
57
+                if ($cat['parentid'] && $k != $id && $cat['parentid'] == $id) {
58
+                    $arrchildid .= ',' . self::getArrchildid($k);
59
+                }
60
+            }
61
+        }
62
+        return $arrchildid;
63
+    }
64
+}

+ 34
- 0
application/admin/model/AuthRule.php Datei anzeigen

@@ -0,0 +1,34 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2007 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+
12
+namespace app\admin\Model;
13
+
14
+use think\Model;
15
+use think\facade\Cache;
16
+
17
+/**
18
+ * 权限规则模型
19
+ */
20
+class AuthRule extends Model
21
+{
22
+    const RULE_URL = 1;
23
+    const RULE_MAIN = 2; //主菜单
24
+
25
+    // 自动写入时间戳
26
+    protected $autoWriteTimestamp = true;
27
+
28
+    protected static function init()
29
+    {
30
+        self::afterWrite(function ($row) {
31
+            Cache::rm('__menu__');
32
+        });
33
+    }
34
+}

+ 98
- 0
application/admin/model/Config.php Datei anzeigen

@@ -0,0 +1,98 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// | 后台配置模型
14
+// +----------------------------------------------------------------------
15
+namespace app\admin\model;
16
+
17
+use think\Model;
18
+
19
+class Config extends Model
20
+{
21
+    // 追加属性
22
+    protected $append = [
23
+        'extend_html',
24
+    ];
25
+
26
+    // 自动写入时间戳
27
+    protected $autoWriteTimestamp = true;
28
+
29
+    public function getExtendHtmlAttr($value, $data)
30
+    {
31
+        $result = preg_replace_callback("/\{([a-zA-Z]+)\}/", function ($matches) use ($data) {
32
+            if (isset($data[$matches[1]])) {
33
+                return $data[$matches[1]];
34
+            }
35
+        }, $data['extend']);
36
+        return $result;
37
+    }
38
+
39
+    /**
40
+     * 刷新配置文件
41
+     */
42
+    public static function refreshFile()
43
+    {
44
+        $configs    = self::where('status', 1)->column('name,value,type,options');
45
+        $newConfigs = [];
46
+        foreach ($configs as $key => $value) {
47
+            if ($value['options'] != '') {
48
+                $value['options'] = parse_attr($value['options']);
49
+            }
50
+            switch ($value['type']) {
51
+                case 'array':
52
+                    $newConfigs[$key] = (array) json_decode($value['value'], true);
53
+                    break;
54
+                case 'select':
55
+                case 'radio':
56
+                    $newConfigs[$key] = $value['value'];
57
+                    if (isset($value['options'][$value['value']])) {
58
+                        $newConfigs[$key . '_text'] = $value['options'][$value['value']];
59
+                    } else {
60
+                        $newConfigs[$key . '_text'] = $value['value'];
61
+                    }
62
+                    //$newConfigs[$key] = isset($value['options'][$value['value']]) ? ['key' => $value['value'], 'value' => $value['options'][$value['value']]] : ['key' => $value['value'], 'value' => $value['value']];
63
+                    break;
64
+                case 'selects':
65
+                case 'checkbox':
66
+                    if (empty($value['value'])) {
67
+                        $newConfigs[$key] = [];
68
+                    } else {
69
+                        $valueArr = explode(',', $value['value']);
70
+                        foreach ($valueArr as $v) {
71
+                            if (isset($value['options'][$v])) {
72
+                                $newConfigs[$key][$v] = $value['options'][$v];
73
+                            } elseif ($v) {
74
+                                $newConfigs[$key][$v] = $v;
75
+                            }
76
+                        }
77
+                    }
78
+                    break;
79
+                case 'files':
80
+                case 'images':
81
+                    $newConfigs[$key] = empty($value['value']) ? [] : explode(',', $value['value']);
82
+                    break;
83
+                case 'Ueditor':
84
+                    $newConfigs[$key] = htmlspecialchars_decode($value['value']);
85
+                    break;
86
+                default:
87
+                    $newConfigs[$key] = $value['value'];
88
+                    break;
89
+            }
90
+        }
91
+        file_put_contents(
92
+            ROOT_PATH . 'config' . DS . 'site.php',
93
+            '<?php' . "\n\nreturn " . var_export_short($newConfigs) . ";\n"
94
+        );
95
+        return true;
96
+    }
97
+
98
+}

+ 49
- 0
application/admin/model/ModelField.php Datei anzeigen

@@ -0,0 +1,49 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// | 字段模型
14
+// +----------------------------------------------------------------------
15
+namespace app\admin\model;
16
+
17
+use think\Db;
18
+use think\Model;
19
+
20
+/**
21
+ * 字段模型
22
+ */
23
+class ModelField extends Model
24
+{
25
+
26
+    //生成模型字段缓存
27
+    public function model_field_cache()
28
+    {
29
+        $cache     = array();
30
+        $modelList = Db::name("Model")->select();
31
+        foreach ($modelList as $info) {
32
+            $data      = Db::name("ModelField")->where(array("modelid" => $info['id'], "status" => 1))->order('listorder DESC, id DESC')->select();
33
+            $fieldList = array();
34
+            if (!empty($data) && is_array($data)) {
35
+                foreach ($data as $rs) {
36
+                    //扩展配置
37
+                    if (!empty($rs['setting'])) {
38
+                        $rs = array_merge($rs, unserialize($rs['setting']));
39
+                    }
40
+                    $fieldList[$rs['name']] = $rs;
41
+                }
42
+            }
43
+            $cache[$info['id']] = $fieldList;
44
+        }
45
+        cache('ModelField', $cache);
46
+        return $cache;
47
+    }
48
+
49
+}

+ 60
- 0
application/admin/model/Models.php Datei anzeigen

@@ -0,0 +1,60 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// | 模型模型
14
+// +----------------------------------------------------------------------
15
+namespace app\admin\model;
16
+
17
+use think\Db;
18
+use \think\Model;
19
+
20
+/**
21
+ * 模型
22
+ */
23
+class Models extends Model
24
+{
25
+    protected $name = 'model';
26
+
27
+    /**
28
+     * 根据模型类型取得数据用于缓存
29
+     * @param type $type
30
+     * @return type
31
+     */
32
+    public function getModelAll($type = null, $module = null)
33
+    {
34
+        $where = array('status' => 1);
35
+        if (!is_null($type)) {
36
+            $where['type'] = $type;
37
+        }
38
+        if (!is_null($module)) {
39
+            $where['module'] = $module;
40
+        }
41
+        $data = Db::name('Model')->where($where)->select();
42
+        $Cache = array();
43
+        foreach ($data as $v) {
44
+            $Cache[$v['id']] = $v;
45
+        }
46
+        return $Cache;
47
+    }
48
+
49
+    /**
50
+     * 生成模型缓存,以模型ID为下标的数组
51
+     * @return boolean
52
+     */
53
+    public function model_cache()
54
+    {
55
+        $data = $this->getModelAll();
56
+        cache('Model', $data);
57
+        return $data;
58
+    }
59
+
60
+}

+ 407
- 0
application/admin/service/User.php Datei anzeigen

@@ -0,0 +1,407 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// |  后台用户服务
14
+// +----------------------------------------------------------------------
15
+namespace app\admin\service;
16
+
17
+use app\admin\model\AdminUser;
18
+use think\Db;
19
+use think\facade\Config;
20
+use think\facade\Cookie;
21
+use think\facade\Request;
22
+use think\facade\Session;
23
+use util\Random;
24
+use util\Tree;
25
+
26
+class User extends \libs\Auth
27
+{
28
+    //当前登录会员详细信息
29
+    protected $error   = '';
30
+    protected $logined = false; //登录状态
31
+
32
+    public function __construct()
33
+    {
34
+        parent::__construct();
35
+    }
36
+
37
+    public function __get($name)
38
+    {
39
+        return Session::get('admin.' . $name);
40
+    }
41
+
42
+    /**
43
+     * 取出当前管理员所拥有权限的分组.
44
+     *
45
+     * @param bool $withself 是否包含当前所在的分组
46
+     *
47
+     * @return array
48
+     */
49
+    public function getChildrenGroupIds($withself = false)
50
+    {
51
+        //取出当前管理员所有的分组
52
+        $groups   = $this->getGroups();
53
+        $groupIds = [];
54
+        foreach ($groups as $k => $v) {
55
+            $groupIds[] = $v['id'];
56
+        }
57
+        $originGroupIds = $groupIds;
58
+        foreach ($groups as $k => $v) {
59
+            if (in_array($v['parentid'], $originGroupIds)) {
60
+                $groupIds = array_diff($groupIds, [$v['id']]);
61
+                unset($groups[$k]);
62
+            }
63
+        }
64
+        // 取出所有分组
65
+        $groupList = \app\admin\model\AuthGroup::where('status', 1)->select()->toArray();
66
+        $objList   = [];
67
+        foreach ($groups as $k => $v) {
68
+            if ($v['rules'] === '*') {
69
+                $objList = $groupList;
70
+                break;
71
+            }
72
+            // 取出包含自己的所有子节点
73
+            $childrenList = Tree::instance()->init($groupList)->getChildren($v['id'], true);
74
+            $obj          = Tree::instance()->init($childrenList)->getTreeArray($v['parentid']);
75
+            $objList      = array_merge($objList, Tree::instance()->getTreeList($obj, 'title'));
76
+        }
77
+        $childrenGroupIds = [];
78
+        foreach ($objList as $k => $v) {
79
+            $childrenGroupIds[] = $v['id'];
80
+        }
81
+        if (!$withself) {
82
+            $childrenGroupIds = array_diff($childrenGroupIds, $groupIds);
83
+        }
84
+        return $childrenGroupIds;
85
+    }
86
+
87
+    public function check($name, $uid = '', $mode = 'url', $relation = 'or')
88
+    {
89
+        $uid = $uid ? $uid : $this->id;
90
+        return parent::check($name, $uid, $mode, $relation);
91
+    }
92
+
93
+    /**
94
+     * 取出当前管理员所拥有权限的管理员.
95
+     *
96
+     * @param bool $withself 是否包含自身
97
+     *
98
+     * @return array
99
+     */
100
+    public function getChildrenAdminIds($withself = false)
101
+    {
102
+        $childrenAdminIds = [];
103
+        if (!$this->isAdministrator()) {
104
+            $groupIds         = $this->getChildrenGroupIds(false);
105
+            $childrenAdminIds = Db::name('Admin')->where('roleid', 'in', $groupIds)->column('id');
106
+        } else {
107
+            //超级管理员拥有所有人的权限
108
+            $childrenAdminIds = Db::name('Admin')->column('id');
109
+        }
110
+        if ($withself) {
111
+            if (!in_array($this->id, $childrenAdminIds)) {
112
+                $childrenAdminIds[] = $this->id;
113
+            }
114
+        } else {
115
+            $childrenAdminIds = array_diff($childrenAdminIds, [$this->id]);
116
+        }
117
+        return $childrenAdminIds;
118
+    }
119
+
120
+    public function getGroups($uid = null)
121
+    {
122
+        $uid = is_null($uid) ? $this->id : $uid;
123
+        return parent::getGroups($uid);
124
+    }
125
+
126
+    public function getAuthList($uid = null)
127
+    {
128
+        $uid = is_null($uid) ? $this->id : $uid;
129
+        return parent::getAuthList($uid);
130
+    }
131
+
132
+    public function getRuleIds($uid = null)
133
+    {
134
+        $uid = is_null($uid) ? $this->id : $uid;
135
+        return parent::getRuleIds($uid);
136
+    }
137
+
138
+    /**
139
+     * 用户登录
140
+     * @param string $username 用户名
141
+     * @param string $password 密码
142
+     * @return bool|mixed
143
+     */
144
+    public function login($username = '', $password = '', $keeptime = 0)
145
+    {
146
+        $username = trim($username);
147
+        $password = trim($password);
148
+        $admin    = AdminUser::get(['username' => $username]);
149
+        if (!$admin) {
150
+            $this->setError('用户名不正确');
151
+            return false;
152
+        }
153
+        if ($admin['status'] !== 1) {
154
+            $this->setError('管理员已经被禁止登录');
155
+            return false;
156
+        }
157
+        if (Config::get('login_failure_retry') && $admin->login_failure >= 10 && time() - $admin->getData('update_time') < 86400) {
158
+            $this->setError('请于1天后再尝试登录');
159
+            return false;
160
+        }
161
+        if ($admin->password != encrypt_password($password, $admin->encrypt)) {
162
+            $admin->login_failure++;
163
+            $admin->save();
164
+            $this->setError('密码不正确');
165
+            return false;
166
+        }
167
+        $admin->login_failure   = 0;
168
+        $admin->last_login_time = time();
169
+        $admin->last_login_ip   = request()->ip();
170
+        $admin->token           = Random::uuid();
171
+        $admin->save();
172
+        Session::set("admin", $admin->toArray());
173
+        Session::set("admin.safecode", $this->getEncryptSafecode($admin));
174
+        $this->keeplogin($admin, $keeptime);
175
+        return true;
176
+    }
177
+
178
+    /**
179
+     * 刷新保持登录的Cookie
180
+     *
181
+     * @param int $keeptime
182
+     * @return  boolean
183
+     */
184
+    protected function keeplogin($admin, $keeptime = 0)
185
+    {
186
+        if ($keeptime) {
187
+            $expiretime = time() + $keeptime;
188
+            $key        = $this->getKeeploginKey($admin, $keeptime, $expiretime);
189
+            Cookie::set('keeplogin', implode('|', [$admin['id'], $keeptime, $expiretime, $key]), $keeptime);
190
+            return true;
191
+        }
192
+        return false;
193
+    }
194
+
195
+    /**
196
+     * 自动登录
197
+     * @return boolean
198
+     */
199
+    public function autologin()
200
+    {
201
+        $keeplogin = Cookie::get('keeplogin');
202
+        if (!$keeplogin) {
203
+            return false;
204
+        }
205
+        list($id, $keeptime, $expiretime, $key) = explode('|', $keeplogin);
206
+        if ($id && $keeptime && $expiretime && $key && $expiretime > time()) {
207
+            $admin = AdminUser::get($id);
208
+            if (!$admin || !$admin->token) {
209
+                return false;
210
+            }
211
+            //token有变更
212
+            if ($key != $this->getKeeploginKey($admin, $keeptime, $expiretime)) {
213
+                return false;
214
+            }
215
+            $ip = request()->ip();
216
+            //IP有变动
217
+            if ($admin->last_login_ip != $ip) {
218
+                return false;
219
+            }
220
+            Session::set("admin", $admin->toArray());
221
+            Session::set("admin.safecode", $this->getEncryptSafecode($admin));
222
+            //刷新自动登录的时效
223
+            $this->keeplogin($admin, $keeptime);
224
+            return true;
225
+        } else {
226
+            return false;
227
+        }
228
+    }
229
+
230
+    /**
231
+     * 检验用户是否已经登陆
232
+     * @return boolean 失败返回false,成功返回当前登陆用户基本信息
233
+     */
234
+    public function isLogin()
235
+    {
236
+        if ($this->logined) {
237
+            return true;
238
+        }
239
+        $admin = Session::get('admin');
240
+        if (!$admin) {
241
+            return false;
242
+        }
243
+        $my = AdminUser::get($admin['id']);
244
+        if (!$my) {
245
+            return false;
246
+        }
247
+        //校验安全码,可用于判断关键信息发生了变更需要重新登录
248
+        if (!isset($admin['safecode']) || $this->getEncryptSafecode($my) !== $admin['safecode']) {
249
+            $this->logout();
250
+            return false;
251
+        }
252
+        //判断是否同一时间同一账号只能在一个地方登录
253
+        if (Config::get('login_unique')) {
254
+            if ($my['token'] != $admin['token']) {
255
+                $this->logout();
256
+                return false;
257
+            }
258
+        }
259
+        //判断管理员IP是否变动
260
+        if (Config::get('loginip_check')) {
261
+            if (!isset($admin['last_login_ip']) || $admin['last_login_ip'] != request()->ip()) {
262
+                $this->logout();
263
+                return false;
264
+            }
265
+        }
266
+        $this->logined = true;
267
+        return true;
268
+    }
269
+
270
+    /**
271
+     * 检查当前用户是否超级管理员
272
+     * @return boolean
273
+     */
274
+    public function isAdministrator()
275
+    {
276
+        return in_array('*', $this->getRuleIds()) ? true : false;
277
+    }
278
+
279
+    /**
280
+     * 获取自动登录Key
281
+     * @param $params
282
+     * @param $keeptime
283
+     * @param $expiretime
284
+     * @return string
285
+     */
286
+    public function getKeeploginKey($params, $keeptime, $expiretime)
287
+    {
288
+        $key = md5(md5($params['id']) . md5($keeptime) . md5($expiretime) . $params['token'] . Config::get('token.key'));
289
+        return $key;
290
+    }
291
+
292
+    /**
293
+     * 获取加密后的安全码
294
+     * @param $params
295
+     * @return string
296
+     */
297
+    public function getEncryptSafecode($params)
298
+    {
299
+        return md5(md5($params['username']) . md5(substr($params['password'], 0, 6)) . Config::get('token.key'));
300
+    }
301
+
302
+    /**
303
+     * 检测当前控制器和方法是否匹配传递的数组
304
+     *
305
+     * @param array $arr 需要验证权限的数组
306
+     * @return bool
307
+     */
308
+    public function match($arr = [])
309
+    {
310
+        $request = Request::instance();
311
+        $arr     = is_array($arr) ? $arr : explode(',', $arr);
312
+        if (!$arr) {
313
+            return false;
314
+        }
315
+
316
+        $arr = array_map('strtolower', $arr);
317
+        // 是否存在
318
+        if (in_array(strtolower($request->action()), $arr) || in_array('*', $arr)) {
319
+            return true;
320
+        }
321
+
322
+        // 没找到匹配
323
+        return false;
324
+    }
325
+
326
+    /**
327
+     * 注销登录
328
+     */
329
+    public function logout()
330
+    {
331
+        $admin = AdminUser::get(intval($this->id));
332
+        if ($admin) {
333
+            $admin->token = '';
334
+            $admin->save();
335
+        }
336
+        $this->logined = false; //重置登录状态
337
+        Session::delete("admin");
338
+        Cookie::delete("keeplogin");
339
+        return true;
340
+    }
341
+
342
+    public function getSidebar()
343
+    {
344
+        $module = request()->module();
345
+        // 读取管理员当前拥有的权限节点
346
+        $userRule = $this->getAuthList();
347
+        // 必须将结果集转换为数组
348
+        $ruleList = \app\admin\model\AuthRule::where('status', 1)
349
+            ->where('ismenu', 1)
350
+            ->order('listorder', 'desc')
351
+            ->cache("__menu__")
352
+            ->select()->toArray();
353
+        $indexRuleList = \app\admin\model\AuthRule::where('status', 1)
354
+            ->where('ismenu', 0)
355
+            ->where('name', 'like', '%/index')
356
+            ->column('name,parentid');
357
+        $pidArr = array_unique(array_filter(array_column($ruleList, 'parentid')));
358
+        foreach ($ruleList as $k => &$v) {
359
+            if (!in_array($v['name'], $userRule)) {
360
+                unset($ruleList[$k]);
361
+                continue;
362
+            }
363
+            $indexRuleName = $v['name'] . '/index';
364
+            if (isset($indexRuleList[$indexRuleName]) && !in_array($indexRuleName, $userRule)) {
365
+                unset($ruleList[$k]);
366
+                continue;
367
+            }
368
+            $v['openType'] = $v['menutype']; //兼容前端
369
+            $v['type']     = $v['ismenu']; //兼容前端
370
+            $v['href']     = isset($v['url']) && $v['url'] ? $v['url'] : '/' . $module . '/' . $v['name'];
371
+            $v['href']     = preg_match("/^((?:[a-z]+:)?\/\/|data:image\/)(.*)/i", $v['href']) ? $v['href'] : url($v['href']);
372
+        }
373
+        $lastArr    = array_unique(array_filter(array_column($ruleList, 'parentid')));
374
+        $pidDiffArr = array_diff($pidArr, $lastArr);
375
+        foreach ($ruleList as $index => $item) {
376
+            if (in_array($item['id'], $pidDiffArr)) {
377
+                unset($ruleList[$index]);
378
+            }
379
+        }
380
+        // 构造菜单数据
381
+        Tree::instance()->init($ruleList);
382
+        return Tree::instance()->getTreeArray(0);
383
+    }
384
+
385
+    /**
386
+     * 获取错误信息
387
+     * @access public
388
+     * @return mixed
389
+     */
390
+    public function getError()
391
+    {
392
+        return $this->error ? $this->error : '';
393
+    }
394
+
395
+    /**
396
+     * 设置错误信息
397
+     *
398
+     * @param string $error 错误信息
399
+     * @return Auth
400
+     */
401
+    public function setError($error)
402
+    {
403
+        $this->error = $error;
404
+        return $this;
405
+    }
406
+
407
+}

+ 18
- 0
application/admin/tags.php Datei anzeigen

@@ -0,0 +1,18 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: liu21st <liu21st@gmail.com>
10
+// +----------------------------------------------------------------------
11
+
12
+// 应用行为扩展定义文件
13
+return [
14
+    // 应用结束
15
+    'app_end' => [
16
+        'app\\admin\\behavior\\Adminlog',
17
+    ],
18
+];

+ 44
- 0
application/admin/validate/AdminUser.php Datei anzeigen

@@ -0,0 +1,44 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// | 登录验证
14
+// +----------------------------------------------------------------------
15
+namespace app\admin\validate;
16
+
17
+use think\Validate;
18
+
19
+class AdminUser extends Validate
20
+{
21
+
22
+    //定义验证规则
23
+    protected $rule = [
24
+        'username|用户名' => 'unique:admin|require|alphaDash|length:3,20',
25
+        'password|密码'  => 'require|length:3,20|confirm',
26
+        'email|邮箱'     => 'email|unique:admin',
27
+        'mobile|手机'    => 'mobile|unique:admin',
28
+        'roleid|权限组'   => 'require',
29
+    ];
30
+
31
+    // 登录验证场景定义
32
+    public function sceneUpdate()
33
+    {
34
+        return $this->only(['username', 'password', 'email', 'mobile', 'roleid'])
35
+            ->remove('password', 'require');
36
+    }
37
+
38
+    //定义验证场景
39
+    protected $scene = [
40
+        'insert' => ['username', 'password', 'email', 'roleid', 'mobile'],
41
+
42
+    ];
43
+
44
+}

+ 36
- 0
application/admin/validate/AuthGroup.php Datei anzeigen

@@ -0,0 +1,36 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2007 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+namespace app\admin\validate;
12
+
13
+use \think\Validate;
14
+
15
+/**
16
+ * 用户组验证器
17
+ */
18
+class AuthGroup extends Validate
19
+{
20
+    //定义验证规则
21
+    protected $rule = [
22
+        'title' => 'require',
23
+
24
+    ];
25
+
26
+    //定义验证提示
27
+    protected $message = [
28
+        'title.require' => '用户组名称不得为空',
29
+
30
+    ];
31
+
32
+    //定义验证场景
33
+    protected $scene = [
34
+
35
+    ];
36
+}

+ 27
- 0
application/admin/validate/AuthRule.php Datei anzeigen

@@ -0,0 +1,27 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2007 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+namespace app\admin\validate;
12
+
13
+use think\Validate;
14
+
15
+/**
16
+ * 菜单验证器
17
+ */
18
+class AuthRule extends Validate
19
+{
20
+    //定义验证规则
21
+    protected $rule = [
22
+        'parentid|上级菜单'  => 'require|number',
23
+        'name|规则'       => 'require|unique:AuthRule',
24
+        'title|标题'       => 'require',
25
+    ];
26
+
27
+}

+ 29
- 0
application/admin/validate/Config.php Datei anzeigen

@@ -0,0 +1,29 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: 御宅男 <530765310@qq.com>
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// | 登录验证
14
+// +----------------------------------------------------------------------
15
+namespace app\admin\validate;
16
+
17
+use think\Validate;
18
+
19
+class Config extends Validate
20
+{
21
+    //定义验证规则
22
+    protected $rule = [
23
+        'group|配置分组' => 'require',
24
+        'type|配置类型' => 'require|alpha',
25
+        'title|配置标题' => 'require|chsAlphaNum',
26
+        'name|配置名称' => 'require|regex:^[a-zA-Z]\w{0,39}$|unique:config',
27
+        'listorder|排序' => 'number',
28
+    ];
29
+}

+ 132
- 0
application/admin/view/addons/config.html Datei anzeigen

@@ -0,0 +1,132 @@
1
+{extend name="admin@index_layout"/}
2
+{block name="main"}
3
+<form class="layui-form" action="" method="post" data-field="config">
4
+    {if $data.tips  && $data.tips.value}
5
+    <div class="alert alert-info-light" style="margin-bottom:10px;">
6
+        {if $data.tips.title}
7
+        <b>{$data.tips.title}</b><br>
8
+        {/if}
9
+        {$data.tips.value|raw}
10
+    </div>
11
+    {/if}
12
+
13
+    {foreach name="data['config']" item="form" key="o_key"}
14
+    <div data-favisible="{$form.visible|default=''}">
15
+        <div class="layui-form-item">
16
+            <label class="layui-form-label">{$form.title}</label>
17
+            {switch name="form.type"}
18
+                {case value="text"}
19
+                <div class="layui-input-block">
20
+                    <input {$form.extend|raw} type="text" name="config[{$form.name}]" placeholder="请输入{$form.title}" autocomplete="off" class="layui-input" value="{$form.value}">
21
+                </div>
22
+                {/case}
23
+
24
+                {case value="password"}
25
+                <div class="layui-input-block">
26
+                    <input {$form.extend|raw} type="password" name="config[{$form.name}]" placeholder="请输入{$form.title}" autocomplete="off" class="layui-input" value="{$form.value}">
27
+                </div>
28
+                {/case}
29
+
30
+                {case value="array"}
31
+                <dl {$form.extend|raw} class="layui-input-block fieldlist" data-name="config[{$form.name}]" data-id="{$form.name}">
32
+                    <dd>
33
+                        <ins>键名</ins>
34
+                        <ins>键值</ins>
35
+                    </dd>
36
+                    <dd><button type="button" class="layui-btn btn-append">追加</button></dd>
37
+                    <textarea name="config[{$form.name}]" class="layui-textarea layui-hide">{$form.value|json_encode}</textarea>
38
+                </dl>
39
+                <script type="text/html" id="{$form.name}Tpl">
40
+                    <dd class="layui-form-item rules-item">
41
+                    {{# layui.each(d.lists, function(index, item) { }}
42
+                	    <input type="text" class="layui-input" name="{{item.name}}[{{item.index}}][key]" placeholder="键" value="{{item.key|| ''}}" />
43
+                	    <input type="text" class="layui-input" name="{{item.name}}[{{item.index}}][value]" placeholder="值" value="{{item.value|| ''}}" />
44
+                	    <button type="button" class="layui-btn layui-btn-danger btn-remove layui-btn-xs"><i class="iconfont icon-close"></i></button>
45
+                	    <button type="button" class="layui-btn btn-dragsort layui-btn-xs"><i class="iconfont icon-yidong"></i></button>
46
+                    {{# }); }}
47
+                    </dd>
48
+                </script>
49
+                {/case}
50
+
51
+                {case value="checkbox"}
52
+                <div class="layui-input-block">
53
+                    {foreach name="form.options" item="opt" key="opt_k"}
54
+                    <input type="checkbox" name="config[{$form.name}][]" lay-skin="primary" title="{$opt}" value="{$opt_k}" {in name="opt_k" value="$form.value|default=''"} checked{/in}>
55
+                    {/foreach}
56
+                </div>
57
+                {/case}
58
+
59
+                {case value="radio"}
60
+                <div class="layui-input-block">
61
+                    {foreach name="form.options" item="opt" key="opt_k"}
62
+                    <input type="radio" name="config[{$form.name}]" value="{$opt_k}" title="{$opt}" {eq name="form.value" value="$opt_k"} checked{/eq}>
63
+                    {/foreach}
64
+                </div>
65
+                {/case}
66
+
67
+                {case value="select"}
68
+                <div class="layui-input-block">
69
+                    <select name="config[{$form.name}]">
70
+                        <option value=""></option>
71
+                        {foreach name="form.options" item="opt" key="opt_k"}
72
+                        <option value="{$opt_k}" {eq name="form.value" value="$opt_k"}selected{/eq}>{$opt}</option>
73
+                        {/foreach}
74
+                    </select>
75
+                </div>
76
+                {/case}
77
+
78
+                {case value="textarea"}
79
+                <div class="layui-input-block">
80
+                    <textarea {$form.extend|raw} placeholder="请输入{$form.title}" class="layui-textarea" name="config[{$form.name}]">{$form.value}</textarea>
81
+                </div>
82
+                {/case}
83
+
84
+                {case value="image" break="0"}{/case}
85
+                {case value="images"}
86
+                <div class="layui-input-block">
87
+                    <div class="layui-col-xs4">
88
+                        <input type="text" name="config[{$form.name}]"  id="c-{$form.name}" value="{$form.value|default=''}" class="layui-input">
89
+                    </div>
90
+                    <button type="button" class="layui-btn faupload" id="faupload-{$form.name}" data-multiple="{$form.type=='image'?'false':'true'}" data-input-id="c-{$form.name}" data-preview-id="p-{$form.name}" data-type="image"><i class="layui-icon layui-icon-upload"></i> 上传</button><button type="button" class="layui-btn fachoose" data-multiple="{$form.type=='image'?'false':'true'}" data-input-id="c-{$form.name}" id="fachoose-c-{$form.name}"><i class="iconfont icon-other"></i> 选择</button>
91
+                    <ul class="layui-row list-inline plupload-preview" id="p-{$form.name}"></ul>
92
+                </div>
93
+                {/case}
94
+
95
+                {case value="file" break="0"}{/case}
96
+                {case value="files"}
97
+                <div class="layui-input-block">
98
+                    <div class="layui-col-xs4">
99
+                        <input type="text" name="config[{$form.name}]"  id="c-{$form.name}" value="{$form.value|default=''}" class="layui-input">
100
+                    </div>
101
+                    <button type="button" class="layui-btn faupload" id="faupload-{$form.name}" data-multiple="{$form.type=='file'?'false':'true'}" data-input-id="c-{$form.name}" data-preview-id="p-{$form.name}" data-type="file"><i class="layui-icon layui-icon-upload"></i> 上传</button>
102
+                </div>
103
+                {/case}
104
+
105
+                {case value="Ueditor"}
106
+                <div class="layui-input-block">
107
+                    <script {$form.extend|raw} type="text/plain" class="js-ueditor" id="{$form.name}" name="config[{$form.name}]">{$form.value|raw}</script>
108
+                </div>
109
+                {/case}
110
+            {/switch}
111
+            {if isset($form.tip) && $form.tip}<div class="layui-form-mid no-float layui-word-aux">{$form.tip|raw}</div>{/if}
112
+        </div>
113
+    </div>
114
+    {/foreach}
115
+
116
+    <div class="layui-form-item layer-footer">
117
+        <div class="layui-input-block">
118
+            <button class="layui-btn" lay-submit data-refresh="false">立即提交</button>
119
+            <button type="reset" class="layui-btn layui-btn-primary">重置</button>
120
+        </div>
121
+    </div>
122
+</form>
123
+{/block}
124
+{block name="script"}
125
+<script type="text/javascript">
126
+layui.use('yznForm', function() {
127
+    var yznForm = layui.yznForm;
128
+
129
+    yznForm.bindevent($("form.layui-form"));
130
+});
131
+</script>
132
+{/block}

+ 688
- 0
application/admin/view/addons/index.html Datei anzeigen

@@ -0,0 +1,688 @@
1
+{extend name="admin@index_layout"/}
2
+{block name="main"}
3
+<style type="text/css">
4
+.releasetips {position: relative;}
5
+.releasetips i {display: block;background: #f00;border-radius: 50%;width: 0.3em;height: 0.3em;top: 0px;right: -8px;position: absolute;box-shadow: 0px 0px 2px #f11414;}
6
+</style>
7
+<div class="alert alert-info-light">
8
+    <b>温馨提示</b><br>
9
+    1、插件在线安装,升级和购买需要注册YznCMS官网账号,请点击下方【会员信息】,点击【注册】按钮<br>
10
+    2、点击插件名称可查看详情,截图和日志等信息
11
+</div>
12
+{if $type=='online'}
13
+<div class="layui-tab layui-tab-card" lay-filter="tabswitch" style="box-shadow: none;">
14
+    <ul class="layui-tab-title nav-category">
15
+        <li class="layui-this" data-id="">全部</li>
16
+    </ul>
17
+</div>
18
+{/if}
19
+<input type="hidden" name="category_id" value="0">
20
+<div class="layui-card">
21
+    <div class="layui-card-header">插件管理</div>
22
+    <div class="layui-card-body">
23
+        <table class="layui-hide" id="currentTable" lay-filter="currentTable"></table>
24
+    </div>
25
+</div>
26
+
27
+<script type="text/html" id="titleTpl">
28
+    <div>
29
+    {{#  if(d.url){ }}
30
+        {{#  if(d.name != 'recommend' ){ }}
31
+        <a href="javascript:;" data-open="{{d.url}}" lay-tips="查看插件介绍和帮助" title="插件详情">{{= d.title }}</a>
32
+        {{#  } else { }}
33
+        <a href="{{= d.url}}" target="_blank" lay-tips="查看插件介绍和帮助">{{= d.title }}</a>
34
+        {{#  } }}
35
+    {{#  } else { }}
36
+        <a href="javascript:;">{{= d.title }}</a>
37
+    {{#  } }}
38
+    </div>
39
+</script>
40
+
41
+<script type="text/html" id="operateTpl">
42
+{{#  if(d.addon){ }}
43
+    {{# if(d.addon.version!=d.version){ }}
44
+        {{# if(typeof d.releaselist !="undefined" && d.releaselist.length>1){ }}
45
+        <a class="layui-btn layui-btn-xs layui-btn-normal" data-name="{{d.name}}" data-version="{{d.version}}" data-title="{{d.title}}"  data-value="{{= JSON.stringify(d.releaselist)}}" lay-event="upgrade">升级 <i class="layui-icon layui-icon-down"></i></a>
46
+        {{# }else if(typeof d.releaselist !="undefined" && d.releaselist.length>0){ }}
47
+        <a class="layui-btn layui-btn-xs layui-btn-normal btn-upgrade" data-name="{{d.name}}" data-version="{{d.version}}" data-title="{{d.title}}">升级</a>
48
+        {{#  } }}
49
+    {{#  } }}
50
+
51
+    {{#  if(d.addon.config == 1 ){ }}
52
+    <a class="layui-btn layui-btn-xs" data-open='{:url("config")}?name={{d.name}}' title="插件设置 -【{{d.title}}】">设置</a>
53
+    {{#  } }}
54
+
55
+    <a class="layui-btn layui-btn-xs layui-btn-danger btn-uninstall" data-name="{{d.name}}" data-status="{{d.addon.status}}"  data-title="确定卸载【{{d.title}}】,进行该操作?" confirm>卸载</a>
56
+{{#  } else { }}
57
+    {{# if(typeof d.releaselist !="undefined" && d.releaselist.length>1){ }}
58
+    <a class="layui-btn layui-btn-xs" data-name="{{d.name}}" data-version="{{d.version}}" data-title="确定安装【{{d.title}}】,进行该操作?"  data-value="{{= JSON.stringify(d.releaselist)}}" lay-event="install">安装 <i class="layui-icon layui-icon-down"></i></a>
59
+    {{# }else if(typeof d.releaselist !="undefined" && d.releaselist.length>0){ }}
60
+    <a class="layui-btn layui-btn-xs btn-install" data-name="{{d.name}}" data-version="{{d.version}}" data-title="确定安装【{{d.title}}】,进行该操作?">安装</a>
61
+    {{#  } }}
62
+
63
+    {{# if(d.button){ }}
64
+        <a href="{{d.url}}" class="layui-btn layui-btn-xs layui-btn-normal" target="_blank">
65
+            {{d.button}}
66
+        </a>
67
+    {{#  } }}
68
+{{#  } }}
69
+</script>
70
+
71
+<script id="statusTpl" type="text/html">
72
+{{#  if(d.addon){ }}
73
+    <input type="checkbox" value="{{= d.id }}" title="启用|禁用" lay-skin="switch" lay-filter="templet-status" data-name="{{d.name}}" data-action="{{= d.addon.status == 1 ? "disable" : "enable"}}" {{= d.addon.status == 1 ? "checked" : "" }}>
74
+{{#  } }} 
75
+</script>
76
+
77
+<script id="conflicttpl" type="text/html">
78
+    <div class="alert alert-danger-light">
79
+        <strong>温馨提示</strong><br>
80
+        此插件中发现和现有系统中部分文件发现冲突!以下文件将会被影响,请备份好相关文件后再继续操作
81
+    </div>
82
+    <table class="layui-table">
83
+        <thead>
84
+        <tr>
85
+            <th>#</th>
86
+            <th>文件</th>
87
+        </tr>
88
+        </thead>
89
+        <tbody>
90
+        {{# for(var i=0;i < d.list.conflictlist.length;i++){ }}
91
+        <tr>
92
+            <th scope="row">{{i+1}}</th>
93
+            <td>{{d.list.conflictlist[i]}}</td>
94
+        </tr>
95
+        {{#  }; }}
96
+        </tbody>
97
+    </table>
98
+</script>
99
+
100
+<script id="uninstalltpl" type="text/html">
101
+    <div class="">
102
+        <div class="">{{ d.title }}
103
+            <p class="text-danger">卸载将会删除所有插件文件且不可找回!!! </p>
104
+            {if config('app_debug')}
105
+            <p class="text-danger"><input type="checkbox" name="droptables" id="droptables" data-name="{{ d.name }}"/> 删除所有插件相关数据表且不可找回!!! </p>
106
+            {/if}
107
+            <p class="text-danger">如有重要数据请备份后再操作!!!</p>
108
+        </div>
109
+    </div>
110
+</script>
111
+
112
+<script id="logintpl" type="text/html">
113
+    <div>
114
+        <form class="layui-form">
115
+            <div class="alert alert-danger-light">
116
+                <strong>温馨提示</strong><br/>此处登录账号为<a href="https://www.yzncms.com" target="_blank">Yzncms官网账号</a>
117
+            </div>
118
+            <div class="layui-form-item">
119
+                <div class="layui-input-wrap">
120
+                    <div class="layui-input-prefix">
121
+                      <i class="layui-icon layui-icon-username"></i>
122
+                    </div>
123
+                    <input type="text" class="layui-input" id="inputAccount" value="" autocomplete="off" placeholder="你的手机号、用户名或邮箱">
124
+
125
+                </div>
126
+            </div>
127
+            <div class="layui-form-item">
128
+                <div class="layui-input-wrap">
129
+                    <div class="layui-input-prefix">
130
+                      <i class="layui-icon layui-icon-password"></i>
131
+                    </div>
132
+                    <input type="password" class="layui-input" id="inputPassword" value="" autocomplete="off" lay-affix="eye" placeholder="你的密码">
133
+
134
+                </div>
135
+            </div>
136
+        </form>
137
+    </div>
138
+</script>
139
+
140
+<script id="userinfotpl" type="text/html">
141
+    <div>
142
+        <form class="form-horizontal form-userinfo">
143
+            <div class="alert alert-dismissable alert-info-light">
144
+                <strong>温馨提示</strong><br/>你好!{{d.username}}<br />当前你已经登录,将同步保存你的购买记录
145
+            </div>
146
+        </form>
147
+    </div>
148
+</script>
149
+{/block}
150
+{block name="script"}
151
+<script>
152
+layui.use(['yznTable','table','laytpl','yzn','notice','element','dropdown','form','yznUpload'], function() {
153
+    var yznTable = layui.yznTable,
154
+        $ = layui.$,
155
+        table = layui.table,
156
+        laytpl = layui.laytpl,
157
+        yzn = layui.yzn,
158
+        notice = layui.notice,
159
+        element = layui.element,
160
+        dropdown = layui.dropdown,
161
+        form = layui.form,
162
+        yznUpload = layui.yznUpload;
163
+
164
+    var init = {
165
+        table_elem: '#currentTable',
166
+        table_render_id: 'currentTable',
167
+        layFilter: 'currentTable_LayFilter',
168
+    };
169
+
170
+    var area = [$(window).width() > 800 ? '800px' : '95%', $(window).height() > 600 ? '600px' : '95%'];
171
+
172
+    var switch_local = function () {
173
+        layer.confirm('插件市场暂不可用,是否切换到本地插件?', {
174
+            title: '温馨提示',
175
+            btn: ['切换到本地插件', '重新尝试加载']
176
+        }, function (index) {
177
+            layer.close(index);
178
+            $(".btn-switch[data-type='local']")[0].click();
179
+        }, function (index) {
180
+            layer.close(index);
181
+            table.reload(init.table_render_id);
182
+        });
183
+        return false;
184
+    };
185
+
186
+    yznTable.render({
187
+        init: init,
188
+        toolbar: ['refresh',
189
+            [{
190
+                html:'<a id="faupload-addon" class="layui-btn layui-btn-sm faupload" data-url="{:url("local")}" data-chunking="false" data-mimetype="zip" data-multiple="false"><i class="iconfont icon-upload-fill"></i>&nbsp;本地安装</a>'
191
+             },{
192
+                html:'<a class="layui-btn {if $type=='online'}layui-btn-normal{else}layui-btn-primary layui-border-blue{/if} layui-btn-sm btn-switch" data-type="all" href="{:url("index")}" style="margin-left: 10px !important;"><i class="iconfont icon-other"></i>&nbsp;全部</a>'
193
+             },{
194
+                html:'<a class="layui-btn {if $type=='local'}layui-btn-normal{else}layui-btn-primary layui-border-blue{/if} layui-btn-sm btn-switch" data-type="local" href="{:url("index",["type"=>"local"])}"><i class="iconfont icon-manage"></i>&nbsp;本地插件</a>'
195
+             },{
196
+                html:'<a class="layui-btn layui-bg-black layui-btn-sm btn-userinfo" style="margin-left: 10px !important;" href="javascript:;"><i class="iconfont icon-user-line"></i>&nbsp;会员信息</a>'
197
+             }
198
+            ],
199
+        ],
200
+        url: '{:url("index",["type"=>$type])}',
201
+        css :'.layui-table-tool-temp{padding-right: 85px;}',
202
+        showSearch:false,
203
+        cols: [
204
+            [
205
+                { field: 'title', width: 200, title: '名称',templet: '#titleTpl'},
206
+                { field: 'name', width: 150, title: '标识' },
207
+                { field: 'description', title: '描述' },
208
+                { field: 'author', width: 90, title: '作者' },
209
+                {if $type == 'online'}
210
+                { field: 'price', width: 95, title: '价格' , templet: function(d){
211
+                    if (isNaN(d.price)) {
212
+                        return d.price;
213
+                    }
214
+                    return parseFloat(d.price) == 0 ? '<span class="text-success">' + '免费' + '</span>' : '<span class="text-danger">¥' + d.price + '</span>';
215
+                }},
216
+                { field: 'downloads', width: 90, title: '下载' },
217
+                {/if}
218
+                { field: 'version', width: 80, title: '版本',templet: function(d){
219
+                    return d.addon && d.addon.version != d.version ? '<a href="javascript:;"><span class="releasetips text-primary" lay-tips="发现新版本:'+d.version+'">' + d.addon.version + '<i></i></span></a>' : d.version;
220
+                }},
221
+                { field: 'status', width: 90, title: '状态', templet: '#statusTpl' },
222
+                { fixed: 'right', width: 180, title: '操作', templet: '#operateTpl' }
223
+            ]
224
+        ],
225
+        page: {limit:20},
226
+        before(){
227
+          let userinfo = yzn.cache.getStorage('yzncms_userinfo');
228
+
229
+          this.where.uid = userinfo ? userinfo.id : '';
230
+          this.where.token = userinfo ? userinfo.token : '';
231
+          this.where.category_id = $('input[name="category_id"]').val();
232
+        },
233
+        done: function(res, curr, count){
234
+            if (res && typeof res.category != 'undefined' && $(".nav-category li").length == 1) {
235
+                $.each(res.category, function (i, j) {
236
+                    $("<li data-id='" + j.id + "'>" + j.title + "</li>").insertAfter($(".nav-category li:first"));
237
+                });
238
+            }
239
+            if(count==-1){
240
+                switch_local();
241
+            }
242
+            yznUpload.api.upload("#faupload-addon", function (data, ret) {
243
+                //上传完毕回调
244
+                var addon = data.addon;
245
+                var testdata = data.addon.testdata;
246
+                operate(addon.name, 'enable', false, function (data, ret) {
247
+                    layer.alert('安装成功!清除浏览器缓存和框架缓存后生效!' + (testdata ? '<br>你还可以继续导入测试数据!' : ""), {
248
+                        btn: testdata ? ['导入测试数据', '暂不导入'] : ['确定'],
249
+                        title:'温馨提示',
250
+                        yes: function (index) {
251
+                            if (testdata) {
252
+                                yzn.request.post({
253
+                                    url: '{:url("testdata")}',
254
+                                    data: {
255
+                                        name: addon.name,
256
+                                    }
257
+                                }, function (data,res) {
258
+                                    layer.close(index);
259
+                                    yzn.msg.success(res.msg);
260
+                                });
261
+                            } else {
262
+                                layer.close(index);
263
+                            }
264
+                        },
265
+                        icon: 1
266
+                    });
267
+                });  
268
+                return false;
269
+            }, function (data, ret) {
270
+                if (ret.msg && ret.msg.match(/(login|登录)/g)) {
271
+                    return layer.alert(ret.msg, {
272
+                        title: '温馨提示',
273
+                        btn: ['立即登录'],
274
+                        yes: function (index, layero) {
275
+                            $(".btn-userinfo").trigger("click");
276
+                        }
277
+                    });
278
+                }
279
+            })
280
+        }
281
+    });
282
+
283
+    yznTable.bindevent();
284
+
285
+    // 检测是否登录
286
+    $(document).on("mousedown", "#faupload-addon", function (e) {
287
+        if(!isLogin()) return
288
+    });
289
+
290
+
291
+    table.on('tool('+init.layFilter+')', function(obj){
292
+        var that = this;
293
+        jsondata = $(this).data('value');
294
+        var name = $(this).data("name");
295
+        var title = $(this).data("title");
296
+        const arr = jsondata.map(({id, version}) => ({ id, title: version }))
297
+
298
+        dropdown.render({
299
+            elem: that,
300
+            show: true, // 外部事件触发即显示
301
+            data: arr,
302
+            click: function(data, othis){
303
+
304
+                if(!isLogin()) return
305
+
306
+                if(obj.event === 'install'){
307
+                    yzn.msg.confirm(title, function(index){
308
+                        install(name, data.title, false);
309
+                    });
310
+                }
311
+                if(obj.event === 'upgrade'){
312
+                    layer.confirm('确认升级<b>《'+title+'》</b>?<p class="text-danger">1、请务必做好代码和数据库备份!备份!备份!<br>2、升级后如出现冗余数据,请根据需要移除即可!<br>3、不建议在生产环境升级,请在本地完成升级测试</p>如有重要数据请备份后再操作!', function (index, layero) {
313
+                        upgrade(name, data.title);
314
+                    });
315
+                }
316
+            }
317
+        })
318
+    })
319
+
320
+
321
+    element.on('tab(tabswitch)', function(data){
322
+      var value = $(this).data("id");
323
+      $('input[name="category_id"]').val(value);
324
+      table.reload(init.table_render_id);
325
+    });
326
+
327
+    // 会员信息
328
+    $(document).on("click", ".btn-userinfo", function (e, name, version) {
329
+        var that = this;
330
+        var area = [$(window).width() > 800 ? '500px' : '95%', $(window).height() > 600 ? '400px' : '95%'];
331
+        var userinfo = yzn.cache.getStorage('yzncms_userinfo');
332
+        if (!userinfo) {
333
+            layer.open({
334
+                content: laytpl($("#logintpl").html()).render({}),
335
+                zIndex: 99,
336
+                area: area,
337
+                title: '登录',
338
+                resize: false,
339
+                btn: ['登录', '注册'],
340
+                yes: function (index, layero) {
341
+                    yzn.request.post({
342
+                        url: '{$api_url}/member/login',
343
+                        type: 'post',
344
+                        data: {
345
+                            account: $("#inputAccount", layero).val(),
346
+                            password: $("#inputPassword", layero).val(),
347
+                        }
348
+                    }, function (data, ret) {
349
+                        yzn.cache.setStorage('yzncms_userinfo',data.userinfo);
350
+                        layer.closeAll();
351
+                        layer.alert(ret.msg, {title: '温馨提示', icon: 1});
352
+                        return false;
353
+                    }, function (data, ret) {
354
+                        notice.error({ message: ret.msg });
355
+                    });
356
+                },
357
+                btn2: function () {
358
+                    return false;
359
+                },
360
+                success: function (layero, index) {
361
+                    this.checkEnterKey = function (event) {
362
+                        if (event.keyCode === 13) {
363
+                            $(".layui-layer-btn0").trigger("click");
364
+                            return false;
365
+                        }
366
+                    };
367
+                    $(document).on('keydown', this.checkEnterKey);
368
+                    $(".layui-layer-btn1", layero).prop("href", "https://www.yzncms.com/member/index/register.html").prop("target", "_blank");
369
+                },
370
+                end: function () {
371
+                    $(document).off('keydown', this.checkEnterKey);
372
+                }
373
+            });
374
+        } else {
375
+            yzn.request.post({
376
+                url: '{$api_url}/member/index',
377
+                data: {
378
+                    uid: userinfo.id,
379
+                    token: userinfo.token,
380
+                }
381
+            }, function (data) {
382
+                layer.open({
383
+                    content: laytpl($("#userinfotpl").html()).render(userinfo),
384
+                    area: area,
385
+                    title: '会员信息',
386
+                    resize: false,
387
+                    btn: ['退出登录', '关闭'],
388
+                    yes: function () {
389
+                        yzn.request.post({
390
+                            url: '{$api_url}/member/logout',
391
+                            data: {uid: userinfo.id, token: userinfo.token}
392
+                        }, function (data, ret) {
393
+                            yzn.cache.setStorage('yzncms_userinfo','');
394
+                            layer.closeAll();
395
+                            layer.alert(ret.msg, {title: '温馨提示', icon: 0});
396
+                        }, function (data, ret) {
397
+                            yzn.cache.setStorage('yzncms_userinfo','');
398
+                            layer.closeAll();
399
+                            layer.alert(ret.msg, {title: '温馨提示', icon: 0});
400
+                        });
401
+                    }
402
+                });
403
+                return false;
404
+            }, function (data) {
405
+                yzn.cache.setStorage('yzncms_userinfo','');
406
+                $(that).trigger('click');
407
+                return false;
408
+            });
409
+
410
+        }
411
+    });
412
+
413
+    var install = function (name, version, force) {
414
+        var userinfo = yzn.cache.getStorage('yzncms_userinfo');
415
+        var uid = userinfo ? userinfo.id : 0;
416
+        var token = userinfo ? userinfo.token : '';
417
+        yzn.request.post({
418
+            url: '{:url("install")}',
419
+            data: {
420
+                name: name,
421
+                force: force ? 1 : 0,
422
+                uid: uid,
423
+                token: token,
424
+                version: version
425
+            },
426
+        }, function(data,res) {
427
+            layer.closeAll();
428
+            layer.alert('安装成功!清除浏览器缓存和框架缓存后生效!' + (data.addon.testdata ? '<br>你还可以继续导入测试数据!' : ""), {
429
+                btn: data.addon.testdata ? ['导入测试数据', '暂不导入'] : ['确定'],
430
+                title:'温馨提示',
431
+                yes: function (index) {
432
+                    if (data.addon.testdata) {
433
+                        yzn.request.post({
434
+                            url: '{:url("testdata")}',
435
+                            data: {
436
+                                name: name,
437
+                            }
438
+                        }, function (data,res) {
439
+                            layer.close(index);
440
+                            yzn.msg.success(res.msg);
441
+                        });
442
+                    } else {
443
+                        layer.close(index);
444
+                    }
445
+                    table.reload(init.table_render_id);
446
+                },
447
+                icon: 1
448
+            });
449
+        },function(data,res) {
450
+            var area = [$(window).width() > 650 ? '650px' : '95%', $(window).height() > 710 ? '710px' : '95%'];
451
+            if (res && res.code === -2) {
452
+                layer.closeAll();
453
+                yzn.open('立即支付' , res.data.payurl,'','', {
454
+                    area: area,
455
+                    end: function () {
456
+                        yzn.request.post({
457
+                            url: '{:url("isbuy")}',
458
+                            data: {
459
+                                name: name,
460
+                                force: force ? 1 : 0,
461
+                                uid: uid,
462
+                                token: token,
463
+                                version: version
464
+                            }
465
+                        }, function () {
466
+                            top.layer.alert('购买成功!请点击继续安装按钮完成安装!', {
467
+                                btn: ['继续安装'],
468
+                                title: '温馨提示',
469
+                                icon: 1,
470
+                                yes: function (index) {
471
+                                    top.layer.close(index);
472
+                                    install(name, version);
473
+                                }
474
+                            });
475
+                            return false;
476
+                        }, function () {
477
+                            console.log('已取消');
478
+                            return false;
479
+                        });
480
+                    }
481
+                });
482
+            }else if (res && res.code === -3) {
483
+                //插件目录发现影响全局的文件
484
+                layer.open({
485
+                    content: laytpl($("#conflicttpl").html()).render({list:res.data}),
486
+                    shade: 0.8,
487
+                    area: area,
488
+                    title:'温馨提示',
489
+                    btn: ['继续操作', '取消'],
490
+                    end: function () {
491
+
492
+                    },
493
+                    yes: function () {
494
+                        install(name, version, true);
495
+                    }
496
+                });
497
+            } else if (res && res.code === 401) {
498
+                yzn.cache.setStorage('yzncms_userinfo','');
499
+                layer.alert('登录已经失效,请重新登录后操作!', {
500
+                    title: '温馨提示',
501
+                    btn: ['立即登录'],
502
+                    yes: function (index, layero) {
503
+                        $(".btn-userinfo").trigger("click");
504
+                    },
505
+                });
506
+            } else {
507
+                layer.alert(res.msg, {title: '温馨提示', icon: 0});
508
+            }
509
+            return false;
510
+        })
511
+    }
512
+
513
+    var uninstall = function (name, force, droptables) {
514
+        yzn.request.post({
515
+            url: '{:url("uninstall")}',
516
+            data: {name: name, force: force ? 1 : 0, droptables: droptables ? 1 : 0}
517
+        }, function (data,res) {
518
+            layer.closeAll();
519
+            yzn.msg.success(res.msg);
520
+            table.reload(init.table_render_id);
521
+        }, function (data,res) {
522
+            if (res && res.code === -3) {
523
+                //插件目录发现影响全局的文件
524
+                layer.open({
525
+                    content: laytpl($("#conflicttpl").html()).render({list:res.data}),
526
+                    shade: 0.8,
527
+                    area: area,
528
+                    title:'温馨提示',
529
+                    btn: ['继续操作', '取消'],
530
+                    end: function () {
531
+
532
+                    },
533
+                    yes: function () {
534
+                        uninstall(name, true, droptables);
535
+                    }
536
+                });
537
+            } else {
538
+                layer.alert(res.msg, {title: '温馨提示', icon: 0});
539
+            }
540
+            return false;
541
+        });
542
+    };
543
+
544
+    // 点击升级
545
+    $(document).on("click", ".btn-upgrade", function () {
546
+        var name = $(this).data('name');
547
+        var version = $(this).data("version");
548
+        var title = $(this).data("title");
549
+
550
+        if(!isLogin()) return
551
+
552
+        layer.confirm('确认升级<b>《'+title+'》</b>?<p class="text-danger">1、请务必做好代码和数据库备份!备份!备份!<br>2、升级后如出现冗余数据,请根据需要移除即可!<br>3、不建议在生产环境升级,请在本地完成升级测试</p>如有重要数据请备份后再操作!', function (index, layero) {
553
+            upgrade(name, version);
554
+        });
555
+    });
556
+
557
+    var upgrade = function (name, version) {
558
+        var userinfo = yzn.cache.getStorage('yzncms_userinfo');
559
+        var uid = userinfo ? userinfo.id : 0;
560
+        var token = userinfo ? userinfo.token : '';
561
+
562
+        yzn.request.post({
563
+            url: '{:url("upgrade")}',
564
+            data: {
565
+                name: name,
566
+                uid: uid,
567
+                token: token,
568
+                version: version
569
+            }
570
+        }, function (data, ret) {
571
+            table.reload(init.table_render_id);
572
+            notice.success({ message: ret.msg });
573
+            layer.closeAll();
574
+        }, function (data, ret) {
575
+            layer.alert(ret.msg, {title: '温馨提示'});
576
+            return false;
577
+        });
578
+    };
579
+
580
+    var operate = function (name, action, force, success) {
581
+        yzn.request.post({
582
+            url: '{:url("state")}',
583
+            data: {name: name, action: action, force: force ? 1 : 0}
584
+        }, function(data,res) {
585
+            layer.closeAll();
586
+            if (typeof success === 'function') {
587
+                success(res);
588
+            }
589
+            table.reload(init.table_render_id);
590
+            return false;   
591
+        }, function(data,res) {
592
+            if (res && res.code === -3) {
593
+                //插件目录发现影响全局的文件
594
+                layer.open({
595
+                    content: laytpl($("#conflicttpl").html()).render({list:res.data}),
596
+                    shade: 0.8,
597
+                    area: area,
598
+                    title:'温馨提示',
599
+                    btn: ['继续操作', '取消'],
600
+                    end: function () {
601
+
602
+                    },
603
+                    yes: function () {
604
+                        operate(name, action, true, success);
605
+                    }
606
+                });
607
+            } else {
608
+                layer.alert(res.msg, {title: '温馨提示', icon: 0});
609
+            }
610
+        })
611
+    };
612
+
613
+    var isLogin = function (){
614
+        var userinfo = yzn.cache.getStorage('yzncms_userinfo');
615
+        var uid = userinfo ? userinfo.id : 0;
616
+
617
+        if (parseInt(uid) === 0) {
618
+            layer.alert('你当前未登录Yzncms,请登录后操作!', {
619
+                title: '温馨提示',
620
+                btn: ['立即登录'],
621
+                yes: function (index, layero) {
622
+                    $(".btn-userinfo").trigger("click");
623
+                },
624
+            });
625
+            return false;
626
+        }
627
+        return true;
628
+    }
629
+
630
+    var tables = [];
631
+    $(document).on("click", "#droptables", function () {
632
+        if ($(this).prop("checked")) {
633
+            yzn.request.post({
634
+                url: '{:url("get_table_list")}',
635
+                async: false,
636
+                data: {name: $(this).data("name")}
637
+            }, function (res) {
638
+                tables = res.tables;
639
+                return false;
640
+            });
641
+            var html;
642
+            html = tables.length > 0 ? '<div class="alert alert-warning-light droptablestips" style="max-width:480px;max-height:300px;overflow-y: auto;">以下插件数据表将会被删除:<br>' + tables.join("<br>") + '<br>注意:部分插件还同时会删除演示数据和关联表等</div>'
643
+                : '<div class="alert alert-warning-light droptablestips">注意:部分插件还同时会删除演示数据和关联表等</div>';
644
+            $(html).insertAfter($(this).closest("p"));
645
+        } else {
646
+            $(".droptablestips").remove();
647
+        }
648
+        $(window).resize();
649
+    });
650
+
651
+    // 点击安装
652
+    $(document).on("click", ".btn-install", function () {
653
+        var name = $(this).data("name");
654
+        var title = $(this).data("title");
655
+        var version = $(this).data("version");
656
+        
657
+        if(!isLogin()) return
658
+
659
+        yzn.msg.confirm(title, function(index){
660
+            install(name, version, false);
661
+        });
662
+    });
663
+    
664
+    // 点击启用/禁用
665
+    form.on('switch(templet-status)', function (obj) {
666
+        var name = $(this).data("name");
667
+        var action = $(this).data("action");
668
+        $(this).trigger('click');
669
+        form.render('checkbox');
670
+        operate(name, action, false);
671
+    })
672
+
673
+    // 点击卸载
674
+    $(document).on("click", ".btn-uninstall", function () {
675
+        var name = $(this).data('name');
676
+        var title = $(this).data('title');
677
+        if ($(this).data('status') == 1) {
678
+            layer.alert('请先禁用插件再进行卸载', {icon: 7});
679
+            return false;
680
+        }
681
+        yzn.msg.confirm(laytpl($("#uninstalltpl").html()).render({name:name,title:title}), function (index, layero) {
682
+            uninstall(name, false, $("input[name='droptables']", layero).prop("checked"));
683
+        });
684
+    });
685
+
686
+});
687
+</script>
688
+{/block}

+ 27
- 0
application/admin/view/auth/adminlog/detail.html Datei anzeigen

@@ -0,0 +1,27 @@
1
+{extend name="index_layout"/}
2
+{block name="main"}
3
+<table class="layui-table">
4
+    <colgroup>
5
+        <col width="150">
6
+        <col width="150">
7
+        <col>
8
+    </colgroup>
9
+    <thead>
10
+        <tr>
11
+            <th>标题</th>
12
+            <th>内容</th>
13
+        </tr>
14
+    </thead>
15
+    <tbody>
16
+    {volist name="row" id="vo"}
17
+    <tr>
18
+        <td>{$key}</td>
19
+        <td>{$vo}</td>
20
+    </tr>
21
+    {/volist}
22
+    </tbody>
23
+</table>
24
+<div class="layui-hide layer-footer">
25
+    <button type="reset" class="layui-btn btn-close" onclick="layer.closeAll();">关闭</button>
26
+</div>
27
+{/block}

+ 66
- 0
application/admin/view/auth/adminlog/index.html Datei anzeigen

@@ -0,0 +1,66 @@
1
+{extend name="index_layout"/}
2
+{block name="main"}
3
+<div class="layui-card">
4
+    <div class="layui-card-header">管理日志</div>
5
+    <div class="layui-card-body">
6
+        <table class="layui-hide" id="currentTable" lay-filter="currentTable" data-auth-delete="{:$auth->check('auth.adminlog/deletelog')}"></table>
7
+    </div>
8
+</div>
9
+{/block}
10
+{block name="script"}
11
+<script>
12
+layui.use(['yznTable','util'], function() {
13
+    var table = layui.yznTable,
14
+        util = layui.util;
15
+
16
+    var init = {
17
+        table_elem: '#currentTable',
18
+        table_render_id: 'currentTable',
19
+        delete_url: "{:url('deletelog')}",
20
+        detail_url: "{:url('detail')}",
21
+    };
22
+
23
+    table.render({
24
+        init: init,
25
+        toolbar: ['refresh',
26
+            [{
27
+                text: '删除一个月前日志',
28
+                url: init.delete_url,
29
+                method: 'request',
30
+                auth: 'delete',
31
+                icon: 'iconfont icon-trash',
32
+                class: 'layui-btn layui-btn-sm layui-btn-danger',
33
+                extend: 'data-table="currentTable"',
34
+            }]
35
+        ],
36
+        url: '{:url("index")}',
37
+        cols: [
38
+            [
39
+                { field: 'id', width: 80, title: 'ID', sort: true },
40
+                { field: 'username', width: 120, title: '用户名' },
41
+                { field: 'title', title: '标题', searchOp: 'like'  },
42
+                { field: 'url', title: '操作URL' },
43
+                { field: 'ip', width: 120, title: 'IP' },
44
+                { field: 'useragent', width: 120, title: 'Browser',templet:function(d){
45
+                        return '<span>' + util.escape(d.useragent.split(" ")[0]) + '</span>';
46
+                    }
47
+                },
48
+                { field: 'create_time', width: 180, title: '时间', search: 'range' },
49
+                { width:90, title: '操作',templet: yznTable.formatter.tool,operat: [
50
+                    [{
51
+                        text: '详情',
52
+                        url: init.detail_url,
53
+                        method: 'open',
54
+                        icon: 'iconfont icon-zoom-in-line',
55
+                        class: 'layui-btn layui-btn-xs layui-btn-normal',
56
+                    }],
57
+                ]}
58
+            ]
59
+        ],
60
+        page: {}
61
+    });
62
+
63
+    yznTable.bindevent();
64
+});
65
+</script>
66
+{/block}

+ 93
- 0
application/admin/view/auth/group/access.html Datei anzeigen

@@ -0,0 +1,93 @@
1
+{extend name="index_layout"/}
2
+{block name="main"}
3
+<form class="layui-form" method="post">
4
+    <div class="zTreeDemoBackground left">
5
+        <ul class="ztree" style="margin-left: 5px;margin-top:5px; padding: 0;">
6
+            <li><a title="全部展开、折叠 "><span class="button ico_open"></span><span id="ztree_expandAll">全部展开、折叠 </span></a> </li>
7
+        </ul>
8
+        <ul id="treeDemo" class="ztree"></ul>
9
+    </div>
10
+    {:token()}
11
+    <input type="hidden" name="row[rules]" value="" />
12
+    <input type="hidden" name="row[id]" value="{$data.id}" />
13
+    <input type="hidden" name="row[parentid]" value="{$data.parentid}">
14
+    <div class="layui-form-item layer-footer">
15
+        <div class="">
16
+            <button class="layui-btn" lay-submit>立即提交</button>
17
+        </div>
18
+    </div>
19
+</form>
20
+{/block}
21
+{block name="script"}
22
+<script>
23
+layui.use(['jquery', 'ztree','yznForm'], function() {
24
+    var $ = layui.$,
25
+        yznForm = layui.yznForm;
26
+    //配置
27
+    var setting = {
28
+        //设置 zTree 的节点上是否显示 checkbox / radio
29
+        check: {
30
+            enable: true,
31
+            chkboxType: { "Y": "ps", "N": "ps" }
32
+        },
33
+        data: {
34
+            simpleData: {
35
+                enable: true,
36
+                idKey: "id",
37
+                pIdKey: "parentid",
38
+            }
39
+        },
40
+        callback: {
41
+            beforeClick: function(treeId, treeNode) {
42
+                if (treeNode.isParent) {
43
+                    zTree.expandNode(treeNode);
44
+                    return false;
45
+                } else {
46
+                    return true;
47
+                }
48
+            },
49
+            /*onClick: function(event, treeId, treeNode) {
50
+                //栏目ID
51
+                var catid = treeNode.catid;
52
+                //保存当前点击的栏目ID
53
+                setCookie('tree_catid', catid, 1);
54
+            }*/
55
+        }
56
+    };
57
+    //节点数据
58
+    var zNodes = {$nodeList|raw};
59
+    //zTree对象
60
+    var zTree = null;
61
+    $(document).ready(function() {
62
+        $.fn.zTree.init($("#treeDemo"), setting, zNodes);
63
+        zTree = $.fn.zTree.getZTreeObj("treeDemo");
64
+        zTree.expandAll(true);
65
+        $("#ztree_expandAll").click(function() {
66
+            if ($(this).data("open")) {
67
+                zTree.expandAll(false);
68
+                $(this).data("open", false);
69
+            } else {
70
+                zTree.expandAll(true);
71
+                $(this).data("open", true);
72
+            }
73
+        });
74
+    });
75
+
76
+    $('.layui-btn').click(function() {
77
+        _form = $('.layui-form');
78
+        //处理被选中的数据
79
+        _form.find('input[name="rules"]').val("");
80
+        var nodes = zTree.getCheckedNodes(true);
81
+        var str = "";
82
+        $.each(nodes, function(i, value) {
83
+            if (str != "") {
84
+                str += ",";
85
+            }
86
+            str += value.id;
87
+        });
88
+        _form.find('input[name="row[rules]"]').val(str);
89
+        yznForm.bindevent($("form.layui-form"));
90
+    })
91
+});
92
+</script>
93
+{/block}

+ 46
- 0
application/admin/view/auth/group/add.html Datei anzeigen

@@ -0,0 +1,46 @@
1
+{extend name="index_layout"/}
2
+{block name="main"}
3
+<form class="layui-form" method="post">
4
+    {:token()}
5
+    <div class="layui-form-item">
6
+        <label class="layui-form-label">父级</label>
7
+        <div class="layui-input-block">
8
+            {:Form::select('row[parentid]',$groupdata,0,["lay-verify"=>"required"])}
9
+        </div>
10
+    </div>
11
+    <div class="layui-form-item">
12
+        <label class="layui-form-label">权限组</label>
13
+        <div class="layui-input-block">
14
+            <input type="text" name="row[title]" lay-verify="required" autocomplete="off" placeholder="权限组" class="layui-input" value="">
15
+        </div>
16
+    </div>
17
+    <input type="hidden" name="row[rules]" value="">
18
+    <div class="layui-form-item">
19
+        <label class="layui-form-label">描述</label>
20
+        <div class="layui-input-block">
21
+            <textarea name="row[description]" placeholder="权限的相关描述" class="layui-textarea"></textarea>
22
+        </div>
23
+    </div>
24
+    <div class="layui-form-item">
25
+        <label class="layui-form-label">状态</label>
26
+        <div class="layui-input-block">
27
+            <input type="radio" name="row[status]" value="1" title="正常" checked>
28
+            <input type="radio" name="row[status]" value="0" title="禁用">
29
+        </div>
30
+    </div>
31
+    <div class="layui-form-item layer-footer">
32
+        <div class="layui-input-block">
33
+            <button class="layui-btn" lay-submit>立即提交</button>
34
+            <button type="reset" class="layui-btn layui-btn-primary">重置</button>
35
+        </div>
36
+    </div>
37
+</form>
38
+{/block}
39
+{block name="script"}
40
+<script type="text/javascript">
41
+layui.use('yznForm', function() {
42
+    var yznForm = layui.yznForm;
43
+    yznForm.bindevent($("form.layui-form"));
44
+});
45
+</script>
46
+{/block}

+ 46
- 0
application/admin/view/auth/group/edit.html Datei anzeigen

@@ -0,0 +1,46 @@
1
+{extend name="index_layout"/}
2
+{block name="main"}
3
+<form class="layui-form" method="post">
4
+    {:token()}
5
+    <div class="layui-form-item">
6
+        <label class="layui-form-label">父级</label>
7
+        <div class="layui-input-block">
8
+            {:Form::select('row[parentid]',$groupdata,$data['parentid'],["lay-verify"=>"required"])}
9
+        </div>
10
+    </div>
11
+    <div class="layui-form-item">
12
+        <label class="layui-form-label">权限组</label>
13
+        <div class="layui-input-block">
14
+            <input type="text" name="row[title]" lay-verify="required" autocomplete="off" placeholder="权限组" class="layui-input" value="{$data.title}">
15
+        </div>
16
+    </div>
17
+    <div class="layui-form-item">
18
+        <label class="layui-form-label">描述</label>
19
+        <div class="layui-input-block">
20
+            <textarea name="row[description]" placeholder="权限的相关描述" class="layui-textarea">{$data.description}</textarea>
21
+        </div>
22
+    </div>
23
+    <input type="hidden" name="row[rules]" value="{$data.rules}">
24
+    <div class="layui-form-item">
25
+        <label class="layui-form-label">状态</label>
26
+        <div class="layui-input-block">
27
+            <input type="radio" name="row[status]" value="1" title="正常" {eq name="data.status" value="1" }checked{/eq}>
28
+            <input type="radio" name="row[status]" value="0" title="禁用" {eq name="data.status" value="0" }checked{/eq}>
29
+        </div>
30
+    </div>
31
+    <div class="layui-form-item layer-footer">
32
+        <div class="layui-input-block">
33
+            <button class="layui-btn" lay-submit>立即提交</button>
34
+            <button type="reset" class="layui-btn layui-btn-primary">重置</button>
35
+        </div>
36
+    </div>
37
+</form>
38
+{/block}
39
+{block name="script"}
40
+<script type="text/javascript">
41
+layui.use('yznForm', function() {
42
+    var yznForm = layui.yznForm;
43
+    yznForm.bindevent($("form.layui-form"));
44
+});
45
+</script>
46
+{/block}

+ 60
- 0
application/admin/view/auth/group/index.html Datei anzeigen

@@ -0,0 +1,60 @@
1
+{extend name="index_layout"/}
2
+{block name="main"}
3
+<div class="layui-card">
4
+    <div class="layui-card-header">角色管理</div>
5
+    <div class="layui-card-body">
6
+        <div class="layui-form">
7
+            <table class="layui-hide" id="currentTable" lay-filter="currentTable"
8
+            data-auth-add="{:$auth->check('auth.group/add')}"
9
+            data-auth-edit="{:$auth->check('auth.group/edit')}"
10
+            data-auth-delete="{:$auth->check('auth.group/del')}" ></table>
11
+            <script type="text/html" id="authTool">
12
+                {{#  if(d.id == {$userInfo.roleid}){ }}
13
+                <a class="layui-btn layui-btn-xs layui-btn-danger layui-btn-disabled">不可操作</a>
14
+                {{#  } else { }}
15
+                <a class="layui-btn layui-btn-xs {:$auth->check('auth.group/access')?'':'layui-hide'}" data-open='{:url("access")}?id={{d.id}}&pid={{d.parentid}}' title="访问授权">访问授权</a>
16
+                {{#  } }}
17
+            </script>
18
+        </div>
19
+    </div>
20
+</div>
21
+{/block}
22
+{block name="script"}
23
+<script>
24
+layui.use('yznTable', function() {
25
+    var table = layui.yznTable;
26
+
27
+    var init = {
28
+        table_elem: '#currentTable',
29
+        table_render_id: 'currentTable',
30
+        add_url: "{:url('add')}",
31
+        edit_url: "{:url('edit')}",
32
+        delete_url: "{:url('del')}",
33
+    };
34
+
35
+    table.render({
36
+        init: init,
37
+        toolbar: ['refresh','add'],
38
+        url: '{:url("index")}',
39
+        search:false,
40
+        cols: [
41
+            [
42
+                { field: 'title',width: 200, title: '授权',templet: '#authTool' },
43
+                { field: 'title', width: 200, align: 'left', title: '权限组'},
44
+                { field: 'description', title: '描述' },
45
+                { field: 'status', width: 80,title: '状态',templet: yznTable.formatter.status,selectList:{0:'禁用',1:'正常'},search:false},
46
+                { width:100, title: '操作',templet: function (d){
47
+                    if(d.id=={$userInfo.roleid}){
48
+                        return '<a class="layui-btn layui-btn-xs layui-btn-danger layui-btn-disabled">不可操作</a>';
49
+                    }else{
50
+                        return yznTable.formatter.tool.call(this,d,this);
51
+                    }
52
+                },operat: ['edit','delete']}
53
+            ]
54
+        ],
55
+    });
56
+
57
+    yznTable.bindevent();
58
+});
59
+</script>
60
+{/block}

+ 62
- 0
application/admin/view/auth/manager/add.html Datei anzeigen

@@ -0,0 +1,62 @@
1
+{extend name="index_layout"/}
2
+{block name="main"}
3
+<form class="layui-form" method="post">
4
+    {:token()}
5
+    <div class="layui-form-item">
6
+        <label class="layui-form-label">用户名</label>
7
+        <div class="layui-input-block">
8
+            <input type="text" name="username" lay-verify="required" autocomplete="off" placeholder="用户名" class="layui-input">
9
+        </div>
10
+    </div>
11
+    <div class="layui-form-item">
12
+        <label class="layui-form-label">密码</label>
13
+        <div class="layui-input-block">
14
+            <input type="password" name="password" lay-verify="required" autocomplete="off" placeholder="密码" class="layui-input">
15
+        </div>
16
+    </div>
17
+    <div class="layui-form-item">
18
+        <label class="layui-form-label">确认密码</label>
19
+        <div class="layui-input-block">
20
+            <input type="password" name="password_confirm" lay-verify="required" autocomplete="off" placeholder="确认密码" class="layui-input">
21
+        </div>
22
+    </div>
23
+    <div class="layui-form-item">
24
+        <label class="layui-form-label">邮箱</label>
25
+        <div class="layui-input-block">
26
+            <input type="text" name="email" lay-verify="required|email" autocomplete="off" placeholder="邮箱" class="layui-input">
27
+        </div>
28
+    </div>
29
+    <div class="layui-form-item">
30
+        <label class="layui-form-label">手机</label>
31
+        <div class="layui-input-block">
32
+            <input type="text" name="mobile" autocomplete="off" placeholder="手机" class="layui-input">
33
+        </div>
34
+    </div>
35
+    <div class="layui-form-item">
36
+        <label class="layui-form-label">真实姓名</label>
37
+        <div class="layui-input-block">
38
+            <input type="text" name="nickname" autocomplete="off" placeholder="真实姓名" class="layui-input">
39
+        </div>
40
+    </div>
41
+    <div class="layui-form-item">
42
+        <label class="layui-form-label">权限组</label>
43
+        <div class="layui-input-block">
44
+             {:Form::select('roleid',$groupdata,'',["lay-verify"=>"required"])}
45
+        </div>
46
+    </div>
47
+    <div class="layui-form-item layer-footer">
48
+        <div class="layui-input-block">
49
+            <button class="layui-btn" lay-submit="">立即提交</button>
50
+            <button type="reset" class="layui-btn layui-btn-primary">重置</button>
51
+        </div>
52
+    </div>
53
+</form>
54
+{/block}
55
+{block name="script"}
56
+<script type="text/javascript">
57
+layui.use('yznForm', function() {
58
+    var yznForm = layui.yznForm;
59
+    yznForm.bindevent($("form.layui-form"));
60
+});
61
+</script>
62
+{/block}

+ 64
- 0
application/admin/view/auth/manager/edit.html Datei anzeigen

@@ -0,0 +1,64 @@
1
+{extend name="index_layout"/}
2
+{block name="main"}
3
+<form class="layui-form" method="post">
4
+    <input type="hidden" name="id" value="{$data.id}" />
5
+    {:token()}
6
+    <div class="layui-form-item">
7
+        <label class="layui-form-label">用户名</label>
8
+        <div class="layui-input-block">
9
+            <input type="text" name="username" lay-verify="required" autocomplete="off" placeholder="用户名" class="layui-input" value="{$data.username}">
10
+        </div>
11
+    </div>
12
+    <div class="layui-form-item">
13
+        <label class="layui-form-label">密码</label>
14
+        <div class="layui-input-inline">
15
+            <input type="password" name="password" autocomplete="off" placeholder="密码" class="layui-input">
16
+        </div>
17
+        <div class="layui-form-mid layui-word-aux">不修改留空即可。</div>
18
+    </div>
19
+    <div class="layui-form-item">
20
+        <label class="layui-form-label">确认密码</label>
21
+        <div class="layui-input-block">
22
+            <input type="password" name="password_confirm" autocomplete="off" placeholder="确认密码" class="layui-input">
23
+        </div>
24
+    </div>
25
+    <div class="layui-form-item">
26
+        <label class="layui-form-label">邮箱</label>
27
+        <div class="layui-input-block">
28
+            <input type="text" name="email" lay-verify="required|email" autocomplete="off" placeholder="邮箱" class="layui-input" value="{$data.email}">
29
+        </div>
30
+    </div>
31
+    <div class="layui-form-item">
32
+        <label class="layui-form-label">手机</label>
33
+        <div class="layui-input-block">
34
+            <input type="text" name="mobile" autocomplete="off" placeholder="手机" class="layui-input" value="{$data.mobile}">
35
+        </div>
36
+    </div>
37
+    <div class="layui-form-item">
38
+        <label class="layui-form-label">真实姓名</label>
39
+        <div class="layui-input-block">
40
+            <input type="text" name="nickname" autocomplete="off" placeholder="真实姓名" class="layui-input" value="{$data.nickname}">
41
+        </div>
42
+    </div>
43
+    <div class="layui-form-item">
44
+        <label class="layui-form-label">权限组</label>
45
+        <div class="layui-input-block">
46
+            {:Form::select('roleid',$groupdata,$data['roleid'],["lay-verify"=>"required"])}
47
+        </div>
48
+    </div>
49
+    <div class="layui-form-item layer-footer">
50
+        <div class="layui-input-block">
51
+            <button class="layui-btn" lay-submit="">立即提交</button>
52
+            <button type="reset" class="layui-btn layui-btn-primary">重置</button>
53
+        </div>
54
+    </div>
55
+</form>
56
+{/block}
57
+{block name="script"}
58
+<script type="text/javascript">
59
+layui.use('yznForm', function() {
60
+    var yznForm = layui.yznForm;
61
+    yznForm.bindevent($("form.layui-form"));
62
+});
63
+</script>
64
+{/block}

+ 57
- 0
application/admin/view/auth/manager/index.html Datei anzeigen

@@ -0,0 +1,57 @@
1
+{extend name="index_layout"/}
2
+{block name="main"}
3
+<div class="layui-card">
4
+    <div class="layui-card-header">管理员管理</div>
5
+    <div class="layui-card-body">
6
+        <div class="layui-form">
7
+            <table class="layui-hide" id="currentTable" lay-filter="currentTable"
8
+                data-auth-add="{:$auth->check('auth.manager/add')}" 
9
+                data-auth-edit="{:$auth->check('auth.manager/edit')}"
10
+                data-auth-delete="{:$auth->check('auth.manager/del')}"></table>
11
+        </div>
12
+    </div>
13
+</div>
14
+{/block}
15
+{block name="script"}
16
+<script type="text/javascript">
17
+layui.use('yznTable', function() {
18
+    var table = layui.yznTable;
19
+
20
+    var init = {
21
+        table_elem: '#currentTable',
22
+        table_render_id: 'currentTable',
23
+        add_url: '{:url("add")}',
24
+        edit_url: '{:url("edit")}',
25
+        delete_url: '{:url("del")}',
26
+    };
27
+
28
+    table.render({
29
+        init: init,
30
+        toolbar: ['refresh','add'],
31
+        url: '{:url("index")}',
32
+        cols: [
33
+            [
34
+                { field: 'id', width: 80, title: 'ID'},
35
+                { field: 'username', width: 80, title: '登录名', searchOp: 'like' },
36
+                { field: 'groups', width: 120, title: '所属角色', search:false},
37
+                { field: 'last_login_ip', title: '最后登录IP', searchOp: 'like'  },
38
+                { field: 'last_login_time', width: 200, title: '最后登录时间', search: 'range' },
39
+                { field: 'email',width: 200, title: '邮箱', searchOp: 'like'  },
40
+                { field: 'mobile',width: 200, title: '手机', searchOp: 'like'  },
41
+                { field: 'nickname', title: '真实姓名', searchOp: 'like'  },
42
+                { width:100, title: '操作',templet: function (d){
43
+                    if(d.id=={$userInfo.id}){
44
+                        return '<a class="layui-btn layui-btn-xs layui-btn-danger layui-btn-disabled">不可操作</a>';
45
+                    }else{
46
+                        return yznTable.formatter.tool.call(this,d,this);
47
+                    }
48
+                },operat: ['edit','delete']}
49
+            ]
50
+        ],
51
+        page: {}
52
+    });
53
+
54
+    yznTable.bindevent();
55
+});
56
+</script>
57
+{/block}

+ 136
- 0
application/admin/view/auth/rule/add.html Datei anzeigen

@@ -0,0 +1,136 @@
1
+{extend name="index_layout"/}
2
+{block name="main"}
3
+<form class="layui-form" method="post">
4
+    {:token()}
5
+    <div class="layui-form-item">
6
+        <label class="layui-form-label">菜单</label>
7
+        <div class="layui-input-block">
8
+            <input type="radio" name="row[ismenu]" value="1" title="是" checked lay-filter="ismenu">
9
+            <input type="radio" name="row[ismenu]" value="0" title="否" lay-filter="ismenu">
10
+        </div>
11
+    </div>
12
+    <div class="layui-form-item">
13
+        <label class="layui-form-label">上级菜单</label>
14
+        <div class="layui-input-block">
15
+            <select name="row[parentid]" lay-verify="required">
16
+                <option value="0">作为一级菜单</option>
17
+                {$select_categorys|raw}
18
+            </select>
19
+        </div>
20
+    </div>
21
+    <div class="layui-form-item">
22
+        <label class="layui-form-label">规则</label>
23
+        <div class="layui-input-block">
24
+            <input type="text" name="row[name]" lay-verify="required" autocomplete="off"  data-placeholder-node="控制器/方法名,如果有目录请使用 目录名.控制器名/方法名" data-placeholder-menu="父级菜单无需匹配控制器和方法,子级菜单请使用控制器名" class="layui-input">
25
+        </div>
26
+    </div>
27
+    <div class="layui-form-item">
28
+        <label class="layui-form-label">标题</label>
29
+        <div class="layui-input-block">
30
+            <input type="text" name="row[title]" lay-verify="required" autocomplete="off" placeholder="标题" class="layui-input">
31
+        </div>
32
+    </div>
33
+    <div class="layui-form-item" data-type="menu">
34
+        <label class="layui-form-label">URL</label>
35
+        <div class="layui-input-block">
36
+            <input type="text" name="row[url]" autocomplete="off" placeholder="url" class="layui-input">
37
+        </div>
38
+    </div>
39
+    <div class="layui-form-item">
40
+        <label class="layui-form-label">图标</label>
41
+        <div class="layui-input-block">
42
+            <input type="text" id="iconPicker" lay-filter="iconPicker" class="hide" name="row[icon]">
43
+        </div>
44
+    </div>
45
+    <div class="layui-form-item">
46
+        <label class="layui-form-label">规则条件</label>
47
+        <div class="layui-input-block">
48
+            <textarea name="row[condition]" placeholder="规则条件" class="layui-textarea"></textarea>
49
+        </div>
50
+    </div>
51
+    </div>
52
+    <div class="layui-form-item">
53
+        <label class="layui-form-label">菜单类型</label>
54
+        <div class="layui-input-block">
55
+            <input type="radio" name="row[menutype]" value="_iframe" title="选项卡(默认)" checked>
56
+            <input type="radio" name="row[menutype]" value="_blank" title="链接">
57
+        </div>
58
+    </div>
59
+    <div class="layui-form-item">
60
+        <label class="layui-form-label">扩展属性</label>
61
+        <div class="layui-input-block">
62
+            <textarea name="row[extend]" placeholder="扩展属性" class="layui-textarea"></textarea>
63
+        </div>
64
+    </div>
65
+    <div class="layui-form-item">
66
+        <label class="layui-form-label">备注</label>
67
+        <div class="layui-input-block">
68
+            <textarea name="row[remark]" placeholder="备注" class="layui-textarea"></textarea>
69
+        </div>
70
+    </div>
71
+    <div class="layui-form-item">
72
+        <label class="layui-form-label">显示排序</label>
73
+        <div class="layui-input-block">
74
+            <input type="text" name="row[listorder]" autocomplete="off" placeholder="显示排序" class="layui-input" value="0">
75
+        </div>
76
+    </div>
77
+    <div class="layui-form-item">
78
+        <label class="layui-form-label">是否显示</label>
79
+        <div class="layui-input-block">
80
+            <input type="radio" name="row[status]" value="1" title="显示" checked>
81
+            <input type="radio" name="row[status]" value="0" title="隐藏">
82
+        </div>
83
+    </div>
84
+    <div class="layui-form-item layer-footer">
85
+        <div class="layui-input-block">
86
+            <button class="layui-btn" lay-submit="">立即提交</button>
87
+            <button type="reset" class="layui-btn layui-btn-primary">重置</button>
88
+        </div>
89
+    </div>
90
+</form>
91
+{/block}
92
+{block name="script"}
93
+<script type="text/javascript">
94
+layui.use(['iconPicker', 'form', 'layer','yznForm'], function() {
95
+    var iconPicker = layui.iconPicker,
96
+        form = layui.form,
97
+        layer = layui.layer,
98
+        $ = layui.$,
99
+        yznForm = layui.yznForm;
100
+        yznForm.bindevent($("form.layui-form"));
101
+        iconPicker.render({
102
+            elem: '#iconPicker4',
103
+            type: 'fontClass'
104
+        });
105
+
106
+        iconPicker.render({
107
+            // 选择器,推荐使用input
108
+            elem: '#iconPicker',
109
+            // 数据类型:fontClass/unicode,推荐使用fontClass
110
+            type: 'fontClass',
111
+            // 是否开启搜索:true/false,默认true
112
+            search: true,
113
+            // 是否开启分页:true/false,默认true
114
+            page: true,
115
+            // 每页显示数量,默认12
116
+            limit: 12,
117
+            // 点击回调
118
+            click: function (data) {
119
+                //console.log(data);
120
+            },
121
+            // 渲染成功后的回调
122
+            success: function(d) {
123
+                //console.log(d);
124
+            }
125
+        });
126
+
127
+        form.on('radio(ismenu)', function(data){
128
+            var name = $("input[name='row[name]']");
129
+            var ismenu = data.value == 1;
130
+            name.prop("placeholder", ismenu ? name.data("placeholder-menu") : name.data("placeholder-node"));
131
+            $('div[data-type="menu"]').toggleClass("layui-hide", !ismenu);
132
+        })
133
+        $("input[name='row[ismenu]']:checked").next().trigger("click");
134
+});
135
+</script>
136
+{/block}

+ 136
- 0
application/admin/view/auth/rule/edit.html Datei anzeigen

@@ -0,0 +1,136 @@
1
+{extend name="index_layout"/}
2
+{block name="main"}
3
+<form class="layui-form" method="post">
4
+    {:token()}
5
+    <div class="layui-form-item">
6
+        <label class="layui-form-label">菜单</label>
7
+        <div class="layui-input-block">
8
+            <input type="radio" name="row[ismenu]" value="1" title="是" {if $data.ismenu==1}checked{/if}>
9
+            <input type="radio" name="row[ismenu]" value="0" title="否" {if $data.ismenu==0}checked{/if}>
10
+        </div>
11
+    </div>
12
+    <div class="layui-form-item">
13
+        <label class="layui-form-label">上级菜单</label>
14
+        <div class="layui-input-block">
15
+            <select name="row[parentid]" lay-verify="required">
16
+                <option value="0">作为一级菜单</option>
17
+                {$select_categorys|raw}
18
+            </select>
19
+        </div>
20
+    </div>
21
+    <div class="layui-form-item">
22
+        <label class="layui-form-label">规则</label>
23
+        <div class="layui-input-block">
24
+            <input type="text" name="row[name]" lay-verify="required" autocomplete="off"  data-placeholder-node="控制器/方法名,如果有目录请使用 目录名.控制器名/方法名" data-placeholder-menu="父级菜单无需匹配控制器和方法,子级菜单请使用控制器名" class="layui-input" value="{$data.name}">
25
+        </div>
26
+    </div>
27
+    <div class="layui-form-item">
28
+        <label class="layui-form-label">标题</label>
29
+        <div class="layui-input-block">
30
+            <input type="text" name="row[title]" lay-verify="required" autocomplete="off" placeholder="标题" class="layui-input" value="{$data.title}">
31
+        </div>
32
+    </div>
33
+    <div class="layui-form-item" data-type="menu">
34
+        <label class="layui-form-label">URL</label>
35
+        <div class="layui-input-block">
36
+            <input type="text" name="row[url]" autocomplete="off" placeholder="url" class="layui-input" value="{$data.url}">
37
+        </div>
38
+    </div>
39
+    <div class="layui-form-item">
40
+        <label class="layui-form-label">图标</label>
41
+        <div class="layui-input-block">
42
+            <input type="text" id="iconPicker" lay-filter="iconPicker" class="hide" value="{$data.icon}" name="row[icon]">
43
+        </div>
44
+    </div>
45
+    <div class="layui-form-item">
46
+        <label class="layui-form-label">规则条件</label>
47
+        <div class="layui-input-block">
48
+            <textarea name="row[condition]" placeholder="规则条件" class="layui-textarea">{$data.condition}</textarea>
49
+        </div>
50
+    </div>
51
+    <div class="layui-form-item">
52
+        <label class="layui-form-label">菜单类型</label>
53
+        <div class="layui-input-block">
54
+            <input type="radio" name="row[menutype]" value="_iframe" title="选项卡(默认)" {if $data.menutype=="iframe"}checked{/if}>
55
+            <input type="radio" name="row[menutype]" value="_blank" title="链接" {if $data.menutype=="blank"}checked{/if}>
56
+        </div>
57
+    </div>
58
+    <div class="layui-form-item">
59
+        <label class="layui-form-label">扩展属性</label>
60
+        <div class="layui-input-block">
61
+            <textarea name="row[extend]" placeholder="扩展属性" class="layui-textarea">{$data.extend}</textarea>
62
+        </div>
63
+    </div>
64
+    <div class="layui-form-item">
65
+        <label class="layui-form-label">备注</label>
66
+        <div class="layui-input-block">
67
+            <textarea name="row[remark]" placeholder="备注" class="layui-textarea">{$data.remark}</textarea>
68
+        </div>
69
+    </div>
70
+    <div class="layui-form-item">
71
+        <label class="layui-form-label">显示排序</label>
72
+        <div class="layui-input-block">
73
+            <input type="text" name="row[listorder]" autocomplete="off" placeholder="显示排序" class="layui-input" value="{$data.listorder}">
74
+        </div>
75
+    </div>
76
+    <div class="layui-form-item">
77
+        <label class="layui-form-label">是否显示</label>
78
+        <div class="layui-input-block">
79
+            <input type="radio" name="row[status]" value="1" title="显示" {if $data.status==1}checked{/if}>
80
+            <input type="radio" name="row[status]" value="0" title="隐藏" {if $data.status==0}checked{/if}>
81
+        </div>
82
+    </div>
83
+    <input type="hidden" name="row[id]" value="{$data.id}" />
84
+    <div class="layui-form-item layer-footer">
85
+        <div class="layui-input-block">
86
+            <button class="layui-btn" lay-submit="">立即提交</button>
87
+            <button type="reset" class="layui-btn layui-btn-primary">重置</button>
88
+        </div>
89
+    </div>
90
+</form>
91
+{/block}
92
+{block name="script"}
93
+<script type="text/javascript">
94
+layui.use(['iconPicker', 'form', 'layer','yznForm'], function() {
95
+    var iconPicker = layui.iconPicker,
96
+        form = layui.form,
97
+        layer = layui.layer,
98
+        $ = layui.$,
99
+        yznForm = layui.yznForm;
100
+        yznForm.bindevent($("form.layui-form"));
101
+        iconPicker.render({
102
+            elem: '#iconPicker4',
103
+            type: 'fontClass'
104
+        });
105
+
106
+        iconPicker.render({
107
+            // 选择器,推荐使用input
108
+            elem: '#iconPicker',
109
+            // 数据类型:fontClass/unicode,推荐使用fontClass
110
+            type: 'fontClass',
111
+            // 是否开启搜索:true/false,默认true
112
+            search: true,
113
+            // 是否开启分页:true/false,默认true
114
+            page: true,
115
+            // 每页显示数量,默认12
116
+            limit: 12,
117
+            // 点击回调
118
+            click: function (data) {
119
+                //console.log(data);
120
+            },
121
+            // 渲染成功后的回调
122
+            success: function(d) {
123
+                //console.log(d);
124
+            }
125
+        });
126
+
127
+        form.on('radio(ismenu)', function(data){
128
+            var name = $("input[name='row[name]']");
129
+            var ismenu = data.value == 1;
130
+            name.prop("placeholder", ismenu ? name.data("placeholder-menu") : name.data("placeholder-node"));
131
+            $('div[data-type="menu"]').toggleClass("layui-hide", !ismenu);
132
+        })
133
+        $("input[name='row[ismenu]']:checked").next().trigger("click");
134
+});
135
+</script>
136
+{/block}

+ 83
- 0
application/admin/view/auth/rule/index.html Datei anzeigen

@@ -0,0 +1,83 @@
1
+{extend name="index_layout"/}
2
+{block name="main"}
3
+<div class="layui-card">
4
+    <div class="layui-card-header">菜单管理</div>
5
+    <div class="layui-card-body">
6
+        <div class="layui-form">
7
+            <table class="layui-hide" id="currentTable" lay-filter="currentTable"></table>
8
+        </div>
9
+    </div>
10
+</div>
11
+<script type="text/html" id="toolbarDemo">
12
+    <div class="layui-table-tool-temp">
13
+        <button class="layui-btn layui-btn-sm yzn-btn-primary" data-table-refresh="currentTable"><i class="iconfont icon-shuaxin1"></i></button>
14
+        <button class="layui-btn layui-btn-sm {:$auth->check('auth.rule/add')?'':'layui-hide'}" data-open="{:url('add')}" title="添加"><i class="iconfont icon-add"></i>&nbsp;新增后台菜单</button>
15
+        <button class="layui-btn layui-btn-sm layui-btn-normal" id="openAll"><i class="iconfont icon-add"></i>&nbsp;展开或折叠全部</button>
16
+    </div>
17
+</script>
18
+<script type="text/html" id="barTool">
19
+    <a data-open='{:url("add")}?parentid={{ d.id }}' class="layui-btn layui-btn-xs layui-btn-normal {:$auth->check('auth.rule/add')?'':'layui-hide'}" title="添加">添加</a>
20
+    <a data-open='{:url("edit")}?id={{ d.id }}' class="layui-btn layui-btn-xs {:$auth->check('auth.rule/edit')?'':'layui-hide'}" title="编辑"><i class='iconfont icon-brush_fill'></i></a>
21
+    <a href='{:url("del")}?id={{ d.id }}' class="layui-btn layui-btn-danger layui-btn-xs layui-tr-del {:$auth->check('auth.rule/del')?'':'layui-hide'}"><i class='iconfont icon-trash_fill'></i></a>
22
+</script>
23
+{/block}
24
+{block name="script"}
25
+<script>
26
+layui.use(['table', 'yznTable','treeTable'], function() {
27
+    var $ = layui.$,
28
+        tableId = 'currentTable',
29
+        yznTable = layui.yznTable;
30
+     var treeTable = layui.treeTable;
31
+
32
+    var init = {
33
+        table_elem: '#currentTable',
34
+        table_render_id: 'currentTable',
35
+        modify_url:'{:url("multi")}',
36
+    };
37
+
38
+    treeTable.render({
39
+        init: init,
40
+        id: tableId,
41
+        elem: init.table_elem,
42
+        toolbar: '#toolbarDemo',
43
+        url: "{:url('index')}",
44
+        tree: {
45
+            customName: {
46
+                children:'childlist',
47
+                pid:'parentid',
48
+                name:'title'
49
+            },
50
+            view:{
51
+                showIcon:false
52
+            }
53
+        },
54
+        escape:false,
55
+        // @todo 不直接使用yznTable.render(); 进行表格初始化, 需要使用 yznTable.formatCols(); 方法格式化`cols`列数据
56
+        cols: yznTable.formatCols([
57
+            [
58
+                { field: 'listorder', width: 60, title: '排序', edit: 'text' },
59
+                { field: 'id', width: 60, title: 'ID' },
60
+                { field: 'title', align: 'left', title: '菜单名称', },
61
+                { width: 80, title: '图标', align: 'center', templet: "<div><i class='iconfont {{d.icon}}'></i></div>" },
62
+                { field: 'name', width: 200, title: '规则' },
63
+                { field: 'status', align: 'center', width: 120, title: '状态', unresize: true ,templet: yznTable.formatter.switch,tips:"显示|隐藏"},
64
+                { fixed: 'right', align: 'center', width: 140, title: '操作', toolbar: '#barTool' }
65
+            ]
66
+        ], init),
67
+    });
68
+
69
+    $('body').on('click', '#openAll', function() {
70
+        var that = this;
71
+        var show = $("i", that).hasClass("icon-add");
72
+        treeTable.expandAll(tableId, show);
73
+        $("i", that).toggleClass("icon-add", !show);
74
+        $("i", that).toggleClass("icon-min", show);
75
+    })
76
+
77
+    yznTable.listenSwitch({filter: 'status', url: init.modify_url});
78
+    yznTable.listenEdit(init, 'currentTable', init.table_render_id, false);
79
+    yznTable.bindevent();
80
+
81
+});
82
+</script>
83
+{/block}

+ 1
- 0
application/admin/view/custom/.gitkeep Datei anzeigen

@@ -0,0 +1 @@
1
+

+ 387
- 0
application/admin/view/general/attachments/cropper.html Datei anzeigen

@@ -0,0 +1,387 @@
1
+<!doctype html>
2
+<html lang="en">
3
+
4
+<head>
5
+    <meta charset="UTF-8">
6
+    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
7
+    <meta http-equiv="X-UA-Compatible" content="ie=edge">
8
+    <title>图片剪裁</title>
9
+    <link rel="stylesheet" href="__STATIC__/libs/layui/css/layui.css">
10
+    <link rel="stylesheet" href="__STATIC__/libs/cropper/cropper.min.css">
11
+    <link rel="stylesheet" href="__STATIC__/libs/cropper/main.css">
12
+    <link rel="stylesheet" href="__STATIC__/common/font/iconfont.css">
13
+    <style type="text/css">
14
+    .layui-btn-group+.layui-btn-group {margin-left: 0px;}
15
+    .layui-container {width: 100% !important;}
16
+    .modal-body {width: 300px;}
17
+    .modal-body canvas {width: 100%;}
18
+    </style>
19
+</head>
20
+
21
+<body style="padding: 10px;">
22
+    <div class="layui-container" style="overflow: hidden;">
23
+        <div class="layui-row layui-col-space10">
24
+            <div class="layui-col-md8 layui-col-sm8">
25
+                <div class="img-container">
26
+                    <img id="image" src="{$Think.get.url}" alt="Picture">
27
+                </div>
28
+            </div>
29
+            <div class="layui-col-md4 layui-col-sm4">
30
+                <div class="docs-preview clearfix">
31
+                    <div class="img-preview preview-lg"></div>
32
+                    <div class="img-preview preview-md"></div>
33
+                    <div class="img-preview preview-sm"></div>
34
+                    <div class="img-preview preview-xs"></div>
35
+                </div>
36
+                <div class="docs-data layui-form layui-form-pane">
37
+                    <div class="layui-form-item">
38
+                        <label class="layui-form-label" style="width: 60px;">宽度</label>
39
+                        <div class="layui-input-inline" style="width: 80px;">
40
+                            <input type="text" id="dataWidth" class="layui-input">
41
+                        </div>
42
+                        <div class="layui-form-mid layui-word-aux">PX</div>
43
+                    </div>
44
+                    <div class="layui-form-item">
45
+                        <label class="layui-form-label" style="width: 60px;">高度</label>
46
+                        <div class="layui-input-inline" style="width: 80px;">
47
+                            <input type="text" id="dataHeight" class="layui-input">
48
+                        </div>
49
+                        <div class="layui-form-mid layui-word-aux">PX</div>
50
+                    </div>
51
+                </div>
52
+                <div class="docs-toggles">
53
+                    <div class="layui-btn-group">
54
+                        <button type="button" class="layui-btn layui-btn-sm" data-name="aspectRatio" value="1.7777777777777777">16:9</button>
55
+                        <button type="button" class="layui-btn layui-btn-sm" data-name="aspectRatio" value="1.3333333333333333">4:3</button>
56
+                        <button type="button" class="layui-btn layui-btn-sm" data-name="aspectRatio" value="1">1:1</button>
57
+                        <button type="button" class="layui-btn layui-btn-sm" data-name="aspectRatio" value="0.6666666666666666">2:3</button>
58
+                        <button type="button" class="layui-btn layui-btn-sm" data-name="aspectRatio" value="NaN">自由</button>
59
+                    </div>
60
+                </div>
61
+            </div>
62
+        </div>
63
+        <div class="layui-row docs-buttons" style="text-align: center;">
64
+            <div class="layui-col-md10 layui-col-sm10">
65
+                <div class="layui-btn-group">
66
+                    <button type="button" class="layui-btn layui-btn-sm" data-method="setDragMode" data-option="move"><i class="iconfont icon-yidong"></i></button>
67
+                    <button type="button" class="layui-btn layui-btn-sm" data-method="setDragMode" data-option="crop"><i class="iconfont icon-tailor"></i></button>
68
+                </div>
69
+                <div class="layui-btn-group">
70
+                    <button type="button" class="layui-btn layui-btn-sm" data-method="zoom" data-option="0.1"><i class="iconfont icon-fullscreen"></i></button>
71
+                    <button type="button" class="layui-btn layui-btn-sm" data-method="zoom" data-option="-0.1"><i class="iconfont icon-narrow"></i></button>
72
+                </div>
73
+                <div class="layui-btn-group">
74
+                    <button type="button" class="layui-btn layui-btn-sm" data-method="move" data-option="-10" data-second-option="0"><i class="iconfont icon-leftarrow"></i></button>
75
+                    <button type="button" class="layui-btn layui-btn-sm" data-method="move" data-option="10" data-second-option="0"><i class="iconfont icon-Rightarrow"></i></button>
76
+                    <button type="button" class="layui-btn layui-btn-sm" data-method="move" data-option="0" data-second-option="-10"><i class="iconfont icon-rising"></i></button>
77
+                    <button type="button" class="layui-btn layui-btn-sm" data-method="move" data-option="0" data-second-option="10"><i class="iconfont icon-falling"></i></button>
78
+                </div>
79
+                <div class="layui-btn-group">
80
+                    <button type="button" class="layui-btn layui-btn-sm" data-method="rotate" data-option="-45"><i class="iconfont icon-undo-alt"></i></button>
81
+                    <button type="button" class="layui-btn layui-btn-sm" data-method="rotate" data-option="45"><i class="iconfont icon-redo-alt"></i></button>
82
+                </div>
83
+                <div class="layui-btn-group">
84
+                    <button type="button" class="layui-btn layui-btn-sm" data-method="scaleX" data-option="-1"><i class="iconfont icon-arrows-alt-h"></i></button>
85
+                    <button type="button" class="layui-btn layui-btn-sm" data-method="scaleY" data-option="-1"><i class="iconfont icon-fa-arrows-alt-v"></i></button>
86
+                </div>
87
+                <div class="layui-btn-group" style="position: relative;">
88
+                    <input type="file" class="sr-only" id="inputImage" name="file" accept=".jpg,.jpeg,.png,.gif,.bmp,.tiff" style="opacity:0;position: absolute;left: 37px;width: 37px;height: 30px; padding: 0;overflow: hidden;white-space: nowrap;border: 0;">
89
+                    <button type="button" class="layui-btn layui-btn-sm" data-method="reset"><i class="iconfont icon-shuaxin1"></i></button>
90
+                    <button type="button" class="layui-btn layui-btn-sm"><i class="iconfont icon-cloud-upload"></i></button>
91
+                    <button type="button" class="layui-btn layui-btn-sm getCroppedCanvas" data-method="getCroppedCanvas"><i class="iconfont icon-cloud-download"></i></button>
92
+                </div>
93
+            </div>
94
+            <div id="getCroppedCanvasModal" style="display: none;">
95
+                <div class="modal-content" style="padding: 20px;">
96
+                    <h5 style="font-size: 16px;line-height: 2;">已剪裁</h5>
97
+                    <div class="modal-body"></div>
98
+                    <a href="javascript:;" type="button" class="layui-btn layui-btn-sm layui-btn-fluid" id="download" download="cropped.jpg">下载图片</a>
99
+                </div>
100
+            </div>
101
+            <div class="layui-col-md2 layui-col-sm2">
102
+                <button type="button" class="layui-btn layui-btn-sm layui-btn-normal btn-submit">确定</button>
103
+                <button type="button" class="layui-btn layui-btn-sm layui-btn-danger btn-cancel">取消</button>
104
+            </div>
105
+        </div>
106
+    </div>
107
+    <script src="__STATIC__/libs/jquery/jquery.min.js"></script>
108
+    <script src="__STATIC__/libs/layui/layui.js"></script>
109
+    <script src="__STATIC__/libs/cropper/cropper.min.js"></script>
110
+    <script type="text/javascript">
111
+    layui.use(['layer', 'jquery'], function() {
112
+
113
+        var $ = layui.jquery,
114
+            layer = layui.layer;
115
+        var console = window.console || { log: function() {} };
116
+        var URL = window.URL || window.webkitURL;
117
+        var $image = $('#image');
118
+        var $download = $('#download');
119
+        /*var $dataX = $('#dataX');
120
+        var $dataY = $('#dataY');*/
121
+        var $dataHeight = $('#dataHeight');
122
+        var $dataWidth = $('#dataWidth');
123
+        /*var $dataRotate = $('#dataRotate');
124
+        var $dataScaleX = $('#dataScaleX');
125
+        var $dataScaleY = $('#dataScaleY');*/
126
+        var options = {
127
+            aspectRatio: 16 / 9,
128
+            preview: '.img-preview',
129
+            crop: function(e) {
130
+                /*$dataX.val(Math.round(e.detail.x));
131
+                $dataY.val(Math.round(e.detail.y));*/
132
+                $dataHeight.val(Math.round(e.detail.height));
133
+                $dataWidth.val(Math.round(e.detail.width));
134
+                /*$dataRotate.val(e.detail.rotate);
135
+                $dataScaleX.val(e.detail.scaleX);
136
+                $dataScaleY.val(e.detail.scaleY);*/
137
+            }
138
+        };
139
+        var originalImageURL = $image.attr('src');
140
+        var uploadedImageName = 'cropped.jpg';
141
+        var uploadedImageType = 'image/jpeg';
142
+        var uploadedImageURL;
143
+
144
+        // Cropper
145
+        $image.cropper(options);
146
+
147
+        $(document).on("click", ".getCroppedCanvas", function() {
148
+            layer.open({
149
+                type: 1,
150
+                title: false,
151
+                area: '340px',
152
+                content: $('#getCroppedCanvasModal')
153
+            });
154
+        })
155
+
156
+        function doUpload(filename, fileobj, index) {
157
+            var formData = new FormData();
158
+            formData.append(filename, fileobj);
159
+            $.ajax({
160
+                url: '{:url("admin/ajax/upload", ["dir" => "images", "module" => "admin"])}',
161
+                type: 'POST',
162
+                data: formData,
163
+                cache: false,
164
+                async: false,
165
+                contentType: false,
166
+                processData: false,
167
+                success: function(res) {
168
+                    if (res.code == 1) {
169
+                        var arr = parent.$("#layui-layer" + index).data("arr");
170
+                        var input = parent.$("#" + arr[0]);
171
+                        input.val(input.val().replace(arr[1], res.path)).attr('data-original', res.path).trigger("change");
172
+                    } else {
173
+                        console.log(res);
174
+                    }
175
+                },
176
+                error: function(res) {
177
+                    console.log(res);
178
+                }
179
+            });
180
+        }
181
+
182
+        function dataURLtoFile(dataurl) {
183
+             var arr = dataurl.split(','),
184
+                 mime = arr[0].match(/:(.*?);/)[1],
185
+                 bstr = atob(arr[1]),
186
+                 n = bstr.length,
187
+                 u8arr = new Uint8Array(n);
188
+             while (n--) {
189
+                 u8arr[n] = bstr.charCodeAt(n);
190
+             }
191
+             var urlArr = originalImageURL.split('.');
192
+             var suffix = 'png';
193
+             originalImageURL = urlArr.join('');
194
+             var filename = originalImageURL.substr(originalImageURL.lastIndexOf('/') + 1);
195
+             var exp = new RegExp("\\." + suffix + "$", "i");
196
+             filename = exp.test(filename) ? filename : filename + "." + suffix;
197
+             return new File([u8arr], filename, { type: mime });
198
+         }
199
+
200
+
201
+        $(document).on("click", ".btn-submit", function() {
202
+            var dataUrl = $image.cropper('getCroppedCanvas').toDataURL('image/png');
203
+            //console.log(dataUrl);
204
+            var index = parent.layer.getFrameIndex(window.name); //获取窗口索引
205
+            doUpload('file', dataURLtoFile(dataUrl), index);
206
+            parent.layer.close(index);
207
+        });
208
+
209
+        //取消事件
210
+        $(document).on("click", ".btn-cancel", function() {
211
+            var index = parent.layer.getFrameIndex(window.name); //获取窗口索引
212
+            parent.layer.close(index);
213
+        });
214
+
215
+        // Buttons
216
+        if (!$.isFunction(document.createElement('canvas').getContext)) {
217
+            $('button[data-method="getCroppedCanvas"]').prop('disabled', true);
218
+        }
219
+
220
+        if (typeof document.createElement('cropper').style.transition === 'undefined') {
221
+            $('button[data-method="rotate"]').prop('disabled', true);
222
+            $('button[data-method="scale"]').prop('disabled', true);
223
+        }
224
+
225
+        // Download
226
+        /*if (typeof $download[0].download === 'undefined') {
227
+            $download.addClass('disabled');
228
+        }*/
229
+
230
+        // 选择比例
231
+        $('.docs-toggles').on('click', 'button', function() {
232
+            var $this = $(this);
233
+            var name = $this.data('name');
234
+            var type = $this.prop('type');
235
+            var cropBoxData;
236
+            var canvasData;
237
+            if (!$image.data('cropper')) {
238
+                return;
239
+            }
240
+            options[name] = $this.val();
241
+            $image.cropper('destroy').cropper(options);
242
+        });
243
+
244
+        // 按钮组操作
245
+        $('.docs-buttons').on('click', '[data-method]', function() {
246
+            var $this = $(this);
247
+            var data = $this.data();
248
+            var cropper = $image.data('cropper');
249
+            var cropped;
250
+            var $target;
251
+            var result;
252
+
253
+            if ($this.prop('disabled') || $this.hasClass('disabled')) {
254
+                return;
255
+            }
256
+
257
+            if (cropper && data.method) {
258
+                data = $.extend({}, data); // Clone a new one
259
+                if (typeof data.target !== 'undefined') {
260
+                    $target = $(data.target);
261
+                    if (typeof data.option === 'undefined') {
262
+                        try {
263
+                            data.option = JSON.parse($target.val());
264
+                        } catch (e) {
265
+                            console.log(e.message);
266
+                        }
267
+                    }
268
+                }
269
+                cropped = cropper.cropped;
270
+                switch (data.method) {
271
+                    case 'rotate':
272
+                        if (cropped && options.viewMode > 0) {
273
+                            $image.cropper('clear');
274
+                        }
275
+                        break;
276
+                    case 'getCroppedCanvas':
277
+                        if (uploadedImageType === 'image/jpeg') {
278
+                            if (!data.option) {
279
+                                data.option = {};
280
+                            }
281
+                            data.option.fillColor = '#fff';
282
+                        }
283
+                        break;
284
+                }
285
+                result = $image.cropper(data.method, data.option, data.secondOption);
286
+                switch (data.method) {
287
+                    case 'rotate':
288
+                        if (cropped && options.viewMode > 0) {
289
+                            $image.cropper('crop');
290
+                        }
291
+                        break;
292
+                    case 'scaleX':
293
+                    case 'scaleY':
294
+                        $(this).data('option', -data.option);
295
+                        break;
296
+                    case 'getCroppedCanvas':
297
+                        if (result) {
298
+                            // Bootstrap's Modal
299
+                            $('#getCroppedCanvasModal').find('.modal-body').html(result);
300
+                            if (!$download.hasClass('disabled')) {
301
+                                download.download = uploadedImageName;
302
+                                $download.attr('href', result.toDataURL(uploadedImageType));
303
+                            }
304
+                        }
305
+                        break;
306
+                    case 'destroy':
307
+                        if (uploadedImageURL) {
308
+                            URL.revokeObjectURL(uploadedImageURL);
309
+                            uploadedImageURL = '';
310
+                            $image.attr('src', originalImageURL);
311
+                        }
312
+                        break;
313
+                }
314
+                if ($.isPlainObject(result) && $target) {
315
+                    try {
316
+                        $target.val(JSON.stringify(result));
317
+                    } catch (e) {
318
+                        console.log(e.message);
319
+                    }
320
+                }
321
+            }
322
+        });
323
+
324
+        // 键盘支持
325
+        $(document.body).on('keydown', function(e) {
326
+            if (e.target !== this || !$image.data('cropper') || this.scrollTop > 300) {
327
+                return;
328
+            }
329
+            switch (e.which) {
330
+                case 37:
331
+                    e.preventDefault();
332
+                    $image.cropper('move', -1, 0);
333
+                    break;
334
+                case 38:
335
+                    e.preventDefault();
336
+                    $image.cropper('move', 0, -1);
337
+                    break;
338
+                case 39:
339
+                    e.preventDefault();
340
+                    $image.cropper('move', 1, 0);
341
+                    break;
342
+                case 40:
343
+                    e.preventDefault();
344
+                    $image.cropper('move', 0, 1);
345
+                    break;
346
+            }
347
+        });
348
+
349
+        // 上传图片
350
+        var $inputImage = $('#inputImage');
351
+
352
+        if (URL) {
353
+            $inputImage.change(function() {
354
+                var files = this.files;
355
+                var file;
356
+
357
+                if (!$image.data('cropper')) {
358
+                    return;
359
+                }
360
+
361
+                if (files && files.length) {
362
+                    file = files[0];
363
+
364
+                    if (/^image\/\w+$/.test(file.type)) {
365
+                        uploadedImageName = file.name;
366
+                        uploadedImageType = file.type;
367
+
368
+                        if (uploadedImageURL) {
369
+                            URL.revokeObjectURL(uploadedImageURL);
370
+                        }
371
+
372
+                        uploadedImageURL = URL.createObjectURL(file);
373
+                        $image.cropper('destroy').attr('src', uploadedImageURL).cropper(options);
374
+                        $inputImage.val('');
375
+                    } else {
376
+                        window.alert('Please choose an image file.');
377
+                    }
378
+                }
379
+            });
380
+        } else {
381
+            $inputImage.prop('disabled', true).parent().addClass('disabled');
382
+        }
383
+    })
384
+    </script>
385
+</body>
386
+
387
+</html>

+ 55
- 0
application/admin/view/general/attachments/index.html Datei anzeigen

@@ -0,0 +1,55 @@
1
+{extend name="admin@index_layout"/}
2
+{block name="main"}
3
+<div class="layui-card">
4
+    <div class="layui-card-header">附件管理</div>
5
+    <div class="layui-card-body">
6
+        <table class="layui-hide" id="currentTable" lay-filter="currentTable" data-auth-delete="{:$auth->check('general.attachments/del')}"></table>
7
+    </div>
8
+</div>
9
+<script type="text/html" id="picTpl">
10
+  {{#  if(d.mime.indexOf("image") > -1){ }}
11
+    <img style="max-width:80px; max-height:30px;" src="{{d.path}}" data-image="{{d.name}}">
12
+  {{#  } else { }}
13
+    <img style="max-width:80px; max-height:30px;" src="{:url('admin/ajax/icon')}?suffix={{d.ext}}">
14
+  {{#  } }}
15
+</script>
16
+{/block}
17
+{block name="script"}
18
+<script>
19
+layui.use('yznTable', function() {
20
+    var table = layui.yznTable;
21
+
22
+    var init = {
23
+        table_elem: '#currentTable',
24
+        table_render_id: 'currentTable',
25
+        delete_url: '{:url("del")}',
26
+    };
27
+
28
+    table.render({
29
+        init: init,
30
+        elem: init.table_elem,
31
+        toolbar: ['refresh','delete'],
32
+        url: '{:url("index")}',
33
+        cols: [
34
+            [
35
+                { type: 'checkbox', fixed: 'left' },
36
+                { field: 'id', width: 80, title: 'ID', sort: true },
37
+                { field: 'admin_id', width: 80, title: '用户',hide:true,addClass: "selectpage", extend: "data-source='admin/auth.manager/index' data-field='username'" },
38
+                { field: 'name', title: '名称', searchOp: 'like' },
39
+                { field: 'path', width: 100, align: "center",title: '图片', search: false, templet: '#picTpl' },
40
+                { field: 'path', width: 450, align: "center", title: '物理路径', templet: '<div><a class="layui-btn layui-btn layui-btn-xs" href="{{d.path}}" target="_blank">{{d.path}}</a></div>', searchOp: 'like' },
41
+                { field: 'size', width: 100, title: '大小', sort: true },
42
+                { field: 'ext', width: 100, title: '类型', searchOp: 'like' },
43
+                { field: 'mime', width: 120, title: 'Mime类型' ,selectList:{'image/*':'图片','audio/*':'音频','video/*':'视频','text/*':'文档','application/*':'应用','zip,rar,7z,tar':'压缩包'},extend:"lay-search lay-creatable"},
44
+                { field: 'driver', width: 100, title: '存储引擎', searchOp: 'like' },
45
+                { field: 'create_time', width: 180, title: '上传时间', search: 'range' },
46
+                { width:60, title: '操作',templet: yznTable.formatter.tool,operat: ['delete']}
47
+            ]
48
+        ],
49
+        page: {}
50
+    });
51
+
52
+    yznTable.bindevent();
53
+});
54
+</script>
55
+{/block}

+ 90
- 0
application/admin/view/general/attachments/select.html Datei anzeigen

@@ -0,0 +1,90 @@
1
+{extend name="admin@index_layout"/}
2
+{block name="main"}
3
+<table class="layui-hide" id="dataTable" lay-filter="dataTable"></table>
4
+<script type="text/html" id="barTool">
5
+    <a data-href='{{d.path}}' class="layui-btn layui-btn-danger layui-btn-xs btn-chooseone"><i class="iconfont icon-right"></i> 选择</a>
6
+</script>
7
+<script type="text/html" id="picTpl">
8
+    {{#  if(d.mime.indexOf("image") > -1){ }}
9
+    <img style="max-width:80px; max-height:30px;" src="{{d.path}}" data-image="{{d.name}}">
10
+  {{#  } else { }}
11
+    <img style="max-width:80px; max-height:30px;" src="{:url('admin/ajax/icon')}?suffix={{d.ext}}">
12
+  {{#  } }}
13
+</script>
14
+{/block}
15
+{block name="script"}
16
+<script>
17
+layui.use(['yznTable','yzn','table','yznForm','yznUpload'], function() {
18
+    var yznTable = layui.yznTable,
19
+        yzn = layui.yzn,
20
+        yznForm = layui.yznForm,
21
+        yznUpload = layui.yznUpload,
22
+        table =layui.table;
23
+    var multiple = {$Think.get.multiple};
24
+    var urlArr = [];
25
+
26
+    var init = {};
27
+
28
+    yznTable.render({
29
+        init: init,
30
+        id: 'dataTable',
31
+        toolbar: ['refresh'
32
+        , [{
33
+            html: '<button type="button" class="layui-btn layui-btn-sm faupload" data-multiple="false" data-type="{$mimetype}"><i class="layui-icon layui-icon-upload"></i> 上传</button>'
34
+        }]
35
+        {if $Think.get.multiple == "true"}
36
+        , [{
37
+            html: '<a class="layui-btn layui-btn-danger layui-btn-sm btn-choose-multi"><i class="iconfont icon-right"></i> 选择</a>'
38
+        }]
39
+        {/if}
40
+        ],
41
+        elem: '#dataTable',
42
+        url: '{:url("index")}',
43
+        cols: [
44
+            [
45
+                {if $Think.get.multiple == "true"}
46
+                { type: 'checkbox', fixed: 'left' },
47
+                {/if}
48
+                { field: 'id', width: 60, title: 'ID', sort: true },
49
+                { field: 'name', title: '名称' },
50
+                { field: 'path', width: 100, align: "center", title: '图片', search: false, templet: '#picTpl' },
51
+                { field: 'size', width: 100, title: '大小', sort: true },
52
+                { field: 'mime', width: 120, title: 'Mime类型' },
53
+                { field: 'create_time', width: 160, title: '上传时间', search: 'range' },
54
+                { fixed: 'right', width: 85, title: '操作', toolbar: '#barTool' }
55
+            ]
56
+        ],
57
+        page: {},
58
+        done: function(res, curr, count){
59
+            yznUpload.api.upload('.faupload', function () {
60
+                $("[data-table-refresh]").trigger("click");
61
+            });
62
+        }
63
+    });
64
+
65
+    yznTable.bindevent();
66
+
67
+    table.on('checkbox(dataTable_LayFilter)', function(obj){
68
+        urlArr = [];
69
+        var checkStatus = table.checkStatus('dataTable').data;
70
+        for (var i = 0; i < checkStatus.length; i++) {
71
+            urlArr.push(checkStatus[i]['path']);
72
+        }
73
+    });
74
+
75
+    //选择单个
76
+    $(document).on('click', '.btn-chooseone', function() {
77
+        var that = $(this),
78
+        index = that.parents('tr').eq(0).data('index'),
79
+        tr = $('.layui-table-body').find('tr[data-index="' + index + '"]'),
80
+        href = !that.attr('data-href') ? that.attr('href') : that.attr('data-href');
81
+        yzn.close({ url: href, multiple: multiple});
82
+    });
83
+
84
+    // 选中多个 todo翻页失效
85
+    $(document).on("click", ".btn-choose-multi", function () {
86
+        yzn.close({ url: urlArr.join(","), multiple: multiple});
87
+    });
88
+});
89
+</script>
90
+{/block}

+ 165
- 0
application/admin/view/general/config/add.html Datei anzeigen

@@ -0,0 +1,165 @@
1
+{extend name="index_layout"/}
2
+{block name="main"}
3
+<form class="layui-form" method="post">
4
+    {:token()}
5
+    <div class="layui-form-item">
6
+        <label class="layui-form-label">分组</label>
7
+        <div class="layui-input-block">
8
+            <select name="row[group]" lay-verify="required">
9
+                <option value=""></option>
10
+                {volist name="groupArray" id="vo"}
11
+                <option value="{$key}" {eq name="key" value="$groupType"}selected{/eq}>{$vo}[{$key}]</option>
12
+                {/volist}
13
+            </select>
14
+        </div>
15
+    </div>
16
+    <div class="layui-form-item">
17
+        <label class="layui-form-label">配置类型</label>
18
+        <div class="layui-input-block">
19
+            <select name="row[type]" lay-filter="fieldType" lay-verify="required">
20
+                <option value=""></option>
21
+                {volist name="fieldType" id="vo"}
22
+                <option value="{$vo.name}" {eq name="vo.name" value="text"}selected{/eq}>{$vo.title}[{$vo.name}]</option>
23
+                {/volist}
24
+            </select>
25
+        </div>
26
+    </div>
27
+    <div class="layui-form-item">
28
+        <label class="layui-form-label">配置标题</label>
29
+        <div class="layui-input-inline">
30
+            <input type="text" name="row[title]" lay-verify="required" autocomplete="off" placeholder="字段中文标题" class="layui-input">
31
+        </div>
32
+        <div class="layui-form-mid layui-word-aux">一般由中文组成,仅用于显示</div>
33
+    </div>
34
+    <div class="layui-form-item">
35
+        <label class="layui-form-label">配置名称</label>
36
+        <div class="layui-input-block">
37
+            <input type="text" name="row[name]" lay-verify="required" autocomplete="off" placeholder="字段英文名称" class="layui-input">
38
+        </div>
39
+        <div class="layui-form-mid no-float layui-word-aux">英文字母和下划线组成,如 <code>web_site_title</code></div>
40
+    </div>
41
+    <div class="layui-form-item layui-form-text">
42
+        <label class="layui-form-label">配置默认值</label>
43
+        <div class="layui-input-block">
44
+            <textarea name="row[value]" placeholder="未指定值时默认插入字段的值" class="layui-textarea"></textarea>
45
+        </div>
46
+        <div class="layui-form-mid no-float layui-word-aux">配置类型为<code>数组</code>时请按如下格式填写:
47
+            <br>{"键名1":"键值1","键名2":"键值2"}</div>
48
+    </div>
49
+    <div class="layui-form-item layui-form-text" id="options" style="display: none;">
50
+    </div>
51
+    <div class="layui-form-item">
52
+        <label class="layui-form-label">可见条件</label>
53
+        <div class="layui-input-inline">
54
+            <input type="text" name="row[visible]" autocomplete="off" placeholder="可见条件" class="layui-input">
55
+        </div>
56
+    </div>
57
+    <div class="layui-form-item layui-form-text">
58
+        <label class="layui-form-label">拓展属性</label>
59
+        <div class="layui-input-block">
60
+            <textarea name="row[extend]" placeholder="填写拓展属性" class="layui-textarea"></textarea>
61
+        </div>
62
+        <div class="layui-form-mid no-float layui-word-aux">如:data-field="{name}",扩展属性支持{id}、{name}、{group}、{title}、{value}替换</div>
63
+    </div>
64
+    <div class="layui-form-item layui-form-text">
65
+        <label class="layui-form-label">配置备注</label>
66
+        <div class="layui-input-block">
67
+            <textarea name="row[remark]" placeholder="填写配置说明" class="layui-textarea"></textarea>
68
+        </div>
69
+    </div>
70
+    <div class="layui-form-item">
71
+        <label class="layui-form-label">排序</label>
72
+        <div class="layui-input-block">
73
+            <input type="number" name="row[listorder]" autocomplete="off" placeholder="只能是正整数" class="layui-input" value="100">
74
+        </div>
75
+    </div>
76
+    <div class="layui-form-item">
77
+        <label class="layui-form-label">状态</label>
78
+            <div class="layui-input-block">
79
+                <input type="radio" name="row[status]" value="1" title="显示" checked>
80
+                <input type="radio" name="row[status]" value="0" title="隐藏">
81
+            </div>
82
+    </div>
83
+    <div class="layui-form-item layer-footer">
84
+        <div class="layui-input-block">
85
+            <button class="layui-btn" lay-submit="">立即提交</button>
86
+            <button type="reset" class="layui-btn layui-btn-primary">重置</button>
87
+        </div>
88
+    </div>
89
+</form>
90
+<div id="options1" style="display: none;">
91
+    <label class="layui-form-label">配置项</label>
92
+    <div class="layui-input-block">
93
+        <textarea name="row[options]" placeholder="键值:键名
94
+键值:键名
95
+键值:键名
96
+....." class="layui-textarea"></textarea>
97
+    </div>
98
+    <div class="layui-form-mid no-float layui-word-aux">如:
99
+        <br>1:北京
100
+        <br>2:上海
101
+        <br>3:广东
102
+    </div>
103
+</div>
104
+<div id="options2" style="display: none;">
105
+    <label class="layui-form-label">配置项</label>
106
+    <div class="layui-input-block">
107
+        <textarea name="row[options]" placeholder="url:提供数据源的URL地址
108
+field:列表显示读取的字段,默认[name]
109
+key:列表选中后渲染的字段,默认[id]
110
+pagination:是否开启分页,默认[true]
111
+limit:分页大小,默认[10]
112
+multiple:是否支持多选,默认[false]
113
+max:最多可选择数量
114
+order:排序字段,默认[id]" class="layui-textarea"></textarea>
115
+    </div>
116
+    <div class="layui-form-mid no-float layui-word-aux">如:
117
+<br>url:admin/auth.manager/index
118
+<br>field:username
119
+<br>key:id
120
+<br>pagination:true
121
+<br>limit:10
122
+<br>multiple:true
123
+<br>max:20
124
+<br>order:id
125
+    </div>
126
+</div>
127
+<div id="options3" style="display: none;">
128
+    <label class="layui-form-label">自定义模板</label>
129
+    <div class="layui-input-block">
130
+        <select name="row[options]">
131
+            <option value="custom.html" selected>默认模板:custom.html</option>
132
+            {volist name="custom" id="vo"}
133
+            <option value="{$vo}">{$vo}</option>
134
+            {/volist}
135
+        </select>
136
+    </div>
137
+    <div class="layui-form-mid no-float layui-word-aux">新增模板以custom_xx.html形式<br>模板位于application/admin/view/custom目录下</div>
138
+</div>
139
+{/block}
140
+{block name="script"}
141
+<script type="text/javascript">
142
+layui.use(['form','yznForm'], function() {
143
+    var form = layui.form,
144
+    yznForm = layui.yznForm;
145
+
146
+    form.on('select(fieldType)', function(data) {
147
+        if (data.value == 'checkbox' || data.value == 'select' || data.value == 'selects' || data.value == 'radio' || data.value == 'selectpage' || data.value == 'custom') {
148
+            if (data.value == 'selectpage') {
149
+                $('#options').html($('#options2').html());
150
+            }else if(data.value == 'custom'){
151
+                $('#options').html($('#options3').html()); 
152
+            }else{
153
+                $('#options').html($('#options1').html());   
154
+            }
155
+            $('#options').show();
156
+            form.render();
157
+        }else{
158
+            $('#options').hide();
159
+        }
160
+    });
161
+
162
+    yznForm.bindevent($("form.layui-form"));
163
+});
164
+</script>
165
+{/block}

+ 207
- 0
application/admin/view/general/config/edit.html Datei anzeigen

@@ -0,0 +1,207 @@
1
+{extend name="index_layout"/}
2
+{block name="main"}
3
+<form class="layui-form" method="post">
4
+    {:token()}
5
+    <div class="layui-form-item">
6
+        <label class="layui-form-label">分组</label>
7
+        <div class="layui-input-block">
8
+            <select name="row[group]" lay-verify="required">
9
+                <option value=""></option>
10
+                {volist name="groupArray" id="vo"}
11
+                <option value="{$key}" {if $data.group==$key}selected{/if}>{$vo}[{$key}]</option>
12
+                {/volist}
13
+            </select>
14
+        </div>
15
+    </div>
16
+    <div class="layui-form-item">
17
+        <label class="layui-form-label">配置类型</label>
18
+        <div class="layui-input-block">
19
+            <select name="row[type]" lay-filter="fieldType" lay-verify="required">
20
+                <option value=""></option>
21
+                {volist name="fieldType" id="vo"}
22
+                <option value="{$vo.name}" {if $data.type==$vo.name}selected{/if}>{$vo.title}[{$vo.name}]</option>
23
+                {/volist}
24
+            </select>
25
+        </div>
26
+    </div>
27
+    <div class="layui-form-item">
28
+        <label class="layui-form-label">配置标题</label>
29
+        <div class="layui-input-inline">
30
+            <input type="text" name="row[title]" lay-verify="required" value="{$data.title}" autocomplete="off" placeholder="字段中文标题" class="layui-input">
31
+        </div>
32
+        <div class="layui-form-mid layui-word-aux">一般由中文组成,仅用于显示</div>
33
+    </div>
34
+    <div class="layui-form-item">
35
+        <label class="layui-form-label">配置名称</label>
36
+        <div class="layui-input-block">
37
+            <input type="text" name="row[name]" lay-verify="required" value="{$data.name}" autocomplete="off" placeholder="字段英文名称" class="layui-input">
38
+        </div>
39
+        <div class="layui-form-mid no-float layui-word-aux">英文字母和下划线组成,如 <code>web_site_title</code></div>
40
+    </div>
41
+    <div class="layui-form-item layui-form-text">
42
+        <label class="layui-form-label">配置默认值</label>
43
+        <div class="layui-input-block">
44
+            <textarea name="row[value]" placeholder="未指定值时默认插入字段的值" class="layui-textarea">{$data.value}</textarea>
45
+        </div>
46
+        <div class="layui-form-mid no-float layui-word-aux">配置类型为<code>数组</code>时请按如下格式填写:
47
+            <br>{"键名1":"键值1","键名2":"键值2"}</div>
48
+    </div>
49
+    <div class="layui-form-item layui-form-text" id="options" {if $data.type !== 'custom' && $data.type !== 'checkbox' &&  $data.type !== 'select' &&  $data.type !== 'selects' && $data.type !== 'radio' && $data.type !== 'selectpage' } style="display: none;" {/if}>
50
+        {if $data.type == 'custom'}
51
+        <label class="layui-form-label">自定义模板</label>
52
+        <div class="layui-input-block">
53
+            <select name="row[options]">
54
+                <option value="custom.html">默认模板:custom.html</option>
55
+                {volist name="custom" id="vo"}
56
+                <option value="{$vo}" {if $data.options==$vo}selected{/if}>{$vo}</option>
57
+                {/volist}
58
+            </select>
59
+        </div>
60
+        <div class="layui-form-mid no-float layui-word-aux">新增模板以custom_xx.html形式<br>模板位于application/admin/view/custom目录下</div>
61
+        {else}
62
+        <label class="layui-form-label">配置项</label>
63
+        <div class="layui-input-block">
64
+            <textarea name="row[options]" placeholder="{if $data.type !== 'selectpage'}
65
+键值:键名
66
+键值:键名
67
+键值:键名
68
+.....
69
+{else}
70
+url:提供数据源的URL地址
71
+field:列表显示读取的字段,默认[name]
72
+key:列表选中后渲染的字段,默认[id]
73
+pagination:是否开启分页,默认[true]
74
+limit:分页大小,默认[10]
75
+multiple:是否支持多选,默认[false]
76
+max:最多可选择数量
77
+order:排序字段,默认[id]
78
+{/if}
79
+" class="layui-textarea">{$data.options}</textarea>
80
+        </div>
81
+        <div class="layui-form-mid no-float layui-word-aux">如:
82
+            {if $data.type !== 'selectpage'}
83
+<br>1:北京
84
+<br>2:上海
85
+<br>3:广东
86
+            {else}
87
+<br>url:admin/auth.manager/index
88
+<br>field:username
89
+<br>key:id
90
+<br>pagination:true
91
+<br>limit:10
92
+<br>multiple:true
93
+<br>max:20
94
+<br>order:id
95
+            {/if}
96
+        </div>
97
+        {/if}
98
+    </div>
99
+    <div class="layui-form-item">
100
+        <label class="layui-form-label">可见条件</label>
101
+        <div class="layui-input-inline">
102
+            <input type="text" name="row[visible]" autocomplete="off" placeholder="可见条件" class="layui-input" value="{$data.visible}">
103
+        </div>
104
+    </div>
105
+    <div class="layui-form-item layui-form-text">
106
+        <label class="layui-form-label">拓展属性</label>
107
+        <div class="layui-input-block">
108
+            <textarea name="row[extend]" placeholder="填写拓展属性" class="layui-textarea">{$data.extend}</textarea>
109
+        </div>
110
+        <div class="layui-form-mid no-float layui-word-aux">如:data-field="{name}",扩展属性支持{id}、{name}、{group}、{title}、{value}替换</div>
111
+    </div>
112
+    <div class="layui-form-item layui-form-text">
113
+        <label class="layui-form-label">配置备注</label>
114
+        <div class="layui-input-block">
115
+            <textarea name="row[remark]" placeholder="填写配置说明" class="layui-textarea">{$data.remark}</textarea>
116
+        </div>
117
+    </div>
118
+    <div class="layui-form-item">
119
+        <label class="layui-form-label">排序</label>
120
+        <div class="layui-input-block">
121
+            <input type="number" name="row[listorder]" autocomplete="off" placeholder="只能是正整数" class="layui-input" value="{$data.listorder}">
122
+        </div>
123
+    </div>
124
+    <input type="hidden" name="row[id]" value="{$id}">
125
+    <div class="layui-form-item layer-footer">
126
+        <div class="layui-input-block">
127
+            <button class="layui-btn" lay-submit="">立即提交</button>
128
+            <button type="reset" class="layui-btn layui-btn-primary">重置</button>
129
+        </div>
130
+    </div>
131
+</form>
132
+<div id="options1" style="display: none;">
133
+    <label class="layui-form-label">配置项</label>
134
+    <div class="layui-input-block">
135
+        <textarea name="row[options]" placeholder="键值:键名
136
+键值:键名
137
+键值:键名
138
+....." class="layui-textarea"></textarea>
139
+    </div>
140
+    <div class="layui-form-mid no-float layui-word-aux">如:
141
+        <br>1:北京
142
+        <br>2:上海
143
+        <br>3:广东
144
+    </div>
145
+</div>
146
+<div id="options2" style="display: none;">
147
+    <label class="layui-form-label">配置项</label>
148
+    <div class="layui-input-block">
149
+        <textarea name="row[options]" placeholder="url:提供数据源的URL地址
150
+field:列表显示读取的字段,默认[name]
151
+key:列表选中后渲染的字段,默认[id]
152
+pagination:是否开启分页,默认[true]
153
+limit:分页大小,默认[10]
154
+multiple:是否支持多选,默认[false]
155
+max:最多可选择数量
156
+order:排序字段,默认[id]" class="layui-textarea"></textarea>
157
+    </div>
158
+    <div class="layui-form-mid no-float layui-word-aux">如:
159
+<br>url:admin/auth.manager/index
160
+<br>field:username
161
+<br>key:id
162
+<br>pagination:true
163
+<br>limit:10
164
+<br>multiple:true
165
+<br>max:20
166
+<br>order:id
167
+    </div>
168
+</div>
169
+<div id="options3" style="display: none;">
170
+    <label class="layui-form-label">自定义模板</label>
171
+    <div class="layui-input-block">
172
+        <select name="row[options]">
173
+            <option value="custom.html" selected>默认模板:custom.html</option>
174
+            {volist name="custom" id="vo"}
175
+            <option value="{$vo}">{$vo}</option>
176
+            {/volist}
177
+        </select>
178
+    </div>
179
+    <div class="layui-form-mid no-float layui-word-aux">新增模板以custom_xx.html形式<br>模板位于application/admin/view/custom目录下</div>
180
+</div>
181
+{/block}
182
+{block name="script"}
183
+<script type="text/javascript">
184
+layui.use(['form','yznForm'], function() {
185
+    var form = layui.form,
186
+    yznForm = layui.yznForm;
187
+
188
+    form.on('select(fieldType)', function(data) {
189
+        if (data.value == 'checkbox' || data.value == 'select' || data.value == 'selects' || data.value == 'radio' || data.value == 'selectpage' || data.value == 'custom') {
190
+            if (data.value == 'selectpage') {
191
+                $('#options').html($('#options2').html());
192
+            }else if(data.value == 'custom'){
193
+                $('#options').html($('#options3').html());   
194
+            }else{
195
+                $('#options').html($('#options1').html());   
196
+            }
197
+            $('#options').show();
198
+            form.render();
199
+        }else{
200
+            $('#options').hide();
201
+        }
202
+    });
203
+
204
+    yznForm.bindevent($("form.layui-form"));
205
+});
206
+</script>
207
+{/block}

+ 73
- 0
application/admin/view/general/config/index.html Datei anzeigen

@@ -0,0 +1,73 @@
1
+{extend name="index_layout"/}
2
+{block name="main"}
3
+<div class="layui-card">
4
+    <div class="layui-card-body">
5
+        <div class="layui-tab layui-tab-card">
6
+            <ul class="layui-tab-title">
7
+                {volist name="$groupArray" id="vo"}
8
+                <li class="{if $key==$group}layui-this{/if}"><a href="{:url('index',['group'=>$key])}">{$vo}</a></li>
9
+                {/volist}
10
+            </ul>
11
+            <div class="layui-tab-content">
12
+                <table class="layui-hide" id="currentTable" lay-filter="currentTable" data-auth-add="{:$auth->check('general.config/add')}"></table>
13
+            </div>
14
+        </div>
15
+    </div>
16
+</div>
17
+<script type="text/html" id="barTool">
18
+    {{# if(d.type=='radio' || d.type=='select'){ }}
19
+    <a href='javascript:;' class="layui-btn layui-btn-xs copy" data-clipboard-text="{literal}键:{:config('site.{{d.name}}')} 值:{:config('site.{{d.name}}_text')}{/literal}">代码调用</a>
20
+    {{# } else if(d.type=='checkbox' || d.type=='array' || d.type=='images' || d.type=='files' || d.type == 'selects'){ }}
21
+    <a href='javascript:;' class="layui-btn layui-btn-xs copy" data-clipboard-text="{literal}{volist name=&quot;:config('site.{{d.name}}')&quot; id='vo'}  键:{$key}  值:{$vo} <br> {/volist}{/literal}">代码调用</a>
22
+    {{# } else { }}
23
+    <a href='javascript:;' class="layui-btn layui-btn-xs copy" data-clipboard-text="{literal}{:config('site.{{d.name}}')}{/literal}">代码调用</a>
24
+    {{# } }}
25
+    <a data-open='{:url("edit")}?id={{ d.id }}' title="编辑" class="layui-btn layui-btn-xs {:$auth->check('general.config/edit')?'':'layui-hide'}"><i class='iconfont icon-brush_fill'></i></a>
26
+    <a href='{:url("del")}?id={{ d.id }}' class="layui-btn layui-btn-danger layui-btn-xs layui-tr-del {:$auth->check('general.config/del')?'':'layui-hide'}"><i class='iconfont icon-trash_fill'></i></a>
27
+</script>
28
+{/block}
29
+{block name="script"}
30
+<script>
31
+layui.use(['yznTable','clipboard'], function() {
32
+    var table = layui.yznTable,
33
+        $ = layui.$,
34
+        form = layui.form,
35
+        clipboard =  layui.clipboard;
36
+
37
+    var init = {
38
+        table_elem: '#currentTable',
39
+        table_render_id: 'currentTable',
40
+        add_url: "{:url('add',['groupType'=>$group])}",
41
+        modify_url:'{:url("multi")}',
42
+    };
43
+
44
+    table.render({
45
+        init: init,
46
+        toolbar: ['refresh','add'],
47
+        url: '{:url("index",["group"=>$group])}',
48
+        search:false,
49
+        cols: [
50
+            [
51
+                { field: 'listorder', width: 70, title: '排序', edit: 'text' },
52
+                { field: 'name',align: "left", title: '名称' },
53
+                { field: 'title',align: "left", title: '标题' },
54
+                { field: 'ftitle', width: 150, title: '类型' },
55
+                { field: 'update_time', width: 200, title: '更新时间'},
56
+                { field: 'status', title: '状态', width: 100, unresize: true,templet: yznTable.formatter.switch },
57
+                { fixed: 'right', width: 160, title: '操作', toolbar: '#barTool' }
58
+            ]
59
+        ],
60
+    });
61
+
62
+    yznTable.bindevent();
63
+
64
+    var clipboard = new clipboard('.copy');
65
+    clipboard.on('success', function(e) {
66
+        layer.msg("复制成功",{ icon: 1});
67
+    });
68
+    clipboard.on('error', function(e) {
69
+        layer.msg("复制失败!请手动调用",{ icon: 2});
70
+    });
71
+});
72
+</script>
73
+{/block}

+ 208
- 0
application/admin/view/general/config/setting.html Datei anzeigen

@@ -0,0 +1,208 @@
1
+{extend name="index_layout"/}
2
+{block name="main"}
3
+<div class="layui-card">
4
+    <div class="layui-card-body">
5
+        <div class="layui-tab layui-tab-card">
6
+            <ul class="layui-tab-title">
7
+                {volist name="groupArray" id="vo"}
8
+                <li class="{if $key==$group}layui-this{/if}"><a href="{:url('setting',['group'=>$key])}">{$vo}</a></li>
9
+                {/volist}
10
+            </ul>
11
+            <div class="layui-tab-content">
12
+                <form class="layui-form" method="post">
13
+                    {:token()}
14
+
15
+                    {volist name="fieldList" id="vo"}
16
+                    <div data-favisible="{$vo.visible|default=''}">
17
+                        <div class="layui-form-item">
18
+                            <label class="layui-form-label {if condition="isset($vo.ifrequire) AND $vo.ifrequire"}layui-form-item-required{/if}">{$vo.title}</label>
19
+                            {switch name="vo.type"}
20
+                                {case value="text"}
21
+                                <div class="layui-input-block">
22
+                                    <input {$vo.extend_html|raw} type="text" name="{$vo.fieldArr}[{$vo.name}]" placeholder="请输入{$vo.title}" autocomplete="off" class="layui-input" value="{$vo.value}">
23
+                                </div>
24
+                                {/case}
25
+
26
+                                {case value="password"}
27
+                                <div class="layui-input-block">
28
+                                    <input {$vo.extend_html|raw} type="password" name="{$vo.fieldArr}[{$vo.name}]" placeholder="请输入{$vo.title}" autocomplete="off" class="layui-input" value="{$vo.value}" lay-affix="eye">
29
+                                </div>
30
+                                {/case}
31
+
32
+                                {case value="tags"}
33
+                                <div class="layui-input-block">
34
+                                    <input {$vo.extend_html|raw} type="text" name="{$vo.fieldArr}[{$vo.name}]" class="layui-input form-tags tags-{$vo.name}" value="{$vo.value}" data-remark="{$vo.remark|default='输入回车键确认'}">
35
+                                </div>
36
+                                {/case}
37
+
38
+                                {case value="number"}
39
+                                <div class="layui-input-block">
40
+                                    <input {$vo.extend_html|raw} type="number" name="{$vo.fieldArr}[{$vo.name}]" placeholder="请输入{$vo.title}" autocomplete="off" lay-affix="number" class="layui-input" value="{$vo.value}">
41
+                                </div>
42
+                                {/case}
43
+
44
+                                {case value="switch"}
45
+                                <div class="layui-input-block">
46
+                                    <input type="checkbox" name="{$vo.fieldArr}[{$vo.name}]" lay-skin="switch" lay-text="ON|OFF" value="{$vo['value']}" {if 1==$vo[ 'value' ]}checked='' {/if}>
47
+                                </div>
48
+                                {/case}
49
+
50
+                                {case value="array"}
51
+                                <dl {$vo.extend_html|raw} class="layui-input-block fieldlist" data-name="{$vo.fieldArr}[{$vo.name}]" data-id="{$vo.name}">
52
+                                    <dd>
53
+                                        <ins>键名</ins>
54
+                                        <ins>键值</ins>
55
+                                    </dd>
56
+                                    <dd><button type="button" class="layui-btn btn-append">追加</button></dd>
57
+                                    <textarea name="{$vo.fieldArr}[{$vo.name}]" class="layui-textarea layui-hide">{$vo.value}</textarea>
58
+                                </dl>
59
+                                <script type="text/html" id="{$vo.name}Tpl">
60
+                                    <dd class="layui-form-item rules-item">
61
+                                        {{# layui.each(d.lists, function(index, item) { }}
62
+                                        <input type="text" class="layui-input" name="{{item.name}}[{{item.index}}][key]" placeholder="键" value="{{item.key|| ''}}" />
63
+                                        <input type="text" class="layui-input" name="{{item.name}}[{{item.index}}][value]" placeholder="值" value="{{item.value|| ''}}" />
64
+                                        <button type="button" class="layui-btn layui-btn-danger btn-remove layui-btn-xs"><i class="iconfont icon-close"></i></button>
65
+                                        <button type="button" class="layui-btn btn-dragsort layui-btn-xs"><i class="iconfont icon-yidong"></i></button>
66
+                                        {{# }); }}
67
+                                    </dd>
68
+                                </script>
69
+                                {/case}
70
+
71
+                                {case value="checkbox"}
72
+                                <div class="layui-input-block">
73
+                                    {volist name="vo.options" id="v"}
74
+                                    <input  type="checkbox" name="{$vo.fieldArr}[{$vo.name}][]" lay-skin="primary" title="{$v}" value="{$key}" {if in_array($key,$vo[ 'value' ])}checked{/if}>
75
+                                    {/volist}
76
+                                </div>
77
+                                {/case}
78
+
79
+                                {case value="radio"}
80
+                                <div class="layui-input-block">
81
+                                    {volist name="vo.options" id="v"}
82
+                                    <input type="radio" name="{$vo.fieldArr}[{$vo.name}]" value="{$key}" title="{$v}" {if $key==$vo [ 'value' ]}checked='' {/if}> {/volist}
83
+                                </div>
84
+                                {/case}
85
+
86
+                                {case value="select"}
87
+                                <div class="layui-input-block">
88
+                                    <select name="{$vo.fieldArr}[{$vo.name}]">
89
+                                        <option value=""></option>
90
+                                        {volist name="vo.options" id="v"}
91
+                                        <option value="{$key}" {if $key==$vo[ 'value' ]}selected="" {/if}>{$v}</option>
92
+                                        {/volist}
93
+                                    </select>
94
+                                </div>
95
+                                {/case}
96
+
97
+                                {case value="selects"}
98
+                                <div class="layui-input-block">
99
+                                    <div {$vo.extend_html|raw} class="form-selects" data-name="{$vo.fieldArr}[{$vo.name}]" data-id="{$vo.name}" data-value="{$vo.value}" data-list="{$vo.options|json_encode}"></div>
100
+                                </div>
101
+                                {/case}
102
+
103
+                                {case value="selectpage"}
104
+                                <div class="layui-input-block">
105
+                                    <input {$vo.extend_html|raw} class="form-control layui-input selectpage" type="text" name="{$vo.fieldArr}[{$vo.name}]" data-source="{$vo.options.url|url}" data-multiple="{$vo.options.multiple|default='false'}" data-field="{$vo.options.field|default='name'}" data-primary-key="{$vo.options.key|default='id'}" data-max-select-limit="{$vo.options.max|default='20'}" data-pagination="{$vo.options.pagination|default='true'}" data-page-size="{$vo.options.limit|default='10'}" data-order-by="{$vo.options.order|default='id'}"  value="{$vo.value}">
106
+                                </div>
107
+                                {/case}
108
+
109
+                                {case value="color"}
110
+                                <div class="layui-input-block">
111
+                                    <div class="layui-input-inline" style="width: 120px;">
112
+                                        <input {$vo.extend_html|raw} type="text" name="{$vo.fieldArr}[{$vo.name}]" value="{$vo.value}" placeholder="请选择颜色" class="layui-input test-form-input" id="c-{$vo.name}">
113
+                                    </div>
114
+                                    <div class="layui-inline" style="left: -11px;">
115
+                                        <div class="colorpicker" data-input-id="c-{$vo.name}"></div>
116
+                                    </div>
117
+                                </div>
118
+                                {/case}
119
+
120
+                                {case value="datetime"}
121
+                                <div class="layui-input-block">
122
+                                    <input {$vo.extend_html|raw} type="text" class="layui-input datetime" name="{$vo.fieldArr}[{$vo.name}]" placeholder="请输入{$vo.title}" value="{$vo.value}">
123
+                                </div>
124
+                                {/case}
125
+
126
+                                {case value="datetimerange"}
127
+                                <div class="layui-input-block">
128
+                                    <input {$vo.extend_html|raw} type="text" class="layui-input datetime" name="{$vo.fieldArr}[{$vo.name}]" placeholder="请输入{$vo.title}" value="{$vo.value}" data-date-range="-">
129
+                                </div>
130
+                                {/case}
131
+
132
+                                {case value="textarea"}
133
+                                <div class="layui-input-block">
134
+                                    <textarea {$vo.extend_html|raw} placeholder="请输入{$vo.title}" class="layui-textarea" name="{$vo.fieldArr}[{$vo.name}]">{$vo.value}</textarea>
135
+                                </div>
136
+                                {/case}
137
+
138
+                                {case value="image" break="0"}{/case}
139
+                                {case value="images"}
140
+                                <div class="layui-input-block">
141
+                                    <div class="layui-col-xs4">
142
+                                        <input type="text" name="{$vo.fieldArr}[{$vo.name}]"  id="c-{$vo.name}" value="{$vo.value|default=''}" class="layui-input">
143
+                                    </div>
144
+                                    <button type="button" class="layui-btn faupload" id="faupload-{$vo.name}" data-multiple="{$vo.type=='image'?'false':'true'}" data-input-id="c-{$vo.name}" data-preview-id="p-{$vo.name}" data-type="image"><i class="layui-icon layui-icon-upload"></i> 上传</button><button type="button" class="layui-btn fachoose" data-multiple="{$vo.type=='image'?'false':'true'}" data-mimetype="image" data-input-id="c-{$vo.name}" id="fachoose-c-{$vo.name}"><i class="iconfont icon-other"></i> 选择</button>
145
+                                    <ul class="layui-row list-inline plupload-preview" id="p-{$vo.name}"></ul>
146
+                                </div>
147
+                                {/case}
148
+
149
+                                {case value="file" break="0"}{/case}
150
+                                {case value="files"}
151
+                                <div class="layui-input-block">
152
+                                    <div class="layui-col-xs4">
153
+                                        <input type="text" name="{$vo.fieldArr}[{$vo.name}]"  id="c-{$vo.name}" value="{$vo.value|default=''}" class="layui-input">
154
+                                    </div>
155
+                                    <button type="button" class="layui-btn faupload" id="faupload-{$vo.name}" data-multiple="{$vo.type=='file'?'false':'true'}" data-input-id="c-{$vo.name}" data-preview-id="p-{$vo.name}" data-type="file"><i class="layui-icon layui-icon-upload"></i> 上传</button><button type="button" class="layui-btn fachoose" data-multiple="{$vo.type=='file'?'false':'true'}" data-mimetype="file" data-input-id="c-{$vo.name}" id="fachoose-c-{$vo.name}"><i class="iconfont icon-other"></i> 选择</button>
156
+                                </div>
157
+                                {/case}
158
+
159
+                                {case value="Ueditor"}
160
+                                <div class="layui-input-block">
161
+                                    <script {$vo.extend_html|raw} type="text/plain" class="js-ueditor" id="{$vo.name}" name="{$vo.fieldArr}[{$vo.name}]">{$vo.value|raw}</script>
162
+                                </div>
163
+                                <div class="{$vo.name}_attr editor_tool" style="margin-left: 140px;">
164
+                                    <a class="layui-btn layui-btn-sm" id="{$vo.name}grabimg" style="margin-top: 4px;">图片本地化</a>
165
+                                    <a class="layui-btn layui-btn-sm" id="{$vo.name}filterword" style="margin-top: 4px;">检测违禁词</a>
166
+                                </div>
167
+                                {/case}
168
+
169
+                                {case value="city"}
170
+                                <div class="layui-input-block">
171
+                                    <input {$vo.extend_html|raw} type="text" autocomplete="on" class="layui-input" name="{$vo.fieldArr}[{$vo.name}]" id="{$vo.name}" value="{$vo.value}" data-toggle="city-picker" placeholder="请选择"/>
172
+                                </div>
173
+                                {/case}
174
+
175
+                                {case value="custom"}
176
+                                   {$vo.options|raw}
177
+                                {/case}
178
+
179
+                                {case value="markdown"}
180
+                                    {:hook('markdown',$vo)}
181
+                                {/case}
182
+                            {/switch}
183
+                            {if $vo.remark}<div class="layui-form-mid no-float layui-word-aux">{$vo.remark|raw}</div>{/if}
184
+                        </div>
185
+                    </div>
186
+                    {/volist}
187
+
188
+                    {if count($fieldList)}
189
+                    <div class="layui-form-item layer-footer">
190
+                        <div class="layui-input-block">
191
+                            <button class="layui-btn" lay-submit data-refresh="false">立即提交</button>
192
+                        </div>
193
+                    </div>
194
+                    {/if}
195
+                </form>
196
+            </div>
197
+        </div>
198
+    </div>
199
+</div>
200
+{/block}
201
+{block name="script"}
202
+<script type="text/javascript">
203
+layui.use('yznForm', function() {
204
+    var yznForm = layui.yznForm;
205
+    yznForm.bindevent($("form.layui-form"));
206
+});
207
+</script>
208
+{/block}

+ 106
- 0
application/admin/view/general/profile/index.html Datei anzeigen

@@ -0,0 +1,106 @@
1
+{extend name="index_layout"/}
2
+{block name="main"}
3
+<style type="text/css">
4
+.admin-info {padding: 50px 20px 35px 20px; }
5
+.admin-info .photo {display: block; width: 130px; height: 130px; margin: 0 auto; border-radius: 50%; }
6
+.admin-info .photo img {display: block; width: 100%; height: 100%; border: none; border-radius: 50%; }
7
+.layui-user {display: block; width: 100%; height: 30px; line-height: 30px; text-align: center; padding-top: 15px; color: #333; font-size: 16px; }
8
+.login-time {display: block; width: 100%; height: 30px; line-height: 30px; text-align: center; font-size: 14px; color: #666777; }
9
+</style>
10
+<div class="layui-row layui-col-space20">
11
+    <div class="layui-col-sm4 layui-col-md3 layui-col-lg3">
12
+        <div class="layui-card">
13
+            <div class="layui-card-header">个人资料</div>
14
+            <div class="layui-card-body">
15
+                <div class="admin-info">
16
+                    <div class="photo">
17
+                        <img id="avatar" src="__STATIC__/admin/img/avatar.png">
18
+                    </div>
19
+                    <h3 class="layui-user">{$userInfo.email}</h3>
20
+                    <p class="login-time">上次登录:<?php if($userInfo['last_login_time'] > 0) { echo $userInfo['last_login_time'];} else { echo '--';}?></p>
21
+                </div>
22
+                <form class="layui-form" method="post">
23
+                    {:token()}
24
+                    <div class="layui-form-item">
25
+                        <label class="layui-form-label">用户名</label>
26
+                        <div class="layui-input-inline">
27
+                            <input type="text" autocomplete="off" placeholder="用户名" class="layui-input layui-disabled" value="{$userInfo.username}" disabled>
28
+                        </div>
29
+                    </div>
30
+                    <div class="layui-form-item">
31
+                        <label class="layui-form-label">密码</label>
32
+                        <div class="layui-input-inline">
33
+                            <input type="password" name="password" autocomplete="off" placeholder="不修改留空即可" class="layui-input">
34
+                        </div>
35
+                    </div>
36
+                    <div class="layui-form-item">
37
+                        <label class="layui-form-label">邮箱</label>
38
+                        <div class="layui-input-inline">
39
+                            <input type="text" name="email" lay-verify="required|email" autocomplete="off" placeholder="邮箱" class="layui-input" value="{$userInfo.email}">
40
+                        </div>
41
+                    </div>
42
+                    <div class="layui-form-item">
43
+                        <label class="layui-form-label">手机</label>
44
+                        <div class="layui-input-inline">
45
+                            <input type="text" name="mobile" autocomplete="off" placeholder="手机" class="layui-input" value="{$userInfo.mobile}">
46
+                        </div>
47
+                    </div>
48
+                    <div class="layui-form-item">
49
+                        <label class="layui-form-label">真实姓名</label>
50
+                        <div class="layui-input-inline">
51
+                            <input type="text" name="nickname" autocomplete="off" placeholder="真实姓名" class="layui-input" value="{$userInfo.nickname}">
52
+                        </div>
53
+                    </div>
54
+                    <div class="layui-form-item layer-footer">
55
+                        <div class="layui-input-block">
56
+                            <button class="layui-btn" lay-submit="{:url('update')}" data-refresh="false">立即提交</button>
57
+                            <button type="reset" class="layui-btn layui-btn-primary">重置</button>
58
+                        </div>
59
+                    </div>
60
+                </form>
61
+            </div>
62
+        </div>
63
+    </div>
64
+    <div class="layui-col-sm8 layui-col-md9 layui-col-lg9">
65
+        <div class="layui-card">
66
+            <div class="layui-card-header">操作日志</div>
67
+            <div class="layui-card-body">
68
+                <table class="layui-hide" id="currentTable" lay-filter="currentTable"></table>
69
+            </div>
70
+        </div>
71
+    </div>
72
+</div>
73
+{/block}
74
+{block name="script"}
75
+<script>
76
+layui.use(['yznTable','yznForm'], function() {
77
+    var table = layui.yznTable,
78
+    yznForm=layui.yznForm;
79
+
80
+    yznForm.bindevent($("form.layui-form"));
81
+
82
+    var init = {
83
+        table_elem: '#currentTable',
84
+        table_render_id: 'currentTable',
85
+    };
86
+
87
+    table.render({
88
+        init: init,
89
+        toolbar: ['refresh'],
90
+        url: '{:url("index")}',
91
+        cols: [
92
+            [
93
+                { field: 'id', width: 80, title: 'ID', sort: true },
94
+                { field: 'title', title: '标题' },
95
+                { field: 'url', title: '操作URL' },
96
+                { field: 'create_time', width: 180, title: '时间', search: 'range' },
97
+                { field: 'ip', width: 120, title: 'IP' },
98
+            ]
99
+        ],
100
+        page: {}
101
+    });
102
+
103
+    yznTable.bindevent();
104
+});
105
+</script>
106
+{/block}

+ 1
- 0
application/admin/view/index/index.html Datei anzeigen

@@ -0,0 +1 @@
1
+{extend name="layout"/}

+ 122
- 0
application/admin/view/index/login.html Datei anzeigen

@@ -0,0 +1,122 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+
4
+<head>
5
+    <meta charset="UTF-8">
6
+    <meta name="renderer" content="webkit">
7
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
8
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=0">
9
+    <title>登录</title>
10
+    <link rel="stylesheet" type="text/css" href="__STATIC__/libs/layui/css/layui.css" />
11
+    <script language="JavaScript">
12
+        <!--
13
+    if(top!=self)
14
+    if(self!=top) top.location=self.location;
15
+//-->
16
+</script>
17
+    <style type="text/css">
18
+    body {background-repeat: no-repeat; background-color: #f1f4fd; background-size: cover; background-image: url(__STATIC__/admin/img/bg.png); }
19
+    ::-webkit-input-placeholder {color: #999;}
20
+    ::-moz-placeholder {color: #999;}
21
+    :-ms-input-placeholder {color: #999;}
22
+    .login-main {width: 400px;margin: auto !important;margin-top: 140px !important; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; -o-box-sizing: border-box; box-sizing: border-box; background: #fff; -webkit-border-radius: 2px; -moz-border-radius: 2px; border-radius: 2px; box-shadow:0 0 30px rgb(0 0 0 / 10%);overflow: hidden;}
23
+    .login-main .layui-elip{margin-bottom: 15px;height: 117px; background-color: #148be4;font-size: 30px; font-weight: 400; font-stretch: normal; letter-spacing: 0; color: #fff; line-height: 117px; text-align: center; overflow: hidden; -webkit-transform: rotate(0); -moz-transform: rotate(0); -ms-transform: rotate(0); -o-transform: rotate(0); transform: rotate(0); }
24
+    .login-main .layui-elip span.version {font-size: 12px; }
25
+    .login-main .layui-elip .bg1 {display: inline-block; width: 74px; height: 74px; background: #fff; opacity: .1; border-radius: 0 74px 0 0; position: absolute; left: 0; top: 43px; }
26
+    .login-main .layui-elip .bg2 {display: inline-block; width: 94px; height: 94px; background: #fff; opacity: .1; border-radius: 50%; position: absolute; right: -16px; top: -16px; }
27
+    .login-main .login-container{padding: 0 20px 20px;}
28
+    .login-main form .verify-box input {display: inline-block; width: 60%; }
29
+    .login-main form .verify-box img {display: inline-block; margin-left: 2%; width: 36%; height: 38px; }
30
+    .login-main .layui-form-checkbox[lay-skin=primary] i {border-color: #1e9fff!important; color: #fff !important;background: #fff}
31
+    .login-main .layui-form-checked[lay-skin=primary] i {border-color: #1e9fff!important;background-color: #1e9fff;color: #fff;}
32
+    .login-main .layui-form-checkbox[lay-skin=primary]:hover i {border-color: #1e9fff; color: #fff; }
33
+    @media screen and (max-width: 450px) {
34
+        body{height: 100vh;}
35
+        .login-main {width: 300px; margin: -150px 0 0 -150px; }
36
+        .login-main .layui-elip {height: 90px; line-height: 90px; }
37
+        .login-main .layui-elip {font-size: 20px; }
38
+    }
39
+    </style>
40
+    {:hook('admin_login_style')}
41
+</head>
42
+
43
+<body>
44
+    <div id="mydiv">
45
+        <div class="login-main">
46
+            <div class="layui-elip">YZNCMS管理系统&nbsp;<span class="version">v{$Think.config.version.yzncms_version}</span><span class="bg1"></span><span class="bg2"></span></div>
47
+            <form class="layui-form" action="{:url('login')}">
48
+                <!--@AdminLoginFormBegin-->
49
+                {:token()}
50
+                <div class="login-container">
51
+                    <div class="layui-form-item">
52
+                        <div class="layui-input-wrap">
53
+                            <div class="layui-input-prefix">
54
+                                <i class="layui-icon layui-icon-username"></i>
55
+                            </div>
56
+                            <input type="text" name="username" lay-verify="required" autocomplete="off" placeholder="账号" class="layui-input" lay-affix="clear">
57
+                        </div>
58
+                    </div>
59
+                    <div class="layui-form-item">
60
+                        <div class="layui-input-wrap">
61
+                            <div class="layui-input-prefix">
62
+                                <i class="layui-icon layui-icon-password"></i>
63
+                            </div>
64
+                            <input type="password" name="password" lay-verify="required" autocomplete="off" placeholder="密码" class="layui-input" lay-affix="eye">
65
+                        </div>
66
+                    </div>
67
+                    <!--@CaptchaBegin-->
68
+                    <div class="layui-form-item">
69
+                        <div class="layui-input-wrap verify-box">
70
+                            <div class="layui-input-prefix">
71
+                                <i class="layui-icon layui-icon-vercode"></i>
72
+                            </div>
73
+                            <input type="text" name="verify" lay-verify="required" placeholder="验证码" autocomplete="off" class="layui-input">
74
+                            <img id="verify" src="{:captcha_src()}" alt="验证码" class="captcha">
75
+                        </div>
76
+                    </div>
77
+                    <!--@CaptchaEnd-->
78
+                    {if $keeyloginhours>0}
79
+                    <div class="layui-form-item" lay-tips="设定会话有效时长为{$keeyloginhours}小时">
80
+                        <input type="checkbox" name="keeplogin" title="保持会话" lay-skin="primary">
81
+                    </div>
82
+                    {/if}
83
+                    <div class="layui-form-item login-btn">
84
+                        <button class="layui-btn layui-btn-normal layui-btn-fluid" lay-submit>登录</button>
85
+                    </div>
86
+                </div>
87
+                <!--@AdminLoginFormEnd-->
88
+            </form>
89
+        </div>
90
+    </div>
91
+    <script type="text/javascript" src="__STATIC__/libs/layui/layui.js"></script>
92
+    <script src="__STATIC__/libs/jquery/jquery.min.js"></script>
93
+    {include file='layui' /}
94
+    <script type="text/javascript">
95
+    layui.use(['form', 'layer', 'yznForm'], function() {
96
+        var form = layui.form,
97
+            layer = layui.layer,
98
+            yznForm = layui.yznForm;
99
+
100
+        yznForm.bindevent($(".layui-form"),'', function(data,res) {
101
+            layer.msg('登入成功', {
102
+                offset: '15px',
103
+                icon: 1,
104
+                time: 1000
105
+            }, function() {
106
+                window.location.href = res.url;
107
+            });
108
+            return false;
109
+        }, function(res) {
110
+            $("#verify").click();
111
+        });
112
+    });
113
+    //刷新验证码
114
+    $("#verify").click(function() {
115
+        var verifyimg = $("#verify").attr("src");
116
+        $("#verify").attr("src", verifyimg.replace(/\?.*$/, '') + '?' + Math.random());
117
+    });
118
+    </script>
119
+    {:hook('admin_login_form')}
120
+</body>
121
+
122
+</html>

+ 33
- 0
application/admin/view/index_layout.html Datei anzeigen

@@ -0,0 +1,33 @@
1
+<!DOCTYPE html>
2
+<html>
3
+
4
+<head>
5
+    <meta charset="utf-8">
6
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
7
+    <title>YZNCMS后台管理系统</title>
8
+    <meta name="author" content="YZNCMS">
9
+    <link rel="stylesheet" href="__STATIC__/libs/layui/css/layui.css">
10
+    <link rel="stylesheet" href="__STATIC__/admin/css/admin.css?v={$Think.config.version.yzncms_release}">
11
+    <link rel="stylesheet" href="__STATIC__/common/font/iconfont.css?v={$Think.config.version.yzncms_release}">
12
+    <script src="__STATIC__/libs/layui/layui.js"></script>
13
+    <script src="__STATIC__/libs/jquery/jquery.min.js"></script>
14
+    <script type="text/javascript">
15
+    //全局变量
16
+    var GV = {
17
+        'site':{$site|raw|json_encode},
18
+        'image_upload_url': '{$image_upload_url ? $image_upload_url : url("admin/ajax/upload", ["dir" => "images"])}',
19
+        'file_upload_url': '{$file_upload_url ? $file_upload_url : url("admin/ajax/upload", ["dir" => "files"])}',
20
+        'attachment_select_url': '{$attachment_select_url ? $attachment_select_url : url("general.attachments/select")}',
21
+    };
22
+    </script>
23
+</head>
24
+
25
+<body class="childrenBody {:defined('IS_DIALOG') && IS_DIALOG ? 'is-dialog' : ''}">
26
+    {block name="main"}主内容{/block}
27
+    {block name="layui"}
28
+    {include file='admin@layui' /}
29
+    {/block}
30
+    {block name="script"}{/block}
31
+</body>
32
+
33
+</html>

+ 176
- 0
application/admin/view/inputItem.html Datei anzeigen

@@ -0,0 +1,176 @@
1
+{volist name="fieldList" id="vo"}
2
+    <div data-favisible="{$vo.visible|default=''}">
3
+        <div class="layui-form-item">
4
+            {if $vo.type!='hidden'}<label class="layui-form-label {if condition="isset($vo.ifrequire) AND $vo.ifrequire"}layui-form-item-required{/if}">{$vo.title}</label>{/if}
5
+            {switch name="vo.type"}
6
+                {case value="hidden"}
7
+                {if $vo.value}<input type="hidden" class="form-control" name="{$vo.fieldArr}[{$vo.name}]"  value="{$vo.value}">{/if}
8
+                {/case}
9
+
10
+                {case value="text"}
11
+                <div class="layui-input-block">
12
+                    <input type="text" name="{$vo.fieldArr}[{$vo.name}]" placeholder="请输入{$vo.title}" autocomplete="off" class="layui-input" value="{$vo.value}">
13
+                </div>
14
+                {/case}
15
+
16
+                {case value="password"}
17
+                <div class="layui-input-block">
18
+                    <input type="password" name="{$vo.fieldArr}[{$vo.name}]" placeholder="请输入{$vo.title}" autocomplete="off" class="layui-input" value="{$vo.value}" lay-affix="eye">
19
+                </div>
20
+                {/case}
21
+
22
+                {case value="tags"}
23
+                <div class="layui-input-block">
24
+                    <input type="text" name="{$vo.fieldArr}[{$vo.name}]" class="layui-input form-tags tags-{$vo.name}" value="{$vo.value}" data-remark="{$vo.remark|default='输入回车键确认'}">
25
+                </div>
26
+                {/case}
27
+
28
+                {case value="number"}
29
+                <div class="layui-input-block">
30
+                    <input type="number" name="{$vo.fieldArr}[{$vo.name}]" placeholder="请输入{$vo.title}" autocomplete="off" lay-affix="number" class="layui-input" value="{$vo.value}">
31
+                </div>
32
+                {/case}
33
+
34
+                {case value="switch"}
35
+                <div class="layui-input-block">
36
+                    <input type="checkbox" name="{$vo.fieldArr}[{$vo.name}]" lay-skin="switch" lay-text="ON|OFF" value="{$vo['value']}" {if 1==$vo[ 'value' ]}checked='' {/if}>
37
+                </div> 
38
+                {/case}
39
+
40
+                {case value="array"}
41
+                <dl class="layui-input-block fieldlist" data-name="{$vo.fieldArr}[{$vo.name}]" data-id="{$vo.name}">
42
+                    <dd>
43
+                        <ins>键名</ins>
44
+                        <ins>键值</ins>
45
+                    </dd>
46
+                    <dd><button type="button" class="layui-btn btn-append">追加</button></dd>
47
+                    <textarea name="{$vo.fieldArr}[{$vo.name}]" class="layui-textarea layui-hide">{$vo.value}</textarea>
48
+                </dl>
49
+                <script type="text/html" id="{$vo.name}Tpl">
50
+                    <dd class="layui-form-item rules-item">
51
+                        {{# layui.each(d.lists, function(index, item) { }}
52
+                        <input type="text" class="layui-input" name="{{item.name}}[{{item.index}}][key]" placeholder="键" value="{{item.key|| ''}}" />
53
+                        <input type="text" class="layui-input" name="{{item.name}}[{{item.index}}][value]" placeholder="值" value="{{item.value|| ''}}" />
54
+                        <button type="button" class="layui-btn layui-btn-danger btn-remove layui-btn-xs"><i class="iconfont icon-close"></i></button>
55
+                        <button type="button" class="layui-btn btn-dragsort layui-btn-xs"><i class="iconfont icon-yidong"></i></button>
56
+                        {{# }); }}
57
+                    </dd>
58
+                </script>
59
+                {/case}
60
+
61
+                {case value="checkbox"}
62
+                <div class="layui-input-block">
63
+                    {volist name="vo.options" id="v"}
64
+                    <input type="checkbox" name="{$vo.fieldArr}[{$vo.name}][]" lay-skin="primary" title="{$v}" value="{$key}" {if in_array($key,$vo[ 'value' ])}checked{/if}>
65
+                    {/volist}
66
+                </div>
67
+                {/case}
68
+
69
+                {case value="radio"}
70
+                <div class="layui-input-block">
71
+                    {volist name="vo.options" id="v"}
72
+                    <input type="radio" name="{$vo.fieldArr}[{$vo.name}]" value="{$key}" title="{$v}" {if $key==$vo [ 'value' ]}checked='' {/if}> {/volist}
73
+                </div>
74
+                {/case}
75
+
76
+                {case value="select"}
77
+                <div class="layui-input-block">
78
+                    <select name="{$vo.fieldArr}[{$vo.name}]">
79
+                        <option value=""></option>
80
+                        {volist name="vo.options" id="v"}
81
+                        <option value="{$key}" {if $key==$vo[ 'value' ]}selected="" {/if}>{$v}</option>
82
+                        {/volist}
83
+                    </select>
84
+                </div>
85
+                {/case}
86
+
87
+                {case value="selects"}
88
+                <div class="layui-input-block">
89
+                    <div class="form-selects" data-name="{$vo.fieldArr}[{$vo.name}]" data-id="{$vo.name}" data-value="{$vo.value}" data-list="{$vo.options|json_encode}"></div>
90
+                </div>
91
+                {/case}
92
+
93
+                {case value="selectpage"}
94
+                <div class="layui-input-block">
95
+                    <input class="form-control layui-input selectpage" type="text" name="{$vo.fieldArr}[{$vo.name}]" data-source="{$vo.options.url|url}" data-multiple="{$vo.options.multiple|default='false'}" data-field="{$vo.options.field|default='name'}" data-primary-key="{$vo.options.key|default='id'}" data-max-select-limit="{$vo.options.max|default='20'}" data-pagination="{$vo.options.pagination|default='true'}" data-page-size="{$vo.options.limit|default='10'}" data-order-by="{$vo.options.order|default='id'}"  value="{$vo.value}">
96
+                </div>
97
+                {/case}
98
+
99
+                {case value="color"}
100
+                <div class="layui-input-block">
101
+                    <div class="layui-input-inline" style="width: 120px;">
102
+                        <input type="text" name="{$vo.fieldArr}[{$vo.name}]" value="{$vo.value}" placeholder="请选择颜色" class="layui-input test-form-input" id="c-{$vo.name}">
103
+                    </div>
104
+                    <div class="layui-inline" style="left: -11px;">
105
+                        <div class="colorpicker" data-input-id="c-{$vo.name}"></div>
106
+                    </div>
107
+                </div>
108
+                {/case}
109
+
110
+                {case value="datetime"}
111
+                <div class="layui-input-block">
112
+                    <input type="text" class="layui-input datetime" name="{$vo.fieldArr}[{$vo.name}]" placeholder="请输入{$vo.title}" value="{$vo.value}">
113
+                </div>
114
+                {/case}
115
+
116
+                {case value="datetimerange"}
117
+                <div class="layui-input-block">
118
+                    <input type="text" class="layui-input datetime" name="{$vo.fieldArr}[{$vo.name}]" placeholder="请输入{$vo.title}" value="{$vo.value}" data-date-range="-">
119
+                </div>
120
+                {/case}
121
+
122
+                {case value="textarea"}
123
+                <div class="layui-input-block">
124
+                    <textarea placeholder="请输入{$vo.title}" class="layui-textarea" name="{$vo.fieldArr}[{$vo.name}]">{$vo.value}</textarea>
125
+                </div>
126
+                {/case}
127
+
128
+                {case value="image" break="0"}{/case}
129
+                {case value="images"}
130
+                <div class="layui-input-block">
131
+                    <div class="layui-col-xs4">
132
+                        <input type="text" name="{$vo.fieldArr}[{$vo.name}]"  id="c-{$vo.name}" value="{$vo.value|default=''}" class="layui-input">
133
+                    </div>
134
+                    <button type="button" class="layui-btn faupload" id="faupload-{$vo.name}" data-multiple="{$vo.type=='image'?'false':'true'}" data-input-id="c-{$vo.name}" data-preview-id="p-{$vo.name}" data-type="image"><i class="layui-icon layui-icon-upload"></i> 上传</button><button type="button" class="layui-btn fachoose" data-multiple="{$vo.type=='image'?'false':'true'}" data-mimetype="image" data-input-id="c-{$vo.name}" id="fachoose-c-{$vo.name}"><i class="iconfont icon-other"></i> 选择</button>
135
+                    <ul class="layui-row list-inline plupload-preview" id="p-{$vo.name}"></ul>
136
+                </div>
137
+                {/case}
138
+
139
+                {case value="file" break="0"}{/case}
140
+                {case value="files"}
141
+                <div class="layui-input-block">
142
+                    <div class="layui-col-xs4">
143
+                        <input type="text" name="{$vo.fieldArr}[{$vo.name}]"  id="c-{$vo.name}" value="{$vo.value|default=''}" class="layui-input">
144
+                    </div>
145
+                    <button type="button" class="layui-btn faupload" id="faupload-{$vo.name}" data-multiple="{$vo.type=='file'?'false':'true'}" data-input-id="c-{$vo.name}" data-preview-id="p-{$vo.name}" data-type="file"><i class="layui-icon layui-icon-upload"></i> 上传</button><button type="button" class="layui-btn fachoose" data-multiple="{$vo.type=='file'?'false':'true'}" data-mimetype="file" data-input-id="c-{$vo.name}" id="fachoose-c-{$vo.name}"><i class="iconfont icon-other"></i> 选择</button>
146
+                </div>
147
+                {/case}
148
+
149
+                {case value="Ueditor"}
150
+                <div class="layui-input-block">
151
+                    <script type="text/plain" class="js-ueditor" id="{$vo.name}" name="{$vo.fieldArr}[{$vo.name}]">{$vo.value|raw}</script>
152
+                </div>
153
+                <div class="{$vo.name}_attr editor_tool" style="margin-left: 140px;">
154
+                    <a class="layui-btn layui-btn-sm" id="{$vo.name}grabimg" style="margin-top: 4px;">图片本地化</a>
155
+                    <a class="layui-btn layui-btn-sm" id="{$vo.name}filterword" style="margin-top: 4px;">检测违禁词</a>
156
+                </div>
157
+                {/case}
158
+
159
+                {case value="city"}
160
+                <div class="layui-input-block">
161
+                    <input type="text" autocomplete="on" class="layui-input" name="{$vo.fieldArr}[{$vo.name}]" id="{$vo.name}" value="{$vo.value}" data-toggle="city-picker" placeholder="请选择"/>
162
+                </div>
163
+                {/case}
164
+
165
+                {case value="custom"}
166
+                   {$vo.options|raw}
167
+                {/case}
168
+
169
+                {case value="markdown"}
170
+                    {:hook('markdown',$vo)}
171
+                {/case}
172
+            {/switch}
173
+            {if $vo.remark}<div class="layui-form-mid no-float layui-word-aux">{$vo.remark|raw}</div>{/if}
174
+        </div>
175
+    </div>
176
+{/volist}

+ 212
- 0
application/admin/view/layout.html Datei anzeigen

@@ -0,0 +1,212 @@
1
+<!DOCTYPE html>
2
+<html>
3
+
4
+<head>
5
+    <meta charset="utf-8">
6
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
7
+    <title>YZNCMS后台管理系统</title>
8
+    <meta name="author" content="YZNCMS">
9
+    <link rel="stylesheet" href="__STATIC__/libs/layui/css/layui.css">
10
+    <!-- 布 局 样 式 -->
11
+    <link rel="stylesheet" href="__STATIC__/admin/css/global.css?v={$Think.config.version.yzncms_release}" />
12
+    <link rel="stylesheet" href="__STATIC__/common/font/iconfont.css?v={$Think.config.version.yzncms_release}">
13
+    <script type="text/javascript">
14
+    //全局变量
15
+    var GV = {
16
+        'site':{$site|raw|json_encode}
17
+    };
18
+    </script>
19
+</head>
20
+<!-- 结 构 代 码 -->
21
+<body class="layui-layout-body pear-admin">
22
+    <!-- 布 局 框 架 -->
23
+    <div class="layui-layout layui-layout-admin">
24
+        <!-- 顶 部 样 式 -->
25
+        <div class="layui-header">
26
+            <!-- 菜 单 顶 部 -->
27
+            <div class="layui-logo">
28
+                <!-- 图 标 -->
29
+                <div class="logo"><i class="layui-icon layui-icon-component"></i></div>
30
+                <!-- 标 题 -->
31
+                <span class="title">YznCMS后台</span>
32
+            </div>
33
+            <!-- 顶 部 左 侧 功 能 -->
34
+            <ul class="layui-nav layui-layout-left">
35
+                <li class="collapse layui-nav-item"><a href="#" class="layui-icon layui-icon-shrink-right"></a></li>
36
+                <li class="refresh layui-nav-item"><a href="#" class="layui-icon layui-icon-refresh-1" loading=600></a></li>
37
+            </ul>
38
+            <!-- 多 系 统 菜 单 -->
39
+            <div id="control" class="layui-layout-control"></div>
40
+            <!-- 顶 部 右 侧 菜 单 -->
41
+            <ul class="layui-nav layui-layout-right">
42
+                <li class="layui-nav-item layui-hide-xs"><a href="{$Think.ROOT_URL}" target="_blank" class="layui-icon layui-icon-website"></a></li>
43
+                <!-- <li class="layui-nav-item layui-hide-xs"><a href="#" class="menuSearch layui-icon layui-icon-search"></a></li> -->
44
+                <li class="layui-nav-item layui-hide-xs"><a href="#" class="fullScreen layui-icon layui-icon-screen-full"></a></li>
45
+                <li class="layui-nav-item"><a href="javascript:;"><i class="iconfont icon-trash" title="清空缓存"></i></a>
46
+                        <dl class="layui-nav-child" id="deletecache">
47
+                            <dd><a href="javascript:;" data-type="all">一键清理缓存<span class="layui-badge-dot"></span></a></dd>
48
+                            <hr>
49
+                            <dd><a href="javascript:;" data-type="data">清理数据缓存</a></dd>
50
+                            <dd><a href="javascript:;" data-type="template">清理模板缓存</a></dd>
51
+                            <dd><a href="javascript:;" data-type="addons">清理插件缓存</a></dd>
52
+                        </dl>
53
+                </li>
54
+                <li class="layui-nav-item user">
55
+                    <!-- 头 像 -->
56
+                    <a href="javascript:;"><img src="__STATIC__/admin/img/avatar.png" class="layui-nav-img userAvatar" width="35" height="35">{$userInfo.username}</a>
57
+                    <!-- 功 能 菜 单 -->
58
+                    <dl class="layui-nav-child">
59
+                        <dd><a user-menu-url="{:url('general.profile/index')}" user-menu-id="3" user-menu-title="个人资料"><i class="iconfont icon-user-line"></i>&nbsp;个人资料</a></dd>
60
+                        <dd><a href="javascript:void(0);" class="logout"><i class="iconfont icon-logout-box-r-line"></i>&nbsp;注销登录</a></dd>
61
+                    </dl>
62
+                </li>
63
+                <!-- 主 题 配 置 -->
64
+                <li class="layui-nav-item setting"><a href="#" class="layui-icon layui-icon-more-vertical"></a></li>
65
+            </ul>
66
+        </div>
67
+        <!-- 侧 边 区 域 -->
68
+        <div class="layui-side layui-bg-black">
69
+            <!-- 菜 单 顶 部 -->
70
+            <div class="layui-logo">
71
+                <!-- 图 标 -->
72
+                <div class="logo"><i class="layui-icon layui-icon-component"></i></div>
73
+                <!-- 标 题 -->
74
+                <span class="title">YznCMS后台</span>
75
+            </div>
76
+            <!-- 菜 单 内 容 -->
77
+            <div class="layui-side-scroll">
78
+                <div class="sider-search menuSearch">
79
+                    <div class="search-input">
80
+                        <span class="search-text">搜索菜单</span>
81
+                        <div class="search-icon"><i class="layui-icon layui-icon-search"></i></div>
82
+                    </div>
83
+                </div>
84
+                <div id="sideMenu"></div>
85
+            </div>
86
+        </div>
87
+        <!-- 视 图 页 面 -->
88
+        <div class="layui-body">
89
+            <!-- 内 容 页 面 -->
90
+            <div id="content"></div>
91
+        </div>
92
+        <!-- 页脚 -->
93
+        <div class="layui-footer layui-text">
94
+            <span class="left">
95
+                <strong>Copyright © 2017-{:date("Y")}&nbsp;<a href="https://www.yzncms.com/">YznCMS</a>.</strong>&nbsp;All rights reserved.
96
+
97
+            </span>
98
+            <span class="center"></span>
99
+            <span class="right">
100
+                <b>Version</b>&nbsp;{$Think.config.version.yzncms_version}
101
+            </span>
102
+        </div>
103
+        <!-- 遮 盖 层 -->
104
+        <div class="pear-cover"></div>
105
+        <!-- 加 载 动 画 -->
106
+        <div class="loader-main">
107
+            <!-- 动 画 对 象 -->
108
+            <div class="loader"></div>
109
+        </div>
110
+    </div>
111
+    <!-- 移 动 端 便 捷 操 作 -->
112
+    <div class="pear-collapsed-pe collapse">
113
+        <a href="#" class="layui-icon layui-icon-shrink-right"></a>
114
+    </div>
115
+    <!-- 依 赖 脚 本 -->
116
+    <script src="__STATIC__/libs/layui/layui.js"></script>
117
+    <script type="text/javascript">
118
+    layui.config({
119
+       base: '__STATIC__/libs/layui_exts/'
120
+    }).extend({
121
+       admin: "pear/admin",           // 框架布局组件
122
+       tab: "pear/tab",               // 多选项卡组件
123
+       menu: "pear/menu",             // 数据菜单组件
124
+       frame: "pear/frame",           // 内容页面组件
125
+       theme: "pear/theme",           // 主题转换
126
+       fullscreen:"pear/fullscreen",  // 全屏组件
127
+    }).use(['layer', 'theme'], function () {
128
+        layui.theme.changeTheme(window, false);
129
+    })
130
+
131
+    layui.use(['admin'], function() {
132
+        var admin = layui.admin;
133
+        var $ = layui.jquery;
134
+
135
+        admin.render({
136
+            "menu": {
137
+                "data": "{:url('index')}",
138
+                "method": "POST",
139
+                "accordion": true,
140
+                "collapse": false,
141
+                "control": false,//菜单模式 false 为常规菜单,true 为多系统菜单
142
+                "select": 0,
143
+                "async": true
144
+            }, 
145
+            "tab": {
146
+                "enable": true,
147
+                "keepState": true,
148
+                "session": true,
149
+                "preload": true,
150
+                "max": "10", 
151
+                "index": {
152
+                    "id": 0,
153
+                    "href":"{:url('main/index')}",
154
+                    "title": "首页"
155
+                }
156
+            }, 
157
+            "theme": {
158
+                "defaultColor": "2",
159
+                "defaultMenu": "dark-theme",
160
+                "defaultHeader": "light-theme",
161
+                "allowCustom": true,
162
+                "banner": false
163
+            },
164
+            "colors": [
165
+                    {"id": "1", "color": "#2d8cf0", "second": "#ecf5ff"}, 
166
+                    {"id": "2", "color": "#36b368", "second": "#f0f9eb"}, 
167
+                    {"id": "3", "color": "#f6ad55", "second": "#fdf6ec"}, 
168
+                    {"id": "4", "color": "#f56c6c", "second": "#fef0f0"}, 
169
+                    {"id": "5", "color": "#3963bc", "second": "#ecf5ff"}
170
+                ], 
171
+            "other": {
172
+                "keepLoad": "1200",
173
+                "autoHead": false,
174
+                "footer": false
175
+            }
176
+        });
177
+
178
+        // 登出逻辑 
179
+        admin.logout(function(){
180
+            location.href = "{:url('admin/index/logout')}";
181
+            // 注销逻辑 返回 true / false
182
+            return true;
183
+        })
184
+
185
+        //清除缓存
186
+        $('body').on('click', "dl#deletecache dd a", function() {
187
+            $.ajax({
188
+                url: "{:url('admin/index/cache')}",
189
+                dataType: 'json',
190
+                data: { type: $(this).data("type") },
191
+                cache: false,
192
+                success: function(res) {
193
+                    if (res.code == 1) {
194
+                        var index = layer.msg('清除缓存中,请稍候', { icon: 16, time: false, shade: 0.8 });
195
+                        setTimeout(function() {
196
+                            layer.close(index);
197
+                            layer.msg("缓存清除成功!");
198
+                        }, 1000);
199
+                    }else{
200
+                        layer.msg('清除缓存失败');
201
+                    }
202
+                },
203
+                error: function() {
204
+                    layer.msg('清除缓存失败');
205
+                }
206
+            });
207
+        });
208
+    })
209
+    </script>
210
+</body>
211
+
212
+</html>

+ 27
- 0
application/admin/view/layui.html Datei anzeigen

@@ -0,0 +1,27 @@
1
+<script type="text/javascript">
2
+layui.config({
3
+	version: '{$Think.config.version.yzncms_release}',
4
+	base: '__STATIC__/libs/layui_exts/'
5
+}).extend({
6
+	yzn: 'yzn/yzn',
7
+	yznForm: 'yznForm/yznForm',
8
+	tab: "pear/tab",               // 多选项卡组件
9
+	yznUpload: 'yznUpload/yznUpload',
10
+	yznTable: 'yznTable/yznTable',
11
+	clipboard: 'clipboard/clipboard.min',
12
+	notice: 'notice/notice.min',
13
+	iconPicker: 'iconPicker/iconPicker.min',
14
+	ztree: 'ztree/ztree',
15
+	dragsort: 'dragsort/dragsort.min',
16
+	tagsinput: 'tagsinput/tagsinput',
17
+	xmSelect: 'xmSelect/xm-select',
18
+	selectPage: 'selectPage/selectpage.min',
19
+	echarts: 'echarts/echarts',
20
+	echartsTheme: 'echarts/echartsTheme',
21
+	citypicker: 'citypicker/city-picker',
22
+	dropzone: 'dropzone/dropzone.min',
23
+	ueditor: 'ueditor/ueditor.min',
24
+	cxselect: 'cxselect/cxselect',
25
+	addons: 'addons',
26
+});
27
+</script>

+ 159
- 0
application/admin/view/main/index.html Datei anzeigen

@@ -0,0 +1,159 @@
1
+{extend name="index_layout"/}
2
+{block name="main"}
3
+{notempty name="default_pass"}
4
+<div class="alert alert-danger-light">
5
+    安全提示:超级管理员默认密码未修改,建议马上修改。
6
+</div>
7
+{/notempty}
8
+{if $Think.config.app_debug}
9
+<div class="alert alert-danger-light">
10
+    安全提示:当前网站【调试模式】开启中,强烈建议在正式部署后关闭调试模式
11
+</div>
12
+{/if}
13
+{if $Think.config.app_trace}
14
+<div class="alert alert-danger-light">
15
+    安全提示:当前网站【Trace调试】开启中,强烈建议在正式部署后关闭Trace调试
16
+</div>
17
+{/if}
18
+<div class="alert alert-success-light">
19
+    <div id="nowTime"></div>
20
+</div>
21
+<div class="layui-row layui-col-space10 panel_box">
22
+    <div class="panel layui-col-xs12 layui-col-sm6 layui-col-md4 layui-col-lg3">
23
+        <a href="https://www.kancloud.cn/ken678/yzncms/352683" target="_blank">
24
+            <div class="panel_icon yzn-bg-blue">
25
+                <i class="layui-icon layui-icon-read layui-anim"></i>
26
+            </div>
27
+            <div class="panel_word">
28
+                <span>文档</span>
29
+                <cite>使用手册</cite>
30
+            </div>
31
+        </a>
32
+    </div>
33
+    <div class="panel layui-col-xs12 layui-col-sm6 layui-col-md4 layui-col-lg3">
34
+        <a href="https://gitee.com/ken678/YZNCMS" target="_blank">
35
+            <div class="panel_icon yzn-bg-orange">
36
+                <i class="layui-icon layui-icon-fonts-code layui-anim"></i>
37
+            </div>
38
+            <div class="panel_word">
39
+                <span>代码</span>
40
+                <cite>下载链接</cite>
41
+            </div>
42
+        </a>
43
+    </div>
44
+    <div class="panel layui-col-xs12 layui-col-sm6 layui-col-md4 layui-col-lg3">
45
+        <a href="http://blog.yzncms.com" target="_blank">
46
+            <div class="panel_icon yzn-bg-green">
47
+                <i class="layui-icon layui-icon-survey layui-anim"></i>
48
+            </div>
49
+            <div class="panel_word">
50
+                <span>博客</span>
51
+                <cite>技术分享</cite>
52
+            </div>
53
+        </a>
54
+    </div>
55
+    <div class="panel layui-col-xs12 layui-col-sm6 layui-col-md4 layui-col-lg3">
56
+        <a href="javascript:;">
57
+            <div class="panel_icon yzn-bg-purple">
58
+                <i class="layui-icon layui-icon-log layui-anim"></i>
59
+            </div>
60
+            <div class="panel_word">
61
+                <span class="loginTime">
62
+                    <?php if($userInfo['last_login_time'] > 0) { echo $userInfo['last_login_time'];} else { echo '--';}?></span>
63
+                <cite>上次登录时间</cite>
64
+            </div>
65
+        </a>
66
+    </div>
67
+</div>
68
+<div class="layui-row layui-col-space10">
69
+    <div class="layui-col-lg12 layui-col-md12">
70
+        <div class="layui-card">
71
+            <div class="layui-card-header" style="border-bottom: 1px solid #eee;">版本信息</div>
72
+            <div class="layui-card-body">
73
+                <table class="layui-table magt0" lay-even>
74
+                    <colgroup>
75
+                        <col width="10%">
76
+                        <col width="40%">
77
+                        <col width="10%">
78
+                        <col width="40%">
79
+                    </colgroup>
80
+                    <tbody>
81
+                        <tr>
82
+                            <td>当前版本</td>
83
+                            <td class="version">yzncms v{$Think.config.version.yzncms_version}</td>
84
+                            <td>ThinkPHP版本</td>
85
+                            <td class="version">{$Think.VERSION}</td>
86
+                        </tr>
87
+                        <tr>
88
+                            <td>PHP 版本</td>
89
+                            <td>{$sys_info.phpv}</td>
90
+                            <td>数据库版本</td>
91
+                            <td>{$sys_info.mysql_version}</td>
92
+                        </tr>
93
+                        <tr>
94
+                            <td>服务器域名/IP</td>
95
+                            <td>{$sys_info.domain} [ {$sys_info.ip} ]</td>
96
+                            <td>服务器环境</td>
97
+                            <td class="server">{$sys_info.web_server}</td>
98
+                        </tr>
99
+                        <tr>
100
+                            <td>服务器时间</td>
101
+                            <td>{$sys_info.time}</td>
102
+                            <td>剩余空间</td>
103
+                            <td>{$sys_info.remaining_space}</td>
104
+                        </tr>
105
+                        <tr>
106
+                            <td>最大上传限制</td>
107
+                            <td>{$sys_info.fileupload}</td>
108
+                            <td>最大占用内存</td>
109
+                            <td>{$sys_info.memory_limit}</td>
110
+                        </tr>
111
+                    </tbody>
112
+                </table>
113
+            </div>
114
+        </div>
115
+    </div>
116
+</div>
117
+{/block}
118
+{block name="script"}
119
+<script type="text/javascript">
120
+//获取系统时间
121
+var newDate = '';
122
+getLangDate();
123
+//值小于10时,在前面补0
124
+function dateFilter(date) {
125
+    if (date < 10) { return "0" + date; }
126
+    return date;
127
+}
128
+
129
+function getLangDate() {
130
+    var dateObj = new Date(); //表示当前系统时间的Date对象
131
+    var year = dateObj.getFullYear(); //当前系统时间的完整年份值
132
+    var month = dateObj.getMonth() + 1; //当前系统时间的月份值
133
+    var date = dateObj.getDate(); //当前系统时间的月份中的日
134
+    var day = dateObj.getDay(); //当前系统时间中的星期值
135
+    var weeks = ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"];
136
+    var week = weeks[day]; //根据星期值,从数组中获取对应的星期字符串
137
+    var hour = dateObj.getHours(); //当前系统时间的小时值
138
+    var minute = dateObj.getMinutes(); //当前系统时间的分钟值
139
+    var second = dateObj.getSeconds(); //当前系统时间的秒钟值
140
+    var timeValue = "" + ((hour >= 12) ? (hour >= 18) ? "晚上" : "下午" : "上午"); //当前时间属于上午、晚上还是下午
141
+    newDate = dateFilter(year) + "年" + dateFilter(month) + "月" + dateFilter(date) + "日 " + " " + dateFilter(hour) + ":" + dateFilter(minute) + ":" + dateFilter(second);
142
+    document.getElementById("nowTime").innerHTML = "亲爱的{$userInfo.username}," + timeValue + "好! 欢迎使用YznCMS v{$Think.config.version.yzncms_version},当前时间为: " + newDate + " " + week;
143
+    setTimeout("getLangDate()", 1000);
144
+}
145
+
146
+layui.use(['jquery'], function() {
147
+    var $ = layui.jquery;
148
+    //icon动画
149
+    $(".panel a").hover(function() {
150
+        $(this).find(".layui-anim").addClass("layui-anim-scaleSpring");
151
+    }, function() {
152
+        $(this).find(".layui-anim").removeClass("layui-anim-scaleSpring");
153
+    })
154
+    $(".panel a").click(function() {
155
+        parent.addTab($(this));
156
+    })
157
+})
158
+</script>
159
+{/block}

+ 17
- 0
application/api/config/app.php Datei anzeigen

@@ -0,0 +1,17 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | ThinkPHP [ WE CAN DO IT JUST THINK ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2006~2018 http://thinkphp.cn All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | Author: liu21st <liu21st@gmail.com>
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// | api设置
14
+// +----------------------------------------------------------------------
15
+return [
16
+    'exception_handle' => '\\app\\api\\library\\ExceptionHandle',
17
+];

+ 92
- 0
application/api/controller/Ems.php Datei anzeigen

@@ -0,0 +1,92 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | fastadmin: https://www.fastadmin.net/
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// | 邮箱验证码接口
14
+// +----------------------------------------------------------------------
15
+namespace app\api\controller;
16
+
17
+use app\common\controller\Api;
18
+use app\common\library\Ems as Emslib;
19
+use app\member\model\Member;
20
+use think\facade\Hook;
21
+use think\facade\Validate;
22
+
23
+/**
24
+ * @title 邮箱验证码接口
25
+ * @controller api\controller\Ems
26
+ * @group base
27
+ */
28
+class Ems extends Api
29
+{
30
+    /**
31
+     * @title 发送验证码
32
+     * @desc 最基础的接口注释写法
33
+     * @author 御宅男
34
+     * @url /api/Ems/send
35
+     * @method GET
36
+     * @tag 邮箱 验证码
37
+     * @param name:email type:string require:1 desc:邮箱
38
+     * @param name:event type:string require:1 desc:事件名称
39
+     * @return name:data type:array ref:definitions\dictionary
40
+     */
41
+    public function send()
42
+    {
43
+        $email = $this->request->request("email");
44
+        $event = $this->request->request("event");
45
+        $event = $event ? $event : 'register';
46
+
47
+        if (!$email || !Validate::isEmail($email)) {
48
+            $this->error('邮箱格式不正确!');
49
+        }
50
+        $last = Emslib::get($email, $event);
51
+        if ($last && time() - $last['create_time'] < 60) {
52
+            $this->error('发送频繁');
53
+        }
54
+        if ($event) {
55
+            $userinfo = Member::getByEmail($email);
56
+            if ($event == 'register' && $userinfo) {
57
+                $this->error('已被注册');
58
+            } elseif (in_array($event, ['changeemail']) && $userinfo) {
59
+                $this->error('已被占用');
60
+            } elseif (in_array($event, ['changepwd', 'resetpwd', 'actemail']) && !$userinfo) {
61
+                $this->error('未注册');
62
+            }
63
+        }
64
+        if (!Hook::get('ems_send')) {
65
+            $this->error('请在后台插件管理安装邮箱验证插件');
66
+        }
67
+        $ret = Emslib::send($email, null, $event);
68
+        if ($ret) {
69
+            $this->success('发送成功');
70
+        } else {
71
+            $this->error('发送失败');
72
+        }
73
+    }
74
+
75
+    /**
76
+     * @title 检测验证码
77
+     * @desc 最基础的接口注释写法
78
+     * @author 御宅男
79
+     * @url /api/Ems/check
80
+     * @method GET
81
+     * @tag 邮箱 验证码
82
+     * @param name:email type:string require:1 desc:邮箱
83
+     * @param name:event type:string require:1 desc:事件名称
84
+     * @param name:captcha type:string require:1 desc:验证码
85
+     * @return name:data type:array ref:definitions\dictionary
86
+     */
87
+    public function check()
88
+    {
89
+
90
+    }
91
+
92
+}

+ 98
- 0
application/api/controller/Sms.php Datei anzeigen

@@ -0,0 +1,98 @@
1
+<?php
2
+// +----------------------------------------------------------------------
3
+// | Yzncms [ 御宅男工作室 ]
4
+// +----------------------------------------------------------------------
5
+// | Copyright (c) 2018 http://yzncms.com All rights reserved.
6
+// +----------------------------------------------------------------------
7
+// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
8
+// +----------------------------------------------------------------------
9
+// | fastadmin: https://www.fastadmin.net/
10
+// +----------------------------------------------------------------------
11
+
12
+// +----------------------------------------------------------------------
13
+// | 手机短信接口
14
+// +----------------------------------------------------------------------
15
+namespace app\api\controller;
16
+
17
+use app\common\controller\Api;
18
+use app\common\library\Sms as Smslib;
19
+use app\member\model\Member;
20
+use think\facade\Hook;
21
+use think\facade\Validate;
22
+
23
+/**
24
+ * @title 手机短信接口
25
+ * @controller api\controller\Sms
26
+ * @group base
27
+ */
28
+class Sms extends Api
29
+{
30
+
31
+    /**
32
+     * @title 发送验证码
33
+     * @desc 最基础的接口注释写法
34
+     * @author 御宅男
35
+     * @url /api/Sms/send
36
+     * @method GET
37
+     * @tag 手机 验证码
38
+     * @param name:mobile type:string require:1 desc:手机号
39
+     * @param name:event type:string require:1 desc:事件名称
40
+     * @return name:data type:array ref:definitions\dictionary
41
+     */
42
+    public function send()
43
+    {
44
+        $mobile = $this->request->request("mobile");
45
+        $event  = $this->request->request("event");
46
+        $event  = $event ? $event : 'register';
47
+
48
+        if (!$mobile || !Validate::isMobile($mobile)) {
49
+            $this->error('手机号不正确');
50
+        }
51
+        $last = Smslib::get($mobile, $event);
52
+        if ($last && time() - $last['create_time'] < 60) {
53
+            $this->error('发送频繁');
54
+        }
55
+        $ipSendTotal = \app\common\model\Sms::where(['ip' => $this->request->ip()])->whereTime('create_time', '-1 hours')->count();
56
+        if ($ipSendTotal >= 5) {
57
+            $this->error('发送频繁');
58
+        }
59
+        if ($event) {
60
+            $userinfo = Member::getByMobile($mobile);
61
+            if ($event == 'register' && $userinfo) {
62
+                $this->error('已被注册');
63
+            } elseif (in_array($event, ['changemobile']) && $userinfo) {
64
+                $this->error('已被占用');
65
+            } elseif (in_array($event, ['changepwd', 'resetpwd']) && !$userinfo) {
66
+                $this->error('未注册');
67
+            }
68
+        }
69
+        if (!Hook::get('sms_send')) {
70
+            $this->error('请在后台插件管理安装短信验证插件');
71
+        }
72
+        $ret = Smslib::send($mobile, null, $event);
73
+        if ($ret) {
74
+            $this->success('发送成功');
75
+        } else {
76
+            $this->error('发送失败');
77
+        }
78
+
79
+    }
80
+
81
+    /**
82
+     * @title 检测验证码
83
+     * @desc 最基础的接口注释写法
84
+     * @author 御宅男
85
+     * @url /api/Sms/check
86
+     * @method GET
87
+     * @tag 手机 验证码
88
+     * @param name:mobile type:string require:1 desc:手机号
89
+     * @param name:event type:string require:1 desc:事件名称
90
+     * @param name:captcha type:string require:1 desc:验证码
91
+     * @return name:data type:array ref:definitions\dictionary
92
+     */
93
+    public function check()
94
+    {
95
+
96
+    }
97
+
98
+}

+ 38
- 0
application/api/library/ExceptionHandle.php Datei anzeigen

@@ -0,0 +1,38 @@
1
+<?php
2
+
3
+namespace app\api\library;
4
+
5
+use Exception;
6
+use think\exception\Handle;
7
+use think\facade\Config;
8
+
9
+/**
10
+ * 自定义API模块的错误显示
11
+ */
12
+class ExceptionHandle extends Handle
13
+{
14
+
15
+    public function render(Exception $e)
16
+    {
17
+        // 在生产环境下返回code信息
18
+        if (!Config::get('app_debug')) {
19
+            $statuscode = $code = 500;
20
+            $msg = 'An error occurred';
21
+            // 验证异常
22
+            if ($e instanceof \think\exception\ValidateException) {
23
+                $code = 0;
24
+                $statuscode = 200;
25
+                $msg = $e->getError();
26
+            }
27
+            // Http异常
28
+            if ($e instanceof \think\exception\HttpException) {
29
+                $statuscode = $code = $e->getStatusCode();
30
+            }
31
+            return json(['code' => $code, 'msg' => $msg, 'time' => time(), 'data' => null], $statuscode);
32
+        }
33
+
34
+        //其它此交由系统处理
35
+        return parent::render($e);
36
+    }
37
+
38
+}

+ 0
- 0
application/command.php Datei anzeigen


Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.

Laden…
Abbrechen
Speichern