Compare commits

..

971 Commits

Author SHA1 Message Date
Karson c74b9dc24a 修复会员表结构字段
优化版本依赖
2024-09-06 11:05:48 +08:00
Karson 5188621e9c 优化语言包加载 2024-09-04 12:10:37 +08:00
Karson 7238632f14 更新版本号 2024-09-03 17:51:45 +08:00
Karson 4329b47c56 更新phpoffice/phpspreadsheet依赖版本 2024-09-03 17:48:46 +08:00
Karson 418d93f448 打包JS 2024-09-03 15:24:23 +08:00
Karson 8e88e1c9dc 优化格式化输出时文本编码
# Conflicts:
#	public/assets/js/require-table.js
2024-09-03 15:21:22 +08:00
Karson 592a42fcc5 优化后台参数传递 2024-09-03 15:17:29 +08:00
Simon a532001e7b 修复添加时,权重传值不生效 2024-09-03 15:15:44 +08:00
苏小马 9876dc22db update application/admin/command/Crud.php.
问题:删除模式时不需要强制读取数据表,如果在删除前已手动删除数据表,将抛出异常“table not found”导致删除失败

修复:增加删除模式判断,如果是删除模式,无需读取数据表

Signed-off-by: 苏小马 <179906767@qq.com>
2024-09-03 15:15:22 +08:00
AriFe.Liu 37c18d4e92 修复iframe打开窗口时链接内问号重复的问题
如果在添加菜单时, 在规则或URL中追加了 ? , 则会导致在刷新这个标签页时, 请求链接中使用了两个问号导致参数取出错误, 此处原本追加的?addtabs=1未做判断.

Signed-off-by: AriFe.Liu <88468560@qq.com>
2024-09-03 15:14:52 +08:00
AriFe.Liu 5c9c6ef89c 修复附件选择组件因没有id导致无法响应事件的bug
该问题曾导致附件选择组件在没有添加ID时造成无法正常响应回调事件
官方文档: https://doc.fastadmin.net/doc/183.html 附件选择 中, 示例及文档均未提及添加了faselect的组件必须添加id
如非bug而有意为之, 请修改官方文档说明, 告知用户添加了faselect的标签必须同时添加ID才能正确处理回调.
2024-09-03 15:14:52 +08:00
Karson 03ccce86f0 修复autocomplete选中后未触发验证的问题
修复Table.api.formatter.file无法渲染的问题
2024-09-03 15:10:11 +08:00
小和 18a661fb22 !465 修改字段错误问题
* 修复字段错误
2024-09-03 15:09:46 +08:00
AriFe.Liu c01628a914 修复sidebar左侧菜单因大小写问题导致的菜单列表不能正确显示问题
当手动添加菜单规则时, 规则字段未提示和处理用户录入的字符
在application/admin/library/Auth.php -> getSidebar方法中, 会对当前用户所拥有的权限节点做校验移除不相关的权限
当判断当前菜单规则是否在$userRule中时, 原本的$v['name']为直接从数据库中取出未做处理, $userRule则是通过$this->getRuleList方法获取, 方法内已经对字符串做了转小写处理, 假如用户填写了大小写混合的规则, 则此处将会被错误的移除掉; 导致左侧菜单无法正常展示该菜单规则;

Signed-off-by: AriFe.Liu <88468560@qq.com>
2024-09-03 15:08:20 +08:00
Karson 996fa27d62 优化数据表结构 2024-09-03 15:05:53 +08:00
Karson b13a1ca7b5 优化冗余参数 2024-09-03 10:53:19 +08:00
Karson c78b3aecc5 优化API资源URL 2024-09-03 10:53:19 +08:00
Karson 54d6e0904b 优化安装脚本资源加载 2024-09-03 10:53:19 +08:00
Karson 6d4aaf5ea8 优化邮箱验证码发送参数验证
# Conflicts:
#	application/api/controller/Ems.php
2024-09-03 10:50:29 +08:00
Karson 4a97bdaf19 Merge branch '1.x' of gitee.com:karson/fastadmin into 1.x 2024-09-03 10:28:37 +08:00
Karson 028a0b5f22 Fieldlist新增使用数组保存和保留空数据选项 2024-09-03 10:27:56 +08:00
Karson 7f54960449 优化PHP版本依赖 2024-09-03 10:27:24 +08:00
Karson e7c1922cf3 优化前台会员登录 2024-09-03 10:23:19 +08:00
Karson c5285b8fdd 优化多语言加载 2024-09-03 10:22:34 +08:00
Karson 5400c6ff2a 优化视图变量输出 2024-09-03 10:20:59 +08:00
建伟F4nniu 090381a1a2
!462 按照注释规则修改API模块下控制器方法的注解
Merge pull request !462 from Henry/fix_phpdoc
2024-07-03 08:03:46 +00:00
Karson 8f7a55928c 修复列表多个分组按钮显示优化 2024-04-01 15:08:03 +08:00
Karson e8a804afad 优化后台编辑默认加载模型 2024-03-28 12:03:14 +08:00
Karson 26a5ca4a2c 修改install.sql默认数据 2024-03-28 11:56:50 +08:00
Karson f932156c8e 修改默认配置 2024-03-28 11:55:34 +08:00
Karson 77c386ed47 打包新版本JS和CSS 2024-03-28 11:47:38 +08:00
Karson 57da65acd0 更新版本号 2024-03-28 11:25:20 +08:00
Karson 6dd941e168 表格导出新增提示文本 2024-03-28 11:16:38 +08:00
Karson b7eef593a3 优化插件配置分组
优化版本依赖
2024-03-28 09:58:30 +08:00
Karson 2b14bc4e2b 优化fa_test表测试数据 2024-03-28 09:42:20 +08:00
Karson 5e7d398b49 优化nice-validator依赖 2024-03-27 21:59:07 +08:00
Karson 58035763ea 优化本地安装点击事件判断 2024-03-27 20:17:41 +08:00
Karson 7c9841cee9 新增html转义函数
优化插件管理标题和描述
2024-03-27 17:40:08 +08:00
Karson a5e1fe9cb6 优化Http多维数组参数传递 2024-03-27 16:11:15 +08:00
Karson 1a31ac2dc9 新增无键名数组CRUD生成
优化注释判断
2024-03-27 15:09:05 +08:00
Karson 56b33f1514 移除冗余代码 2024-03-27 11:48:32 +08:00
Karson c5d26b7f3a 新增插件配置switch支持
优化默认SQL
2024-03-27 11:11:59 +08:00
Karson de002debd7 修改默认请求超时时长 2024-03-26 18:11:20 +08:00
Karson d1c240bd91 新增插件管理本地安装升级和版本检测
优化前台
优化fieldlist组件
优化上传组件传递参数
优化composer依赖
2024-03-26 17:51:30 +08:00
Karson e45b435b48 修复列表显示全部时选择卡切换问题 2024-03-26 09:48:43 +08:00
Karson aec4efe9e0 优化代码 2024-03-25 22:29:48 +08:00
Karson 0a5484b738 优化代码 2024-03-25 22:29:23 +08:00
Karson 9f2c08414a 优化后台管理日志记录 2024-03-25 22:28:43 +08:00
Karson 448eaad5f5 优化CRUD生成
优化代码
2024-03-25 15:26:58 +08:00
Karson 578044505a 移除JSONP加载 2024-03-25 14:58:39 +08:00
Karson 36bf77df6c 移除API基类自动判断JSONP的支持 2024-03-23 18:21:20 +08:00
Karson bac6606786 修复datetimerange组件自定义配置覆盖的问题 2024-03-14 20:11:31 +08:00
Karson 131ef803d1 修复后台修改个人资料导致退出的问题 2024-03-14 19:52:35 +08:00
Karson 17eb182063 修复超级管理员隐藏的组别不显示的问题 2024-03-14 19:44:35 +08:00
Karson f7e47d90f1 修复slider重复渲染时的问题 2024-03-14 18:56:17 +08:00
Karson a9a6065fde Merge branch '1.x' of gitee.com:karson/fastadmin into 1.x 2024-02-28 12:00:44 +08:00
Karson 6a94a07903 修复favisible中selectpage验证失效的问题
优化通用搜索
2024-02-28 12:00:37 +08:00
Karson 5bf63c557b 优化插件卸载 2024-02-28 12:00:04 +08:00
Karson 5d3d667143 修复分页选中all后刷新问题 2024-02-28 11:59:26 +08:00
Karson 8a0e8b1d3b 优化代码 2024-02-28 11:59:00 +08:00
Henry ee0be09841 style: 去掉多余的空格 2024-02-26 22:34:06 +08:00
Henry c8c1573c27 docs: 按照注释规则修改API模块下控制器方法的注解 2024-02-26 22:22:59 +08:00
F4nniu 76abb2dcd7 docs: 更新年份 2024-02-17 14:59:02 +08:00
F4nniu (FastAdmin开源框架) 787334972e
!460 fix: 优化 v1.x 对 php8 的兼容
Merge pull request !460 from F4nniu (FastAdmin开源框架)/php8_compatibily
2024-02-16 06:19:27 +00:00
F4nniu 1c7d0b710e nelexa zip 组件以 fastadmin-addons 组件中的依赖为准
(cherry picked from commit db487a52df)
2024-02-15 15:48:11 +08:00
F4nniu b0a3851d93 fix(common): getEncryptedToken 的 php8 兼容修复
(cherry picked from commit 198604c584)
2024-02-15 13:58:13 +08:00
F4nniu 2e4fe16f44 修复 select 生成在 php8 环境中的报错
(cherry picked from commit 7fe625cf5b)
2024-02-15 13:57:59 +08:00
F4nniu e3779a1f05 让 IDE 提示友好
(cherry picked from commit dd63aa5948)
2024-02-15 13:57:44 +08:00
F4nniu d8acbf8abe 使用框架 get 自带的过滤参数
(cherry picked from commit 85271d1cf7)
2024-02-15 13:57:36 +08:00
F4nniu e76eba0c21 优化 php8 兼容
(cherry picked from commit a64cf1173f)
2024-02-15 13:57:22 +08:00
F4nniu 7b66d0bf58 captcha 为了兼容 php8
(cherry picked from commit 927510f5ad)
2024-02-15 13:57:09 +08:00
F4nniu bb68eb4254 修复显示本地插件列表时的 null 值报错
(cherry picked from commit c7da57a109)
2024-02-15 13:56:58 +08:00
liuan 879554d5a8 fix(Lang): 修复后台登录时用户名和密码只有英文提示的问题 2024-02-13 16:27:59 +08:00
lande 403c6a2a7d 修复api上传文件时,最大上传文件报错,不显示语言包的提示语的bug
(cherry picked from commit d5b07d49cb)
2024-02-12 21:27:06 +08:00
Karson 42e91e10e6 优化菜单搜索结果点击 2024-01-26 16:34:13 +08:00
Karson 22628fca0e fieldlist组件一维数组支持 2023-12-27 14:38:17 +08:00
Karson 709424da7f 优化favisible适配textarea多行文本框 2023-12-14 18:04:13 +08:00
Karson ae57feebb6 修复语言检测未匹配时的错误
优化分片上传
2023-12-14 17:57:38 +08:00
Karson cdb41c5334 优化表格&表单 2023-10-26 17:08:14 +08:00
Karson 2fe68b4c27 Merge branch '1.x' of gitee.com:karson/fastadmin into 1.x 2023-08-30 16:57:37 +08:00
Karson 31b307a4a1 优化上传异常捕获 2023-08-30 16:56:33 +08:00
Karson 7fffba8963 优化表格列表图片显示 2023-08-30 16:27:03 +08:00
Karson 4aa2666c98 优化后台菜单切换 2023-08-30 16:24:11 +08:00
Karson 940e9a0814 新增IP发送频繁检测 2023-08-30 16:22:21 +08:00
Karson 901bea7d59 修复check_url_allowed判断不全的问题 2023-08-30 16:20:55 +08:00
bran faff9fc96f [fix]公共函数字节转换bug修复
(cherry picked from commit 6b4cdff2e0)
2023-08-30 08:35:33 +08:00
Karson cc9802877a 更新版本号1.4.0 2023-07-11 10:45:29 +08:00
Karson 681558500c 优化代码 2023-07-10 15:41:27 +08:00
Karson 033af96e1a 优化代码 2023-07-10 14:30:50 +08:00
Karson ad166f07eb 打包JS 2023-07-10 11:55:56 +08:00
Karson b76b99f3a0 优化跨域检测 2023-07-10 11:54:43 +08:00
Karson b4dc742fed 优化Sidebar 2023-07-09 11:52:57 +08:00
Karson 37e844181e 优化后台页面样式和Sidebar 2023-07-09 11:44:21 +08:00
Karson 1a660364af 优化通用搜索和表格Tabs联动 2023-07-07 16:39:42 +08:00
Karson 65068c9a29 修复checkbox和radio组件生成 2023-07-07 16:09:17 +08:00
Karson 4845b08077 新增自定义错误码模板配置 2023-07-07 15:45:53 +08:00
Karson b0387e668b 优化CRUD生成 2023-07-07 15:25:03 +08:00
Karson aca4e9221c 优化表单重置时组件渲染
移除CRUD默认的重置按钮
优化上传大小判断
优化插件管理
2023-07-07 14:56:30 +08:00
Karson 7a4d05bbd9 优化语言包
优化cdnurl计算
2023-07-06 18:19:21 +08:00
Karson a9003ecce4 优化获取类名兼容性 2023-07-06 17:31:21 +08:00
Karson 7f28eaa570 优化服务端cdnurl函数计算
优化代码
2023-07-05 15:03:30 +08:00
Karson 31528c8951 修复cdnurl处理
新增表格列表内容弹窗hover模式
2023-07-05 11:38:57 +08:00
Karson e4094e24e2 优化代码 2023-07-04 10:05:40 +08:00
Karson 80ad9e307a 优化事件绑定 2023-07-03 16:58:33 +08:00
Karson e6f2894af7 新增表格列表内容弹窗展示 2023-06-28 18:14:34 +08:00
Karson c170985264 优化表格拖拽移动时tooltip定位 2023-06-28 15:20:08 +08:00
Karson 9d9c8dcf52 优化拖拽选择复选框功能
移除drag和drop依赖
2023-06-27 11:47:45 +08:00
Karson 4eaae6ac4e 优化站内相对链接判断
优化后台链接跳转
2023-06-20 16:46:04 +08:00
Karson 7e6314a701 增强后台安全限制
优化登录判断机制
移除冗余代码
2023-06-20 11:51:59 +08:00
Karson a40420b8cd 优化CRUD生成 2023-06-20 10:10:55 +08:00
Karson 31a6a47dc3 优化菜单规则管理 2023-06-19 18:12:01 +08:00
Karson 2c66c67d71 优化权限规则列表显示 2023-06-16 23:05:05 +08:00
Karson 42fdea065a 优化前台验证提示位置 2023-06-16 21:13:21 +08:00
Karson 0fb7924420 优化前台弹窗宽高 2023-06-16 21:13:06 +08:00
Karson 7cd9281c74 优化前台模板 2023-06-16 21:12:49 +08:00
Karson dc466cb1c9 优化url检测 2023-06-16 17:45:07 +08:00
Karson d2c275e20a 优化检测的URL默认值 2023-06-16 17:37:34 +08:00
Karson 3549e95ea1 新增URL检测和清理函数
优化登录和注册链接跳转
2023-06-16 17:32:00 +08:00
Karson a34c5fbdb8 优化打包JS 2023-06-16 16:33:35 +08:00
Karson 9fe7a6f9a0 优化拖拽组件高版本jQuery兼容 2023-06-16 16:33:16 +08:00
Karson 94b61ab7dc 优化bower依赖 2023-06-16 14:39:34 +08:00
Karson 6ebde793bd 优化插件管理JS 2023-06-16 14:38:52 +08:00
Karson faca2e0be0 优化前后台JS和CSS 2023-06-16 14:38:39 +08:00
Karson 7c8d1a6683 优化验证码 2023-06-16 11:32:58 +08:00
Karson 76c48c00fd 优化插件管理
兼容旧版本jQuery的size方法
2023-06-16 11:32:58 +08:00
Karson bc6bcb84f9 优化插件管理
# Conflicts:
#	public/assets/js/backend/addon.js
2023-06-16 11:03:58 +08:00
Karson 5f35be92b5 优化CRUD代码 2023-06-16 11:03:14 +08:00
Karson 9cadda6c3f 优化插件管理前端JS代码 2023-06-15 15:30:33 +08:00
Karson 152ac2cdcb 优化插件管理前端代码 2023-06-15 15:29:43 +08:00
Karson cd5fafde38 移除默认的api文档
优化api文档生成文本框
2023-06-15 15:08:54 +08:00
Karson 3d4a02160c 优化jQuery兼容
优化插件管理功能
2023-06-15 15:02:09 +08:00
Karson 1004bdedad Merge branch 'develop' of gitee.com:karson/fastadmin into develop
# Conflicts:
#	composer.json
2023-06-15 14:53:08 +08:00
Karson 8ce9f1f884 优化会员编辑模板 2023-06-15 14:52:41 +08:00
Karson bfd3737b28 按钮配置属性extend支持function 2023-06-15 14:52:29 +08:00
Karson 4b4d4493aa Merge branch 'develop' of gitee.com:karson/fastadmin into develop 2023-06-15 14:51:53 +08:00
Karson 164a4f6664 新增think-queue配置 2023-06-15 14:51:39 +08:00
Karson e59668c0f2 优化iOS下后台样式 2023-06-15 14:50:57 +08:00
Karson afe0fdce73 修复超级管理管理员无法显示部分日志的问题 2023-06-15 14:50:44 +08:00
Karson 03f46a637d !437 解决fast/Date::unixtime() 报错非法日期BUG
Merge pull request !437 from bluehow/develop
2023-06-15 14:50:15 +08:00
Karson 0a062a8d63 !439 CRUD生成字段默认值bug
Merge pull request !439 from Jon/develop
2023-06-15 14:49:52 +08:00
小和 db5574582e !435 修改密码类型默认手机号
* 修改密码类型默认手机号
2023-06-15 14:48:02 +08:00
王子涵 e4ef9f1db0 update application/common/controller/Backend.php.
修复后台控制器关联查询参数设置无效

Signed-off-by: 王子涵 <3125976329@qq.com>
2023-06-15 14:47:49 +08:00
Karson 2c02ed019b 优化本地上传cdnurl
修复移动端站点配置不可见的问题
优化代码和注释
2023-03-09 17:42:20 +08:00
F4nniu eee98aad11
!432 更新统一数据库字符集
Merge pull request !432 from F4nniu/fix-utf8mb4
2023-02-19 13:31:24 +00:00
F4nniu 6d8f98a554 更新统一数据库字符集 2023-02-19 21:28:29 +08:00
Karson 9e9d729c62 优化打包压缩CSS 2022-12-14 20:20:43 +08:00
Karson 91549dbfea 优化CRUD回收站生成
优化前台多语言
优化组件样式
2022-12-14 17:16:33 +08:00
Karson 85f0e4f041 修改默认允许上传的文件
更新版本号
优化上传归类
优化默认GIT忽略
2022-12-14 16:35:58 +08:00
Karson 17256db17a 优化前台默认首页 2022-12-14 16:34:33 +08:00
Karson 2b81652072 优化会员规则多语言 2022-12-14 16:33:49 +08:00
Karson e846244791 优化语言包获取 2022-12-14 16:33:10 +08:00
F4nniu f8292a8813
!421 调整正确的语言文字
Merge pull request !421 from 椒颜玉米粒/N/A
2022-11-07 08:42:00 +00:00
椒颜玉米粒 89ed50d790
调整正确的语言文字,用户名必须6-30个字符
Signed-off-by: 椒颜玉米粒 <1336749157@qq.com>
2022-11-03 04:59:14 +00:00
F4nniu 776a4fedb5
!417 修复插件配置页面显示问题
Merge pull request !417 from sixXing/develop
2022-10-25 15:23:22 +00:00
sixXing 6b190fe111
update application/admin/view/addon/config.html.
1.解决按钮换行问题,2.解决图片和文件组件有tip时重叠问题

Signed-off-by: sixXing <464401240@qq.com>
2022-10-13 11:45:28 +00:00
F4nniu d562f3d17c 更新 less 文件 2022-10-10 00:06:47 +08:00
F4nniu 445f0c94d8 调整 select padding-right
压缩 css
2022-09-30 20:49:02 +08:00
F4nniu 6e4321d5cf
!381 优化 select.form-control 样式遮盖选中项文字
Merge pull request !381 from 黑化肥挥发会发灰/N/A
2022-09-30 12:40:17 +00:00
F4nniu 6d4d564ae8 压缩 css js 2022-09-30 10:56:39 +08:00
F4nniu b1af836710 更新 QQ 群信息。 2022-09-28 16:40:37 +08:00
F4nniu 7f0e4eb25c 删除无用的 build.php 2022-09-28 16:23:58 +08:00
F4nniu eaa1832852 easywechat 升级 2022-09-04 00:17:13 +08:00
F4nniu aa9fa16451 升级 bootstrap-select
解决微软拼音下输入重复的文字问题
2022-08-04 22:58:50 +08:00
F4nniu f02121cfcc 更新 Layer 地址 2022-07-25 21:53:24 +08:00
F4nniu 4fb1379afa 修改在线安装 php 版本要求 2022-07-20 22:49:31 +08:00
Karson ae935d260c
!409 导入时枚举型字段型备注只截取冒号前面部分
Merge pull request !409 from simon429/develop
2022-07-06 01:55:14 +00:00
Karson 44887b8aec
!411 fix: 回收站无法清空
Merge pull request !411 from Henry/fix
2022-07-06 01:53:15 +00:00
Henry 1dbafec12f fix: 回收站无法清空 2022-07-06 09:48:08 +08:00
F4nniu 9a90cbe329 phpspreadsheet 升级到 1.19 2022-06-23 18:51:21 +08:00
F4nniu ae11381309 php 版本依赖修改为大于等于 7.2 2022-06-23 00:31:19 +08:00
F4nniu c3bdbbf779 忽略 .user.ini 2022-06-15 23:56:37 +08:00
simon 3ba1192555 导入时字段备注截取 2022-06-09 21:04:52 +08:00
Karson 80d928d872 修复联动调用选择空时的错误 2022-06-07 10:00:20 +08:00
Karson b9877c10ff 移除冗余代码 2022-05-30 14:51:49 +08:00
Karson a7983055fc 优化密码长度判断
新增个人资料手机号显示
2022-05-30 11:24:23 +08:00
Karson 3fd25c3de0 优化表格分页判断
优化语言包加载
2022-05-27 17:14:33 +08:00
Karson 85e0f4bacc 管理员表新增mobile字段 2022-05-27 16:21:36 +08:00
Karson b3c4692164 优化注释文字 2022-05-27 15:51:31 +08:00
Karson dfeff713bd 优化配置打包JS 2022-05-27 15:46:18 +08:00
Karson f639cecf73 Merge https://github.com/karsonzhang/fastadmin into develop
# Conflicts:
#	application/config.php
#	public/assets/js/require-backend.min.js
#	public/assets/js/require-frontend.min.js
2022-05-27 15:45:07 +08:00
Karson 466aa4233f 优化安装脚本
优化JS
2022-05-27 15:33:27 +08:00
Karson 378aad4409 Merge branch 'develop' of gitee.com:karson/fastadmin into develop 2022-05-27 15:30:18 +08:00
Karson 1a07280f79 优化语言获取和加载 2022-05-27 15:30:05 +08:00
Karson b3e172ddce 优化后台菜单生成
优化菜单写入字段
2022-05-27 15:03:00 +08:00
Karson 079f6df9dd 优化生成表格工具栏按钮权限判断 2022-05-27 15:01:40 +08:00
Karson 9400aa6e4e 优化size判断 2022-05-27 11:47:02 +08:00
Karson 0b4eceea54 移除头部语言配置 2022-05-27 11:43:42 +08:00
Karson ce811fa904 优化默认安装脚本 2022-05-27 11:43:01 +08:00
Karson 06ccca81c9 优化CRUD生成 2022-05-27 11:42:51 +08:00
F4nniu be2f342044
!398 解决在使用Table组件时,传递pageSize参数确被设置全局的问题,并开放全局页面显示数方法
Merge pull request !398 from Henry/page-size
2022-05-17 14:11:39 +00:00
Henry 1898e481e0 压缩打包 backend、frontend 文件 2022-05-09 09:26:29 +08:00
Henry e59d2df641 将插件页面表格的设置分页数方法修改为全局统一的方法 2022-05-09 09:26:04 +08:00
F4nniu f4795e90da 为适应现代编辑器 VSCODE 的提示修改注释 2022-05-08 09:50:15 +08:00
F4nniu 9eeb35a79b 修改 framework git 地址 2022-05-07 23:43:44 +08:00
F4nniu 12e7bbf5f2
!400 代码优化,处理destroy方法未判断$ids变量为空情况的问题
Merge pull request !400 from Henry/optimize
2022-05-07 05:16:43 +00:00
Henry 940d4f0410 代码优化 2022-05-04 11:49:25 +08:00
Henry 5022da9cea 解决在使用Table组件时,传递pageSize参数确被设置全局的问题,并开放全局页面显示数方法 2022-04-25 15:26:41 +08:00
F4nniu 8e48aa7edd
!396 语言文件说明不对
Merge pull request !396 from dublog/develop
2022-04-20 00:20:51 +00:00
qhyan 036c2dcc25 修复说明不对 2022-04-19 20:29:47 +08:00
F4nniu 9116b86a36
!385 基于PHP7.1版本使用Catch多个语句
Merge pull request !385 from Henry/optimiza-exception-handle
2022-04-02 09:13:44 +00:00
F4nniu a4617c7f28 修复验证码不支持 0 开头的错误 2022-04-01 09:58:43 +08:00
F4nniu a9a5696f58
!389 修改think框架引导文件为绝对路径
Merge pull request !389 from aa820t@126.com/master
2022-03-31 15:33:06 +00:00
F4nniu e67f9a20c5 修复手机验证码不支持 0 开头的错误 2022-03-31 23:28:16 +08:00
aa820t@126.com ff51380686
修改 think 框架引导文件 为绝对路径
在非本项目根目录下, 执行php think 命令行, 使用引用路径会导致后续代码无法执行.
2022-03-30 08:21:20 +00:00
Henry 2a97c21c0c 基于PHP7.1版本使用Catch多个语句 2022-02-17 14:54:57 +08:00
Karson 6470a8ed74 更新版本号&依赖
压缩打包JS文件
2022-01-21 16:00:42 +08:00
Karson d7ffc690c2 更新JS压缩文件 2022-01-21 15:31:52 +08:00
Karson 41682bd850 更新版本号和依赖 2022-01-21 15:29:59 +08:00
Karson 2401cd3ba1 优化插件配置分组 2022-01-21 11:36:57 +08:00
Karson 8b55020b3a 优化邮件发送插件检测 2022-01-21 09:33:29 +08:00
Karson 3dd4276ca1 优化插件插件列表
优化登录注册模板注释,便于插件整合。
2022-01-20 22:05:05 +08:00
Karson 02cc257faf 新增是否默认展示子菜单功能 2022-01-20 17:23:43 +08:00
Karson cd60f5f381 优化控制台统计 2022-01-20 16:46:02 +08:00
Karson d4531c7df0 优化计算月天数 2022-01-20 16:00:57 +08:00
Karson d2d6648d9c Merge branch 'develop' of gitee.com:karson/fastadmin into develop 2022-01-20 15:48:43 +08:00
Karson f700dbcdc7 修复表格dropdown被遮挡的BUG
优化表格
优化动态显示组件
2022-01-20 15:48:36 +08:00
黑化肥挥发会发灰 9ed5fbd2e3
优化 select.form-control 样式遮盖选中项文字 2022-01-20 03:13:19 +00:00
Karson 6569dfbb3c
!377 修复当 $myid 为字符串类型时出现的无限递归的问题
Merge pull request !377 from T2cc/N/A
2022-01-20 02:47:14 +00:00
Karson c87a7f9f3c 优化插件配置分组
优化fieldlist触发事件
2022-01-20 10:43:23 +08:00
T2cc 077bba602d
修复当 $myid 为字符串类型时出现的无限递归的问题
由于 PHP 中 0 == 字符串永远等于 true 的特性,该处容易出现判断错误。
- 为什么不用 ===
因为很多情况可能会搞不清当前数据是 0 还是 '0' ,使用类型转换更加兼容。
2022-01-18 05:27:34 +00:00
Karson edec9f7fe1 Merge branch 'develop' of gitee.com:karson/fastadmin into develop 2022-01-16 19:05:04 +08:00
Karson 0c950a93cf 优化隐藏元素验证 2022-01-16 19:04:48 +08:00
F4nniu 96cb06f825 Copyright 2017-2022 2022-01-16 18:47:30 +08:00
Karson 65073c30b9 新增插件配置分组功能
新增插件配置分组功能
新增插件配置可见条件
新增常规管理系统配置可见条件功能
新增表单可见条件组件
2022-01-16 18:37:33 +08:00
Karson d785d321f7 优化移动端弹窗大小判断
优化上传文件名过滤
2022-01-13 14:41:43 +08:00
Karson ff85bb3b21 修复新版本密码长度导致无法添加管理员的BUG
优化错误提示
2022-01-13 09:38:44 +08:00
Karson c65e3b23e2 优化默认SQL 2022-01-12 01:37:47 +08:00
Karson c00584a5b5 优化插件管理&优化前台弹窗 2022-01-12 01:23:41 +08:00
Karson 017970a2e3 优化用户名密码长度检测
优化CRUD文件类型
移除cdnurl后台系统配置中配置
修复附件选择列表无法上传文件的BUG
2022-01-08 15:00:25 +08:00
Karson 5673ce72fd 优化插件管理请求接口 2022-01-01 01:59:32 +08:00
Karson 4fa13dca39 更新bower依赖 2022-01-01 00:19:32 +08:00
Karson 8366745875 优化打包压缩文件 2021-12-31 22:46:04 +08:00
Karson a176508bb9 优化依赖 2021-12-31 22:04:02 +08:00
Karson 5ea57836c8 优化CRUD 2021-12-31 21:51:16 +08:00
Karson 82ea7fcd15 优化依赖版本 2021-12-31 21:36:40 +08:00
Karson 03bfe24893 优化addtion
优化blockquote字体大小
2021-12-31 18:18:59 +08:00
Karson acca6a0584 优化后台首页Cookie限制path 2021-12-29 11:03:24 +08:00
Karson 12ea49d937 修复后台首页Cookie限制path 2021-12-29 10:46:54 +08:00
Karson 41fc1d1b3a 优化控制栏&时间区间回调 2021-12-28 16:43:28 +08:00
Karson b52df110ee 优化后台控制栏选项 2021-12-27 16:03:01 +08:00
Karson e3169bcaf4 新增验证码长度配置&优化后台管理菜单 2021-12-27 15:53:09 +08:00
Karson d1cdc51798 修复默认皮肤
改进内置服务器
优化伪静态
2021-12-17 16:01:59 +08:00
Karson cd9d2e6516 Merge branch 'develop' of gitee.com:karson/fastadmin into develop
# Conflicts:
#	application/admin/command/Crud.php
#	application/common/library/Upload.php
#	public/assets/js/require-form.js
2021-12-17 15:59:04 +08:00
Karson bc99cfddd8 优化附件选择 2021-12-17 15:30:49 +08:00
Karson a7317ec08c 新增一对多CRUD功能
新增一键生成tagsinput组件
新增autocomplete和tagsinput前端组件
新增配置全局启用上传时使用完整URL功能
新增附件列表上传归类
新增后台管理布局个性化配置
新增插件安装成功导入测试数据提示
新增后缀生成icon缓存
新增全局列表图片缩略图样式
优化插件安装后自动刷新JS缓存
优化后台管理左侧菜单展现方式
优化全局样式
优化fieldlist组件
优化上传组件归类功能
优化Bootstrap-table固定列高度
优化Layer弹窗焦点框
2021-12-16 10:47:18 +08:00
Karson 824f337481 优化复合组件检测 2021-12-02 09:41:45 +08:00
Karson fce5b8e374 新增自动生成标签&时间区间组件
新增自动生成固定列
新增弱密码检测
优化插件相关命令行命令
优化默认管理员和前台用户头像
2021-12-01 16:35:07 +08:00
Karson 093d60b719 !371 修复CRUD关联模型时,自定义控制器生成的被关联表模型namespace错误
Merge pull request !371 from simon429/develop
2021-11-18 01:54:50 +00:00
F4nniu a7b7b772fd 删除 public 目录下无用的 router.php。 2021-11-11 18:46:59 +08:00
simon429 48066342da 修复CRUD关联模型下,自定义控制器生成的被关联表模型namespace错误 2021-10-26 16:31:38 +08:00
F4nniu ecdaa81a8d !370 修正fa_area中城市层级说明
Merge pull request !370 from lscho/master
2021-10-26 06:21:27 +00:00
Karson a87cfefd50 !361 修复fieldlist在初始值为空时,不会绑定fa.event.refreshfieldlist事件
Merge pull request !361 from 诛心/fieldlist
2021-10-20 08:06:16 +00:00
Karson e7f8b75ecf !327 修复 User 模型附加字段不存在时导致接口报错的 BUG。
Merge pull request !327 from T2cc/N/A
2021-10-20 08:02:52 +00:00
Karson 9a239de4b1 !364 update application/common/library/Upload.php.
Merge pull request !364 from 御宅男/N/A
2021-10-20 07:55:25 +00:00
lscho db69a36d28 修正城市层级说明 2021-10-15 08:05:06 +00:00
Karson 70b9c8affe 更新版本号 2021-10-11 17:22:37 +08:00
Karson 9d1d1248e9 Merge branch 'develop' of gitee.com:karson/fastadmin into develop 2021-10-11 17:17:21 +08:00
Karson 84eef812f3 修复上传文件的安全隐患 2021-10-11 17:16:48 +08:00
F4nniu 0266534d95 !365 前台关闭会员中心时,报错信息页链接跳转到首页
Merge pull request !365 from simon429/develop
2021-10-06 10:06:25 +00:00
simon429 e7ae8fb4d1 关闭会员中心时,报错信息页链接跳转到首页 2021-10-01 23:22:54 +08:00
御宅男 c12f9bca84 update application/common/library/Upload.php.
多余类属性
2021-09-26 02:02:26 +00:00
F4nniu 69ad41a3e9 !362 【轻量级 PR】:修复当文件名称为中文时截断导致的乱码报错问题。
Merge pull request !362 from T2cc/N/A
2021-09-12 14:02:29 +00:00
T2cc 3a0352e183 修复当文件名称为中文时截断导致的乱码报错问题。 2021-09-10 01:44:42 +00:00
诛心 ab53e00a75 修复fieldlist在初始值为空时,不会绑定fa.event.refreshfieldlist事件 2021-09-08 20:12:57 +08:00
Karson ac2b3ffbff !358 表格赛选为BETWEEN时 无法筛选0,0的数据
Merge pull request !358 from 码龍/editBuildParams
2021-08-27 15:06:40 +00:00
码龍 9cf930a932 修改表格BETWEEN查询 值为0时无法筛选问题 2021-08-24 06:48:39 +00:00
Karson 343ad6055d !348 解决huamn函数无法格式未来时的问题
Merge pull request !348 from Henry/prefect
2021-08-09 02:06:06 +00:00
Karson 2ab8d7fa81 !349 系统配置新增密码类型参数
Merge pull request !349 from Henry/prefect2
2021-08-09 02:04:49 +00:00
Karson f3b841c456 !350 插件配置新增密码类型参数
Merge pull request !350 from Henry/prefect3
2021-08-09 02:04:21 +00:00
Henry 683d837d20 插件配置新增密码类型参数 2021-08-05 16:45:49 +08:00
Henry 3f1cac43e4 系统配置新增密码类型参数 2021-08-05 16:23:57 +08:00
Henry 75141e192d 解决huamn函数无法格式未来时的问题 2021-08-05 16:03:24 +08:00
Karson 1d7c7d7ce6 更新新版本
移除冗余代码
2021-07-30 16:17:04 +08:00
Karson 6178a5c231 优化附件未归类功能 2021-07-28 09:59:58 +08:00
Karson 0c7a4850a2 优化报错页面 2021-07-23 17:29:44 +08:00
Karson a502a98df0 新增附件类型筛选
优化Layer样式
2021-07-23 17:13:25 +08:00
Karson 3d9805eea7 优化Layer弹窗
优化表格Loading
优化个人日志搜索
优化附件列表
2021-07-21 18:30:03 +08:00
Karson 8925cbc9f7 优化API生成提示
优化后台菜单刷新
2021-07-21 14:51:42 +08:00
Karson 80a25c9749 优化附件列表变量输出
优化前后台退出机制
2021-07-20 17:34:38 +08:00
Karson 70e215cdde !340 优化菜单规则的拼音转换
Merge pull request !340 from Henry/repair
2021-07-20 02:45:47 +00:00
Henry f501638884 优化菜单规则的拼音转换 2021-07-20 09:39:54 +08:00
Karson d1d3ea1dea 修复后台边框部分情况下无法滚动的BUG
优化后台刷新跳转效率
优化后台直接跳转登录
优化后台菜单搜索
优化前台变量输出
2021-07-19 21:49:20 +08:00
Karson b3b9a61723 !319 优化后台首页打开速度,避免后期菜单过多导致加载超时问题
Merge pull request !319 from Henry/develop_2
2021-07-19 09:49:40 +00:00
Karson 60902457bb 优化跨域提示 2021-07-19 15:44:14 +08:00
Karson 8b11a77294 !330 cors 检测的时候不直接使用 exit
Merge pull request !330 from T2cc/N/A
2021-07-19 07:28:56 +00:00
Karson dfd0ba0524 !302 点击表格删除按钮后 Layer弹出层 确定 取消 按钮 的 多语言 版本修改
Merge pull request !302 from Ylianjie/develop
2021-07-19 06:39:14 +00:00
Karson 5599170ce4 !337 解决api模块上传文件获取不到uid的问题
Merge pull request !337 from Henry/repair
2021-07-19 06:36:30 +00:00
Karson ca8238df06 !339 解决后台用户余额和积分修改时,因类型不同导致写入记录表无用记录的问题
Merge pull request !339 from Henry/repair_user_fund
2021-07-19 06:34:54 +00:00
Karson 2c4efe19f2 Merge remote-tracking branch 'origin/develop' into develop 2021-07-19 11:59:56 +08:00
Karson b3d32e2bf3 修复前台URL跳转输出 2021-07-19 11:59:03 +08:00
Henry 4275d769ea 解决后台用户余额和积分修改时,因类型不同导致写入记录表无用记录的问题 2021-07-13 11:43:17 +08:00
Henry 9b4d4c78dd 解决api模块上传文件获取不到uid的问题 2021-07-12 14:10:22 +08:00
Karson 88df7bd386 !336 解决Form类内部分方法的可选参在必选参前的问题
Merge pull request !336 from Henry/repair
2021-07-10 01:55:09 +00:00
Henry 503c67f9d8 解决Form类内部分方法的可选参在必选参前的问题 2021-07-09 13:34:32 +08:00
F4nniu e05a8584e2 !333 解决 Soft 单词拼写错误问题
Merge pull request !333 from Henry/naming
2021-07-07 04:50:08 +00:00
F4nniu 82748502ea !335 修复API模块用户中心关闭无效
Merge pull request !335 from F4nniu/fix-user-api
2021-07-07 04:47:18 +00:00
King超 33788fc93e 修复API模块用户中心关闭无效 2021-07-07 12:37:44 +08:00
Henry 49ed6cb808 解决 Soft 单词命名错误问题 2021-07-07 10:29:53 +08:00
Karson 4b1c3a3b4b Merge branch 'develop' of gitee.com:karson/fastadmin into develop 2021-07-05 11:47:38 +08:00
Karson a5163732af 优化附件选择按钮事件 2021-07-05 11:28:16 +08:00
Karson 7adc36ae4f 优化一键生成API文档
优化接口请求方法
2021-07-05 11:27:38 +08:00
Karson 9a7e29a080 优化CRUD自定义删除字段
优化CRUD无符号字段
2021-07-05 11:23:13 +08:00
T2cc 3dfa4f7065 cors 检测的时候不直接使用 exit 2021-06-22 02:32:54 +00:00
T2cc bbf728b5e6 修复 User 模型附加字段不存在时导致接口报错的 BUG。 2021-06-18 14:51:08 +00:00
F4nniu 99c3fac317 !326 修复crud生成文件时未选中id主键时导致编辑功能错误问题
Merge pull request !326 from F4nniu/crud-pk
2021-06-17 07:03:44 +00:00
wanger 614384a2bb 修复crud生成文件时未选中id主键时导致编辑功能错误问题 2021-06-17 13:36:32 +08:00
F4nniu 44fd7d14ec !320 发送邮件时,邮件服务商返回的错误信息编码导致系统500错误
Merge pull request !320 from Henry/email_revision
2021-06-17 03:23:01 +00:00
F4nniu c615eda2dc !324 禁止系统表被 CRUD
Merge pull request !324 from F4nniu/prohibit-crud-system-tables
2021-06-15 15:34:10 +00:00
F4nniu e5235a4991 将用户相关表加入到受保护列表中。 2021-06-15 23:28:58 +08:00
wanger f05e5ae722 添加admin_log表 2021-06-15 23:00:08 +08:00
wanger 6edcaffa75 防止系统表文件生成被覆盖 2021-06-15 22:59:58 +08:00
F4nniu de1c8194f1 !322 修复顶部选项卡icon不显示问题
Merge pull request !322 from King超/master
2021-06-15 06:35:26 +00:00
King超 364d7be7ba 修复顶部菜单icon不显示问题 2021-06-10 11:00:46 +08:00
Henry e0b008097a 解决邮件发送时,腾讯企业邮箱返回的错误信息编码影响系统报错的问题 2021-06-09 14:08:50 +08:00
Henry 4f28da05fe 移除拼音转换,代码优化 2021-06-09 14:03:49 +08:00
Henry a5eb7ef3b7 模型定义拼音自动完成字段 2021-06-09 14:03:12 +08:00
Henry 119b0273d4 auth_rule表冗余拼音字段 2021-06-09 14:02:35 +08:00
Karson bba702bd18 优化分片上传 2021-06-04 15:21:28 +08:00
Karson 8130a49fab 优化通用搜索表单重置
优化标签渲染
2021-06-04 14:57:03 +08:00
Karson 4e657d1f25 优化权重排序
优化权限规则写入
2021-06-04 14:53:42 +08:00
Karson ed0da37370 优化附件归类文字和权限判断 2021-06-04 14:49:23 +08:00
Karson bf720c176e 新增附件分类功能
新增上传接口设定分类功能
优化分片上传进行百分比计算
优化系统配置字典文字显示
2021-06-04 14:33:44 +08:00
Karson 7045f0d02b Merge branch 'develop' of gitee.com:karson/fastadmin into develop 2021-05-19 17:54:04 +08:00
Karson cdebb284c3 优化菜单规则中的条件判断 2021-05-19 17:52:34 +08:00
F4nniu 9f725a062a !299 修复生成 api 文档报错的 bug
Merge pull request !299 from T2cc/N/A
2021-05-13 22:35:31 +08:00
F4NNIU d59ff78fd8 修改 framework type 为 git 2021-05-13 22:31:12 +08:00
unknown 289e9306bd 点击表格删除按钮后 LAYER 弹出层 确定取消 按钮的 多语言版本 修改 2021-04-13 13:11:54 +08:00
F4nniu dba3ca50fc !300 优化user的Validate验证
Merge pull request !300 from YouKnow/master
2021-04-12 14:10:35 +08:00
T2cc 6b9e5ba379 修复生成 api 文档报错的 bug 2021-04-11 22:36:53 +08:00
YouKnow 1c62049989 优化user验证 2021-04-09 15:56:03 +08:00
Karson ab083686a8 优化积分和余额并发 2021-04-06 12:03:25 +08:00
Karson 0236b39df0 修复控制台管理员统计 2021-04-02 11:07:29 +08:00
Karson 61cf9c2896 修复菜单规则缺少url字段 2021-04-02 11:05:41 +08:00
Karson 04e1f7f0ae 更新版本号
移除冗余图片
2021-04-01 22:26:14 +08:00
Karson 43d8570337 优化验证控制器变量名 2021-04-01 18:05:48 +08:00
Karson f1fb9cbb3b 优化插件目录和缓存目录缓存 2021-04-01 17:53:47 +08:00
Karson e1fe326597 优化Fieldlist组件
优化上传图片顺序
优化Composer依赖
2021-04-01 17:46:42 +08:00
Karson 454fdcded9 修复分片上传参数过滤不严的BUG 2021-04-01 17:36:22 +08:00
Karson a58a147143 优化后台离线上传 2021-04-01 17:35:42 +08:00
Karson 4db3a64b2c 前后台API添加IP屏蔽功能 2021-04-01 17:34:14 +08:00
Karson 0c494ee2b3 优化接口请求方式 2021-04-01 17:31:59 +08:00
Karson 132dc960b2 修复分片上传兼容性BUG
优化后台控制器分组统计
优化清除缓存提示
2021-03-29 23:42:39 +08:00
Karson 5ad3d5125a 新增清除浏览器缓存功能
调整优化下拉菜单的字体大小和间距
2021-03-29 16:44:00 +08:00
Karson dc691dbe84 新增菜单规则自定义URL功能
新增菜单规则弹窗&Ajax&外部链接功能
修复清除缓存事件重复绑定的BUG
2021-03-29 11:47:07 +08:00
Karson 120aa6b200 优化过期Token删除
优化控制台DAU计算
2021-03-28 16:04:50 +08:00
Karson cd37a9e7e4 优化后台右上角图标文字
优化权限规则菜单展示
2021-03-27 23:03:59 +08:00
Karson e2772b164b 优化控制台数据显示 2021-03-27 23:02:33 +08:00
Karson ab89d2d506 优化API文档模板参数格式校验 2021-03-26 16:46:52 +08:00
Karson 916722b139 修复插件配置规则图片和文件类型时不生效 2021-03-26 14:57:04 +08:00
Karson 65daf79d0a 优化备案链接 2021-03-26 11:04:52 +08:00
Karson e4b2066e0f 优化管理员增删改事务处理 2021-03-26 11:01:22 +08:00
Karson d610fe6141 Merge remote-tracking branch 'origin/develop' into develop 2021-03-26 10:58:35 +08:00
Karson 97a6b53cec 优化管理员增删改事务处理 2021-03-26 10:58:11 +08:00
F4NNIU 63d74029f0 关闭后台验证码的自动完成 2021-03-25 11:29:34 +08:00
Karson fb2b5aed30 Merge remote-tracking branch 'origin/develop' into develop 2021-03-24 11:18:33 +08:00
Karson c7126fe7bb 优化拼音转换 2021-03-24 11:18:12 +08:00
F4NNIU 2e74e2c119 更新 Composer 依赖 2021-03-18 15:33:18 +08:00
F4NNIU 2b5cab420a Merge branch 'master' into develop 2021-03-14 20:20:52 +08:00
Karson f815edab04 !212 补充Auth兼容调用user模型的属性
Merge pull request !212 from caiqy/master
2021-03-10 14:19:11 +08:00
Karson 0b752a3dfa !282 后台系统配置发送测试邮件时,多语言变量传入支持格式错误
Merge pull request !282 from Henry/revision
2021-03-10 11:02:29 +08:00
Karson 6bac3fca8a !286 解决自带Email类发送邮件收件人账号名是数组索引,移除未使用形参
Merge pull request !286 from Henry/email_bug_revision
2021-03-10 11:01:08 +08:00
Karson 025f37f1f1 !288 在线命令生成Api文档,控制器设置隐藏仍然会显示
Merge pull request !288 from Henry/api_revision
2021-03-10 10:40:23 +08:00
Karson 89e14e8f8b 安装脚本PHP版本检测 2021-03-10 10:28:32 +08:00
Karson 74a4aacb66 修复unixtime方法计算周偏移时的错误 2021-03-10 10:27:32 +08:00
Karson 6c0ea3a420 修复邮件发送错误 2021-03-10 10:26:54 +08:00
F4NNIU b909c0b27a 后台禁止搜索引擎收录和索引。 2021-03-09 15:58:19 +08:00
Henry 8127f56953 解决在线命令生成Api文档,控制器设置隐藏仍然会显示问题 2021-03-09 09:17:33 +08:00
F4nniu 351475ca06 !285 解决生成CURD时,指定需要使用富文本编辑器字段时的报错问题
Merge pull request !285 from Henry/revision3
2021-03-02 16:17:42 +08:00
Henry 5d7181fe1d 移除to方法从未使用的$name变量 2021-03-02 13:45:19 +08:00
Henry 5ab531e0ec 解决使用自带Email类发送邮件时,收件人账号名是数组索引的问题 2021-03-02 13:44:37 +08:00
Henry dc5a2e01dd 解决生成CURD时,指定需要使用富文本编辑器字段时的报错问题 2021-03-02 13:21:22 +08:00
Henry 5a9dbf0abf 解决后台系统配置发送测试邮件时,语言包变量未替换的问题 2021-03-02 13:08:35 +08:00
F4NNIU af241c7c21 Merge branch 'master' into develop 2021-02-23 09:08:24 +08:00
F4nniu 9174b6c306 !274 修复图片 value 问题
Merge pull request !274 from F4nniu/fix-image-value-split
2021-02-02 15:29:36 +08:00
F4NNIU 1fc50ed06d 修复图片分隔缺少 value 问题 2021-02-02 15:27:41 +08:00
F4nniu 504e50e65c !273 修复邮件发送报类找不到的问题
Merge pull request !273 from 一半/master
2021-01-30 17:21:40 +08:00
一半 76b31ced93 修复邮件发送时Log类找不到的致命错误 2021-01-27 04:36:37 +08:00
Karson 700184c8eb 修复安装脚本错误 2021-01-25 21:04:13 +08:00
Karson 35220f545c Merge branch 'master' of gitee.com:karson/fastadmin 2021-01-25 16:57:47 +08:00
Karson b82dbd7eee Merge branch 'develop'
# Conflicts:
#	application/admin/command/Api.php
#	application/admin/command/Install.php
#	application/config.php
2021-01-25 16:57:18 +08:00
Karson 6f17a39d90 更新版本号 2021-01-25 16:54:21 +08:00
Karson ce8c3a94d9 优化IP获取配置 2021-01-25 16:51:26 +08:00
Karson e097ae2429 优化上传图片后预览事件 2021-01-25 16:50:10 +08:00
Karson c2460a3241 修复权限管理员越权上级权限的BUG
修复管理员日志查看超级权限的BUG
优化权限管理分组列表
2021-01-24 22:10:53 +08:00
Karson c585fa1228 !266 修复cal_days_in_month的month参数错误,导致计算跨年时间戳报错
Merge pull request !266 from Chrisleung/master
2021-01-14 10:32:49 +08:00
Chrisleung 00bca5e0c8 修复cal_days_in_month的month参数错误,导致计算跨年时间戳报错 2021-01-08 11:39:24 +08:00
Karson 4a9ebb9c7d 优化安装脚本
优化cookie加密
修复系统配置关联字段链接错误BUG
修复addtion方法空数据错误的BUG
修复后台管理获取子级分组列表BUG
优化邮件发送方式
优化cdnurl判断
优化插件管理提示
优化会员附件列表搜索
优化会员登录注册跳转链接获取
2020-12-29 09:46:41 +08:00
Karson 7daf3601ed 修复上传异常时不提示的BUG 2020-12-25 10:26:29 +08:00
Karson 6609ac1470 修复fieldlist中textarea刷新不渲染的BUG 2020-12-25 10:25:52 +08:00
Karson 794f02c502 新增自动修复下拉列表功能
新增自定义弹窗标题功能
优化部分formatter格式化判断
2020-12-25 10:18:29 +08:00
Karson 39c6720cfc 新增自定义插件API文档生成
新增自定义插件API文档生成
新增登录和鉴权状态显示
新增自定义测试提交参数
2020-12-21 11:43:16 +08:00
Karson dd3d6f3172 优化安装脚本
添加随机token设置
2020-12-21 11:38:25 +08:00
Karson 12a62eaa05 !166 增加可选api生成文档(修改参数冲突)
Merge pull request !166 from kingang/master
2020-12-21 09:38:16 +08:00
Karson 662d211946 !260 修复selectpage自定义 data-primary-key 为非数值型内容导致自定义排序SQL报错BUG
Merge pull request !260 from 傲杰笔记/fix_bug
2020-12-19 12:01:27 +08:00
傲杰笔记 2a131ae573 修复自定义data-primary-key为字符串内容时排序报错BUG 2020-12-19 11:18:07 +08:00
Karson c4d676aa8c !263 修改 后台-》常规配置-》管理配置-》系统配置 的 初始化方法
Merge pull request !263 from 小鸡炖蘑菇/master
2020-12-19 10:51:10 +08:00
cf king 9c13b07f73 优化为 ConfigModel 2020-12-10 17:43:56 +08:00
Karson 8af7f5653a !258 update public/assets/js/frontend/user.js.
Merge pull request !258 from EZ/N/A
2020-12-03 20:57:12 +08:00
傲杰笔记 8d0384ee49 简单修复selectpage 自定义data-primary-key为非数值型内容时,自定义排序导致SQL报错BUG 2020-12-03 09:03:39 +08:00
I'am EZ fadcb5432a update public/assets/js/frontend/user.js. 2020-11-29 04:50:56 +08:00
Karson eca3ae7086 优化在非80端口下的跨域判断
优化安装脚本
2020-10-08 22:51:06 +08:00
Karson 346f6eab1a 更新版本号 2020-09-30 16:27:39 +08:00
Karson 08ee8d3362 开启未知来源验证 2020-09-30 16:24:17 +08:00
Karson 2a811716b9 优化API接口上传配置 2020-09-30 15:44:41 +08:00
Karson 0f3ee250ad 优化上传配置和API接口上传配置 2020-09-30 15:17:29 +08:00
Karson 06057850c1 优化上传配置
优化API接口配置
2020-09-30 13:32:22 +08:00
Karson d1bdd4982c 优化selectpage字段筛选 2020-09-30 08:43:57 +08:00
Karson 3e48396821 移除冗余依赖包
优化规则菜单操作逻辑
2020-09-30 00:27:25 +08:00
Karson bb97452fe1 优化切换按钮禁用状态
优化语言包判断
优化权限管理规则管理列表逻辑
2020-09-29 23:13:42 +08:00
Karson e8fa069fe9 优化安装脚本
优化SelectPage编辑时按顺序显示
优化分类、省市联动列表接口逻辑
2020-09-29 21:25:39 +08:00
Karson bcfbe92d51 !245 修复后台分类调用PID为0时无法调用顶级分类
Merge pull request !245 from simon429/master
2020-09-29 17:17:00 +08:00
simon429 908039c13d 修复调用PID为0时无法调用顶级分类 2020-09-29 15:46:49 +08:00
Karson 32f256f3fa !225 多选字段存储和编辑时按选择顺序存储及显示
Merge pull request !225 from HID丨emotion/adv_multi_order
2020-09-29 09:53:36 +08:00
Karson 750f5d7887 !243 修复前台菜单多语言
Merge pull request !243 from DonsonLau/master
2020-09-29 09:23:27 +08:00
Karson 7b0ce82bbb !242 优化 用户注册隐私问题,支持号段1[3-9]
Merge pull request !242 from 前海万联/N/A
2020-09-29 09:15:41 +08:00
Donson bcdbd82c5c 修复前台菜单多语言 2020-09-28 16:15:35 +08:00
万联科技 fffad26373 优化 用户注册隐私问题,支持号段1[3-9] 2020-09-28 11:09:23 +08:00
Karson ad69fbab5c 修复composer和bower依赖 2020-09-24 15:24:07 +08:00
Karson acb42e8eb8 新增日志过滤
新增安装未知来源插件开关
新增插件纯净模式
修复分片合并未创建文件夹BUG
优化插件管理逻辑
2020-09-24 15:15:24 +08:00
Karson 9d5ccb91b4 !240 修复管理员日志中的标题获取
Merge pull request !240 from DonsonLau/master
2020-09-21 17:38:29 +08:00
Donson 1473fd93cf 修复管理员日志中的标题获取 2020-09-21 11:45:50 +08:00
Karson aa5d413cc0 update application/index/controller/User.php.
移除空请求判断
2020-09-20 19:24:57 +08:00
Karson e550b327dd 优化会员列表显示
优化个人资料列表显示
优化基类模型
2020-09-18 17:17:13 +08:00
Karson 82fc4f971c 新增时间字段CRUD默认关闭autocomplete 2020-09-18 16:49:43 +08:00
Karson c8085a406e 新增列表搜索autocomplete配置
优化关联搜索
优化附件列表缩略图显示
2020-09-18 16:42:19 +08:00
Karson b4185e2da8 优化SelectPage多选删除样式 2020-09-17 22:13:44 +08:00
Karson dfd326a6e1 修复多选全部移除后输入框的宽度BUG
优化SelectPage初始化样式
2020-09-17 21:31:59 +08:00
Karson ebb5d34414 优化列表固定宽度功能 2020-09-17 17:12:20 +08:00
Karson b277d31c60 优化列表导出功能
修复导出全部BUG
优化移动端列表展示
优化iOS移动端展示
2020-09-17 15:01:16 +08:00
Karson d0f71f4f1d 优化上传返回错误时的提示文字 2020-09-14 21:35:34 +08:00
Karson 98b813aff1 更新Echarts
优化表格content格式化方法
2020-09-14 14:49:47 +08:00
Karson 15f6fb7553 优化选项卡关闭按钮颜色 2020-09-12 22:47:00 +08:00
Karson f70e1f3349 优化后台回收站列表逻辑 2020-09-12 16:41:04 +08:00
Karson 045b756272 修复关联主表时间字段无法搜索的BUG 2020-09-12 16:27:56 +08:00
Karson 788b1d57f6 优化后台列表空格显示 2020-09-12 11:31:59 +08:00
Karson 077eebec86 优化前台退出文字
修复分页搜索BUG
2020-09-11 22:52:08 +08:00
Karson 36d762fc81 优化跳转和错误页面样式 2020-09-11 17:59:48 +08:00
Karson 4f7b083e8d 修复登录页背景图错误 2020-09-11 17:23:16 +08:00
Karson 7150bea947 全新后台登录页面 2020-09-11 16:54:24 +08:00
Karson a2d99f4bdb 修复关联字段时间无法搜索的BUG
优化后台列表逻辑
2020-09-11 14:10:59 +08:00
Karson 8b772d30cd !235 update application/index/view/index/index.html.
Merge pull request !235 from 万联科技/N/A
2020-09-10 21:35:10 +08:00
Karson 5ea682edf4 优化移动端下Tabs滚动
优化移动端下系统配置页面展示
2020-09-10 21:05:00 +08:00
Karson 1cc0cadbdd 优化后台皮肤 2020-09-10 17:57:39 +08:00
Karson 29976027fc 优化后台皮肤 2020-09-10 17:46:40 +08:00
万联科技 e37ca7506a update application/index/view/index/index.html.
首页添加备案号,防止开发期间站点被恶意投诉 “无工信部备案号标识链接”
2020-09-10 04:05:38 +08:00
Karson b7964f7aa9 优化多级菜单在移动端的显示效果
优化附件选择列表
2020-09-09 23:11:41 +08:00
HID丨emotion c3eae7b1aa Merge branch 'master' of gitee.com:karson/fastadmin into adv_multi_order 2020-09-09 15:25:47 +08:00
Karson b194c91398 优化附件选择 2020-09-09 14:48:28 +08:00
Karson 30f1702b4c 优化前台首页资源为本地加载 2020-09-09 10:28:33 +08:00
F4NNIU e9755a31d7 !234 修正 Http 请求类注释
Merge pull request !234 from F4NNIU/fix-http-comment
2020-09-09 09:23:19 +08:00
F4NNIU df72ef06fd 修正 Http 请求类注释 2020-09-09 09:22:13 +08:00
Karson a685e48ada 优化后台皮肤样式 2020-09-08 23:35:19 +08:00
Karson a1a0d0f70c 新增多个后台皮肤
全新前台默认首页
优化后台菜单折叠
优化nice-validator加载
优化压缩打包后的JS文件
2020-09-08 22:17:42 +08:00
Karson 07679a3fe3 新增后台管理面包屑开关配置
优化插件卸载删除表
优化固定列初始化样式
2020-09-04 23:26:36 +08:00
Karson adb3cc080a 新增安全过滤类 2020-09-04 17:00:48 +08:00
Karson cdd53ac377 优化安装脚本检测 2020-09-04 14:22:13 +08:00
Karson bdb9b61f24 优化前台会员中心H5下展示
优化后台菜单栏切换
优化后台Logo展示
2020-09-03 21:18:19 +08:00
Karson 4a675d5689 新增附件管理分片模式上传按钮
优化系统配置显示
2020-09-02 22:41:12 +08:00
Karson 6f56a83422 优化后台会员列表头像显示
优化后台请求方法判断
2020-09-02 21:54:10 +08:00
Karson 9d2e7f1c95 优化前台默认引入的组件样式 2020-09-02 20:28:10 +08:00
Karson a3a5707cdd 优化searchList修改后刷新事件
优化标题过长时固定宽度不生效的BUG
2020-09-02 16:48:04 +08:00
Karson b9d55fca25 优化JS分片上传默认配置
优化菜单规则和分组JS安全配置
2020-09-02 15:48:15 +08:00
Karson 2fecaeae0b 新增开关切换确认提示功能
优化拖拽排序
2020-09-01 21:36:50 +08:00
Karson b44add7ad8 新增前台会员选择图片附件功能 2020-09-01 16:04:11 +08:00
Karson 8ddbd81d6d 新增表格列表固定列功能 2020-08-31 17:57:56 +08:00
Karson 46bd263186 新增API基类Token验证
优化后台选项卡判断
2020-08-31 14:28:57 +08:00
Karson 4493a69b80 Merge remote-tracking branch 'origin/master' 2020-08-28 14:58:52 +08:00
Karson 8058ba312b 修复注册成功后登录状态未变更的BUG
优化安装脚本跳转
2020-08-28 14:58:02 +08:00
Karson cd99b2d1f0 !232 修复附件管理模型中文件类型写死,导致语言切换无法正常读取语言包的问题
Merge pull request !232 from HID丨emotion/fix_attachment_i18n
2020-08-27 21:24:16 +08:00
HID丨emotion 2968a38abb 修复附件管理模型中文件类型写死,导致语言切换无法正常读取语言包的问题 2020-08-27 15:56:49 +08:00
Karson e31a546b48 新增记忆列表分页大小
优化插件管理列表加载
2020-08-26 22:26:18 +08:00
Karson 481256dff3 优化基类控制器Traits的方法 2020-08-23 21:12:42 +08:00
Karson 0a164777fe 新增搜索字段安全检测
修复find_in_set多值时无法查询的BUG
移除冗余查询代码
2020-08-23 12:30:26 +08:00
Karson dd83a6255e 修复分片上传在Windows下的兼容问题
优化分片上传合并失败时清除分片文件
2020-08-22 23:26:55 +08:00
Karson 260198e1cb 新增插件卸载时删除相关数据表功能 2020-08-22 20:55:13 +08:00
Karson 3d9cde96a1 新增系统配置关联字段、城市专区类型
新增自定义数组类型标题
新增多选项卡配置
新增自定义皮肤配置
2020-08-21 23:25:34 +08:00
Karson f36a3415d7 优化前台下拉列表样式 2020-08-20 15:51:49 +08:00
Karson 9ddcb1953f 新增会员中心边栏高亮检查方法
优化跨域检测方法
2020-08-19 22:23:48 +08:00
Karson da4402b44e Merge https://github.com/karsonzhang/fastadmin 2020-08-19 21:45:00 +08:00
Karson cb4c41e8bf
Merge pull request #69 from Jayin/patch-1
修复关闭路由错误
2020-08-19 21:43:51 +08:00
Karson 5ee9219627 Merge https://github.com/karsonzhang/fastadmin 2020-08-19 21:42:12 +08:00
Karson cdbcca0b0e
Merge pull request #71 from SunQifeng1988/patch-1
Update Crud.php
2020-08-19 21:40:51 +08:00
Karson 28a1321b4d !228 根据 get 参数判断活动页签
Merge pull request !228 from 开兴/edit-index-tabs
2020-08-19 21:35:13 +08:00
Karson 66e69dfd26 !227 文件、图片、权重等字段默认不加入搜索栏,字符串类型默认LIKE
Merge pull request !227 from 开兴/add-search-filter
2020-08-19 21:32:31 +08:00
Karson bae3079574 !224 修复管理入口文件名不能包含admin
Merge pull request !224 from youwo/dev
2020-08-19 21:28:12 +08:00
Karson 1c1f457e1d 新增跨域检测方法
修复本地上传后cdnurl获取不正确的BUG
2020-08-19 21:22:29 +08:00
zhuangkaixing 2012f7f9f2 根据 get 参数判断活动页签 2020-08-19 17:12:32 +08:00
zhuangkaixing 9f2aa5cd3a 文件、图片、权重等字段默认不加入搜索栏,字符串类型默认LIKE 2020-08-19 16:52:28 +08:00
HID丨emotion 1a673fc758 增加参数过滤,防止sql注入
多选字段选择多个时,再次编辑保持选择顺序不变
对参数增加过滤,防止sql注入
2020-08-18 13:35:33 +08:00
HID丨emotion 69d155b1d0 多选字段编辑时按选择顺序显示
多选字段选择多个时,再次编辑保持选择顺序不变
2020-08-18 12:37:08 +08:00
youwo 7f2ca19ef8 修复管理入口文件名不能包含admin 2020-08-17 22:44:06 +08:00
Karson 13da01a48c 新增跨页记忆已选中的行
新增多个扁平化样式按钮
优化表格高亮颜色
2020-08-16 22:36:22 +08:00
Karson 84a181be73 优化多选下拉列表初始化样式 2020-08-14 16:10:33 +08:00
Karson 3abfacb86e 优化多选下拉列表初始化样式 2020-08-14 10:13:25 +08:00
Karson 3f4e54d0dd 修复API接口错误提示
优化上传选择器优先逻辑
优化插件离线安装后续限制
优化后台头像无法保存的BUG
2020-08-14 09:46:16 +08:00
Karson ac97988661 优化文件名长度限制
优化上传文件mimetype限制
2020-08-13 23:12:47 +08:00
Karson 926e16bde6 优化分片上传配置开关和分片大小 2020-08-13 22:37:04 +08:00
Karson 1d37f23a5e 优化普通文件图片预览大小 2020-08-13 16:20:28 +08:00
Karson 285095f80f 修复上传类型配置mimetype无效的BUG 2020-08-13 16:11:13 +08:00
Karson 82966e07c3 附件上传新增分片上传按钮
修复附件列表按钮失效的BUG
修复上传单一文件无法再次上传的BUG
2020-08-13 15:13:09 +08:00
Karson 9f04fcdb66 修复分片上传未检测目录 2020-08-13 11:07:04 +08:00
Karson beccee0811 新增null或空字符串筛选过滤
新增表格列表字段renderDefault控制默认数据
2020-08-13 10:46:11 +08:00
Karson 614d88eb81 修复附件表初始化数据 2020-08-13 09:34:50 +08:00
Karson e0231e4051 !220 附件字段filename缺失
Merge pull request !220 from 放码过来/editInstallSql
2020-08-13 09:29:40 +08:00
放码过来 07234a16c8 修复上传提示没有字段filename 2020-08-12 22:57:58 +08:00
Karson ed20e5ac6f 新增菜单类upgrade方法
新增上传存储文件名功能
新增文件分片上传功能
优化上传组件及方法
优化系统配置中冗余的JS代码
优化下拉框显示样式
优化大数据列表时表格列表渲染速度
修复在PHP高版本下Token验证错误的BUG
修复在PHP高版本下会员登录页错误的BUG
修复Token::get默认值不生效的BUG
2020-08-12 21:49:44 +08:00
Karson e29a9e41fc Merge branch 'master' of https://gitee.com/karson/fastadmin 2020-08-12 21:16:55 +08:00
Karson 049e719d89 !219 随机类注释错误
Merge pull request !219 from 放码过来/editNotes
2020-08-12 21:16:36 +08:00
Karson d101bcd3e6 Merge branch 'master' of https://gitee.com/karson/fastadmin 2020-08-12 21:07:19 +08:00
放码过来 af3aa231f8 update extend/fast/Random.php. 2020-08-12 21:03:17 +08:00
Karson 541a182a7e !215 修复生成API文档相同分组方法名相同被覆盖问题
Merge pull request !215 from HID丨emotion/fix_api_same_method
2020-08-12 20:48:09 +08:00
Karson c5da47b16b !217 update application/admin/library/Auth.php.
Merge pull request !217 from sixXing/N/A
2020-08-12 20:39:36 +08:00
zq 6d34058826 '随机类字符错误' 2020-08-12 16:38:22 +08:00
sixXing dbf47c182a update application/admin/library/Auth.php.
解决同一时间只能在一个地方登录的时候token清空导致两边都挤掉的BUG
2020-08-10 16:04:47 +08:00
孙奇峰 1dfbc62783
Update Crud.php
There is a mistake when the amount of relationModel more than one
2020-08-06 22:19:46 +08:00
Karson 0d800cbbb0 Merge branch 'master' of https://gitee.com/karson/fastadmin 2020-08-06 21:55:20 +08:00
Karson df183a86e4 !214 解决Config系统配置自定义类型字段定界符被修改后保存到数据库导致编辑后无法正常替换定界符得问题
Merge pull request !214 from Twhmr/master
2020-08-06 21:47:23 +08:00
twhmr ac7f3f2690 update application/common/library/Token.php. 2020-08-03 14:48:55 +08:00
HID丨emotion 2786259135 修复生成API文档相同分组方法名相同被覆盖问题
修复如两个Controller使用同一个API分组,相同的方法名会产生覆盖的问题
2020-08-01 14:58:33 +08:00
twhmr b6aaaeaebe update application/admin/view/general/config/index.html.
修改自定义类型的显示字段名extend为extend_html
2020-07-31 10:47:58 +08:00
twhmr 5e908498bd update application/common/model/Config.php.
新增额外属性 extend_html,用来解决直接替换extend的预定义定界符被篡改并保存进数据库的问题的问题
2020-07-31 10:46:43 +08:00
Karson fc701cb901 恢复cookie的httponly默认设置 2020-07-20 16:05:57 +08:00
Caiqy 83ae127480 补充Auth兼容调用user模型的属性 2020-07-10 18:26:07 +08:00
Jayin Ton a6b4a1d355
修复关闭路由错误 2020-07-06 16:16:13 +08:00
Karson bcd1b7fb9c 恢复cookie的httponly默认设置 2020-06-14 21:28:44 +08:00
Karson 6657f92fe6 更新easywechat依赖 2020-06-13 09:32:54 +08:00
Karson d901af60e8 Merge https://github.com/karsonzhang/fastadmin 2020-06-12 18:02:20 +08:00
Karson 3cae744a00 新增会员分组、权限规则、会员编辑Token验证
新增分类编辑Token验证
新增后台禁用Referer携带
新增弹窗自定义关闭按钮事件
新增后台安全提示
新增插件伪静态优先级配置
新增插件卸载移除空目录
修复语言包更新后不生效的BUG
修复fa_area和fa_version表不存在的BUG
修复编辑会员日期错误的BUG
修复会员删除事件未触发的BUG
修复页面跳转BUG
修复后台左侧菜单指示未切换的BUG
修复Bootstrap-Table切换语言不生效的BUG
更新PHPMailer版本
移除EasyWechat和Qrcode依赖
优化本地插件加载速度
2020-06-12 17:59:05 +08:00
Karson 431ac47172
Merge pull request #66 from zhuangkaixing/fix-import
修复 crud 生成的 js 缺少导入连接,控制器缺少导入方法
2020-06-09 15:46:04 +08:00
Karson 1a5661f994 !202 修复多语言问题
Merge pull request !202 from 萤火虫/master
2020-06-09 15:41:09 +08:00
Karson 27d20e2fca !205 修复插件安装时不识别权重排序
Merge pull request !205 from lidongtony/master
2020-06-03 21:13:03 +08:00
lidongtony 3c17401985 fix the weigh of menu 2020-06-03 14:55:09 +08:00
萤火虫 8fb48352ed 修复Bug: 关闭Debug的情况下,切换语言时,请求语言包的url没有变化,使得浏览器使用了缓存,导致没能成功切换语言 2020-05-25 16:00:02 +08:00
Karson d3a1c3978e !189 Api模块lang目录下文件/控制器若定义为_分割,后台一键生成Api文档,无法忽略控制器和方法
Merge pull request !189 from twhmr/master
2020-05-24 09:17:41 +08:00
Karson ce706fde18 !199 后台无控制台权限用户 不显示内容窗口控制台按钮
Merge pull request !199 from HID丨emotion/fix_dashboard_auth
2020-05-24 09:14:03 +08:00
HID丨emotion 4d1b5db9f2 后台无控制台权限用户 不显示内容窗口控制台按钮 2020-05-22 09:01:40 +08:00
F4NNIU cf4949db77 !196 注册时防止昵称重复
Merge pull request !196 from F4NNIU/user_register_improvement
2020-05-16 10:35:22 +08:00
F4NNIU 36d062d612 注册时防止昵称重复 2020-05-15 17:35:51 +08:00
F4NNIU 8c99271e66 !195 改善前台用户设置
Merge pull request !195 from F4NNIU/user_profile_improvement
2020-05-15 08:21:50 +08:00
F4NNIU 6c7b53a9d7 !194 修复通用搜索为 BETWEEN 时 ID 重复的问题
Merge pull request !194 from F4NNIU/fix_common_search_between_id
2020-05-15 08:20:40 +08:00
F4NNIU 7065215109 !191 修正插件管理弹窗在手机中适应手机宽高
Merge pull request !191 from Young小杰/master
2020-05-14 14:21:57 +08:00
F4NNIU 489a29f15d !193 用户管理在编辑时加验证
Merge pull request !193 from F4NNIU/user_management_improvements
2020-05-14 14:05:52 +08:00
F4NNIU 568fd153ad 改善前台用户设置防止昵称重复 2020-05-13 08:57:04 +08:00
F4NNIU caf60d8c2b 修复通用搜索为 BETWEEN 时 ID 重复的问题 2020-05-12 23:12:36 +08:00
F4NNIU 3793266280 加入字段描述 2020-05-11 09:28:14 +08:00
F4NNIU 98c37f5e2b 给用户管理在编辑时加验证 2020-05-11 01:04:42 +08:00
Karson 7e3205fd38 更新版本号 2020-05-06 19:39:28 +08:00
zhuangkaixing 87aa1dcafe 修复 crud 生成的 js 缺少导入连接,控制器缺少导入方法 2020-05-05 10:56:56 +08:00
Karson b06d963a4a 新增Api和Index模块下指定URL前缀不匹配路由。
修复后台图片预览一处BUG
2020-05-01 23:02:32 +08:00
1170535111@qq.com 40eb4ed86c 修正插件管理弹窗在手机中自适应宽高的问题 2020-04-29 22:54:32 +08:00
F4NNIU 1a81c45317 !190 分组表使用 utf8mb4_unicode_ci
Merge pull request !190 from F4NNIU/fix-auth-group-utf8mb4-unicode
2020-04-27 16:02:38 +08:00
F4NNIU 4346b37ab8 分组表使用 utf8mb4_unicode_ci 2020-04-27 15:59:47 +08:00
F4NNIU a588fa629a !185 修复 checkboxs
Merge pull request !185 from F4NNIU/fix-form-checkboxs
2020-04-27 12:17:54 +08:00
Twhmr e9af5be8d6 Api模块生成文档时,使用@ApiInternal需隐藏的控制器没有被隐藏 2020-04-25 10:56:44 +08:00
Twhmr d0e42a27b7 修复Api模块语言包加载不到文件名以_分割的文件 2020-04-25 10:46:44 +08:00
F4NNIU b849191a1d !187 改数据库编码为 utf8mb4
Merge pull request !187 from F4NNIU/db-utf8mb4
2020-04-24 23:48:46 +08:00
F4NNIU 713f882552 改数据库编码为 utf8mb4 2020-04-24 13:22:04 +08:00
F4NNIU f8a1c3975f 修复 checkboxs 2020-04-24 11:28:18 +08:00
Karson a97d0c9a68 !179 修复字段类型为url时列表页JS格式化未对null做处理的问题
Merge pull request !179 from HID丨emotion/fix_url_formatter
2020-04-22 19:20:00 +08:00
Karson 1dc1f7388c !173 首页加上 favicon
Merge pull request !173 from F4NNIU/fix-favicon-ico
2020-04-22 19:18:41 +08:00
Karson 3aacc8421f !180 一键生成CRUD多选字段和多图字段支持text类型
Merge pull request !180 from HID丨emotion/adv_crud
2020-04-22 19:17:44 +08:00
Karson bfaddd1832 !182 update application/index/view/index/index.html.
Merge pull request !182 from jason-张百万/N/A
2020-04-22 19:12:54 +08:00
Karson 327f2a2707 !171 不打开新的窗口
Merge pull request !171 from F4NNIU/fix-home-href
2020-04-22 19:10:56 +08:00
途仕网络 37d77a6e37 update application/index/view/index/index.html.
增加个双引号 jason-张百万2020-4-18
2020-04-18 17:33:53 +08:00
HID丨emotion 8443d2e7c5 修复字段类型为url时列表页JS格式化未对null做处理的问题
修复字段类型为url时列表页JS格式化未对null做处理的问题
2020-03-24 12:20:43 +08:00
HID丨emotion 7e73e0b562 一键生成CRUD多选字段和多图字段支持text类型
解决当字段类型为多选项或多图片时varchar(255)不够存储的情况,数据库字段使用text符合多选字段名或多图片规则时依然生成多选菜单。
2020-03-24 12:18:37 +08:00
Karson 290bfc4f75 新增邮件抄送、附件方法
更新PHPMailer到6.0版本
2020-03-06 14:32:41 +08:00
Karson 30c695ff61 修复列表开关组件未兼容旧版本BUG 2020-03-01 15:52:56 +08:00
F4NNIU b3d8877ead 首页加上 favicon 2020-03-01 15:20:01 +08:00
F4NNIU 1b3c22ead2 不打开新的窗口 2020-02-29 00:01:06 +08:00
Karson c6f44aa060 新增附件管理标签过滤
新增语言包缓存
新增生成文件后缀图片
新增Layer弹窗自动获得焦点
新增附件图片按序选择填充
新增插件管理一键切换无远程插件模式
优化表格列表开关组件
优化安装脚本
优化CRUD菜单删除
优化前台模板
优化插件列表接口检测
优化系统配置
2020-02-28 09:49:06 +08:00
Karson 802346dd1f !170 修复语言包无法加载问题
Merge pull request !170 from Ackleys/master
2020-02-27 10:42:49 +08:00
Karson 597e50e722 !168 修复http request post发送文件失败的问题
Merge pull request !168 from Muchu/fix_http
2020-02-27 10:38:18 +08:00
Ackleys 3ea0b57b97 修复加载语言包时,输入模块名称时,无效 2020-02-11 16:27:45 +08:00
熊熊 a4873a3a9a 修复curl请求接口发送文件找不到文件的问题 2020-01-30 23:25:46 +08:00
kingang fa088b1897 增加可选api生成文档 2020-01-02 22:59:57 +08:00
Karson b552ccd403 Merge https://github.com/karsonzhang/fastadmin 2019-12-27 12:14:09 +08:00
Karson e14008ca02 修复邮箱验证码错误
修复排序表名安全检测
修复Selectpage编辑时分页大小错误
2019-12-27 12:09:20 +08:00
Karson edb6a12c3b
Merge pull request #56 from tsln1998/fix-doc
修复API中参数拼写错误
2019-12-27 12:02:35 +08:00
kingang efc92f71a9 增加可选api生成文档 2019-12-21 15:10:05 +08:00
kingang 63e0fb3fd5 增加可选api生成文档 2019-12-21 15:04:13 +08:00
Karson 4bb4425d45 修复发送邮箱验证码时URL错误 2019-12-18 11:03:49 +08:00
Karson ffd0060fd0 修复超级管理员开关无权限的BUG
移除前台默认首页版本统计
2019-12-17 17:09:48 +08:00
Karson 088d272257 更新README
新增附件大小格式化
2019-12-13 16:34:08 +08:00
Karson a1deccd17a 新增邮箱/手机/公众号/普通文本四种验证码
新增后台列表选项卡切换支持普通文本框
新增fieldlist支持普通HTML标签
修复datetimepicker不触发fieldlist响应的BUG
优化面包屑获取方式
移除冗余图片和样式
2019-12-13 12:16:29 +08:00
Karson d868c0fed3 !160 daterangepicker组件改进
Merge pull request !160 from 无木/master
2019-12-12 20:42:00 +08:00
无木 d3d9a5a751 修复使用data-locale='{"format":"YYYY-MM-DD"}'方式配置daterangepicker时会丢失Custom Range、Apply、Clear国际化的问题 2019-12-11 15:55:38 +08:00
Karson 887a381ff3 !159 权限节点安全性调整
Merge pull request !159 from MrCai/master
2019-12-02 17:54:56 +08:00
cxb c14adbbdfd 权限节点安全性调整 2019-11-29 17:27:30 +08:00
Karson 054e75a96e !158 角色组bug修正
Merge pull request !158 from MrCai/master
2019-11-28 18:43:35 +08:00
Karson 0d20ce6fa9 修复Bootstrap-table引用版本 2019-11-28 18:34:21 +08:00
cxb e216bd7751 解决角色组跨权限查看编辑的问题,修正角色组移动时不能为节点的子节点 2019-11-28 18:17:18 +08:00
Karson 2b60921cf3 新增Bootstrap-table分页跳转
新增后台修改会员积分日志
优化控制台切换时图表展示
修复安卓下图片无法上传的BUG
修复附件类型筛选时多条件的BUG
修复Backend基类无法捕获Exception异常的BUG
修复Form::checkboxs名称生成错误的BUG
2019-11-28 16:15:47 +08:00
Karson 938671cf2b !153 增加移除权限组节点后所有子权限组也一并移除相应权限
Merge pull request !153 from MrCai/pull2fastadmin
2019-11-27 16:31:03 +08:00
Karson 014c29fcd2 !154 修复分类管理中可将父id设置为自己的错误
Merge pull request !154 from saynone/master
2019-11-27 16:25:38 +08:00
saynone a3fc3f85be 修复分类管理中可将父id设置为自己的错误 2019-11-26 14:38:54 +08:00
cxb a4f6a29c09 角色组编辑,当取消某个角色组权限节点时,把该权限组的所有子权限组节点相应权限节点也取消,避免取消父节点节点权限后子节点仍可访问。 2019-11-22 16:25:30 +08:00
Karson f8714972fc !150 修复管理员注销后token未被重置的Bug
Merge pull request !150 from 一半/debug
2019-11-09 15:19:19 +08:00
一半 565bca602b 修复管理员注销后token未被重置的Bug 2019-11-08 16:10:14 +08:00
Karson 49facb7b7c 新增后台IP变动控制开关
优化后台插件管理显示
优化Fast.api.ajax方法返回
优化管理员日志显示
2019-10-31 23:36:54 +08:00
Karson b6f2307737 新增自定义编辑删除按钮 2019-10-12 11:45:40 +08:00
Karson 9b2ec96a03 增加插件配置日期和时间组件
修复系统配置中日期和时间的BUG
2019-10-12 10:50:05 +08:00
Karson b6d51ee5b6 修复Nice-validator失效的BUG 2019-10-11 21:02:34 +08:00
Karson 515f4f78b1 修复后台Auth的check方法指定uid不生效的BUG 2019-10-10 18:03:56 +08:00
Karson 3157f3a574 优化关联查询时模型表名读取
优化插件配置数据过滤
2019-10-10 17:52:32 +08:00
Karson 5cb74b5e36 修复后台管理员权限判断 2019-10-10 16:47:47 +08:00
Karson 193f33f11a 新增系统配置扩展参数替换功能
优化系统配置添加逻辑
修复系统配置无法添加的BUG
2019-10-06 15:16:00 +08:00
Karson 8066902212 修复注册手机验证码验证失败的BUG 2019-09-30 22:38:17 +08:00
Karson 9066b3964c 新增会员注册短信验证码
新增后台菜单栏hook
优化后台修改管理员密码脚本
优化安装脚本,默认禁用admin模块
优化插件列表参数和分页显示
2019-09-30 13:47:46 +08:00
Karson 2f9732e905 优化管理员密码密码 2019-09-30 13:37:51 +08:00
F4NNIU b388289dba !143 修复安装时默认管理员SQL语句
Merge pull request !143 from MyPuppet/master
2019-09-26 15:54:14 +08:00
MyPuppet f5b9e28ffb 修复默认用户SQL语句 2019-09-26 15:23:04 +08:00
Karson 7267c2d531 !141 修复更新插件没有权限addons文件夹
Merge pull request !141 from 镜面/dev
2019-09-25 22:59:21 +08:00
Karson 01521805cd !142 login模板中的lang属性使用配置的参数
Merge pull request !142 from smtop/master
2019-09-25 22:58:37 +08:00
Karson d4900be78f 新增基类token方法
优化后台表单提交安全验证
2019-09-25 22:42:11 +08:00
smdev 3ba70093c5 lang属性使用配置的参数 2019-09-25 22:13:10 +08:00
Karson 678be2f3a9 修复selectpage树状显示的bug
修复后台表格图片点击弹出新窗口的BUG
优化后台站点名称、用户昵称头像的显示渲染
优化后台宽度自适应
增强后台管理员安全鉴权
2019-09-25 20:58:03 +08:00
镜面 2a19697858 修复更新插件没有权限addons文件夹 2019-09-03 14:52:23 +08:00
Karson ebf38b470c !138 增加插件资源移动命令
Merge pull request !138 from 道阻且长/master
2019-08-04 22:48:52 +08:00
道阻且长 b0cfaed544 !1 增加插件资源移动命令
Merge pull request !1 from 道阻且长/addonmove
2019-08-04 03:43:28 +08:00
songli e5bc5718c8 增加插件资源移动命令 2019-08-04 03:30:37 +08:00
tsln1998 42057091e7 修复API中参数拼写错误 2019-07-25 20:34:09 +08:00
Karson 1390a5f1a7 !136 自定义按钮confirm不生效
Merge pull request !136 from Ivan/master
2019-07-14 16:54:01 +08:00
Karson 56d1493ba7 !134 修改头像地址长度为 255
Merge pull request !134 from F4NNIU/fix_admin_avatar
2019-07-14 16:53:55 +08:00
Ivan f03430ccb7 更新 require-table.js
自定义按钮的confirm不生效bug修改
2019-07-12 11:06:37 +08:00
F4NNIU 4cff10ca7e 修改头像地址长度为 255 2019-07-08 09:24:12 +08:00
Karson df68b9d0c0 更新版本号 2019-07-05 22:57:08 +08:00
Karson 92a9545564 修复插件卸载和禁用可能导致的BUG 2019-07-05 22:38:51 +08:00
Karson a85db2323a 新增toggle开关disable参数
新增buttons数组按钮confirm支持function
修复CRUD生成多级模型时事务失效的BUG
修复token为空不验证的BUG
2019-07-04 15:55:43 +08:00
Karson b927b5443f 修复错误的mimetype配置 2019-07-03 23:24:28 +08:00
Karson fbb1370005 修复排序字段错误BUG
优化上传文件图片检测,禁止上传PHP和HTML
2019-07-03 23:16:15 +08:00
Karson cb4a986184 修复表格导出下拉列表显示 2019-06-29 11:02:34 +08:00
Karson 8c76ac77a4 新增多图自定义描述数据功能
新增选择附件页上传功能
新增Api跨域判断
优化API文档绑定域名时URL判断
优化后台左侧无权限菜单的显示逻辑
优化验证码失败时自动刷新验证码
修复Windows下离线安装成功后不删除压缩包的BUG
修复通用搜索需要重置2次的BUG
修复表格导出配置不生效的BUG
修复data-table-id不统一的BUG
修复多图片预览时错误的BUG
2019-06-28 18:20:04 +08:00
Karson f42ea981ae Merge https://github.com/karsonzhang/fastadmin 2019-06-16 15:14:44 +08:00
Karson 7dd47e858c
Merge pull request #52 from jay4497/master
remove duplicate key.
2019-06-15 15:15:02 +08:00
jay4497 a3bdbdd522
remove duplicate key. 2019-06-15 14:27:59 +08:00
Karson 6e94113ebd !130 绑定tab的table在tab切换时页面索引没有重置
Merge pull request !130 from 无木/master
2019-06-15 09:35:09 +08:00
无木 a7a6a68e8f 修复table在绑定tab之后,切换tab时table的当前页没有重置的问题 2019-06-13 17:39:27 +08:00
Karson 535d821ca7 Merge branch 'master' of https://gitee.com/karson/fastadmin 2019-06-11 15:40:41 +08:00
Karson 71195b4185 修复data-table-id不统一的BUG
修复多图片预览时错误的BUG
2019-06-09 18:35:19 +08:00
Karson e3f7268a8b 优化附加扩展参数 2019-06-08 09:52:29 +08:00
Karson 07e5608447 优化会员退出逻辑和mobilelogin鉴权判断 2019-06-08 09:50:27 +08:00
Karson f9d14aa693 修复主键非ID时的排序错误 2019-06-08 09:49:30 +08:00
Karson e45784dec6 !125 修改nick-validator通过Remote验证通过时的消息提示,判断是否提示消息
Merge pull request !125 from Oo小刚oO/master
2019-06-04 15:44:45 +08:00
coolwhg e5e3897374 修改nick-validator通过Remote验证通过时的消息提示,判断是否提示消息 2019-06-04 15:25:46 +08:00
Karson 843a10fadd !111 新窗口打开页面时提示跳转无上一页无法跳转问题
Merge pull request !111 from 陈二木/master
2019-05-30 18:41:19 +08:00
Karson cfba7e04c1 !122 修复无法自定义邮件发件人名称的Bug
Merge pull request !122 from Ralph/master
2019-05-30 18:40:14 +08:00
Karson 830dd7a20d !123 多图片预览改进、JS模块增加空操作_empty
Merge pull request !123 from 无木/master
2019-05-30 18:37:16 +08:00
无木 842cfbedeb JS模块支持空操作重定向到_empty 2019-05-22 14:08:05 +08:00
无木 16e02f643f 单元格多图片预览时可以根据引发单击事件的图片索引来决定初始显示的图片序号 2019-05-21 14:07:50 +08:00
hiralph c550465f37 修复无法自定义邮件发件人名称的Bug 2019-05-20 17:15:37 +08:00
Karson ba215a4593 新增CRUD编辑时添加htmlentities处理
新增CRUD一键生成fieldlist组件
修复Date类一处命名错误
修复带下划线字段枚举型列表错误
修复插件配置下拉列表配置失效的BUG
修复表单重置时selectpicker和citypicker的错误
优化注册登录后的跳转
优化上传或选择文件后的验证提示
2019-05-10 16:36:39 +08:00
Karson b6777b60c3 更新版本号 2019-04-18 17:44:47 +08:00
Karson 407a8eeaa5 新增Ajax请求自定义加载动画
新增Fast.api.msg提示
优化Tooltip显示位置
修复后台编辑时不做修改保存时提示错误的BUG
修复后台Validate验证器不提示消息的BUG
修复后台启用editable后导出数据丢失的BUG
修复后台附件写入uploadtime错误的BUG
2019-04-18 17:03:55 +08:00
Karson 9db281a4c6 移除前台默认头像 2019-04-10 15:38:19 +08:00
Karson 9e8fc90f87 Merge remote-tracking branch 'origin/master' 2019-04-10 15:11:34 +08:00
Karson 7f2ea95489 更新版本号 2019-04-10 15:09:00 +08:00
Karson 9d42f92c95 移除Auth.php类中的Ucenter
优化基类Backend.php中的逻辑
优化插件管理网络失败的逻辑
优化代码格式化
2019-04-07 20:34:22 +08:00
Karson 5298c52e47 新增会员首字母头像功能
新增文本框挺拽上传和粘贴上传
优化注册和登录的跳转
修复后台默认皮肤保存失效的BUG
2019-04-07 20:25:57 +08:00
张尧嵩 4992c59620 !120 Crud生成回收站按钮加入权限控制显示隐藏
Merge pull request !120 from 张尧嵩/master
2019-04-04 09:26:30 +08:00
zhangyaosong 3b671000ad 回收站按钮加入权限控制显示隐藏 2019-04-03 18:05:22 +08:00
Karson 74f0ea14ad 兼容默认fieldlist配置 2019-04-01 13:02:17 +08:00
Karson d9bf3b3715 新增默认过滤函数处理 2019-04-01 13:01:41 +08:00
Karson 2c7546e43f Merge https://github.com/karsonzhang/fastadmin 2019-04-01 10:27:18 +08:00
Karson 840abb714d
Merge pull request #44 from cdddcw/master
一键生成CRUD 多数据库支持 --db=xxx xxx为tp5中配置的数据库key
2019-04-01 10:17:59 +08:00
Karson a3a742cebe Merge branch 'master' of https://gitee.com/karson/fastadmin 2019-03-30 00:12:38 +08:00
CoolAbc d775f062b5 !118 增加配置参数-窗口大小
Merge pull request !118 from CoolAbc/master
2019-03-29 20:33:30 +08:00
Chrisleung 7618ad7dd8 !113 修复当表字段注释为空时,生成的语言文件格式错误
Merge pull request !113 from Chrisleung/master
2019-03-29 20:30:24 +08:00
CoolAbc 5ec2a9893a 增加配置参数-窗口大小,方便统一设置全局或单个模块的大小. 2019-03-29 06:29:12 +08:00
chenwei6 d1b386dd08 一键生成CRUD 多数据库支持 --db=xxx xxx为tp5中配置的数据库key 2019-03-27 11:49:35 +08:00
Chrisleung 6facfcf894 修复当表字段注释为空时,生成的语言文件格式错误 2019-03-24 09:52:16 +08:00
Karson b23d7f9b72 优化会员余额和积分变更逻辑 2019-03-20 16:24:32 +08:00
Karson c0128a2e6c 优化权限规则菜单的请求 2019-03-18 20:22:44 +08:00
Karson 41ae511dc3 更新版本号 2019-03-18 13:33:18 +08:00
Karson 5153cfe3c4 优化峰驼控制器的CRUD生成 2019-03-18 13:31:30 +08:00
CoolAbc 4a12b47021 !110 修复前台路径中控制器是带下划线,权限验证失败
Merge pull request !110 from CoolAbc/master
2019-03-18 13:25:10 +08:00
Karson 499e43c6d5 优化本地插件插件的价格显示 2019-03-18 12:38:10 +08:00
loong f914920078 修改新窗口打开页面时提示跳转无上一页无法跳转,改跳转到首页,体验好一点 2019-03-16 16:19:21 +08:00
CoolAbc 251774d0bd 使用Thinkphp内置转换控制器名为c风格,匹配前台访问路径控制器是xxxx_xx的写法;thinkphp默认是将控制器dict_def转成dictDef,fastadmin直接转成小写,造成权限失败.使用thinkphp内置的方法转换控制器名,与前台路径访问保持一致. 2019-03-16 10:35:08 +08:00
Karson 349c110717 新增回收站增加额外参数 2019-03-16 00:17:33 +08:00
Karson eccf03e43f 新增一键CRUD类名冲突检测
新增一键CRUD自动传递请求参数
新增一键菜单之支持ThinkPHP5标准命名规则
新增Table.api.formatter.toggle自定义URL
新增Table.api.formatter.search自定义搜索字段
修复关联模型不同命名空间之间的BUG
优化时间字段修改器的判断
2019-03-15 23:52:32 +08:00
Karson f5c72393d2 Merge https://github.com/karsonzhang/fastadmin 2019-03-15 23:22:01 +08:00
Karson a22315c18f
Merge pull request #43 from GraySnail/master
完善插件本地安装
2019-03-15 23:21:25 +08:00
惠达浪 524c6c63e1 !107 修改crud,默认情况下为ThinkPHP5标准命名。
Merge pull request !107 from 惠达浪/master
2019-03-15 20:35:11 +08:00
Chrisleung 7d1119386e !108 系统配置符号过滤问题
Merge pull request !108 from Chrisleung/master
2019-03-15 18:34:45 +08:00
Chrisleung a8291db80d 修复tip内容包含双引号时显示不全的问题 2019-03-15 11:13:04 +08:00
灰大狼 d154e79e2c 修复视图目录名未小写的bug 2019-03-09 16:53:25 +08:00
灰大狼 d0c100f85e 优化getParsenameData 2019-03-09 16:47:06 +08:00
灰大狼 065d05d0b4 还原getParseNameData,逻辑错误。 2019-03-09 16:34:39 +08:00
灰大狼 041ed69798 修改crud,默认为TP5标准。 2019-03-09 15:57:19 +08:00
灰大狼 891b175d22 修改crud,默认为TP5标准。 2019-03-09 15:43:58 +08:00
蜗牛 4bd4d15788 完善插件本地安装 2019-03-08 00:03:38 +08:00
Karson 216b8a1c9c !104 Formbuilder增加Slider控件
Merge pull request !104 from Oo小刚oO/master
2019-03-07 14:07:39 +08:00
wanghaigang 9d7970f58b Formbuilder增加Slider控件 2019-03-07 13:42:26 +08:00
Karson af21c68626 修复安卓微信端无法上传的BUG
修复cdnurl在二级目录下获取完整链接的BUG
2019-03-06 10:55:32 +08:00
灰大狼 2c9865e1c9 !102 修改Attachment模型,修改addon语言包
Merge pull request !102 from 灰大狼/master
2019-03-06 10:50:02 +08:00
Karson fd2b888da7 优化命令行文字提示
修复数据表时间字段的BUG
2019-03-05 17:28:34 +08:00
lepig 62283eb2e9 !103 后台用户退出后,重置登录状态
Merge pull request !103 from lepig/master
2019-03-05 17:11:22 +08:00
lepig 95c6834d15 用户退出登录后重置logined属性登录状态
如果不手动设置`$this->logined=false` 那么在`application/admin/model/AdminLog::record()`方法里出现问题。

具体问题就是`$admin_id = $auth->isLogin() ? $auth->id : 0;` 这样代码得到的$admin_id为null,而不是0.   因为执行到$auth->isLogin()后的得到的结果为`true`,那么这个时候系统已经清除了相关session信息,所以$auth->id返回null值。

得到null值后,在进行写日志到adminlog表的时候,由于admin_id字段不允许为null就会报错。类似如下错误
```
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'admin_id' cannot be null
```
2019-03-05 16:42:35 +08:00
Karson b75fa0661f 修复CRUD在无回收站字段情况下的BUG 2019-03-03 19:19:02 +08:00
灰大狼 cb754d169a 去除重复键的内容 2019-03-03 18:15:14 +08:00
灰大狼 7a3fa9d6c1 修改Attachment模型,防止同名文件重复生成记录。 2019-03-03 16:06:17 +08:00
Karson 539cf051fc 优化菜单导入时软删除判断逻辑 2019-03-02 08:45:08 +08:00
Karson d5bdcd1cd6 更新版本号 2019-03-01 14:27:01 +08:00
Karson a7627c347f 修复分类管理和规则管理父级调整的BUG
修复管理员日志数据过长的BUG
2019-03-01 14:18:19 +08:00
Karson 88226015a8 新增CRUD生成回收站视图和JS文件
新增一键生成API文档排序功能
新增表格默认导出选项
新增buttons中text和title的function支持
修复buttons中visible和disable为false时的判断Bug
修复后台添加子管理员的权限错误
2019-03-01 10:14:06 +08:00
Karson a329e5fbb2 Merge branch 'master' of https://gitee.com/karson/fastadmin 2019-03-01 10:10:05 +08:00
NULL a8140635c2 !100 修复表单生成器条件判断错误的问题
Merge pull request !100 from NULL/master
2019-03-01 10:05:32 +08:00
WuXiangJun 2911a30374 修复表单生成器中条件判断错误的问题 2019-02-28 14:47:35 +08:00
PPPSCN 43d9883e7d 优化Selectpage的实现方法 2019-02-21 22:51:20 +08:00
PPPSCN 0f1ce99a25 改进fast.js查询Url参数 2019-02-21 22:48:33 +08:00
PPPSCN 93f8f46eab 优化导入import方法(废弃phpoffice/phpexcel改用phpoffice/phpspreadsheet)
优化批量更新multi方法
2019-02-21 22:47:57 +08:00
PPPSCN 2bfd19267a 优化CRUD生成的lang文件——格式美化 2019-02-21 22:41:07 +08:00
PPPSCN f9b43bdbe4 表单增加slider控件 2019-02-21 22:40:22 +08:00
pppscn f011bf6da2 改进build_toolbar的import支持模版下载 2019-02-21 22:38:01 +08:00
pppscn 366bc7864c 后台首页未登录时不提示错误直接跳转登录页面 2019-02-21 22:36:27 +08:00
pppscn 49464f6135 selectpage支持返回树形结构 2019-02-21 22:35:43 +08:00
pppscn 84ecf5a013 优化角色权限树排序 2019-02-21 22:34:49 +08:00
pppscn 02029bf972 优化规则ruleList排序 2019-02-21 22:34:19 +08:00
pppscn ac312ef41e 上传组件记录extparam参数 2019-02-21 22:33:38 +08:00
pppscn 3763b0d69e 优化 ajax/area兼容一键CRUD生成的row数组 2019-02-21 22:32:30 +08:00
pppscn 70f9bf0197 修复一键生成CRUD时admin_ids字段多选下拉框bug 2019-02-21 22:31:38 +08:00
pppscn a0e8f19056 忽略svn目录 2019-02-21 22:29:04 +08:00
Karson ed0b61ebbf !96 修改原有表格中图片新窗口打开预览为layer相册预览
Merge pull request !96 from 壹世朱名/master
2019-02-21 21:42:54 +08:00
黑猫警长 c9a8b364f5 !99 解决使用redis方式储存token时api接口Token刷新与检测 报未定义数组索引: expires_in BUG
Merge pull request !99 from 黑猫警长/master
2019-02-21 21:42:17 +08:00
BlackSergeant 8d05d78fa0 解决使用redis方式储存token时api接口Token刷新与检测因expires_in拼写错误报错的BUG
解决使用redis方式储存token时api接口Token刷新与检测因expires_in拼写错误报错的BUG
修改expired_in 为 expires_in
2019-02-19 14:52:02 +08:00
BlackSergeant c0d709d3b2 更新 Redis.php
解决使用redis方式储存token时api接口Token刷新与检测报错BUG
修改返回数组参数名称 expired_in -> expires_in
2019-02-19 14:42:08 +08:00
Karson 1c37e2af07 !97 fix 头像字段不显示为图片类型
Merge pull request !97 from longdahai/fix-crud
2019-01-22 17:10:40 +08:00
Ackleys b46b338dcf !94 在生成CURD时,增加一个配置参数,来屏蔽前台提交过来的某些数据.
Merge pull request !94 from Ackleys/master
2019-01-22 16:56:18 +08:00
LongHaiwei dd1a66fcc8 fix 头像字段不显示为图片类型 2019-01-22 10:03:12 +08:00
朱兵 de11e26aa3 修改单元格图片/组为layer图片预览模式 2019-01-18 02:11:47 +08:00
Karson fdd68b5b7a 恢复addons目录和runtime 2019-01-13 11:08:22 +08:00
Karson 0ac3eea2e6 更新版本号 2019-01-11 16:57:53 +08:00
Karson 60b58cc397 更新TP5版本到5.0.24
优化后台验证码显示
2019-01-11 16:56:37 +08:00
F4NNIU 91919dbf0b !95 修复 layer icon 不显示问题
Merge pull request !95 from F4NNIU/fix-layer-icon
2019-01-11 16:38:37 +08:00
F4NNIU 04edfbf5d2 修复 layer icon 不显示问题 2019-01-03 10:18:07 +08:00
F4NNIU 948b9e7ab8 忽略 .vscode 文件夹 2019-01-03 10:16:41 +08:00
Karson 6863ff05b4 新增操作按钮分组下拉列表
新增拖拽上传文件功能
移除后台首页的新闻调用
优化后台修改管理员密码
修复在PHP7.3下的BUG
2018-12-27 17:47:07 +08:00
Karson 83330796a3 更新版本号 2018-12-10 10:31:44 +08:00
Ackleys 5c9818791b 增加 CRUD 字段数据排除 2018-12-03 18:00:47 +08:00
Karson 7e831339c8 新增系统配置删除功能
修复会员分组规则编辑的BUG
2018-12-01 22:02:07 +08:00
Karson 453bb1ddeb 修复后台无法修改会员余额的BUG 2018-11-30 23:51:38 +08:00
F4NNIU c6ef72f5fa !91 修复用户表数据
Merge pull request !91 from F4NNIU/fix_user_table
2018-11-28 18:06:00 +08:00
F4NNIU aa272a54fd 修复用户表 2018-11-28 14:50:29 +08:00
Karson f6ccbb70dd 新增前台会员Money字段和余额日志表
新增插件配置温馨提示的功能
新增后台登录入口为默认时的安全提示
新增余额增减的静态方法
优化前台默认布局的展示
优化附件管理中非图片资源的图标显示
优化移动端左侧菜单栏滑动体验
修复语言包在加载失败时页面不加载的BUG
修复通用搜索在重置后分页的BUG
修复管理员禁用后仍然能登录后台的BUG
修复一键打包插件在Win下打包后目录路径错误的BUG
修复API接口Token无法刷新的BUG
2018-11-27 11:28:24 +08:00
Teemo 2000a876dd !84 修复拖拽排序函数的一处BUG
Merge pull request !84 from Teemo/master
2018-11-07 20:23:27 +08:00
LessCodeToDoMore 2d4a8a3ec8 排序功能的bug 2018-11-07 20:07:43 +08:00
LessCodeToDoMore 307ed3d789 排序功能的bug 2018-11-07 20:02:04 +08:00
LessCodeToDoMore d2c5dfbf0f 排序功能的bug 2018-11-07 19:51:32 +08:00
LessCodeToDoMore a7407ce069 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	application/admin/controller/Ajax.php
2018-11-07 19:49:09 +08:00
Karson f71815a1fd 新增buttons禁用属性控制
优化后台超级管理员权限控制
修复后台清除缓存的一处BUG
2018-11-07 09:53:23 +08:00
LessCodeToDoMore 7dc185fb5f 修复清除缓存的BUG 2018-11-06 19:10:52 +08:00
Vace 3134cb7869 !80 修复当表单中`.layer-footer`有其他标签时,提交按钮无法工作。
Merge pull request !80 from Vace/master
2018-11-06 14:38:13 +08:00
Karson 0a4ed2791f
Merge pull request #38 from raorf/master
Update application/index/controller/Ajax.php
2018-11-06 13:50:01 +08:00
Karson dca32b0a13 修复Form生成冗余form-control类的BUG
更新版本号
2018-10-31 19:22:42 +08:00
Karson 86ba65d907 修复在二级目录下验证器无法匹配的BUG 2018-10-31 16:35:01 +08:00
raorf 336597859a Update application/index/controller/Ajax.php
修正application/config.php中'app_trace'=>Env::get('app.trace', true)时,浏览器访问e用户中心”/index/user"F12调试显示错误:Uncaught SyntaxError: Unexpected token <
2018-10-26 14:55:08 +08:00
Karson d60dcca9cb 优化前台样式
优化前台语言包
优化前台会员空方法数据处理
2018-10-21 16:16:30 +08:00
Karson 571ef508ca 修复上传普通文件无预览的BUG
修复API文档无法上传文件的BUG
修复列表Tab切换后普通搜索失效的BUG
修复Bootcss相关链接
修复前台会员头像在启用云储存丢失的BUG
2018-10-19 17:39:23 +08:00
Vace 69a9de0a4d 修复当表单中`.layer-footer`有`input:hidden`时,提交按钮无法工作。 2018-10-15 16:18:00 +08:00
Karson c86f89b5d7
Merge pull request #35 from gh81997167/patch-1
toggle无效问题
2018-10-11 21:09:28 +08:00
Karson c8e4c56e80
Merge pull request #37 from Chrisleung/master
修复执行单元测试时,fast\Form重复定义的问题
2018-10-11 21:07:12 +08:00
张尧嵩 7a4fdb61c9 !78 提升Win下第一次使用压缩打包的成功率
Merge pull request !78 from 张尧嵩/node
2018-10-11 21:05:13 +08:00
zhang 3250a5f965 提升win下第一次使用压缩打包的成功率 2018-10-11 11:31:57 +08:00
Chrisleung 2c366f06b3 修复执行单元测试时,fast\Form重复定义的问题 2018-10-10 11:25:37 +08:00
Karson 544222fabb !75 新增安装时支持修改MYSQL端口号
Merge pull request !75 from Tommy/master
2018-10-09 21:18:51 +08:00
liuzhen 6bc96814b9 安装时支持修改MYSQL端口号 2018-10-09 15:31:25 +08:00
Karson c462d4783c 新增buttons按钮组click事件
新增系统配置开关控件
修复后台管理员头像在启用云储存时的错误
修复前台公用库切换为jsdelivr.net公用库
2018-10-06 09:50:36 +08:00
lifanko 25ecebc54a !73 修改https为http
Merge pull request !73 from lifanko/master
2018-09-14 08:27:30 +08:00
Karson c15be9773e 修复ajax按钮先刷新后执行操作的bug 2018-09-14 08:25:08 +08:00
lifanko 4c369498ce 工业和信息化部网站是http协议,https无法打开 2018-09-13 19:43:45 +08:00
Karson 9e3f1ccf42 新增附件选择指定管理员或指定会员数据功能
新增附件查找多文件类型功能
新增插件自定义配置功能
新增前台会员中心边栏Hook
新增会员中心控制器空请求捕获
新增自定义编辑、排序、删除按钮功能
修复图片删除按钮无法点击的BUG
修复会员规则排序BUG
优化配置中extend的位置
2018-09-11 11:20:48 +08:00
eviltrue ace56cf050
toggle无效问题
toggle无效问题
2018-08-17 14:40:46 +08:00
Karson 7413cb3a6a 修复Api控制器目录有非PHP文件时的BUG
移除冗余的Layer资源包
2018-08-08 22:45:13 +08:00
Karson 95c08fb8bd 修复在admin.php下可能引发的链接错误 2018-08-07 09:30:12 +08:00
Karson 7fcae26d37 修复在二级目录下加载404的BUG 2018-08-06 23:00:57 +08:00
Karson c5d7fef22e 更新版本号 2018-08-06 11:54:59 +08:00
Karson 9753d94bc0 新增CRUD一键生成筛选选项卡功能
新增一键生成API文档导航列表功能
优化后台默认加载页的响应速度
优化二级栏目挺拽排序功能
修复后台部分列表会被截断的BUG
修复一键生成API文档@ApiInternal不生效的BUG
修复一键生成API文档空类的BUG
修复一键生成CRUD模型不正在字段的BUG
2018-08-05 21:59:13 +08:00
Karson 3683728023 修复switch.stub丢失的BUG 2018-07-02 21:37:37 +08:00
Karson 3eb938e722 修复Layer样式丢失的BUG 2018-07-02 16:09:05 +08:00
Karson fc2f8774e7 修复Layer样式丢失的BUG 2018-07-01 23:28:28 +08:00
Karson 66edd96c02 修复Layer样式丢失的BUG 2018-07-01 23:26:51 +08:00
Karson e9d26ea603 修复Layer皮肤在部分方法下不启作用的BUG
修复Layer.msg的显示错位
优化Layer.tab的显示样式
2018-07-01 10:53:54 +08:00
Karson b65452c7c3 修复通用搜索在启用自定义搜索时的BUG 2018-06-30 16:40:11 +08:00
Karson 4198f126a9 新增多个FormBuilder用于生成组件的方法
修复datetimerange的format特殊情况下失效的BUG
优化layer-footer的显示
新增CRUD一键生成switcher组件
2018-06-30 15:07:41 +08:00
Karson 1b08595857 新增模型支持目录结构命名
新增Table.api.formatter.normal格式化
优化CRUD删除模式移除空目录
优化多个表格内容格式化方法
2018-06-27 13:57:19 +08:00
Karson 52712b644e 新增alert-success-light等样式类
优化压缩基础CSS文件
2018-06-25 23:22:33 +08:00
Karson 98e5f32429
Merge pull request #31 from gh81997167/master
严格匹配
2018-06-24 23:44:40 +08:00
幻灵姬风 d54583e946 !66 修复执行install命令主机不是127.0.0.1的时候安装失败问题
Merge pull request !66 from 小埋酱/master
2018-06-24 23:39:29 +08:00
kira 03f6ba62d6 修复执行install命令主机不是127.0.0.1的时候安装失败问题
执行php think install --hostname 172.17.0.1 --password 1234 --force true的时候,假如用的主机名不是127.0.0.1的话,会安装失败,因为DB实例连接到本地了
2018-06-23 23:51:26 +08:00
kira f4604e1265 修复执行install命令主机不是127.0.0.1的时候安装失败问题
执行php think install --hostname 172.17.0.1 --password 1234 --force true的时候,假如用的主机名不是127.0.0.1的话,会安装失败,因为DB实例连接到本地了
2018-06-23 23:42:38 +08:00
Karson 3920be0f2b 新增短信发送单IP总数限制
新增$selectpageFields字段用于控制selectpage的字段
修复指定语言不起作用的Bug
修复Selectpage过滤字段不起作用的Bug
优化跳转页面,允许页面停留显示
优化弹窗内部显示,去除min-height限制
优化Citypicker城市数据,使用最新数据源
优化install.php错误提醒
优化部分多语言显示
2018-06-18 11:31:35 +08:00
eviltrue 36271ab07b 严格匹配 2018-06-13 09:55:31 +08:00
Karson 4bb1cbd2dd 优化默认生成的CRUD列表中TD不换行
优化通用搜索显示
2018-05-31 21:07:53 +08:00
Karson 045647c407 修复searchList使用二维数组数据源时排序丢失的BUG
修复后台插件管理登录状态失效的处理和判断
优化列表页通用搜索样式和排版调整
修复关联搜索使用find_in_set时的BUG
优化CRUD在生成中文数据时的编码问题
2018-05-31 20:26:35 +08:00
Karson de8ea27542 新增按钮visible配置
修复commonsearch中兼容配置
修复安装数据库在启用严格模式下无法导入的BUG
2018-05-26 22:27:15 +08:00
Karson ddd75e44c0 移除添加规则中冗余元素 2018-05-16 18:29:54 +08:00
Karson 2ad4650730 修复后台关闭当前选项卡未激活前一选项卡的BUG
修复在移动端包含tooltip需要点击两次的BUG
2018-05-16 17:13:30 +08:00
Karson ac6aa072a2 新增Table.api.formatter.toggle切换 2018-05-13 22:59:41 +08:00
F4NNIU 47d2a430f3 !61 增加 env 配置示例
Merge pull request !61 from F4NNIU/add_env_sample
2018-05-13 17:18:57 +08:00
F4NNIU 37fce92178 增加 env 配置示例 2018-05-13 16:52:48 +08:00
Karson afbe9d2770 修复在自定义后以路径时二级菜单下的BUG 2018-05-13 16:42:24 +08:00
大饼博士 0d78f12ac0 !59 php5.* opcache扩展判断
Merge pull request !59 from 大饼博士/master
2018-05-13 15:55:02 +08:00
Karson 1f9a6e4eb5 更新版本号 2018-05-13 15:47:05 +08:00
Karson 6e408fb2d1 新增二级菜单,可在config.php中开启或关闭
新增渲染状态时搜索匹配
优化渲染图片时点击打开
修复渲染日期时非整型字段的BUG
优化通用搜索功能
优化后台显示基础大小为13px
2018-05-13 15:46:11 +08:00
大饼博士 c62c0dfdc2 php5.* opcache加载判断 2018-05-11 12:11:28 +08:00
Karson 3fac3ae3c3 修复插件管理图片无法展示的BUG 2018-05-08 10:53:44 +08:00
Karson 2a990d3ceb 修复插件管理支付弹窗为空的BUG 2018-05-07 12:36:48 +08:00
Karson e57171dea9 新增附件记录上传会员和上传管理员ID
新增后台管理员admin_login_after和admin_logout_after的行为
新增tooltip和popover
优化和改版插件管理功能
优化多个Layer弹窗的切换体验
优化弹窗高度大于视察高度的操作体验
优化系统配置中发送测试邮件的逻辑
修复生成关联模型时未判断selectpage的BUG
修复表单前置返回失败未释放锁定的BUG
修复API文档生成时采用了GET方法的BUG
2018-05-06 02:05:27 +08:00
Karson 924e52b5e5 修复CRUD目录大小写问题
修复API接口的安全问题
优化MutationObserver在IE10的兼容问题
2018-04-20 12:34:03 +08:00
Karson f035876bfb 修复上传文件后缀判断错误的BUG
修复Token永久有效期判断错误的BUG
修复getUserinfo返回错误token的BUG
2018-04-17 22:08:46 +08:00
F4NNIU 8dd699f699 !56 修改码云的网址为 gitee.com
Merge pull request !56 from F4NNIU/fix_gitee_url
2018-04-14 09:02:25 +08:00
F4NNIU 3c8b25a9bf 修改码云的网址为 gitee.com 2018-04-13 11:20:13 +08:00
Karson 58c72de604 修复插件配置丢失的BUG
修复后台登录验证码在admin.php下无法显示的BUG
修复templateView下筛选框无法点击的BUG
2018-04-07 22:26:00 +08:00
Karson b941f0a3e4 新增插件分类、免费插件搜索
新增全局的Template
新增自定义通用搜索表单内容
新增通用搜索按钮显示配置
新增单独清除模板、插件缓存
新增后台登录失败重试配置
优化通用搜索,搜索元素支持自动绑定元素事件
优化本地插件显示
优化前台首页和API文档字体显示
修复元素验证指定data-target不生效的BUG
修复插件命令行添加--force不生效的BUG
修复noNeedLogin和noNeedRight大小写的BUG
修复fieldlist无法挺拽的BUG
修复一键CRUD后指定字段显示后无法显示关联数据的BUG
2018-04-06 21:39:12 +08:00
Karson d07a642a2d 修复登录成功后无法返回之前页的BUG 2018-04-04 17:37:30 +08:00
Karson 064e129342 修复API一键生成时ApiReturn的错误
修改默认Token驱动为Mysql
修复在系统配置中使用富文本编辑器的BUG
2018-04-02 22:26:05 +08:00
Karson f129e8af7d 新增Token的Redis和Mysql驱动方式 2018-04-02 08:21:30 +08:00
Karson 39ad7c97a2 新增Token多种存储方式
新增fieldlist自定义模板功能
新增关闭会员中心接口
优化Token存储,采用加密存储方式
2018-04-02 00:09:12 +08:00
Karson 2ecae60d86 修复开发模式下未显示错误信息的BUG
修复API部分注释无效的BUG
修复API文档无法替换参数的BUG
2018-04-01 15:12:39 +08:00
Karson 685fc267c0 !55 修复验证码右边字符一半
Merge pull request !55 from Stranger/master
2018-03-28 19:07:59 +08:00
Strangere ba5f8675aa 修改验证码右边字节的一半 2018-03-28 18:59:32 +08:00
F4NNIU b5faa8b87c !52 修复管理添加时不支持中文的问题,与编辑页面统一
Merge pull request !52 from F4NNIU/fix_admin_add
2018-03-28 18:44:24 +08:00
Karson e8f85f531a 修复更新后基类未引入Hook和Config类的BUG 2018-03-27 18:24:30 +08:00
Karson 8fa5cc82e8 新增API接口上传配置
新增后台首页登录免费插件下载提示
修复CRUD默认生成Selectpage请求判断错误的BUG
修复前台模块未加载frontend-init.js的BUG
优化默认关闭DEBUG模式
优化插件管理列表,现支持https请求
优化站内FastAdmin链接,全部修改为https
2018-03-27 17:39:24 +08:00
F4NNIU 706741406c 修复管理添加时不支持中文的问题,与编辑页面统一 2018-03-21 11:01:41 +08:00
Karson cd17735bef 修复后台修改会员密码的BUG
修复命令行创建插件名称的BUG
2018-03-15 00:14:00 +08:00
Karson f28089b1f0 新增CRUD多表关联
新增CRUD筛选字段
新增表格按钮组按钮url支持function功能
新增表格按钮组按钮hidden参数
新增菜单生成支持多控制器

优化错误页面和跳转页面多语言
优化一键生成API文档在PHP5下的提示
优化第三方依赖包配置规则
2018-03-14 16:51:56 +08:00
Karson 97ee22dde6 新增命令行插件管理的uid和token参数
修复后台更新内容过长导致无法操作的BUG
修复后台插件管理查看插件信息错误的BUG
修复注册时验证码判断错误的BUG
移除User.php控制器中的third方法
2018-03-10 21:08:23 +08:00
Karson 45b84424e8 新增邮箱验证码功能
新增Token默认有效期
新增Token检测和刷新API接口
新增前台发送验证码事件监听

修复修改个人资料时跳转的BUG
修复API文档Token设置无效的BUG
修复后台移除左侧链接后菜单消失的BUG

优化前台样式CSS,精简多余的CSS
优化会员登录注册视图模板
2018-03-10 00:54:20 +08:00
Karson 3c09a3f918 新增配置默认输出格式
修复在JSONP请求下不响应输出的BUG
2018-03-09 00:27:46 +08:00
Karson d2a523e462 修复一键生成文档在PHP5下的BUG
修复后台刷新导致的链接的错误
2018-03-08 22:29:29 +08:00
Karson d863f93d10 新增命令行一键生成API文档功能
新增插件绑定二级域名功能
新增加载JS公用模块
新增命令行创建插件自动生成菜单功能
新增后台菜单Fast.api.refreshmenu
新增后台菜单在数据变更后自动刷新的功能
新增require.min.js压缩版
新增从Headers中读取授权token的功能
新增Form.events.daterangepicker时间区别事件
新增Form表单提示成功和失败的回调事件
新增Fast.api.getrowbyid和Fast.api.getrowbyindex方法
新增commonsearch的find_in_set类型搜索
新增Menu::export的方法
新增php think api一键生成API文档功能
新增php think min的压缩参数和调试功能

优化API模块生产环境下错误信息的显示
优化移动端显示移除顶部Logo一行
优化bower.json和composer.json的版本依赖
优化插件管理列表显示
优化后台控制区多作的选项卡数据
优化CRUD生成的复选框样式及文字
优化规则管理的列表显示
优化第三方前端资源,移除冗余资源

修复在启用域名部署下的BUG
修复API初始化接口的BUG
修复会员积分日志模型BUG
修复多语言切换不存在的BUG
修复Backend.php中multi操作不触发模型事件的BUG
2018-03-08 14:44:43 +08:00
F4NNIU 5155d80295 !46 修复前台个人资料无法修改个人介绍的问题
Merge pull request !46 from F4NNIU/fix_user_profile_bio
2018-03-02 14:24:05 +08:00
F4NNIU 104f9e7ce7 修复前台个人资料无法修改个人介绍的问题 2018-03-02 13:34:04 +08:00
F4NNIU 8de46a30e4 !45 修复 CRUD 高级搜索中状态的值不正确的问题
Merge pull request !45 from F4NNIU/fix_crud_state
2018-03-02 11:08:59 +08:00
F4NNIU b4c92b2065 修复 CRUD 高级搜索中状态的值不正确的问题 2018-03-02 11:02:34 +08:00
Karson 7dfaabbbe9 2018-02-27 15:26:12 +08:00
Karson 3ea5689a0b 新增上传的onUploadProgress和onUploadComplete事件
新增后台Auth类getError和setError方法
修复组权限管理在特殊情况下无法加载权限的BUG
修复Upload.api.send无法上传至第三方存储的BUG
优化Layer.msg的样式显示
2018-02-27 15:24:59 +08:00
F4NNIU e67192a2d7 !44 修复 category 页 Tab url 显示
Merge pull request !44 from F4NNIU/fix_category_tab_url
2018-02-27 10:12:52 +08:00
F4NNIU 2b2b114b03 修复 category 页 Tab url 显示 2018-02-26 23:01:39 +08:00
F4NNIU 772c47ee0d !42 忽略 install.lock 文件
Merge pull request !42 from F4NNIU/fix_install_lock
2018-02-26 16:12:59 +08:00
F4NNIU 5cad1792de !40 删除 dashboard 重复定义的 Custom zone 语言
Merge pull request !40 from F4NNIU/dashboard_lang_fix
2018-02-26 16:12:49 +08:00
F4NNIU 6e6a749e03 忽略 install.lock 文件 2018-02-26 13:21:02 +08:00
F4NNIU dac47ec40e 删除 dashboard 重复定义的 Custom zone 语言 2018-02-24 22:19:11 +08:00
Karson f572e345d5 新增插件的多语言包
新增附件删除的行为
新增命令行安装时的参数配置
新增一键生成菜单时忽略未启用软删除的方法
修复在iOS下列表中列字段过多未启用卡片视图的BUG
修复会员管理中规则管理分页的BUG
修复导航菜单隐藏后仍显示的BUG
优化数据列表在移动端的显示
优化异常页面的显示
2018-02-22 20:30:36 +08:00
Karson 66c3eb9ad3
Merge pull request #26 from Chrisleung/master
fix
2018-02-08 18:52:15 +08:00
Chrisleung dcd0a80bd4 1. 修复api演示模块返回信息文字错误
2. 修复会员模块,修改,激活邮箱,手机号失败问题
2018-02-08 14:18:47 +08:00
Karson 4260ee14e3 新增命令行插件升级和打包
修复后台保持登录不起作用的BUG
优化安装程序的文字提示
2018-02-07 23:49:17 +08:00
Karson dc99d26252
Merge pull request #24 from xinjiayu/master
分类管理新新增加按类型进行筛选的功能。
2018-02-05 13:55:32 +08:00
microrain 208b94894b 修正未按语言文件显示的问题 2018-02-05 13:52:51 +08:00
microrain 30bc867ed5 分类管理新新增加按类型进行筛选的功能。 2018-02-05 13:46:59 +08:00
Karson c06a108f1b 完善FastAdmin的介绍 2018-02-04 23:00:17 +08:00
Karson 26a35c9738 新增插件在线一键升级功能
新增安装指定插件版本
修复二级管理员无法添加管理员的BUG
优化require-table在add_url的ids替换
2018-02-04 00:36:17 +08:00
Karson 36a392962e 修复二级目录下JS中请求插件地址时错误的BUG 2018-01-31 23:06:49 +08:00
F4NNIU e0987fda2d !36 修复 test 表中添加数据时显示 admin_id 无默认值的报错
Merge pull request !36 from F4NNIU/fix_admin_id_defalut_value
2018-01-31 22:26:54 +08:00
Karson f2152bee1f 新增后台3次登录失败后1天以后才可再次尝试
新增Config中多个变量供JS中使用
修复分类管理在使用CRUD一键生成关联模型后的BUG
修复后台管理员selectpage编辑时的BUG
2018-01-31 22:26:32 +08:00
F4NNIU ac7873f61d 修复 test 表中添加数据时显示 admin_id 无默认值的报错 2018-01-30 15:48:48 +08:00
Karson a85676435a 2018-01-30 00:07:03 +08:00
Karson 5f80d65e27 新增dataLimitFieldAutoFill属性
新增管理员的selectpage接口
新增前台Auth的ucenter同步登录注册登出接口
新增前台User控制器的ucenter插件判断
优化Sms::notice发送通知的方法
优化common-search回调事件
2018-01-29 23:56:04 +08:00
biorz 46f67fc974 !33 表格的拖拽排序功能, 在拖拽的时候有点卡卡的, 做了一点调整
Merge pull request !33 from biorz/master
2018-01-23 23:19:29 +08:00
biorz fbfb5e2f71 优化拖拽排序 2018-01-23 18:01:51 +08:00
Karson 02ff41c0f2 修复后台常规管理修改配置时远程验证的错误
修复SMS发送验证码的错误
2018-01-22 23:25:55 +08:00
Karson f26dec833b 修复后台开启只能同时在一点登录的BUG 2018-01-21 22:18:05 +08:00
Karson 1123445f3c 修复后台admin模块仍然使用路由的BUG
修复后台刷新特殊情况下标签卡不显示标题的BUG
2018-01-21 14:00:51 +08:00
Karson f11a56bef2 修复API接口协议无法下载的BUG 2018-01-20 21:15:04 +08:00
Karson 4893d5fe75 新增fa_sms表结构 2018-01-20 17:16:54 +08:00
Karson 40ff022966 新增前台会员模块和API会员模块
新增后台会员管理、会员规则和会员分组管理
新增短信发送的行为事件
新增前台Token、短信、日志模型
新增自动检测更新配制
新增插件信息查看
优化CRUD生成普通搜索的功能
优化后台登录后管理员Session的存储
2018-01-19 23:40:45 +08:00
Karson 6a1100f018 新增前台模块和API模块的权限控制功能
新增CRUD排除字段的功能
新增API模块短信发送、开发示例控制器
新增前台通用的会员权限类Auth
新增前台Token操作类
新增插件管理本地插件的搜索功能
修复压缩打包在Windows下路径中含有空格导致错误的BUG
修复fastadmin.less无法编译为CSS的BUG
优化插件管理中的搜索
优化权限规则的增改
优化后台皮肤Black,实为White
优化跳转页模板在Chrome下自动翻译的BUG
2018-01-17 17:29:22 +08:00
F4NNIU 55786edeb0 !31 修复系统配置字段为编辑器时,页面无法渲染出编辑器的问题
Merge pull request !31 from F4NNIU/config_editor
2018-01-11 09:12:46 +08:00
F4NNIU 956f003338 修复系统配置字段为编辑器时,页面无法渲染出编辑器的问题,感谢 QQ 群小伙伴古月玄的反馈。 2018-01-06 13:50:35 +08:00
F4NNIU 3fe0025fed !30 将 ThikPHP 5 版本限定在 V5.0.x
Merge pull request !30 from F4NNIU/composer_json_tp5_1
2018-01-02 09:31:25 +08:00
F4NNIU 2c6e4f9421 将 ThikPHP 5 版本限定在 V5.0.x,来自群友 yun-安徽 反馈。 2018-01-02 08:54:02 +08:00
nyzcling b72ddda452 !29 修正附件管理编辑后保存提示uploadtime字段数据库截断错误
Merge pull request !29 from nyzcling/master
2017-12-27 21:02:41 +08:00
nyzcling 6f2f4fb89d 更新 Attachment.php 2017-12-27 20:51:44 +08:00
Karson 483b901cc4 修复导入时在表字段超过26时的错误
新增导入Excel文件首行类型配置,默认注释或表字段名
修复表格在字段为null值时的报错
2017-12-25 12:21:40 +08:00
Karson 5a87939e86 修复本地上传未限制文件格式的BUG 2017-12-23 12:23:56 +08:00
Karson 3a92ef5564 修复非超级管理员添加分组的错误
修复登录后默认不是控制台首页的BUG
2017-12-22 18:42:54 +08:00
Karson b7300ea558 修复表格在多个列头时buttons的事件可能不响应的BUG
移除表格按钮columnIndex,增加fieldIndex属性
2017-12-21 23:04:02 +08:00
Karson 8756cf38a1 新增增/删/改链接{ids}替换功能
优化replaceurl移除value替换功能
2017-12-20 20:39:12 +08:00
F4NNIU 9df6949c55 !28 将文件检查函数改为 is_file
Merge pull request !28 from F4NNIU/chk_install_lock
2017-12-19 23:11:09 +08:00
Karson 50e91c53bb 新增btn-ajax操作成功后自动刷新表格的功能
表格buttons新增refresh参数
修复btn-ajax回调函数在多次调用时的BUG
修复在启用dataLimit时关联查询时的BUG
2017-12-19 23:10:22 +08:00
F4NNIU f8412b828f 将文件检查函数改为 is_file,为了提高效率,来自群友 Jesns 建议。 2017-12-19 16:49:17 +08:00
Karson 2f2c003f65 修复commonsearch在多表格下datetimerange可能出现的BUG
新增Table.api.getrowdata通过行索引获取行数据
2017-12-18 11:03:38 +08:00
Karson 0e41ecf6eb 优化修复在移动端后台个人信息的显示
优化插件管理搜索
2017-12-16 23:23:18 +08:00
Karson f463fa980a 新增buttons配置success/error/callback回调事件
新增buttons配置confirm提示框功能
新增buttons的URL替换行其它数据
完善btn-ajax/btn-addtabs/btn-dialog的功能
修复后台系统配置使用复选框的BUG
2017-12-16 17:29:34 +08:00
Karson 87c12d6847 修复TP5升级到5.0.13后一键生成菜单的BUG
完善安装脚本模块注释
增加安装脚本BUG打印
2017-12-15 11:25:52 +08:00
Karson c42f07928b 修复升级到最新版Layer导致样式错误的BUG 2017-12-07 14:56:34 +08:00
Karson a03435bf70
Merge pull request #19 from Chrisleung/master
修复layer升级后,因layer.css路径改变引起的浮层样式丢失问题
2017-12-07 14:54:30 +08:00
Chrisleung cfdd675c05 修复layer升级,layer.css路径改变引起的样式丢失问题
请执行bower update
2017-12-07 10:38:02 +08:00
Karson 0ac468d85c 修复超级管理员无法在列表查看超级管理员的BUG 2017-12-06 17:50:23 +08:00
Karson 0c953105c8 新增后台登录验证码功能
优化登录相关配置
2017-12-06 17:28:24 +08:00
Karson 4149fce838 修复规则添加正则错误的BUG
优化登录状态session中存储为数组格式
优化Auth的isLogin方法只需查询一次数据库
2017-12-06 10:13:44 +08:00
Karson 65d04fe528 新增管理员添加编辑唯一性验证
新增规则管理添加编辑唯一性验证
修复最新版nice-validator导致提示换行的BUG
修复php think menu -c all-controller无法重建菜单的BUG
2017-12-04 15:34:29 +08:00
F4NNIU 6f3fc7d302 !27 修改 mimetype 长度由 50 改为 100
Merge pull request !27 from F4NNIU/mimetype
2017-12-02 22:24:39 +08:00
F4NNIU af1005dcd7 修改 mimetype 长度由 50 改为 100,根据 QQ 群 Rocky-北京 反馈 mimetype 长度不够,服务器上传了一个表格文件,获取到的类型都大于60了。 2017-12-02 16:05:10 +08:00
Karson 3ece8a3102 压缩打包JS 2017-12-01 20:17:20 +08:00
Karson c156a472cc 修复通过JS刷新彩色角标不工作的BUG
优化后台修改用户名和密码的autocomplete
2017-12-01 17:00:48 +08:00
Karson 02759ae2f8 新增iframeForceRefresh强制刷新iframe功能
修复JS未加载完成时点击链接时的BUG
修复不在菜单栏中选项卡刷新后的BUG
优化选项卡刷新后打开的效率
2017-11-30 23:40:23 +08:00
Karson 55473c9029 新增控制同一时间同一管理员只能在一个地方登录的功能
修复后台背景插件不作用的BUG
修复后台常规管理,系统配置新增时无法保存的BUG
2017-11-30 17:02:34 +08:00
Karson dd3f951f13 修复管理员列表,部分情况下角色组不显示的BUG 2017-11-25 20:35:26 +08:00
Karson 878e186969
Merge pull request #18 from Chrisleung/master
修复后台缺少city-picker.js问题
2017-11-22 22:52:28 +08:00
Chrisleung b50fc0f6f6 修复后台缺少city-picker.js问题 2017-11-22 17:00:46 +08:00
Karson a197664a6e !25 修复windows和unix换行符不同引起的JS、CSS打包压缩失败问题
Merge pull request !25 from 常乐/master
2017-11-22 12:24:08 +08:00
Karson 225fcfc61b !24 修复1062 Duplicate entry 'addon/index' for key 'name'
Merge pull request !24 from private/master
2017-11-22 12:23:57 +08:00
Chirs 0a44858c89 修复windows和unix换行符不同引起的JS、CSS打包压缩失败问题 2017-11-22 11:38:51 +08:00
Amor 8e29c5f9c7 优化 Menu.php getAuthRulePK 方法 2017-11-20 16:20:27 +08:00
Amor 732dd19efd 添加主键,修复无法更新fa_auth_rule 问题 2017-11-20 16:04:50 +08:00
Karson c129974c12 修复php think min无法在Win下压缩的BUG
修复Auth.php类在数据表前缀配置为空情况下报错的BUG
修复网页端安装程序无法写入配置的BUG
2017-11-18 13:05:49 +08:00
F4NNIU 2a00751b81 !22 修复环境变量 env 中设置 app_debug 后出现前后端不一致的问题。
Merge pull request !22 from F4NNIU/env_config_debug
2017-11-16 10:16:08 +08:00
F4NNIU 2e5710bbcd 修复环境变量 env 中设置 app_debug 后出现前后端不一致的问题。 2017-11-15 08:31:19 +08:00
F4NNIU 0c5e4cbe2e !20 允许从环境变量 env 中获得数据库配置信息。
Merge pull request !20 from F4NNIU/env_db
2017-11-13 09:42:18 +08:00
F4NNIU 3c517a0609 允许从环境变量 env 中获得数据库配置信息。 2017-11-12 22:35:49 +08:00
F4NNIU 0803a2ca6a !18 忽略 env 文件
Merge pull request !18 from F4NNIU/master
2017-11-08 21:47:57 +08:00
F4NNIU ae051d66ff 忽略 .env 文件。 2017-11-08 15:22:09 +08:00
Karson a0eca04ae9 统一修改文件换行符
修复后台提示两次的BUG
修复上传不能传xls和xlsx文件的BUG
2017-11-07 23:15:14 +08:00
Karson 65a3080da9 优化gitignore 2017-11-04 19:31:08 +08:00
Karson c247d4d108 修改登录失败时未出现提示的BUG
新增自动记录__PUBLIC__和__ROOT__,可在视图模板中使用
修复后台点击首页时不正确的BUG
2017-11-03 17:33:15 +08:00
Karson e4dfd7082d 修复系统配置不能保存的BUG 2017-10-28 09:48:15 +08:00
Karson 8bc581c07c 新增导入Excel功能
新增回收站功能
新增插件伪静态配置
新增前端上传组件的回调事件
新增表格column字段中的width参数功能
新增上传和选择限时最多可上传、选择数
添加CRUD中admin_id保留字段
修复非summernote富文本插件不渲染的BUG
移除Backend.php中添加和修改时对请求的数据自动处理
优化分类列表的效率
修复后管理权限管理组不显示的BUG
优化权限级和管理员列表,使用树状显示
修复cdnurl使用site.cdnurl的错误
修复Date::unixtime中获取月天数错误的BUG
优化后台样式
2017-10-26 13:43:44 +08:00
Karson df5f784f5d 修复普通搜索的BUG 2017-09-20 20:34:56 +08:00
Karson 4b39ec52f9 修复iOS下无法点击按钮的BUG
优化移动端错误提示
2017-09-20 14:53:57 +08:00
Karson 23f4fb1b8c 修复命令行生成菜单的错误。 2017-09-20 09:44:41 +08:00
Karson cb929e8207 修复时间搜索在between下的BUG 2017-09-19 17:39:14 +08:00
Karson 39593c404c 优化区间placeholder显示 2017-09-19 12:59:59 +08:00
Karson a52a8a1a25 修复搜索时部分参数被过滤的BUG 2017-09-18 21:15:05 +08:00
Karson c29e0accc7 新增daterangepicker插件
新增operate的RANGE值配置
修复后台删除时的错误
优化语言包
2017-09-17 21:52:37 +08:00
Karson 62d77f9f96 修复部分语言标识
修复前台首页统计错误
2017-09-16 09:43:52 +08:00
Karson 47b329822e 修复系统设置被隐藏的问题 2017-09-15 22:20:42 +08:00
Karson 49e6704275 修复traits中add的dataLimitType错误 2017-09-15 19:04:50 +08:00
Karson 28e4a782a9 修复20170915版本号 2017-09-15 18:30:15 +08:00
Karson 75c28dc48a 新增数据限制功能,支持限制当前管理员数据
优化全站多语言的文字标识
优化后台控制台首页显示
优化后台管理员编辑自动填充
修复多语言函数__返回全部语言包的BUG
2017-09-15 17:55:11 +08:00
Karson a12ec606db 修复selectpage打包 2017-09-11 20:09:18 +08:00
Karson 4c16ad18f8 Merge pull request #14 from iuyes/master
列表无图的时候,使用一个透明图片替代
2017-09-11 06:36:30 -05:00
Karson 4e1cec1900 新增Auth的getGroupIds、getChildrenAdminIds和getChildrenGroupIds方法
移除require-table.js的label方法
修复commonsearch普通搜索中queryParmas的BUG
修复operate为小写字母导致的BUG
优化管理员、管理组的代码
2017-09-11 19:35:10 +08:00
李云龙 f2fda511a2 列表无图的时候,使用一个透明图片替代 2017-09-08 21:44:51 +08:00
Karson 20c7963e22 修复批量选中编辑时的错误 2017-09-04 21:54:03 +08:00
西集 f11f4a7738 !17 修复crud命令生成的radio和checkbox的对齐问题
Merge pull request !17 from 西集/fixed/crud_radio_checkbox
2017-09-03 10:26:53 +08:00
Karson 14b67d6be1 新增普通搜索的onCommonSearch事件和onPostCommonSearch事件
优化普通搜索显示
优化菜单双击事件
优化后台插件安装提示
优化普通搜索操作符,Between支持单边搜索
2017-09-03 10:25:50 +08:00
西集 c58ff40bb4 修复crud命令生成的radio和checkbox的对齐问题 2017-09-02 21:39:06 +08:00
Karson 4fbc2c8eb0 新增Table.api.closetabs关闭选项卡的方法
新增表单提交成功后callback功能
新增mb_ucfirst函数
修复CRUD在ENUM中文字符下的乱码错误
修复快速查询在关联模型下的错误
修复filter过滤在部分情况不生效的BUG
2017-09-02 10:38:34 +08:00
Karson 1ceece32d7 修复搜索时op参数被过滤的BUG 2017-08-31 22:39:50 +08:00
Karson 5fa5ef2750 修复bower安装时的依赖提示
新增Table.api.formatter.dialog方法
优化多个Table.api.formatter中的方法
2017-08-31 18:24:45 +08:00
Karson 57a62b94a7 修复命令行新建插件的错误 2017-08-31 13:55:59 +08:00
Karson 7d45e67fdb 新增插件管理本地插件功能
新增插件管理HTTPS提示
新增插件管理登录会会员功能
完善插件管理语言包
修复build_heading的错误
2017-08-31 13:31:08 +08:00
Karson fb6c6bffe2 修复菜单无法通过命令行删除的BUG
修复语言标识可能导致的XSS的BUG
修复路由未对admin模块失效的BUG
2017-08-29 19:10:38 +08:00
Karson 332e786a83 修复复选框、筛选列表为空时不更新的BUG
根据Ajax返回的token修改表单中token的值
2017-08-28 10:50:31 +08:00
Karson e50a737777 修复复选框、筛选列表为空时不更新的BUG
根据Ajax返回的token修改表单中token的值
2017-08-28 10:49:41 +08:00
Karson 37937f1665 新增buttons的extend参数
新增overtrue/pinyin包依赖
修复cdnurl函数获取不了配置的BUG
优化buttons的url参数
优化插件前端文件addons.js,移除backend依赖
优化php think min在Win下可能导致压缩失败的问题
优化后台菜单获取拼音字母可能导致的错误
优化mbstring编码问题,默认为utf-8
2017-08-26 21:24:06 +08:00
Karson d911792d02 Merge pull request #9 from iuyes/master
修复了图片上传表单验证时提示信息覆盖在功能按钮上的错误
2017-08-22 23:54:15 +08:00
李云龙 cde1ca82d4 修复了图片上传表单验证时提示信息覆盖在功能按钮上的错误
已经过测试
2017-08-22 15:33:49 +08:00
Karson 7ae79daead Merge https://github.com/karsonzhang/fastadmin 2017-08-20 22:18:27 +08:00
Karson 0130e070b7 Merge pull request #8 from qiqizjl/fix-error-page
修正异常模板配置逻辑
2017-08-20 13:53:24 +08:00
耐小心 7ae06f0a64 修正异常模板配置逻辑 2017-08-20 01:32:52 +08:00
西集 0c1e95c71a !15 修复radio和checkbox样式对齐
Merge pull request !15 from 西集/master
2017-08-18 23:03:43 +08:00
西集 4eadcd5626 修复radio和checkbox样式对齐 2017-08-18 11:44:49 +08:00
Karson 04f4107423 修复Table.api.formatter.buttons和Table.api.formatter.formatter链接计算错误的BUG 2017-08-18 11:43:57 +08:00
Karson 07a16a6797 新增Table.api.formatter.buttons方法
优化管理员列表和分组列表显示
btn-dialog,btn-addtabs,btn-ajax的URL新增{ids}可替换为表格选中项
禁用分类管理下的排序
新增Trace开启的情况下Ajax不打印trace信息
优化新增、编辑按钮,可动态传参到Layer弹窗
2017-08-17 23:41:58 +08:00
Karson 6b4cb9c52a 修复插件基类Controller被调用两次的BUG
添加插件初始化的行为
修复表格始终显示排序按钮的BUG
优化部分视图的权限判断
优化本地上传插件的提示
2017-08-17 14:19:56 +08:00
Karson 3b295553a6 修复弹出窗出现滚动条的BUG
优化插件安装时的错误提示
优化附件管理上传中的文字
优化fastadmin.sql的基础数据
优化后台添加规则的提示
优化Frontend.php的逻辑
优化前台首页第三方资源的加载
更新版本号
2017-08-16 19:00:11 +08:00
Karson 836eca5b68 修复Layer全局污染的问题
修复错误提交的插件配置
修复iOS下页面底部点击的BUG
优化弹出窗无标题时关闭按钮的显示
2017-08-15 16:58:28 +08:00
Karson d7bc9ffcc2 新增视图中根据权限控制元素显示
新增生产模式下新的异常提示页
新增Table.api.formatter权限控制
新增Table.api.formatter的buttons参数
将后台common.php中的通过方法移动到全局可调用
修复iOS在底部弹出窗时的错误
修复iOS下文本框自动变大的问题
修改CRUD基础index模板
移除Date::cron方法
修复后台登录一处JS报错
修复Fast.api.ajax发生错误时提示不正确的问题
2017-08-15 09:29:10 +08:00
Karson 20933984e4 修复角色组添加BUG 2017-08-13 07:44:50 +08:00
Karson 1bcb46dbee 修复在安卓下后台无法加载的BUG
修复ios下Layer弹窗的错误
修复ios下iframe滚动的BUG
优化table兼容性
2017-08-12 21:27:33 +08:00
Karson e927a7b09d 修复插件安装后不显示配置按钮的BUG
新增dropdown不消失的BUG
新增移动端点击空白处菜单隐藏的功能
2017-08-10 12:01:12 +08:00
Karson 829395b491 修复上传时不返回AJAX格式信息 2017-08-09 18:22:20 +08:00
Karson 7cf6ff9f0b 更新commposer中fastadmin-addons使用dev-master版本 2017-08-09 12:47:08 +08:00
Karson 14d3e9afc8 移除require-upload.js的中x-requested-with 2017-08-09 10:30:12 +08:00
Karson 83efb22403 压缩打包 2017-08-09 01:05:30 +08:00
Karson 151e289cc4 修复composer配置中包的错误 2017-08-08 23:58:56 +08:00
Karson 0cb5862736 新增插件功能
新增后台版本检测功能
新增插件命令行操作
新增citypicker城市选择插件
新增Backend.api.open的callback功能
新增Backend.api.close方法用于回调
新增Menu菜单类
新增Version版本类
新增rmdirs和copydirs函数
新增btn-ajax类直接发送AJAX请求功能
新增$this->assignconfig渲染数据到JS中的功能
启用全新网站首页

变更上传和表单的接口参数
变更菜单栏折叠时的展现方式
修复在包含下线划表的关联模型下的搜索错误
移除auth_rule表中name的admin前缀
移除从DOM中绑定上传和表单的事件
移除插件化后的模块及数据库
移除使用$this->code和$this->data功能
移除前台冗余的控制器和类
2017-08-08 23:36:55 +08:00
528 changed files with 45890 additions and 61288 deletions

View File

@ -1,3 +1,11 @@
{
"directory" : "public/assets/libs"
}
"directory": "public/assets/libs",
"ignoredDependencies": [
"es6-promise",
"file-saver",
"html2canvas",
"jspdf",
"jspdf-autotable",
"pdfmake"
]
}

11
.env.sample 100644
View File

@ -0,0 +1,11 @@
[app]
debug = false
trace = false
[database]
hostname = 127.0.0.1
database = fastadmin
username = root
password = root
hostport = 3306
prefix = fa_

View File

@ -0,0 +1,22 @@
### 类型
类型(问题/建议/其他):?
### 现象
现象(请详细描述一下复现过程):?
### 期望结果
期望结果(请详细描述一下你说期望的结果):?
### 环境
(请详细说明一下你的运行环境)
- 操作系统Linux/Windows/Other
- Web ServerNGINX/Apache/Other
- PHP 版本7.2/7.3/7.4/8.0/8.1/8.2/Other
- MySQL 版本(5.6/5.7/8.0/Other)
- 服务器面板BT/phpStudy/XAMPP/其他/无):?
- FastAdmin 版本:?
- 浏览器Chrome/IE/Edge/其他):?
- 报错信息:?

29
.gitignore vendored 100755 → 100644
View File

@ -1,11 +1,18 @@
/nbproject/
.idea
composer.lock
*.log
*.css.map
thinkphp
vendor
runtime
public/assets/libs/
/application/admin/command/Install/*.lock
/public/uploads
/nbproject/
/thinkphp/
/vendor/
/runtime/*
/addons/*
/public/assets/libs/
/public/assets/addons/*
/public/uploads/*
.idea
composer.lock
*.log
*.css.map
!.gitkeep
.env
.svn
.vscode
node_modules
.user.ini

View File

@ -1,42 +0,0 @@
sudo: false
language: php
branches:
only:
- stable
cache:
directories:
- $HOME/.composer/cache
before_install:
- composer self-update
install:
- composer install --no-dev --no-interaction --ignore-platform-reqs
- zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Core.zip .
- composer require --update-no-dev --no-interaction "topthink/think-image:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-migration:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-captcha:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-mongo:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-worker:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-helper:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-queue:^1.0"
- composer require --update-no-dev --no-interaction "topthink/think-angular:^1.0"
- composer require --dev --update-no-dev --no-interaction "topthink/think-testing:^1.0"
- zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Full.zip .
script:
- php think unit
deploy:
provider: releases
api_key:
secure: TSF6bnl2JYN72UQOORAJYL+CqIryP2gHVKt6grfveQ7d9rleAEoxlq6PWxbvTI4jZ5nrPpUcBUpWIJHNgVcs+bzLFtyh5THaLqm39uCgBbrW7M8rI26L8sBh/6nsdtGgdeQrO/cLu31QoTzbwuz1WfAVoCdCkOSZeXyT/CclH99qV6RYyQYqaD2wpRjrhA5O4fSsEkiPVuk0GaOogFlrQHx+C+lHnf6pa1KxEoN1A0UxxVfGX6K4y5g4WQDO5zT4bLeubkWOXK0G51XSvACDOZVIyLdjApaOFTwamPcD3S1tfvuxRWWvsCD5ljFvb2kSmx5BIBNwN80MzuBmrGIC27XLGOxyMerwKxB6DskNUO9PflKHDPI61DRq0FTy1fv70SFMSiAtUv9aJRT41NQh9iJJ0vC8dl+xcxrWIjU1GG6+l/ZcRqVx9V1VuGQsLKndGhja7SQ+X1slHl76fRq223sMOql7MFCd0vvvxVQ2V39CcFKao/LB1aPH3VhODDEyxwx6aXoTznvC/QPepgWsHOWQzKj9ftsgDbsNiyFlXL4cu8DWUty6rQy8zT2b4O8b1xjcwSUCsy+auEjBamzQkMJFNlZAIUrukL/NbUhQU37TAbwsFyz7X0E/u/VMle/nBCNAzgkMwAUjiHM6FqrKKBRWFbPrSIixjfjkCnrMEPw=
file:
- ThinkPHP_Core.zip
- ThinkPHP_Full.zip
skip_cleanup: true
on:
tags: true

380
LICENSE
View File

@ -1,191 +1,191 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "{}" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.
Copyright 2017 Karson
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "{}" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.
Copyright 2017 Karson
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,71 +1,86 @@
FastAdmin是一款基于ThinkPHP5+Bootstrap的极速后台开发框架。
===============
FastAdmin是一款基于ThinkPHP+Bootstrap的极速后台开发框架。
## **主要特性**
## 主要特性
* 基于`Auth`验证的权限管理系统
* 支持无限级父子级权限继承,父级的管理员可任意增删改子级管理员及权限设置
* 支持单管理员多角色
* 支持目录和控制器结构一键生成权限节点
* 支持管理子级数据或个人数据
* 强大的一键生成功能
* 一键生成CRUD,包括控制器、模型、视图、JS、语言包、菜单、回收站等
* 一键压缩打包JS和CSS文件一键CDN静态资源部署
* 一键生成控制器菜单和规则
* 一键生成API接口文档
* 完善的前端功能组件开发
* 基于`AdminLTE`二次开发
* 基于`Bootstrap`开发自适应手机、平板、PC
* 基于`RequireJS`进行JS模块管理按需加载
* 基于`Less`进行样式开发
* 基于`Bower`进行前端组件包管理
* 数据库表一键生成`CRUD`,包括控制器、模型、视图、JS、语言包
* 一键压缩打包JS和CSS文件
* 强大的插件扩展功能,在线安装卸载升级插件
* 通用的会员模块和API模块
* 共用同一账号体系的Web端会员中心权限验证和API接口会员权限验证
* 二级域名部署支持,同时域名支持绑定到应用插件
* 多语言支持,服务端及客户端支持
* 无缝整合又拍云上传功能
* 支持大文件分片上传、剪切板粘贴上传、拖拽上传,进度条显示,图片上传前压缩
* 支持表格固定列、固定表头、跨页选择、Excel导出、模板渲染等功能
* 强大的第三方应用模块支持([CMS](https://www.fastadmin.net/store/cms.html)、[CRM](https://www.fastadmin.net/store/facrm.html)、[企业网站管理系统](https://www.fastadmin.net/store/ldcms.html)、[知识库文档系统](https://www.fastadmin.net/store/knowbase.html)、[在线投票系统](https://www.fastadmin.net/store/vote.html)、[B2C商城](https://www.fastadmin.net/store/shopro.html)、[B2B2C商城](https://www.fastadmin.net/store/wanlshop.html))
* 整合第三方短信接口(阿里云、腾讯云短信)
* 无缝整合第三方云存储(七牛云、阿里云OSS、腾讯云存储、又拍云)功能,支持云储存分片上传
* 第三方富文本编辑器支持(Summernote、百度编辑器)
* 第三方登录(QQ、微信、微博)整合
* Ucenter整合
* 第三方支付(微信、支付宝)无缝整合微信支持PC端扫码支付
* 丰富的插件应用市场
## **安装使用**
## 安装使用
http://doc.fastadmin.net
https://doc.fastadmin.net
## **在线演示**
## 在线演示
http://demo.fastadmin.net
https://demo.fastadmin.net
用户名admin
 123456
提 示:演示站数据无法进行删除和修改,只能新增,完整体验请下载源码安装体验
提 示:演示站数据无法进行修改,请下载源码安装体验全部功能
## **界面截图**
![控制台](https://git.oschina.net/uploads/images/2017/0411/113717_e99ff3e7_10933.png "控制台")
## 界面截图
![控制台](https://images.gitee.com/uploads/images/2020/0929/202947_8db2d281_10933.gif "控制台")
## **问题反馈**
## 问题反馈
在使用中有任何问题,请使用以下联系方式联系我们
交流社区: http://forum.fastadmin.net
QQ群: [636393962](https://jq.qq.com/?_wv=1027&k=487PNBb)
Email: (karsonzhang#163.com, 把#换成@)
weibo: [@karsonzhang](https://weibo.com/karsonzhang)
问答社区: https://ask.fastadmin.net
Github: https://github.com/karsonzhang/fastadmin
Git@OSC: https://git.oschina.net/karson/fastadmin
Gitee: https://gitee.com/karson/fastadmin
## **特别鸣谢**
## 特别鸣谢
感谢以下的项目,排名不分先后
ThinkPHPhttp://www.thinkphp.cn
AdminLTEhttps://almsaeedstudio.com
AdminLTEhttps://adminlte.io
Bootstraphttp://getbootstrap.com
jQueryhttp://jquery.com
Bootstrap-tablehttps://github.com/wenzhixin/bootstrap-table
Nice-validator: https://validator.niceue.com
SelectPage: https://github.com/TerryZ/SelectPage
Layer: https://layuion.com/layer/
DropzoneJS: https://www.dropzonejs.com
## 版权信息
@ -73,6 +88,6 @@ FastAdmin遵循Apache2开源协议发布并提供免费使用。
本项目包含的第三方源码和二进制文件之版权信息另行标注。
版权所有Copyright © 2017-2018 by FastAdmin (http://www.fastadmin.net)
版权所有Copyright © 2017-2024 by FastAdmin (https://www.fastadmin.net)
All rights reserved。
All rights reserved。

1
addons/.gitkeep 100644
View File

@ -0,0 +1 @@

1
addons/.htaccess 100755
View File

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

View File

@ -2,17 +2,13 @@
namespace app\admin\behavior;
use think\Config;
class AdminLog
{
public function run(&$params)
public function run(&$response)
{
if (request()->isPost())
{
//只记录POST请求的日志
if (request()->isPost() && config('fastadmin.auto_record_log')) {
\app\admin\model\AdminLog::record();
}
}
}

View File

@ -0,0 +1,342 @@
<?php
namespace app\admin\command;
use think\addons\AddonException;
use think\addons\Service;
use think\Config;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
use think\Db;
use think\Exception;
use think\exception\PDOException;
class Addon extends Command
{
protected function configure()
{
$this
->setName('addon')
->addOption('name', 'a', Option::VALUE_REQUIRED, 'addon name', null)
->addOption('action', 'c', Option::VALUE_REQUIRED, 'action(create/enable/disable/uninstall/refresh/package/move)', 'create')
->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override', null)
->addOption('release', 'r', Option::VALUE_OPTIONAL, 'addon release version', null)
->addOption('uid', 'u', Option::VALUE_OPTIONAL, 'fastadmin uid', null)
->addOption('token', 't', Option::VALUE_OPTIONAL, 'fastadmin token', null)
->addOption('domain', 'd', Option::VALUE_OPTIONAL, 'domain', null)
->addOption('local', 'l', Option::VALUE_OPTIONAL, 'local package', null)
->setDescription('Addon manager');
}
protected function execute(Input $input, Output $output)
{
\think\Config::load(dirname(dirname(__FILE__)) . DS . 'config.php');
$name = $input->getOption('name') ?: '';
$action = $input->getOption('action') ?: '';
if (stripos($name, 'addons' . DS) !== false) {
$name = explode(DS, $name)[1];
}
//强制覆盖
$force = $input->getOption('force');
//版本
$release = $input->getOption('release') ?: '';
//uid
$uid = $input->getOption('uid') ?: '';
//token
$token = $input->getOption('token') ?: '';
include dirname(__DIR__) . DS . 'common.php';
if (!$name && !in_array($action, ['refresh'])) {
throw new Exception('Addon name could not be empty');
}
if (!$action || !in_array($action, ['create', 'disable', 'enable', 'install', 'uninstall', 'refresh', 'upgrade', 'package', 'move'])) {
throw new Exception('Please input correct action name');
}
// 查询一次SQL,判断连接是否正常
Db::execute("SELECT 1");
$addonDir = ADDON_PATH . $name . DS;
switch ($action) {
case 'create':
//非覆盖模式时如果存在则报错
if (is_dir($addonDir) && !$force) {
throw new Exception("addon already exists!\nIf you need to create again, use the parameter --force=true ");
}
//如果存在先移除
if (is_dir($addonDir)) {
rmdirs($addonDir);
}
mkdir($addonDir, 0755, true);
mkdir($addonDir . DS . 'controller', 0755, true);
$menuList = \app\common\library\Menu::export($name);
$createMenu = $this->getCreateMenu($menuList);
$prefix = Config::get('database.prefix');
$createTableSql = '';
try {
$result = Db::query("SHOW CREATE TABLE `" . $prefix . $name . "`;");
if (isset($result[0]) && isset($result[0]['Create Table'])) {
$createTableSql = $result[0]['Create Table'];
}
} catch (PDOException $e) {
}
$data = [
'name' => $name,
'addon' => $name,
'addonClassName' => ucfirst($name),
'addonInstallMenu' => $createMenu ? "\$menu = " . var_export_short($createMenu) . ";\n\tMenu::create(\$menu);" : '',
'addonUninstallMenu' => $menuList ? 'Menu::delete("' . $name . '");' : '',
'addonEnableMenu' => $menuList ? 'Menu::enable("' . $name . '");' : '',
'addonDisableMenu' => $menuList ? 'Menu::disable("' . $name . '");' : '',
];
$this->writeToFile("addon", $data, $addonDir . ucfirst($name) . '.php');
$this->writeToFile("config", $data, $addonDir . 'config.php');
$this->writeToFile("info", $data, $addonDir . 'info.ini');
$this->writeToFile("controller", $data, $addonDir . 'controller' . DS . 'Index.php');
if ($createTableSql) {
$createTableSql = str_replace("`" . $prefix, '`__PREFIX__', $createTableSql);
file_put_contents($addonDir . 'install.sql', $createTableSql);
}
$output->info("Create Successed!");
break;
case 'disable':
case 'enable':
try {
//调用启用、禁用的方法
Service::$action($name, 0);
} catch (AddonException $e) {
if ($e->getCode() != -3) {
throw new Exception($e->getMessage());
}
if (!$force) {
//如果有冲突文件则提醒
$data = $e->getData();
foreach ($data['conflictlist'] as $k => $v) {
$output->warning($v);
}
$output->info("Are you sure you want to " . ($action == 'enable' ? 'override' : 'delete') . " all those files? Type 'yes' to continue: ");
$line = fgets(defined('STDIN') ? STDIN : fopen('php://stdin', 'r'));
if (trim($line) != 'yes') {
throw new Exception("Operation is aborted!");
}
}
//调用启用、禁用的方法
Service::$action($name, 1);
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
$output->info(ucfirst($action) . " Successed!");
break;
case 'uninstall':
//非覆盖模式时如果存在则报错
if (!$force) {
throw new Exception("If you need to uninstall addon, use the parameter --force=true ");
}
try {
Service::uninstall($name, 0);
} catch (AddonException $e) {
if ($e->getCode() != -3) {
throw new Exception($e->getMessage());
}
if (!$force) {
//如果有冲突文件则提醒
$data = $e->getData();
foreach ($data['conflictlist'] as $k => $v) {
$output->warning($v);
}
$output->info("Are you sure you want to delete all those files? Type 'yes' to continue: ");
$line = fgets(defined('STDIN') ? STDIN : fopen('php://stdin', 'r'));
if (trim($line) != 'yes') {
throw new Exception("Operation is aborted!");
}
}
Service::uninstall($name, 1);
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
$output->info("Uninstall Successed!");
break;
case 'refresh':
Service::refresh();
$output->info("Refresh Successed!");
break;
case 'package':
$infoFile = $addonDir . 'info.ini';
if (!is_file($infoFile)) {
throw new Exception(__('Addon info file was not found'));
}
$info = get_addon_info($name);
if (!$info) {
throw new Exception(__('Addon info file data incorrect'));
}
$infoname = $info['name'] ?? '';
if (!$infoname || !preg_match("/^[a-z]+$/i", $infoname) || $infoname != $name) {
throw new Exception(__('Addon info name incorrect'));
}
$infoversion = $info['version'] ?? '';
if (!$infoversion || !preg_match("/^\d+\.\d+\.\d+$/i", $infoversion)) {
throw new Exception(__('Addon info version incorrect'));
}
$addonTmpDir = RUNTIME_PATH . 'addons' . DS;
if (!is_dir($addonTmpDir)) {
@mkdir($addonTmpDir, 0755, true);
}
$addonFile = $addonTmpDir . $infoname . '-' . $infoversion . '.zip';
if (!class_exists('ZipArchive')) {
throw new Exception(__('ZinArchive not install'));
}
$zip = new \ZipArchive;
$zip->open($addonFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($addonDir), \RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($files as $name => $file) {
if (!$file->isDir()) {
$filePath = $file->getRealPath();
$relativePath = str_replace(DS, '/', substr($filePath, strlen($addonDir)));
if (!in_array($file->getFilename(), ['.git', '.DS_Store', 'Thumbs.db'])) {
$zip->addFile($filePath, $relativePath);
}
}
}
$zip->close();
$output->info("Package Successed!");
break;
case 'move':
$movePath = [
'adminOnlySelfDir' => ['admin/behavior', 'admin/controller', 'admin/library', 'admin/model', 'admin/validate', 'admin/view'],
'adminAllSubDir' => ['admin/lang'],
'publicDir' => ['public/assets/addons', 'public/assets/js/backend']
];
$paths = [];
$appPath = str_replace('/', DS, APP_PATH);
$rootPath = str_replace('/', DS, ROOT_PATH);
foreach ($movePath as $k => $items) {
switch ($k) {
case 'adminOnlySelfDir':
foreach ($items as $v) {
$v = str_replace('/', DS, $v);
$oldPath = $appPath . $v . DS . $name;
$newPath = $rootPath . "addons" . DS . $name . DS . "application" . DS . $v . DS . $name;
$paths[$oldPath] = $newPath;
}
break;
case 'adminAllSubDir':
foreach ($items as $v) {
$v = str_replace('/', DS, $v);
$vPath = $appPath . $v;
$list = scandir($vPath);
foreach ($list as $_v) {
if (!in_array($_v, ['.', '..']) && is_dir($vPath . DS . $_v)) {
$oldPath = $appPath . $v . DS . $_v . DS . $name;
$newPath = $rootPath . "addons" . DS . $name . DS . "application" . DS . $v . DS . $_v . DS . $name;
$paths[$oldPath] = $newPath;
}
}
}
break;
case 'publicDir':
foreach ($items as $v) {
$v = str_replace('/', DS, $v);
$oldPath = $rootPath . $v . DS . $name;
$newPath = $rootPath . 'addons' . DS . $name . DS . $v . DS . $name;
$paths[$oldPath] = $newPath;
}
break;
}
}
foreach ($paths as $oldPath => $newPath) {
if (is_dir($oldPath)) {
if ($force) {
if (is_dir($newPath)) {
$list = scandir($newPath);
foreach ($list as $_v) {
if (!in_array($_v, ['.', '..'])) {
$file = $newPath . DS . $_v;
@chmod($file, 0777);
@unlink($file);
}
}
@rmdir($newPath);
}
}
copydirs($oldPath, $newPath);
}
}
break;
default:
break;
}
}
/**
* 获取创建菜单的数组
* @param array $menu
* @return array
*/
protected function getCreateMenu($menu)
{
$result = [];
foreach ($menu as $k => & $v) {
$arr = [
'name' => $v['name'],
'title' => $v['title'],
];
if ($v['icon'] != 'fa fa-circle-o') {
$arr['icon'] = $v['icon'];
}
if ($v['ismenu']) {
$arr['ismenu'] = $v['ismenu'];
}
if (isset($v['childlist']) && $v['childlist']) {
$arr['sublist'] = $this->getCreateMenu($v['childlist']);
}
$result[] = $arr;
}
return $result;
}
/**
* 写入到文件
* @param string $name
* @param array $data
* @param string $pathname
* @return mixed
*/
protected function writeToFile($name, $data, $pathname)
{
$search = $replace = [];
foreach ($data as $k => $v) {
$search[] = "{%{$k}%}";
$replace[] = $v;
}
$stub = file_get_contents($this->getStub($name));
$content = str_replace($search, $replace, $stub);
if (!is_dir(dirname($pathname))) {
mkdir(strtolower(dirname($pathname)), 0755, true);
}
return file_put_contents($pathname, $content);
}
/**
* 获取基础模板
* @param string $name
* @return string
*/
protected function getStub($name)
{
return __DIR__ . '/Addon/stubs/' . $name . '.stub';
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace addons\{%name%};
use app\common\library\Menu;
use think\Addons;
/**
* 插件
*/
class {%addonClassName%} extends Addons
{
/**
* 插件安装方法
* @return bool
*/
public function install()
{
{%addonInstallMenu%}
return true;
}
/**
* 插件卸载方法
* @return bool
*/
public function uninstall()
{
{%addonUninstallMenu%}
return true;
}
/**
* 插件启用方法
* @return bool
*/
public function enable()
{
{%addonEnableMenu%}
return true;
}
/**
* 插件禁用方法
* @return bool
*/
public function disable()
{
{%addonDisableMenu%}
return true;
}
}

View File

@ -0,0 +1,44 @@
<?php
return [
[
//配置唯一标识
'name' => 'username',
//显示的标题
'title' => '用户名',
//类型
'type' => 'string',
//分组
'group' => '',
//动态显示
'visible' => '',
//数据字典
'content' => [
],
//值
'value' => '',
//验证规则
'rule' => 'required',
//错误消息
'msg' => '',
//提示消息
'tip' => '',
//成功消息
'ok' => '',
//扩展信息
'extend' => ''
],
[
'name' => 'password',
'title' => '密码',
'type' => 'string',
'content' => [
],
'value' => '',
'rule' => 'required',
'msg' => '',
'tip' => '',
'ok' => '',
'extend' => ''
],
];

View File

@ -0,0 +1,15 @@
<?php
namespace addons\{%addon%}\controller;
use think\addons\Controller;
class Index extends Controller
{
public function index()
{
$this->error("当前插件暂无前台页面");
}
}

View File

@ -0,0 +1,7 @@
name = {%name%}
title = 插件名称{%name%}
intro = 插件介绍
author = yourname
website = https://www.fastadmin.net
version = 1.0.0
state = 1

View File

@ -0,0 +1,189 @@
<?php
namespace app\admin\command;
use app\admin\command\Api\library\Builder;
use think\Config;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
use think\Exception;
class Api extends Command
{
protected function configure()
{
$site = Config::get('site');
$this
->setName('api')
->addOption('url', 'u', Option::VALUE_OPTIONAL, 'default api url', '')
->addOption('module', 'm', Option::VALUE_OPTIONAL, 'module name(admin/index/api)', 'api')
->addOption('output', 'o', Option::VALUE_OPTIONAL, 'output index file name', 'api.html')
->addOption('template', 'e', Option::VALUE_OPTIONAL, '', 'index.html')
->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override general file', false)
->addOption('title', 't', Option::VALUE_OPTIONAL, 'document title', $site['name'] ?? '')
->addOption('class', 'c', Option::VALUE_OPTIONAL | Option::VALUE_IS_ARRAY, 'extend class', null)
->addOption('language', 'l', Option::VALUE_OPTIONAL, 'language', 'zh-cn')
->addOption('addon', 'a', Option::VALUE_OPTIONAL, 'addon name', null)
->addOption('controller', 'r', Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, 'controller name', null)
->setDescription('Build Api document from controller');
}
protected function execute(Input $input, Output $output)
{
$apiDir = __DIR__ . DS . 'Api' . DS;
$force = $input->getOption('force');
$url = $input->getOption('url');
$language = $input->getOption('language');
$template = $input->getOption('template');
if (!preg_match("/^([a-z0-9]+)\.html\$/i", $template)) {
throw new Exception('template file not correct');
}
$language = $language ? $language : 'zh-cn';
$langFile = $apiDir . 'lang' . DS . $language . '.php';
if (!is_file($langFile)) {
throw new Exception('language file not found');
}
$lang = include_once $langFile;
// 目标目录
$output_dir = ROOT_PATH . 'public' . DS;
$output_file = $output_dir . $input->getOption('output');
if (is_file($output_file) && !$force) {
throw new Exception("api index file already exists!\nIf you need to rebuild again, use the parameter --force=true ");
}
// 模板文件
$template_dir = $apiDir . 'template' . DS;
$template_file = $template_dir . $template;
if (!is_file($template_file)) {
throw new Exception('template file not found');
}
// 额外的类
$classes = $input->getOption('class');
// 标题
$title = $input->getOption('title');
// 模块
$module = $input->getOption('module');
// 插件
$addon = $input->getOption('addon');
$moduleDir = $addonDir = '';
if ($addon) {
$addonInfo = get_addon_info($addon);
if (!$addonInfo) {
throw new Exception('addon not found');
}
$moduleDir = ADDON_PATH . $addon . DS;
} else {
$moduleDir = APP_PATH . $module . DS;
}
if (!is_dir($moduleDir)) {
throw new Exception('module not found');
}
if (version_compare(PHP_VERSION, '7.0.0', '<')) {
throw new Exception("Requires PHP version 7.0 or newer");
}
//控制器名
$controller = $input->getOption('controller') ?: [];
if (!$controller) {
$controllerDir = $moduleDir . Config::get('url_controller_layer') . DS;
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($controllerDir),
\RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($files as $name => $file) {
if (!$file->isDir() && $file->getExtension() == 'php') {
$filePath = $file->getRealPath();
$classes[] = $this->getClassFromFile($filePath);
}
}
} else {
foreach ($controller as $index => $item) {
$filePath = $moduleDir . Config::get('url_controller_layer') . DS . $item . '.php';
$classes[] = $this->getClassFromFile($filePath);
}
}
$classes = array_unique(array_filter($classes));
$config = [
'sitename' => config('site.name'),
'title' => $title,
'author' => config('site.name'),
'description' => '',
'apiurl' => $url,
'language' => $language,
];
$builder = new Builder($classes);
$content = $builder->render($template_file, ['config' => $config, 'lang' => $lang]);
if (!file_put_contents($output_file, $content)) {
throw new Exception('Cannot save the content to ' . $output_file);
}
$output->info("Build Successed!");
}
/**
* 从文件获取命名空间和类名
*
* @param string $filename
* @return string
*/
protected function getClassFromFile($filename)
{
$getNext = null;
$isNamespace = false;
$skipNext = false;
$namespace = '';
$class = '';
foreach (\PhpToken::tokenize(file_get_contents($filename)) as $token) {
if (!$token->isIgnorable()) {
$name = $token->getTokenName();
switch ($name) {
case 'T_NAMESPACE':
$isNamespace = true;
break;
case 'T_EXTENDS':
case 'T_USE':
case 'T_IMPLEMENTS':
$skipNext = true;
break;
case 'T_CLASS':
if ($skipNext) {
$skipNext = false;
} else {
$getNext = strtolower(substr($name, 2));
}
break;
case 'T_NAME_QUALIFIED':
case 'T_NS_SEPARATOR':
case 'T_STRING':
case ';':
if ($isNamespace) {
if ($name == ';') {
$isNamespace = false;
} else {
$namespace .= $token->text;
}
} elseif ($skipNext) {
$skipNext = false;
} elseif ($getNext == 'class') {
$class = $token->text;
$getNext = null;
break 2;
}
break;
default:
$getNext = null;
}
}
}
return $namespace . '\\' . $class;
}
}

View File

@ -0,0 +1,25 @@
<?php
return [
'Info' => '基础信息',
'Sandbox' => '在线测试',
'Sampleoutput' => '返回示例',
'Headers' => 'Headers',
'Parameters' => '参数',
'Body' => '正文',
'Name' => '名称',
'Type' => '类型',
'Required' => '必选',
'Description' => '描述',
'Send' => '提交',
'Reset' => '重置',
'Tokentips' => 'Token在会员注册或登录后都会返回,WEB端同时存在于Cookie中',
'Apiurltips' => 'API接口URL',
'Savetips' => '点击保存后Token和Api url都将保存在本地Localstorage中',
'Authorization' => '权限',
'NeedLogin' => '登录',
'NeedRight' => '鉴权',
'ReturnHeaders' => '响应头',
'ReturnParameters' => '返回参数',
'Response' => '响应输出',
];

View File

@ -0,0 +1,259 @@
<?php
namespace app\admin\command\Api\library;
use think\Config;
/**
* @website https://github.com/calinrada/php-apidoc
* @author Calin Rada <rada.calin@gmail.com>
* @author Karson <karson@fastadmin.net>
*/
class Builder
{
/**
*
* @var \think\View
*/
public $view = null;
/**
* parse classes
* @var array
*/
protected $classes = [];
/**
*
* @param array $classes
*/
public function __construct($classes = [])
{
$this->classes = array_merge($this->classes, $classes);
$this->view = new \think\View(Config::get('template'), Config::get('view_replace_str'));
}
protected function extractAnnotations()
{
foreach ($this->classes as $class) {
$classAnnotation = Extractor::getClassAnnotations($class);
// 如果忽略
if (isset($classAnnotation['ApiInternal'])) {
continue;
}
Extractor::getClassMethodAnnotations($class);
//Extractor::getClassPropertyValues($class);
}
$allClassAnnotation = Extractor::getAllClassAnnotations();
$allClassMethodAnnotation = Extractor::getAllClassMethodAnnotations();
//$allClassPropertyValue = Extractor::getAllClassPropertyValues();
// foreach ($allClassMethodAnnotation as $className => &$methods) {
// foreach ($methods as &$method) {
// //权重判断
// if ($method && !isset($method['ApiWeigh']) && isset($allClassAnnotation[$className]['ApiWeigh'])) {
// $method['ApiWeigh'] = $allClassAnnotation[$className]['ApiWeigh'];
// }
// }
// }
// unset($methods);
return [$allClassAnnotation, $allClassMethodAnnotation];
}
protected function generateHeadersTemplate($docs)
{
if (!isset($docs['ApiHeaders'])) {
return [];
}
$headerslist = array();
foreach ($docs['ApiHeaders'] as $params) {
$tr = array(
'name' => $params['name'] ?? '',
'type' => $params['type'] ?? 'string',
'sample' => $params['sample'] ?? '',
'required' => $params['required'] ?? false,
'description' => $params['description'] ?? '',
);
$headerslist[] = $tr;
}
return $headerslist;
}
protected function generateParamsTemplate($docs)
{
if (!isset($docs['ApiParams'])) {
return [];
}
$typeArr = [
'integer' => 'number',
'file' => 'file',
];
$paramslist = array();
foreach ($docs['ApiParams'] as $params) {
$inputtype = $params['type'] && isset($typeArr[$params['type']]) ? $typeArr[$params['type']] : ($params['name'] == 'password' ? 'password' : 'text');
$tr = array(
'name' => $params['name'],
'type' => $params['type'] ?? 'string',
'inputtype' => $inputtype,
'sample' => $params['sample'] ?? '',
'required' => $params['required'] ?? true,
'description' => $params['description'] ?? '',
);
$paramslist[] = $tr;
}
return $paramslist;
}
protected function generateReturnHeadersTemplate($docs)
{
if (!isset($docs['ApiReturnHeaders'])) {
return [];
}
$headerslist = array();
foreach ($docs['ApiReturnHeaders'] as $params) {
$tr = array(
'name' => $params['name'] ?? '',
'type' => 'string',
'sample' => $params['sample'] ?? '',
'required' => isset($params['required']) && $params['required'] ? 'Yes' : 'No',
'description' => $params['description'] ?? '',
);
$headerslist[] = $tr;
}
return $headerslist;
}
protected function generateReturnParamsTemplate($st_params)
{
if (!isset($st_params['ApiReturnParams'])) {
return [];
}
$paramslist = array();
foreach ($st_params['ApiReturnParams'] as $params) {
$tr = array(
'name' => $params['name'] ?? '',
'type' => $params['type'] ?? 'string',
'sample' => $params['sample'] ?? '',
'description' => $params['description'] ?? '',
);
$paramslist[] = $tr;
}
return $paramslist;
}
protected function generateBadgeForMethod($data)
{
$method = strtoupper(is_array($data['ApiMethod'][0]) ? $data['ApiMethod'][0]['data'] : $data['ApiMethod'][0]);
$labes = array(
'POST' => 'label-primary',
'GET' => 'label-success',
'PUT' => 'label-warning',
'DELETE' => 'label-danger',
'PATCH' => 'label-default',
'OPTIONS' => 'label-info'
);
return isset($labes[$method]) ? $labes[$method] : $labes['GET'];
}
public function parse()
{
list($allClassAnnotations, $allClassMethodAnnotations) = $this->extractAnnotations();
$sectorArr = [];
foreach ($allClassAnnotations as $index => &$allClassAnnotation) {
// 如果设置隐藏,则不显示在文档
if (isset($allClassAnnotation['ApiInternal'])) {
continue;
}
$sector = isset($allClassAnnotation['ApiSector']) ? $allClassAnnotation['ApiSector'][0] : $allClassAnnotation['ApiTitle'][0];
$sectorArr[$sector] = isset($allClassAnnotation['ApiWeigh']) ? $allClassAnnotation['ApiWeigh'][0] : 0;
}
unset($allClassAnnotation);
arsort($sectorArr);
$routes = include_once CONF_PATH . 'route.php';
$subdomain = false;
if (config('url_domain_deploy') && isset($routes['__domain__']) && isset($routes['__domain__']['api']) && $routes['__domain__']['api']) {
$subdomain = true;
}
$counter = 0;
$section = null;
$weigh = 0;
$docsList = [];
foreach ($allClassMethodAnnotations as $class => $methods) {
foreach ($methods as $name => $docs) {
if (isset($docs['ApiSector'][0])) {
$section = is_array($docs['ApiSector'][0]) ? $docs['ApiSector'][0]['data'] : $docs['ApiSector'][0];
} else {
$section = $class;
}
if (0 === count($docs)) {
continue;
}
$route = is_array($docs['ApiRoute'][0]) ? $docs['ApiRoute'][0]['data'] : $docs['ApiRoute'][0];
if ($subdomain) {
$route = substr($route, 4);
}
$docsList[$section][$name] = [
'id' => $counter,
'method' => is_array($docs['ApiMethod'][0]) ? $docs['ApiMethod'][0]['data'] : $docs['ApiMethod'][0],
'methodLabel' => $this->generateBadgeForMethod($docs),
'section' => $section,
'route' => $route,
'title' => is_array($docs['ApiTitle'][0]) ? $docs['ApiTitle'][0]['data'] : $docs['ApiTitle'][0],
'summary' => is_array($docs['ApiSummary'][0]) ? $docs['ApiSummary'][0]['data'] : $docs['ApiSummary'][0],
'body' => isset($docs['ApiBody'][0]) ? (is_array($docs['ApiBody'][0]) ? $docs['ApiBody'][0]['data'] : $docs['ApiBody'][0]) : '',
'headersList' => $this->generateHeadersTemplate($docs),
'paramsList' => $this->generateParamsTemplate($docs),
'returnHeadersList' => $this->generateReturnHeadersTemplate($docs),
'returnParamsList' => $this->generateReturnParamsTemplate($docs),
'weigh' => is_array($docs['ApiWeigh'][0]) ? $docs['ApiWeigh'][0]['data'] : $docs['ApiWeigh'][0],
'return' => isset($docs['ApiReturn']) ? (is_array($docs['ApiReturn'][0]) ? $docs['ApiReturn'][0]['data'] : $docs['ApiReturn'][0]) : '',
'needLogin' => $docs['ApiPermissionLogin'][0],
'needRight' => $docs['ApiPermissionRight'][0],
];
$counter++;
}
}
//重建排序
foreach ($docsList as $index => &$methods) {
$methodSectorArr = [];
foreach ($methods as $name => $method) {
$methodSectorArr[$name] = isset($method['weigh']) ? $method['weigh'] : 0;
}
arsort($methodSectorArr);
$methods = array_merge(array_flip(array_keys($methodSectorArr)), $methods);
}
$docsList = array_merge(array_flip(array_keys($sectorArr)), $docsList);
return $docsList;
}
public function getView()
{
return $this->view;
}
/**
* 渲染
* @param string $template
* @param array $vars
* @return string
*/
public function render($template, $vars = [])
{
$docsList = $this->parse();
return $this->view->display(file_get_contents($template), array_merge($vars, ['docsList' => $docsList]));
}
}

View File

@ -0,0 +1,544 @@
<?php
namespace app\admin\command\Api\library;
use Exception;
/**
* Class imported from https://github.com/eriknyk/Annotations
* @author Erik Amaru Ortiz https://github.com/eriknyk
*
* @license http://opensource.org/licenses/bsd-license.php The BSD License
* @author Calin Rada <rada.calin@gmail.com>
*/
class Extractor
{
/**
* Static array to store already parsed annotations
* @var array
*/
private static $annotationCache;
private static $classAnnotationCache;
private static $classMethodAnnotationCache;
private static $classPropertyValueCache;
/**
* Indicates that annotations should has strict behavior, 'false' by default
* @var boolean
*/
private $strict = false;
/**
* Stores the default namespace for Objects instance, usually used on methods like getMethodAnnotationsObjects()
* @var string
*/
public $defaultNamespace = '';
/**
* Sets strict variable to true/false
* @param bool $value boolean value to indicate that annotations to has strict behavior
*/
public function setStrict($value)
{
$this->strict = (bool)$value;
}
/**
* Sets default namespace to use in object instantiation
* @param string $namespace default namespace
*/
public function setDefaultNamespace($namespace)
{
$this->defaultNamespace = $namespace;
}
/**
* Gets default namespace used in object instantiation
* @return string $namespace default namespace
*/
public function getDefaultAnnotationNamespace()
{
return $this->defaultNamespace;
}
/**
* Gets all anotations with pattern @SomeAnnotation() from a given class
*
* @param string $className class name to get annotations
* @return array self::$classAnnotationCache all annotated elements
*/
public static function getClassAnnotations($className)
{
if (!isset(self::$classAnnotationCache[$className])) {
$class = new \ReflectionClass($className);
$annotationArr = self::parseAnnotations($class->getDocComment());
$annotationArr['ApiTitle'] = !isset($annotationArr['ApiTitle'][0]) || !trim($annotationArr['ApiTitle'][0]) ? [$class->getShortName()] : $annotationArr['ApiTitle'];
self::$classAnnotationCache[$className] = $annotationArr;
}
return self::$classAnnotationCache[$className];
}
/**
* 获取类所有方法的属性配置
* @param $className
* @return mixed
* @throws \ReflectionException
*/
public static function getClassMethodAnnotations($className)
{
$class = new \ReflectionClass($className);
foreach ($class->getMethods() as $object) {
self::$classMethodAnnotationCache[$className][$object->name] = self::getMethodAnnotations($className, $object->name);
}
return self::$classMethodAnnotationCache[$className];
}
public static function getClassPropertyValues($className)
{
$class = new \ReflectionClass($className);
foreach ($class->getProperties() as $object) {
self::$classPropertyValueCache[$className][$object->name] = self::getClassPropertyValue($className, $object->name);
}
return self::$classMethodAnnotationCache[$className];
}
public static function getAllClassAnnotations()
{
return self::$classAnnotationCache;
}
public static function getAllClassMethodAnnotations()
{
return self::$classMethodAnnotationCache;
}
public static function getAllClassPropertyValues()
{
return self::$classPropertyValueCache;
}
public static function getClassPropertyValue($className, $property)
{
$_SERVER['REQUEST_METHOD'] = 'GET';
$reflectionClass = new \ReflectionClass($className);
$reflectionProperty = $reflectionClass->getProperty($property);
$reflectionProperty->setAccessible(true);
return $reflectionProperty->getValue($reflectionClass->newInstanceWithoutConstructor());
}
/**
* Gets all anotations with pattern @SomeAnnotation() from a determinated method of a given class
*
* @param string $className class name
* @param string $methodName method name to get annotations
* @return array self::$annotationCache all annotated elements of a method given
*/
public static function getMethodAnnotations($className, $methodName)
{
if (!isset(self::$annotationCache[$className . '::' . $methodName])) {
try {
$method = new \ReflectionMethod($className, $methodName);
$class = new \ReflectionClass($className);
if (!$method->isPublic() || $method->isConstructor()) {
$annotations = array();
} else {
$annotations = self::consolidateAnnotations($method, $class);
}
} catch (\ReflectionException $e) {
$annotations = array();
}
self::$annotationCache[$className . '::' . $methodName] = $annotations;
}
return self::$annotationCache[$className . '::' . $methodName];
}
/**
* Gets all anotations with pattern @SomeAnnotation() from a determinated method of a given class
* and instance its abcAnnotation class
*
* @param string $className class name
* @param string $methodName method name to get annotations
* @return array self::$annotationCache all annotated objects of a method given
*/
public function getMethodAnnotationsObjects($className, $methodName)
{
$annotations = $this->getMethodAnnotations($className, $methodName);
$objects = array();
$i = 0;
foreach ($annotations as $annotationClass => $listParams) {
$annotationClass = ucfirst($annotationClass);
$class = $this->defaultNamespace . $annotationClass . 'Annotation';
// verify is the annotation class exists, depending if Annotations::strict is true
// if not, just skip the annotation instance creation.
if (!class_exists($class)) {
if ($this->strict) {
throw new Exception(sprintf('Runtime Error: Annotation Class Not Found: %s', $class));
} else {
// silent skip & continue
continue;
}
}
if (empty($objects[$annotationClass])) {
$objects[$annotationClass] = new $class();
}
foreach ($listParams as $params) {
if (is_array($params)) {
foreach ($params as $key => $value) {
$objects[$annotationClass]->set($key, $value);
}
} else {
$objects[$annotationClass]->set($i++, $params);
}
}
}
return $objects;
}
private static function consolidateAnnotations($method, $class)
{
$dockblockClass = $class->getDocComment();
$docblockMethod = $method->getDocComment();
$methodName = $method->getName();
$methodAnnotations = self::parseAnnotations($docblockMethod);
$methodAnnotations['ApiTitle'] = !isset($methodAnnotations['ApiTitle'][0]) || !trim($methodAnnotations['ApiTitle'][0]) ? [$method->getName()] : $methodAnnotations['ApiTitle'];
$classAnnotations = self::parseAnnotations($dockblockClass);
$classAnnotations['ApiTitle'] = !isset($classAnnotations['ApiTitle'][0]) || !trim($classAnnotations['ApiTitle'][0]) ? [$class->getShortName()] : $classAnnotations['ApiTitle'];
if (isset($methodAnnotations['ApiInternal']) || $methodName == '_initialize' || $methodName == '_empty') {
return [];
}
$properties = $class->getDefaultProperties();
$noNeedLogin = isset($properties['noNeedLogin']) ? (is_array($properties['noNeedLogin']) ? $properties['noNeedLogin'] : [$properties['noNeedLogin']]) : [];
$noNeedRight = isset($properties['noNeedRight']) ? (is_array($properties['noNeedRight']) ? $properties['noNeedRight'] : [$properties['noNeedRight']]) : [];
preg_match_all("/\*[\s]+(.*)(\\r\\n|\\r|\\n)/U", str_replace('/**', '', $docblockMethod), $methodArr);
preg_match_all("/\*[\s]+(.*)(\\r\\n|\\r|\\n)/U", str_replace('/**', '', $dockblockClass), $classArr);
if (!isset($methodAnnotations['ApiMethod'])) {
$methodAnnotations['ApiMethod'] = ['get'];
}
if (!isset($methodAnnotations['ApiWeigh'])) {
$methodAnnotations['ApiWeigh'] = [0];
}
if (!isset($methodAnnotations['ApiSummary'])) {
$methodAnnotations['ApiSummary'] = $methodAnnotations['ApiTitle'];
}
if ($methodAnnotations) {
foreach ($classAnnotations as $name => $valueClass) {
if (count($valueClass) !== 1) {
continue;
}
if ($name === 'ApiRoute') {
if (isset($methodAnnotations[$name])) {
$methodAnnotations[$name] = [rtrim($valueClass[0], '/') . $methodAnnotations[$name][0]];
} else {
$methodAnnotations[$name] = [rtrim($valueClass[0], '/') . '/' . $method->getName()];
}
}
if ($name === 'ApiSector') {
$methodAnnotations[$name] = $valueClass;
}
}
}
if (!isset($methodAnnotations['ApiRoute'])) {
$urlArr = [];
$className = $class->getName();
list($prefix, $suffix) = explode('\\' . \think\Config::get('url_controller_layer') . '\\', $className);
$prefixArr = explode('\\', $prefix);
$suffixArr = explode('\\', $suffix);
if ($prefixArr[0] == \think\Config::get('app_namespace')) {
$prefixArr[0] = '';
}
$urlArr = array_merge($urlArr, $prefixArr);
$urlArr[] = implode('.', array_map(function ($item) {
return \think\Loader::parseName($item);
}, $suffixArr));
$urlArr[] = $method->getName();
$methodAnnotations['ApiRoute'] = [implode('/', $urlArr)];
}
if (!isset($methodAnnotations['ApiSector'])) {
$methodAnnotations['ApiSector'] = isset($classAnnotations['ApiSector']) ? $classAnnotations['ApiSector'] : $classAnnotations['ApiTitle'];
}
if (!isset($methodAnnotations['ApiParams'])) {
$params = self::parseCustomAnnotations($docblockMethod, 'param');
foreach ($params as $k => $v) {
$arr = explode(' ', preg_replace("/[\s]+/", " ", $v));
$methodAnnotations['ApiParams'][] = [
'name' => isset($arr[1]) ? str_replace('$', '', $arr[1]) : '',
'nullable' => false,
'type' => isset($arr[0]) ? $arr[0] : 'string',
'description' => isset($arr[2]) ? $arr[2] : ''
];
}
}
$methodAnnotations['ApiPermissionLogin'] = [!in_array('*', $noNeedLogin) && !in_array($methodName, $noNeedLogin)];
$methodAnnotations['ApiPermissionRight'] = !$methodAnnotations['ApiPermissionLogin'][0] ? [false] : [!in_array('*', $noNeedRight) && !in_array($methodName, $noNeedRight)];
return $methodAnnotations;
}
/**
* Parse annotations
*
* @param string $docblock
* @param string $name
* @return array parsed annotations params
*/
private static function parseCustomAnnotations($docblock, $name = 'param')
{
$annotations = array();
$docblock = substr($docblock, 3, -2);
if (preg_match_all('/@' . $name . '(?:\s*(?:\(\s*)?(.*?)(?:\s*\))?)??\s*(?:\n|\*\/)/', $docblock, $matches)) {
foreach ($matches[1] as $k => $v) {
$annotations[] = $v;
}
}
return $annotations;
}
/**
* Parse annotations
*
* @param string $docblock
* @return array parsed annotations params
*/
private static function parseAnnotations($docblock)
{
$annotations = array();
// Strip away the docblock header and footer to ease parsing of one line annotations
$docblock = substr($docblock, 3, -2);
if (preg_match_all('/@(?<name>[A-Za-z_-]+)[\s\t]*\((?<args>(?:(?!\)).)*)\)\r?/s', $docblock, $matches)) {
$numMatches = count($matches[0]);
for ($i = 0; $i < $numMatches; ++$i) {
$name = $matches['name'][$i];
$value = '';
// annotations has arguments
if (isset($matches['args'][$i])) {
$argsParts = trim($matches['args'][$i]);
if ($name == 'ApiReturn') {
$value = $argsParts;
} elseif ($matches['args'][$i] != '') {
$argsParts = preg_replace("/\{(\w+)\}/", '#$1#', $argsParts);
$value = self::parseArgs($argsParts);
if (is_string($value)) {
$value = preg_replace("/\#(\w+)\#/", '{$1}', $argsParts);
}
}
}
$annotations[$name][] = $value;
}
}
if (stripos($docblock, '@ApiInternal') !== false) {
$annotations['ApiInternal'] = [true];
}
if (!isset($annotations['ApiTitle'])) {
preg_match_all("/\*[\s]+(.*)(\\r\\n|\\r|\\n)/U", str_replace('/**', '', $docblock), $matchArr);
$title = isset($matchArr[1]) && isset($matchArr[1][0]) ? $matchArr[1][0] : '';
$annotations['ApiTitle'] = [$title];
}
return $annotations;
}
/**
* Parse individual annotation arguments
*
* @param string $content arguments string
* @return array annotated arguments
*/
private static function parseArgs($content)
{
// Replace initial stars
$content = preg_replace('/^\s*\*/m', '', $content);
$data = array();
$len = strlen($content);
$i = 0;
$var = '';
$val = '';
$level = 1;
$prevDelimiter = '';
$nextDelimiter = '';
$nextToken = '';
$composing = false;
$type = 'plain';
$delimiter = null;
$quoted = false;
$tokens = array('"', '"', '{', '}', ',', '=');
while ($i <= $len) {
$prev_c = substr($content, $i - 1, 1);
$c = substr($content, $i++, 1);
if ($c === '"' && $prev_c !== "\\") {
$delimiter = $c;
//open delimiter
if (!$composing && empty($prevDelimiter) && empty($nextDelimiter)) {
$prevDelimiter = $nextDelimiter = $delimiter;
$val = '';
$composing = true;
$quoted = true;
} else {
// close delimiter
if ($c !== $nextDelimiter) {
throw new Exception(sprintf(
"Parse Error: enclosing error -> expected: [%s], given: [%s]",
$nextDelimiter,
$c
));
}
// validating syntax
if ($i < $len) {
if (',' !== substr($content, $i, 1) && '\\' !== $prev_c) {
throw new Exception(sprintf(
"Parse Error: missing comma separator near: ...%s<--",
substr($content, ($i - 10), $i)
));
}
}
$prevDelimiter = $nextDelimiter = '';
$composing = false;
$delimiter = null;
}
} elseif (!$composing && in_array($c, $tokens)) {
switch ($c) {
case '=':
$prevDelimiter = $nextDelimiter = '';
$level = 2;
$composing = false;
$type = 'assoc';
$quoted = false;
break;
case ',':
$level = 3;
// If composing flag is true yet,
// it means that the string was not enclosed, so it is parsing error.
if ($composing === true && !empty($prevDelimiter) && !empty($nextDelimiter)) {
throw new Exception(sprintf(
"Parse Error: enclosing error -> expected: [%s], given: [%s]",
$nextDelimiter,
$c
));
}
$prevDelimiter = $nextDelimiter = '';
break;
case '{':
$subc = '';
$subComposing = true;
while ($i <= $len) {
$c = substr($content, $i++, 1);
if (isset($delimiter) && $c === $delimiter) {
throw new Exception(sprintf(
"Parse Error: Composite variable is not enclosed correctly."
));
}
if ($c === '}') {
$subComposing = false;
break;
}
$subc .= $c;
}
// if the string is composing yet means that the structure of var. never was enclosed with '}'
if ($subComposing) {
throw new Exception(sprintf(
"Parse Error: Composite variable is not enclosed correctly. near: ...%s'",
$subc
));
}
$val = self::parseArgs($subc);
break;
}
} else {
if ($level == 1) {
$var .= $c;
} elseif ($level == 2) {
$val .= $c;
}
}
if ($level === 3 || $i === $len) {
if ($type == 'plain' && $i === $len) {
$data = self::castValue($var);
} else {
$data[trim($var)] = self::castValue($val, !$quoted);
}
$level = 1;
$var = $val = '';
$composing = false;
$quoted = false;
}
}
return $data;
}
/**
* Try determinate the original type variable of a string
*
* @param string $val string containing possibles variables that can be cast to bool or int
* @param boolean $trim indicate if the value passed should be trimmed after to try cast
* @return mixed returns the value converted to original type if was possible
*/
private static function castValue($val, $trim = false)
{
if (is_array($val)) {
foreach ($val as $key => $value) {
$val[$key] = self::castValue($value);
}
} elseif (is_string($val)) {
if ($trim) {
$val = trim($val);
}
$val = stripslashes($val);
$tmp = strtolower($val);
if ($tmp === 'false' || $tmp === 'true') {
$val = $tmp === 'true';
} elseif (is_numeric($val)) {
return $val + 0;
}
unset($tmp);
}
return $val;
}
}

View File

@ -0,0 +1,654 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<title>{$config.title}</title>
<!-- Bootstrap Core CSS -->
<link href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<!-- Plugin CSS -->
<link href="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<style type="text/css">
body {
padding-top: 70px; margin-bottom: 15px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-family: "Roboto", "SF Pro SC", "SF Pro Display", "SF Pro Icons", "PingFang SC", BlinkMacSystemFont, -apple-system, "Segoe UI", "Microsoft Yahei", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
font-weight: 400;
}
h2 { font-size: 1.2em; }
hr { margin-top: 10px; }
.tab-pane { padding-top: 10px; }
.mt0 { margin-top: 0px; }
.footer { font-size: 12px; color: #666; }
.docs-list .label { display: inline-block; min-width: 65px; padding: 0.3em 0.6em 0.3em; }
.string { color: green; }
.number { color: darkorange; }
.boolean { color: blue; }
.null { color: magenta; }
.key { color: red; }
.popover { max-width: 400px; max-height: 400px; overflow-y: auto;}
.list-group.panel > .list-group-item {
}
.list-group-item:last-child {
border-radius:0;
}
h4.panel-title a {
font-weight:normal;
font-size:14px;
}
h4.panel-title a .text-muted {
font-size:12px;
font-weight:normal;
font-family: 'Verdana';
}
#sidebar {
width: 220px;
position: fixed;
margin-left: -240px;
overflow-y:auto;
}
#sidebar > .list-group {
margin-bottom:0;
}
#sidebar > .list-group > a{
text-indent:0;
}
#sidebar .child > a .tag{
position: absolute;
right: 10px;
top: 11px;
}
#sidebar .child > a .pull-right{
margin-left:3px;
}
#sidebar .child {
border:1px solid #ddd;
border-bottom:none;
}
#sidebar .child:last-child {
border-bottom:1px solid #ddd;
}
#sidebar .child > a {
border:0;
min-height: 40px;
}
#sidebar .list-group a.current {
background:#f5f5f5;
}
@media (max-width: 1620px){
#sidebar {
margin:0;
}
#accordion {
padding-left:235px;
}
}
@media (max-width: 768px){
#sidebar {
display: none;
}
#accordion {
padding-left:0px;
}
}
.label-primary {
background-color: #248aff;
}
.docs-list .panel .panel-body .table {
margin-bottom: 0;
}
</style>
</head>
<body>
<!-- Fixed navbar -->
<div class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="./" target="_blank">{$config.title}</a>
</div>
<div class="navbar-collapse collapse">
<form class="navbar-form navbar-right">
<div class="form-group">
Token:
</div>
<div class="form-group">
<input type="text" class="form-control input-sm" data-toggle="tooltip" title="{$lang.Tokentips}" placeholder="token" id="token" />
</div>
<div class="form-group">
Apiurl:
</div>
<div class="form-group">
<input id="apiUrl" type="text" class="form-control input-sm" data-toggle="tooltip" title="{$lang.Apiurltips}" placeholder="https://api.example.com" value="{$config.apiurl}" />
</div>
<div class="form-group">
<button type="button" class="btn btn-success btn-sm" data-toggle="tooltip" title="{$lang.Savetips}" id="save_data">
<span class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span>
</button>
</div>
</form>
</div><!--/.nav-collapse -->
</div>
</div>
<div class="container">
<!-- menu -->
<div id="sidebar">
<div class="list-group panel">
{foreach name="docsList" id="docs"}
<a href="#{$key}" class="list-group-item" data-toggle="collapse" data-parent="#sidebar">{$key} <i class="fa fa-caret-down"></i></a>
<div class="child collapse" id="{$key}">
{foreach name="docs" id="api" }
<a href="javascript:;" data-id="{$api.id}" class="list-group-item">{$api.title}
<span class="tag">
{if $api.needRight}
<span class="label label-danger pull-right"></span>
{/if}
{if $api.needLogin}
<span class="label label-success pull-right noneedlogin"></span>
{/if}
</span>
</a>
{/foreach}
</div>
{/foreach}
</div>
</div>
<div class="panel-group docs-list" id="accordion">
{foreach name="docsList" id="docs"}
<h2>{$key}</h2>
<hr>
{foreach name="docs" id="api" }
<div class="panel panel-default">
<div class="panel-heading" id="heading-{$api.id}">
<h4 class="panel-title">
<span class="label {$api.methodLabel}">{$api.method|strtoupper}</span>
<a data-toggle="collapse" data-parent="#accordion{$api.id}" href="#collapseOne{$api.id}"> {$api.title} <span class="text-muted">{$api.route}</span></a>
</h4>
</div>
<div id="collapseOne{$api.id}" class="panel-collapse collapse">
<div class="panel-body">
<!-- Nav tabs -->
<ul class="nav nav-tabs" id="doctab{$api.id}">
<li class="active"><a href="#info{$api.id}" data-toggle="tab">{$lang.Info}</a></li>
<li><a href="#sandbox{$api.id}" data-toggle="tab">{$lang.Sandbox}</a></li>
<li><a href="#sample{$api.id}" data-toggle="tab">{$lang.Sampleoutput}</a></li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
<div class="tab-pane active" id="info{$api.id}">
<div class="well">
{$api.summary}
</div>
<div class="panel panel-default">
<div class="panel-heading"><strong>{$lang.Authorization}</strong></div>
<div class="panel-body">
<table class="table table-hover">
<tbody>
<tr>
<td>{$lang.NeedLogin}</td>
<td>{$api.needLogin?'是':'否'}</td>
</tr>
<tr>
<td>{$lang.NeedRight}</td>
<td>{$api.needRight?'是':'否'}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><strong>{$lang.Headers}</strong></div>
<div class="panel-body">
{if $api.headersList}
<table class="table table-hover">
<thead>
<tr>
<th>{$lang.Name}</th>
<th>{$lang.Type}</th>
<th>{$lang.Required}</th>
<th>{$lang.Description}</th>
</tr>
</thead>
<tbody>
{foreach name="api['headersList']" id="header"}
<tr>
<td>{$header.name}</td>
<td>{$header.type}</td>
<td>{$header.required?'是':'否'}</td>
<td>{$header.description}</td>
</tr>
{/foreach}
</tbody>
</table>
{else /}
{/if}
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><strong>{$lang.Parameters}</strong></div>
<div class="panel-body">
{if $api.paramsList}
<table class="table table-hover">
<thead>
<tr>
<th>{$lang.Name}</th>
<th>{$lang.Type}</th>
<th>{$lang.Required}</th>
<th>{$lang.Description}</th>
</tr>
</thead>
<tbody>
{foreach name="api['paramsList']" id="param"}
<tr>
<td>{$param.name}</td>
<td>{$param.type}</td>
<td>{:$param.required?'是':'否'}</td>
<td>{$param.description}</td>
</tr>
{/foreach}
</tbody>
</table>
{else /}
{/if}
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><strong>{$lang.Body}</strong></div>
<div class="panel-body">
{$api.body|default='无'}
</div>
</div>
</div><!-- #info -->
<div class="tab-pane" id="sandbox{$api.id}">
<div class="row">
<div class="col-md-12">
{if $api.headersList}
<div class="panel panel-default">
<div class="panel-heading"><strong>{$lang.Headers}</strong></div>
<div class="panel-body">
<div class="headers">
{foreach name="api['headersList']" id="param"}
<div class="form-group">
<label class="control-label" for="{$param.name}">{$param.name}</label>
<input type="{$param.inputtype|default='text'}" class="form-control input-sm" id="{$param.name}" {if $param.required}required{/if} placeholder="{$param.description} - Ex: {$param.sample}" name="{$param.name}">
</div>
{/foreach}
</div>
</div>
</div>
{/if}
<div class="panel panel-default">
<div class="panel-heading"><strong>{$lang.Parameters}</strong>
<div class="pull-right">
<a href="javascript:" class="btn btn-xs btn-info btn-append">追加</a>
</div>
</div>
<div class="panel-body">
<form enctype="application/x-www-form-urlencoded" role="form" action="{$api.route}" method="{$api.method}" name="form{$api.id}" id="form{$api.id}">
{if $api.paramsList}
{foreach name="api['paramsList']" id="param"}
<div class="form-group">
<label class="control-label" for="{$param.name}">{$param.name}</label>
<input type="{$param.inputtype|default='text'}" class="form-control input-sm" id="{$param.name}" {if $param.required}required{/if} placeholder="{$param.description}{if $param.sample} - 例: {$param.sample}{/if}" name="{$param.name}">
</div>
{/foreach}
{else /}
<div class="form-group">
</div>
{/if}
<div class="form-group form-group-submit">
<button type="submit" class="btn btn-success send" rel="{$api.id}">{$lang.Send}</button>
<button type="reset" class="btn btn-info" rel="{$api.id}">{$lang.Reset}</button>
</div>
</form>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><strong>{$lang.Response}</strong></div>
<div class="panel-body">
<div class="row">
<div class="col-md-12" style="overflow-x:auto">
<pre id="response_headers{$api.id}"></pre>
<pre id="response{$api.id}"></pre>
</div>
</div>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading"><strong>{$lang.ReturnParameters}</strong></div>
<div class="panel-body">
{if $api.returnParamsList}
<table class="table table-hover">
<thead>
<tr>
<th>{$lang.Name}</th>
<th>{$lang.Type}</th>
<th>{$lang.Description}</th>
</tr>
</thead>
<tbody>
{foreach name="api['returnParamsList']" id="param"}
<tr>
<td>{$param.name}</td>
<td>{$param.type}</td>
<td>{$param.description}</td>
</tr>
{/foreach}
</tbody>
</table>
{else /}
{/if}
</div>
</div>
</div>
</div>
</div><!-- #sandbox -->
<div class="tab-pane" id="sample{$api.id}">
<div class="row">
<div class="col-md-12">
<pre id="sample_response{$api.id}">{$api.return|default='无'}</pre>
</div>
</div>
</div><!-- #sample -->
</div><!-- .tab-content -->
</div>
</div>
</div>
{/foreach}
{/foreach}
</div>
<hr>
<div class="row mt0 footer">
<div class="col-md-6" align="left">
</div>
<div class="col-md-6" align="right">
Generated on {:date('Y-m-d H:i:s')} <a href="./" target="_blank">{$config.sitename}</a>
</div>
</div>
</div> <!-- /container -->
<!-- jQuery -->
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/jquery/3.6.0/jquery.min.js"></script>
<!-- Bootstrap Core JavaScript -->
<script src="https://lf6-cdn-tos.bytecdntp.com/cdn/expire-1-M/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
<script type="text/javascript">
function syntaxHighlight(json) {
if (typeof json != 'string') {
json = JSON.stringify(json, undefined, 2);
}
json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
var cls = 'number';
if (/^"/.test(match)) {
if (/:$/.test(match)) {
cls = 'key';
} else {
cls = 'string';
}
} else if (/true|false/.test(match)) {
cls = 'boolean';
} else if (/null/.test(match)) {
cls = 'null';
}
return '<span class="' + cls + '">' + match + '</span>';
});
}
function prepareStr(str) {
try {
return syntaxHighlight(JSON.stringify(JSON.parse(str.replace(/'/g, '"')), null, 2));
} catch (e) {
return str;
}
}
var storage = (function () {
var uid = new Date;
var storage;
var result;
try {
(storage = window.localStorage).setItem(uid, uid);
result = storage.getItem(uid) == uid;
storage.removeItem(uid);
return result && storage;
} catch (exception) {
}
}());
$.fn.serializeObject = function ()
{
var o = {};
var a = this.serializeArray();
$.each(a, function () {
if (!this.value) {
return;
}
if (o[this.name] !== undefined) {
if (!o[this.name].push) {
o[this.name] = [o[this.name]];
}
o[this.name].push(this.value || '');
} else {
o[this.name] = this.value || '';
}
});
return o;
};
$(document).ready(function () {
if (storage) {
storage.getItem('token') && $('#token').val(storage.getItem('token'));
storage.getItem('apiUrl') && $('#apiUrl').val(storage.getItem('apiUrl'));
}
$('[data-toggle="tooltip"]').tooltip({
placement: 'bottom'
});
$(window).on("resize", function(){
$("#sidebar").css("max-height", $(window).height()-80);
});
$(window).trigger("resize");
$(document).on("click", "#sidebar .list-group > .list-group-item", function(){
$("#sidebar .list-group > .list-group-item").removeClass("current");
$(this).addClass("current");
});
$(document).on("click", "#sidebar .child a", function(){
var heading = $("#heading-"+$(this).data("id"));
if(!heading.next().hasClass("in")){
$("a", heading).trigger("click");
}
$("html,body").animate({scrollTop:heading.offset().top-70});
});
$('code[id^=response]').hide();
$.each($('pre[id^=sample_response],pre[id^=sample_post_body]'), function () {
if ($(this).html() == 'NA') {
return;
}
var str = prepareStr($(this).html());
$(this).html(str);
});
$("[data-toggle=popover]").popover({placement: 'right'});
$('[data-toggle=popover]').on('shown.bs.popover', function () {
var $sample = $(this).parent().find(".popover-content"),
str = $(this).data('content');
if (typeof str == "undefined" || str === "") {
return;
}
var str = prepareStr(str);
$sample.html('<pre>' + str + '</pre>');
});
$(document).on('click', '#save_data', function (e) {
if (storage) {
storage.setItem('token', $('#token').val());
storage.setItem('apiUrl', $('#apiUrl').val());
} else {
alert('Your browser does not support local storage');
}
});
$(document).on('click', '.btn-append', function (e) {
$($("#appendtpl").html()).insertBefore($(this).closest(".panel").find(".form-group-submit"));
return false;
});
$(document).on('click', '.btn-remove', function (e) {
$(this).closest(".form-group").remove();
return false;
});
$(document).on('keyup', '.input-custom-name', function (e) {
$(this).closest(".row").find(".input-custom-value").attr("name", $(this).val());
return false;
});
$(document).on('click', '.send', function (e) {
e.preventDefault();
var form = $(this).closest('form');
//added /g to get all the matched params instead of only first
var matchedParamsInRoute = $(form).attr('action').match(/[^{]+(?=\})/g);
var theId = $(this).attr('rel');
//keep a copy of action attribute in order to modify the copy
//instead of the initial attribute
var url = $(form).attr('action');
var method = $(form).prop('method').toLowerCase() || 'get';
var formData = new FormData();
$(form).find('input').each(function (i, input) {
if ($(input).attr('type').toLowerCase() == 'file') {
formData.append($(input).attr('name'), $(input)[0].files[0]);
method = 'post';
} else {
formData.append($(input).attr('name'), $(input).val())
}
});
var index, key, value;
if (matchedParamsInRoute) {
var params = {};
formData.forEach(function(value, key){
params[key] = value;
});
for (index = 0; index < matchedParamsInRoute.length; ++index) {
try {
key = matchedParamsInRoute[index];
value = params[key];
if (typeof value == "undefined")
value = "";
url = url.replace("\{" + key + "\}", value);
formData.delete(key);
} catch (err) {
console.log(err);
}
}
}
var headers = {};
var token = $('#token').val();
if (token.length > 0) {
headers['token'] = token;
}
$("#sandbox" + theId + " .headers input[type=text]").each(function () {
val = $(this).val();
if (val.length > 0) {
headers[$(this).prop('name')] = val;
}
});
$.ajax({
url: $('#apiUrl').val() + url,
data: method == 'get' ? $(form).serialize() : formData,
type: method,
dataType: 'json',
contentType: false,
processData: false,
headers: headers,
xhrFields: {
withCredentials: true
},
success: function (data, textStatus, xhr) {
if (typeof data === 'object') {
var str = JSON.stringify(data, null, 2);
$('#response' + theId).html(syntaxHighlight(str));
} else {
$('#response' + theId).html(data || '');
}
$('#response_headers' + theId).html('HTTP ' + xhr.status + ' ' + xhr.statusText + '<br/><br/>' + xhr.getAllResponseHeaders());
$('#response' + theId).show();
},
error: function (xhr, textStatus, error) {
try {
var str = JSON.stringify($.parseJSON(xhr.responseText), null, 2);
} catch (e) {
var str = xhr.responseText;
}
$('#response_headers' + theId).html('HTTP ' + xhr.status + ' ' + xhr.statusText + '<br/><br/>' + xhr.getAllResponseHeaders());
$('#response' + theId).html(syntaxHighlight(str));
$('#response' + theId).show();
}
});
return false;
});
});
</script>
<script type="text/html" id="appendtpl">
<div class="form-group">
<label class="control-label">自定义</label>
<div class="row">
<div class="col-xs-4">
<input type="text" class="form-control input-sm input-custom-name" placeholder="名称">
</div>
<div class="col-xs-6">
<input type="text" class="form-control input-sm input-custom-value" placeholder="值">
</div>
<div class="col-xs-2 text-center">
<a href="javascript:" class="btn btn-sm btn-danger btn-remove">删除</a>
</div>
</div>
</div>
</script>
</body>
</html>

1577
application/admin/command/Crud.php 100644 → 100755

File diff suppressed because it is too large Load Diff

View File

@ -4,8 +4,7 @@
<div class="form-group layer-footer">
<label class="control-label col-xs-12 col-sm-2"></label>
<div class="col-xs-12 col-sm-8">
<button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
<button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
<button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button>
</div>
</div>
</form>

View File

@ -1,37 +1,37 @@
<?php
namespace {%controllerNamespace%};
use app\common\controller\Backend;
use think\Controller;
use think\Request;
/**
* {%tableComment%}
*
* @icon {%iconName%}
*/
class {%controllerName%} extends Backend
{
/**
* {%modelName%}模型对象
*/
protected $model = null;
public function _initialize()
{
parent::_initialize();
$this->model = model('{%modelName%}');
{%controllerAssignList%}
}
/**
* 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个方法
* 因此在当前控制器中可不用编写增删改查的代码,如果需要自己控制这部分逻辑
* 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
*/
{%controllerIndex%}
}
<?php
namespace {%controllerNamespace%};
use app\common\controller\Backend;
/**
* {%tableComment%}
*
* @icon {%iconName%}
*/
class {%controllerName%} extends Backend
{
/**
* {%modelName%}模型对象
* @var \{%modelNamespace%}\{%modelName%}
*/
protected $model = null;
public function _initialize()
{
parent::_initialize();
$this->model = new \{%modelNamespace%}\{%modelName%};
{%controllerAssignList%}
}
{%controllerImport%}
/**
* 默认生成的控制器所继承的父类中有index/add/edit/del/multi五个基础方法、destroy/restore/recyclebin三个回收站方法
* 因此在当前控制器中可不用编写增删改查的代码,除非需要自己控制这部分逻辑
* 需要将application/admin/library/traits/Backend.php中对应的方法复制到当前控制器,然后进行修改
*/
{%controllerIndex%}
}

View File

@ -7,25 +7,28 @@
//当前是否为关联查询
$this->relationSearch = {%relationSearch%};
//设置过滤方法
$this->request->filter(['strip_tags', 'htmlspecialchars']);
if ($this->request->isAjax())
{
$this->request->filter(['strip_tags', 'trim']);
if ($this->request->isAjax()) {
//如果发送的来源是Selectpage则转发到Selectpage
if ($this->request->request('keyField')) {
return $this->selectpage();
}
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$total = $this->model
{%relationWith%}
->where($where)
->order($sort, $order)
->count();
$list = $this->model
{%relationWith%}
{%relationWithList%}
->where($where)
->order($sort, $order)
->limit($offset, $limit)
->select();
$result = array("total" => $total, "rows" => $list);
->paginate($limit);
foreach ($list as $row) {
{%visibleFieldList%}
{%relationVisibleFieldList%}
}
$result = array("total" => $list->total(), "rows" => $list->items());
return json($result);
}
return $this->view->fetch();
}
}

View File

@ -4,8 +4,7 @@
<div class="form-group layer-footer">
<label class="control-label col-xs-12 col-sm-2"></label>
<div class="col-xs-12 col-sm-8">
<button type="submit" class="btn btn-success btn-embossed disabled">{:__('OK')}</button>
<button type="reset" class="btn btn-default btn-embossed">{:__('Reset')}</button>
<button type="submit" class="btn btn-primary btn-embossed disabled">{:__('OK')}</button>
</div>
</div>
</form>

View File

@ -1,4 +1,6 @@
<div class="checkbox">
{foreach name="{%fieldList%}" item="vo"}
<label for="{%fieldName%}-{$key}"><input id="{%fieldName%}-{$key}" name="{%fieldName%}" type="checkbox" value="{$key}" {in name="key" value="{%selectedValue%}"}checked{/in} /> {$vo}</label>
{/foreach}
</div>

View File

@ -0,0 +1,21 @@
<dl class="list-unstyled fieldlist" data-name="{%fieldName%}" data-template="{%fieldName%}tpl">
<dd>
<ins>{:__('{%itemValue%}')}</ins>
</dd>
<dd>
<ins><a href="javascript:;" class="btn btn-sm btn-success btn-append"><i class="fa fa-plus"></i> {:__('Append')}</a></ins>
</dd>
</dl>
<textarea name="{%fieldName%}" class="form-control hide" cols="30" rows="5">{%fieldValue%}</textarea>
<script id="{%fieldName%}tpl" type="text/html">
<dd class="form-inline">
<ins><input type="text" name="<%=name%>[<%=index%>][value]" class="form-control" size="15" value="<%=row%>"/></ins>
<ins>
<span class="btn btn-sm btn-danger btn-remove"><i class="fa fa-times"></i></span>
<span class="btn btn-sm btn-primary btn-dragsort"><i class="fa fa-arrows"></i></span>
</ins>
</dd>
</script>

View File

@ -0,0 +1,20 @@
<table class="table fieldlist" data-name="{%fieldName%}" data-template="{%fieldName%}tpl">
<tr>
{%theadList%}
<td width="90">{:__('Operate')}</td>
</tr>
<tr><td colspan="{%colspan%}">
<a href="javascript:;" class="btn btn-sm btn-success btn-append"><i class="fa fa-plus"></i> {:__('Append')}</a>
<textarea name="{%fieldName%}" class="form-control hide" cols="30" rows="5">{%fieldValue%}</textarea>
</td></tr>
</table>
<script type="text/html" id="{%fieldName%}tpl">
<tr>
{%tbodyList%}
<td width="90">
<span class="btn btn-sm btn-danger btn-remove"><i class="fa fa-times"></i></span>
<span class="btn btn-sm btn-primary btn-dragsort"><i class="fa fa-arrows"></i></span>
</td>
</tr>
</script>

View File

@ -0,0 +1,10 @@
<dl class="fieldlist" data-name="{%fieldName%}">
<dd>
<ins>{:__('{%itemKey%}')}</ins>
<ins>{:__('{%itemValue%}')}</ins>
</dd>
<dd><a href="javascript:;" class="btn btn-sm btn-success btn-append"><i class="fa fa-plus"></i> {:__('Append')}</a></dd>
<textarea name="{%fieldName%}" class="form-control hide" cols="30" rows="5">{%fieldValue%}</textarea>
</dl>

View File

@ -0,0 +1,10 @@
<div class="panel-heading">
{:build_heading(null,FALSE)}
<ul class="nav nav-tabs" data-field="{%field%}">
<li class="{:$Think.get.{%field%} === null ? 'active' : ''}"><a href="#t-all" data-value="" data-toggle="tab">{:__('All')}</a></li>
{foreach name="{%fieldName%}List" item="vo"}
<li class="{:$Think.get.{%field%} === (string)$key ? 'active' : ''}"><a href="#t-{$key}" data-value="{$key}" data-toggle="tab">{$vo}</a></li>
{/foreach}
</ul>
</div>

View File

@ -0,0 +1,8 @@
<div class="dropdown btn-group {:$auth->check('{%controllerUrl%}/multi')?'':'hide'}">
<a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a>
<ul class="dropdown-menu text-left" role="menu">
{foreach name="{%fieldName%}List" item="vo"}
<li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:" data-params="{%field%}={$key}">{:__('Set {%field%} to ' . $key)}</a></li>
{/foreach}
</ul>
</div>

View File

@ -1,4 +1,6 @@
<div class="radio">
{foreach name="{%fieldList%}" item="vo"}
<label for="{%fieldName%}-{$key}"><input id="{%fieldName%}-{$key}" name="{%fieldName%}" type="radio" value="{$key}" {in name="key" value="{%selectedValue%}"}checked{/in} /> {$vo}</label>
{/foreach}
</div>

View File

@ -0,0 +1 @@
<a class="btn btn-success btn-recyclebin btn-dialog {:$auth->check('{%controllerUrl%}/recyclebin')?'':'hide'}" href="{%controllerUrl%}/recyclebin" title="{:__('Recycle bin')}"><i class="fa fa-recycle"></i> {:__('Recycle bin')}</a>

View File

@ -0,0 +1,5 @@
<input {%attrStr%} name="{%fieldName%}" type="hidden" value="{%fieldValue%}">
<a href="javascript:;" data-toggle="switcher" class="btn-switcher" data-input-id="c-{%field%}" data-yes="{%fieldYes%}" data-no="{%fieldNo%}" >
<i class="fa fa-toggle-on text-success {%fieldSwitchClass%} fa-2x"></i>
</a>

View File

@ -1,21 +1,25 @@
<div class="panel panel-default panel-intro">
{:build_heading()}
{%headingHtml%}
<div class="panel-body">
<div id="myTabContent" class="tab-content">
<div class="tab-pane fade active in" id="one">
<div class="widget-body no-padding">
<div id="toolbar" class="toolbar">
{:build_toolbar()}
<div class="dropdown btn-group">
<a class="btn btn-primary btn-more dropdown-toggle btn-disabled disabled" data-toggle="dropdown"><i class="fa fa-cog"></i> {:__('More')}</a>
<ul class="dropdown-menu text-left" role="menu">
<li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=normal"><i class="fa fa-eye"></i> {:__('Set to normal')}</a></li>
<li><a class="btn btn-link btn-multi btn-disabled disabled" href="javascript:;" data-params="status=hidden"><i class="fa fa-eye-slash"></i> {:__('Set to hidden')}</a></li>
</ul>
</div>
<a href="javascript:;" class="btn btn-primary btn-refresh" title="{:__('Refresh')}" ><i class="fa fa-refresh"></i> </a>
<a href="javascript:;" class="btn btn-success btn-add {:$auth->check('{%controllerUrl%}/add')?'':'hide'}" title="{:__('Add')}" ><i class="fa fa-plus"></i> {:__('Add')}</a>
<a href="javascript:;" class="btn btn-success btn-edit btn-disabled disabled {:$auth->check('{%controllerUrl%}/edit')?'':'hide'}" title="{:__('Edit')}" ><i class="fa fa-pencil"></i> {:__('Edit')}</a>
<a href="javascript:;" class="btn btn-danger btn-del btn-disabled disabled {:$auth->check('{%controllerUrl%}/del')?'':'hide'}" title="{:__('Delete')}" ><i class="fa fa-trash"></i> {:__('Delete')}</a>
{%importHtml%}
{%multipleHtml%}
{%recyclebinHtml%}
</div>
<table id="table" class="table table-striped table-bordered table-hover" width="100%">
<table id="table" class="table table-striped table-bordered table-hover table-nowrap"
data-operate-edit="{:$auth->check('{%controllerUrl%}/edit')}"
data-operate-del="{:$auth->check('{%controllerUrl%}/del')}"
width="100%">
</table>
</div>
</div>

View File

@ -5,11 +5,12 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
// 初始化表格参数配置
Table.api.init({
extend: {
index_url: '{%controllerUrl%}/index',
index_url: '{%controllerUrl%}/index' + location.search,
add_url: '{%controllerUrl%}/add',
edit_url: '{%controllerUrl%}/edit',
del_url: '{%controllerUrl%}/del',
multi_url: '{%controllerUrl%}/multi',
import_url: '{%controllerUrl%}/import',
table: '{%table%}',
}
});
@ -20,7 +21,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
table.bootstrapTable({
url: $.fn.bootstrapTable.defaults.extend.index_url,
pk: '{%pk%}',
sortName: '{%order%}',
sortName: '{%order%}',{%fixedColumnsJs%}
columns: [
[
{%javascriptList%}
@ -30,7 +31,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
// 为表格绑定事件
Table.api.bindevent(table);
},
},{%recyclebinJs%}
add: function () {
Controller.api.bindevent();
},
@ -44,4 +45,4 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin
}
};
return Controller;
});
});

View File

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

View File

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

View File

@ -0,0 +1,4 @@
public function import()
{
parent::import();
}

View File

@ -2,6 +2,9 @@
protected static function init()
{
self::afterInsert(function ($row) {
$row->save(['{%order%}' => $row['id']]);
if (!$row['{%order%}']) {
$pk = $row->getPk();
$row->getQuery()->where($pk, $row[$pk])->update(['{%order%}' => $row[$pk]]);
}
});
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,60 @@
recyclebin: function () {
// 初始化表格参数配置
Table.api.init({
extend: {
'dragsort_url': ''
}
});
var table = $("#table");
// 初始化表格
table.bootstrapTable({
url: '{%controllerUrl%}/recyclebin' + location.search,
pk: 'id',
sortName: 'id',
columns: [
[
{checkbox: true},
{field: 'id', title: __('Id')},{%recyclebinTitleJs%}
{
field: '{%deleteTimeField%}',
title: __('Deletetime'),
operate: 'RANGE',
addclass: 'datetimerange',
formatter: Table.api.formatter.datetime
},
{
field: 'operate',
width: '140px',
title: __('Operate'),
table: table,
events: Table.api.events.operate,
buttons: [
{
name: 'Restore',
text: __('Restore'),
classname: 'btn btn-xs btn-info btn-ajax btn-restoreit',
icon: 'fa fa-rotate-left',
url: '{%controllerUrl%}/restore',
refresh: true
},
{
name: 'Destroy',
text: __('Destroy'),
classname: 'btn btn-xs btn-danger btn-ajax btn-destroyit',
icon: 'fa fa-times',
url: '{%controllerUrl%}/destroy',
refresh: true
}
],
formatter: Table.api.formatter.operate
}
]
]
});
// 为表格绑定事件
Table.api.bindevent(table);
},

View File

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

View File

@ -1,33 +1,40 @@
<?php
namespace {%modelNamespace%};
use think\Model;
class {%modelName%} extends Model
{
// 表名
protected ${%modelTableType%} = '{%modelTableName%}';
// 自动写入时间戳字段
protected $autoWriteTimestamp = {%modelAutoWriteTimestamp%};
// 定义时间戳字段名
protected $createTime = {%createTime%};
protected $updateTime = {%updateTime%};
// 追加属性
protected $append = [
{%appendAttrList%}
];
{%modelInit%}
{%getEnumList%}
{%getAttrList%}
{%setAttrList%}
{%modelRelationMethod%}
}
<?php
namespace {%modelNamespace%};
use think\Model;
{%softDeleteClassPath%}
class {%modelName%} extends Model
{
{%softDelete%}
{%modelConnection%}
// 表名
protected ${%modelTableType%} = '{%modelTableTypeName%}';
// 自动写入时间戳字段
protected $autoWriteTimestamp = {%modelAutoWriteTimestamp%};
// 定义时间戳字段名
protected $createTime = {%createTime%};
protected $updateTime = {%updateTime%};
protected $deleteTime = {%deleteTime%};
// 追加属性
protected $append = [
{%appendAttrList%}
];
{%modelInit%}
{%getEnumList%}
{%getAttrList%}
{%setAttrList%}
{%relationMethodList%}
}

View File

@ -0,0 +1,25 @@
<div class="panel panel-default panel-intro">
{:build_heading()}
<div class="panel-body">
<div id="myTabContent" class="tab-content">
<div class="tab-pane fade active in" id="one">
<div class="widget-body no-padding">
<div id="toolbar" class="toolbar">
{:build_toolbar('refresh')}
<a class="btn btn-info btn-multi btn-disabled disabled {:$auth->check('{%controllerUrl%}/restore')?'':'hide'}" href="javascript:;" data-url="{%controllerUrl%}/restore" data-action="restore"><i class="fa fa-rotate-left"></i> {:__('Restore')}</a>
<a class="btn btn-danger btn-multi btn-disabled disabled {:$auth->check('{%controllerUrl%}/destroy')?'':'hide'}" href="javascript:;" data-url="{%controllerUrl%}/destroy" data-action="destroy"><i class="fa fa-times"></i> {:__('Destroy')}</a>
<a class="btn btn-success btn-restoreall {:$auth->check('{%controllerUrl%}/restore')?'':'hide'}" href="javascript:;" data-url="{%controllerUrl%}/restore" title="{:__('Restore all')}"><i class="fa fa-rotate-left"></i> {:__('Restore all')}</a>
<a class="btn btn-danger btn-destroyall {:$auth->check('{%controllerUrl%}/destroy')?'':'hide'}" href="javascript:;" data-url="{%controllerUrl%}/destroy" title="{:__('Destroy all')}"><i class="fa fa-times"></i> {:__('Destroy all')}</a>
</div>
<table id="table" class="table table-striped table-bordered table-hover"
data-operate-restore="{:$auth->check('{%controllerUrl%}/restore')}"
data-operate-destroy="{:$auth->check('{%controllerUrl%}/destroy')}"
width="100%">
</table>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,12 +1,12 @@
<?php
namespace {%modelNamespace%};
use think\Model;
class {%relationModelName%} extends Model
{
// 表名
protected ${%relationModelTableType%} = '{%relationModelTableName%}';
}
<?php
namespace {%modelNamespace%};
use think\Model;
class {%relationName%} extends Model
{
// 表名
protected ${%relationTableType%} = '{%relationTableTypeName%}';
}

View File

@ -1,27 +1,27 @@
<?php
namespace {%validateNamespace%};
use think\Validate;
class {%validateName%} extends Validate
{
/**
* 验证规则
*/
protected $rule = [
];
/**
* 提示消息
*/
protected $message = [
];
/**
* 验证场景
*/
protected $scene = [
'add' => [],
'edit' => [],
];
}
<?php
namespace {%validateNamespace%};
use think\Validate;
class {%validateName%} extends Validate
{
/**
* 验证规则
*/
protected $rule = [
];
/**
* 提示消息
*/
protected $message = [
];
/**
* 验证场景
*/
protected $scene = [
'add' => [],
'edit' => [],
];
}

View File

@ -2,6 +2,7 @@
namespace app\admin\command;
use fast\Random;
use PDO;
use think\Config;
use think\console\Command;
@ -10,48 +11,319 @@ use think\console\input\Option;
use think\console\Output;
use think\Db;
use think\Exception;
use think\Lang;
use think\Request;
use think\View;
class Install extends Command
{
protected $model = null;
/**
* @var \think\View 视图类实例
*/
protected $view;
/**
* @var \think\Request Request 实例
*/
protected $request;
protected function configure()
{
$config = Config::get('database');
$this
->setName('install')
->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override', FALSE)
->setDescription('New installation of FastAdmin');
->setName('install')
->addOption('hostname', 'a', Option::VALUE_OPTIONAL, 'mysql hostname', $config['hostname'])
->addOption('hostport', 'o', Option::VALUE_OPTIONAL, 'mysql hostport', $config['hostport'])
->addOption('database', 'd', Option::VALUE_OPTIONAL, 'mysql database', $config['database'])
->addOption('prefix', 'r', Option::VALUE_OPTIONAL, 'table prefix', $config['prefix'])
->addOption('username', 'u', Option::VALUE_OPTIONAL, 'mysql username', $config['username'])
->addOption('password', 'p', Option::VALUE_OPTIONAL, 'mysql password', $config['password'])
->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force override', false)
->setDescription('New installation of FastAdmin');
}
/**
* 命令行安装
*/
protected function execute(Input $input, Output $output)
{
//覆盖安装
define('INSTALL_PATH', APP_PATH . 'admin' . DS . 'command' . DS . 'Install' . DS);
// 覆盖安装
$force = $input->getOption('force');
$hostname = $input->getOption('hostname');
$hostport = $input->getOption('hostport');
$database = $input->getOption('database');
$prefix = $input->getOption('prefix');
$username = $input->getOption('username');
$password = $input->getOption('password');
$installLockFile = __DIR__ . "/Install/install.lock";
if (is_file($installLockFile) && !$force)
{
$installLockFile = INSTALL_PATH . "install.lock";
if (is_file($installLockFile) && !$force) {
throw new Exception("\nFastAdmin already installed!\nIf you need to reinstall again, use the parameter --force=true ");
}
$sql = file_get_contents(__DIR__ . '/Install/fastadmin.sql');
$adminUsername = 'admin';
$adminPassword = Random::alnum(10);
$adminEmail = 'admin@admin.com';
$siteName = __('My Website');
// 先尝试能否自动创建数据库
$config = Config::get('database');
$pdo = new PDO("{$config['type']}:host={$config['hostname']}" . ($config['hostport'] ? ";port={$config['hostport']}" : ''), $config['username'], $config['password']);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->query("CREATE DATABASE IF NOT EXISTS `{$config['database']}` CHARACTER SET utf8 COLLATE utf8_general_ci;");
$adminName = $this->installation($hostname, $hostport, $database, $username, $password, $prefix, $adminUsername, $adminPassword, $adminEmail, $siteName);
if ($adminName) {
$output->highlight("Admin url:http://www.example.com/{$adminName}");
}
// 查询一次SQL,判断连接是否正常
Db::execute("SELECT 1");
$output->highlight("Admin username:{$adminUsername}");
$output->highlight("Admin password:{$adminPassword}");
// 调用原生PDO对象进行批量查询
Db::getPdo()->exec($sql);
\think\Cache::rm('__menu__');
file_put_contents($installLockFile, 1);
$output->info("Install Successed!");
}
/**
* PC端安装
*/
public function index()
{
$this->view = View::instance(array_merge(Config::get('template'), ['tpl_cache' => false]));
$this->request = Request::instance();
define('INSTALL_PATH', APP_PATH . 'admin' . DS . 'command' . DS . 'Install' . DS);
$lang = $this->request->langset();
$lang = preg_match("/^([a-zA-Z\-_]{2,10})\$/i", $lang) ? $lang : 'zh-cn';
if (!$lang || in_array($lang, ['zh-cn', 'zh-hans-cn'])) {
Lang::load(INSTALL_PATH . 'zh-cn.php');
}
$installLockFile = INSTALL_PATH . "install.lock";
if (is_file($installLockFile)) {
echo __('The system has been installed. If you need to reinstall, please remove %s first', 'install.lock');
exit;
}
$output = function ($code, $msg, $url = null, $data = null) {
return json(['code' => $code, 'msg' => $msg, 'url' => $url, 'data' => $data]);
};
if ($this->request->isPost()) {
$mysqlHostname = $this->request->post('mysqlHostname', '127.0.0.1');
$mysqlHostport = $this->request->post('mysqlHostport', '3306');
$hostArr = explode(':', $mysqlHostname);
if (count($hostArr) > 1) {
$mysqlHostname = $hostArr[0];
$mysqlHostport = $hostArr[1];
}
$mysqlUsername = $this->request->post('mysqlUsername', 'root');
$mysqlPassword = $this->request->post('mysqlPassword', '');
$mysqlDatabase = $this->request->post('mysqlDatabase', '');
$mysqlPrefix = $this->request->post('mysqlPrefix', 'fa_');
$adminUsername = $this->request->post('adminUsername', 'admin');
$adminPassword = $this->request->post('adminPassword', '');
$adminPasswordConfirmation = $this->request->post('adminPasswordConfirmation', '');
$adminEmail = $this->request->post('adminEmail', 'admin@admin.com');
$siteName = $this->request->post('siteName', __('My Website'));
if ($adminPassword !== $adminPasswordConfirmation) {
return $output(0, __('The two passwords you entered did not match'));
}
$adminName = '';
try {
$adminName = $this->installation($mysqlHostname, $mysqlHostport, $mysqlDatabase, $mysqlUsername, $mysqlPassword, $mysqlPrefix, $adminUsername, $adminPassword, $adminEmail, $siteName);
} catch (\PDOException $e) {
throw new Exception($e->getMessage());
} catch (\Exception $e) {
return $output(0, $e->getMessage());
}
return $output(1, __('Install Successed'), null, ['adminName' => $adminName]);
}
$errInfo = '';
try {
$this->checkenv();
} catch (\Exception $e) {
$errInfo = $e->getMessage();
}
return $this->view->fetch(INSTALL_PATH . "install.html", ['errInfo' => $errInfo]);
}
/**
* 执行安装
*/
protected function installation($mysqlHostname, $mysqlHostport, $mysqlDatabase, $mysqlUsername, $mysqlPassword, $mysqlPrefix, $adminUsername, $adminPassword, $adminEmail = null, $siteName = null)
{
$this->checkenv();
if ($mysqlDatabase == '') {
throw new Exception(__('Please input correct database'));
}
if (!preg_match("/^\w{3,12}$/", $adminUsername)) {
throw new Exception(__('Please input correct username'));
}
if (!preg_match("/^[\S]{6,16}$/", $adminPassword)) {
throw new Exception(__('Please input correct password'));
}
$weakPasswordArr = ['123456', '12345678', '123456789', '654321', '111111', '000000', 'password', 'qwerty', 'abc123', '1qaz2wsx'];
if (in_array($adminPassword, $weakPasswordArr)) {
throw new Exception(__('Password is too weak'));
}
if ($siteName == '' || preg_match("/fast" . "admin/i", $siteName)) {
throw new Exception(__('Please input correct website'));
}
$sql = file_get_contents(INSTALL_PATH . 'fastadmin.sql');
$sql = str_replace("`fa_", "`{$mysqlPrefix}", $sql);
// 先尝试能否自动创建数据库
$config = Config::get('database');
try {
$pdo = new PDO("{$config['type']}:host={$mysqlHostname}" . ($mysqlHostport ? ";port={$mysqlHostport}" : ''), $mysqlUsername, $mysqlPassword);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->query("CREATE DATABASE IF NOT EXISTS `{$mysqlDatabase}` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;");
// 连接install命令中指定的数据库
$instance = Db::connect([
'type' => "{$config['type']}",
'hostname' => "{$mysqlHostname}",
'hostport' => "{$mysqlHostport}",
'database' => "{$mysqlDatabase}",
'username' => "{$mysqlUsername}",
'password' => "{$mysqlPassword}",
'prefix' => "{$mysqlPrefix}",
]);
// 查询一次SQL,判断连接是否正常
$instance->execute("SELECT 1");
// 调用原生PDO对象进行批量查询
$instance->getPdo()->exec($sql);
} catch (\PDOException $e) {
throw new Exception($e->getMessage());
}
// 后台入口文件
$adminFile = ROOT_PATH . 'public' . DS . 'admin.php';
// 数据库配置文件
$dbConfigFile = APP_PATH . 'database.php';
$dbConfigText = @file_get_contents($dbConfigFile);
$callback = function ($matches) use ($mysqlHostname, $mysqlHostport, $mysqlUsername, $mysqlPassword, $mysqlDatabase, $mysqlPrefix) {
$field = "mysql" . ucfirst($matches[1]);
$replace = $$field;
if ($matches[1] == 'hostport' && $mysqlHostport == 3306) {
$replace = '';
}
return "'{$matches[1]}'{$matches[2]}=>{$matches[3]}Env::get('database.{$matches[1]}', '{$replace}'),";
};
$dbConfigText = preg_replace_callback("/'(hostname|database|username|password|hostport|prefix)'(\s+)=>(\s+)Env::get\((.*)\)\,/", $callback, $dbConfigText);
// 检测能否成功写入数据库配置
$result = @file_put_contents($dbConfigFile, $dbConfigText);
if (!$result) {
throw new Exception(__('The current permissions are insufficient to write the file %s', 'application/database.php'));
}
// 设置新的Token随机密钥key
$oldTokenKey = config('token.key');
$newTokenKey = \fast\Random::alnum(32);
$coreConfigFile = CONF_PATH . 'config.php';
$coreConfigText = @file_get_contents($coreConfigFile);
$coreConfigText = preg_replace("/'key'(\s+)=>(\s+)'{$oldTokenKey}'/", "'key'\$1=>\$2'{$newTokenKey}'", $coreConfigText);
$result = @file_put_contents($coreConfigFile, $coreConfigText);
if (!$result) {
throw new Exception(__('The current permissions are insufficient to write the file %s', 'application/config.php'));
}
$avatar = request()->domain() . '/assets/img/avatar.png';
// 变更默认管理员密码
$adminPassword = $adminPassword ? $adminPassword : Random::alnum(8);
$adminEmail = $adminEmail ? $adminEmail : "admin@admin.com";
$newSalt = substr(md5(uniqid(true)), 0, 6);
$newPassword = md5(md5($adminPassword) . $newSalt);
$data = ['username' => $adminUsername, 'email' => $adminEmail, 'avatar' => $avatar, 'password' => $newPassword, 'salt' => $newSalt];
$instance->name('admin')->where('username', 'admin')->update($data);
// 变更前台默认用户的密码,随机生成
$newSalt = substr(md5(uniqid(true)), 0, 6);
$newPassword = md5(md5(Random::alnum(8)) . $newSalt);
$instance->name('user')->where('username', 'admin')->update(['avatar' => $avatar, 'password' => $newPassword, 'salt' => $newSalt]);
// 修改后台入口
$adminName = '';
if (is_file($adminFile)) {
$adminName = Random::alpha(10) . '.php';
rename($adminFile, ROOT_PATH . 'public' . DS . $adminName);
}
//修改站点名称
if ($siteName != config('site.name')) {
$instance->name('config')->where('name', 'name')->update(['value' => $siteName]);
$siteConfigFile = CONF_PATH . 'extra' . DS . 'site.php';
$siteConfig = include $siteConfigFile;
$configList = $instance->name("config")->select();
foreach ($configList as $k => $value) {
if (in_array($value['type'], ['selects', 'checkbox', 'images', 'files'])) {
$value['value'] = is_array($value['value']) ? $value['value'] : explode(',', $value['value']);
}
if ($value['type'] == 'array') {
$value['value'] = (array)json_decode($value['value'], true);
}
$siteConfig[$value['name']] = $value['value'];
}
$siteConfig['name'] = $siteName;
file_put_contents($siteConfigFile, '<?php' . "\n\nreturn " . var_export_short($siteConfig) . ";\n");
}
$installLockFile = INSTALL_PATH . "install.lock";
//检测能否成功写入lock文件
$result = @file_put_contents($installLockFile, 1);
if (!$result) {
throw new Exception(__('The current permissions are insufficient to write the file %s', 'application/admin/command/Install/install.lock'));
}
try {
//删除安装脚本
@unlink(ROOT_PATH . 'public' . DS . 'install.php');
} catch (\Exception $e) {
}
return $adminName;
}
/**
* 检测环境
*/
protected function checkenv()
{
// 检测目录是否存在
$checkDirs = [
'thinkphp',
'vendor',
'public' . DS . 'assets' . DS . 'libs'
];
//数据库配置文件
$dbConfigFile = APP_PATH . 'database.php';
if (version_compare(PHP_VERSION, '7.4.0', '<')) {
throw new Exception(__("The current version %s is too low, please use PHP 7.4 or higher", PHP_VERSION));
}
if (!extension_loaded("PDO")) {
throw new Exception(__("PDO is not currently installed and cannot be installed"));
}
if (!is_really_writable($dbConfigFile)) {
throw new Exception(__('The current permissions are insufficient to write the configuration file application/database.php'));
}
foreach ($checkDirs as $k => $v) {
if (!is_dir(ROOT_PATH . $v)) {
throw new Exception(__('Please go to the official website to download the full package or resource package and try to install'));
break;
}
}
return true;
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,316 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>{:__('Installing FastAdmin')}</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1">
<meta name="renderer" content="webkit">
<style>
body {
background: #f1f6fd;
margin: 0;
padding: 0;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body, input, button {
font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, 'Microsoft Yahei', Arial, sans-serif;
font-size: 14px;
color: #7E96B3;
}
.container {
max-width: 480px;
margin: 0 auto;
padding: 20px;
text-align: center;
}
a {
color: #4e73df;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
h1 {
margin-top: 0;
margin-bottom: 10px;
}
h2 {
font-size: 28px;
font-weight: normal;
color: #3C5675;
margin-bottom: 0;
margin-top: 0;
}
form {
margin-top: 40px;
}
.form-group {
margin-bottom: 20px;
}
.form-group .form-field:first-child input {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.form-group .form-field:last-child input {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
}
.form-field input {
background: #fff;
margin: 0 0 2px;
border: 2px solid transparent;
transition: background 0.2s, border-color 0.2s, color 0.2s;
width: 100%;
padding: 15px 15px 15px 180px;
box-sizing: border-box;
}
.form-field input:focus {
border-color: #4e73df;
background: #fff;
color: #444;
outline: none;
}
.form-field label {
float: left;
width: 160px;
text-align: right;
margin-right: -160px;
position: relative;
margin-top: 15px;
font-size: 14px;
pointer-events: none;
opacity: 0.7;
}
button, .btn {
background: #3C5675;
color: #fff;
border: 0;
font-weight: bold;
border-radius: 4px;
cursor: pointer;
padding: 15px 30px;
-webkit-appearance: none;
}
button[disabled] {
opacity: 0.5;
}
.form-buttons {
height: 52px;
line-height: 52px;
}
.form-buttons .btn {
margin-right: 5px;
}
#error, .error, #success, .success, #warmtips, .warmtips {
background: #D83E3E;
color: #fff;
padding: 15px 20px;
border-radius: 4px;
margin-bottom: 20px;
}
#success {
background: #3C5675;
}
#error a, .error a {
color: white;
text-decoration: underline;
}
#warmtips {
background: #ffcdcd;
font-size: 14px;
color: #e74c3c;
}
#warmtips a {
background: #ffffff7a;
display: block;
height: 30px;
line-height: 30px;
margin-top: 10px;
color: #e21a1a;
border-radius: 3px;
}
</style>
</head>
<body>
<div class="container">
<h1>
<svg width="80px" height="96px" viewBox="0 0 768 830" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="logo" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<path d="M64.433651,605.899968 C20.067302,536.265612 0,469.698785 0,389.731348 C0,174.488668 171.922656,0 384,0 C596.077344,0 768,174.488668 768,389.731348 C768,469.698785 747.932698,536.265612 703.566349,605.899968 C614.4,753.480595 441.6,870.4 384,870.4 C326.4,870.4 153.6,753.480595 64.433651,605.899968 L64.433651,605.899968 Z"
id="body" fill="#4e73df"></path>
<path d="M429.648991,190.816 L430.160991,190.816 L429.648991,190.816 L429.648991,190.816 Z M429.648991,156 L427.088991,156 C419.408991,157.024 411.728991,160.608 404.560991,168.8 L403.024991,170.848 L206.928991,429.92 C198.736991,441.184 197.712991,453.984 204.368991,466.784 C210.512991,478.048 222.288991,485.728 235.600991,485.728 L336.464991,486.24 L304.208991,673.632 C301.648991,689.504 310.352991,705.376 325.200991,712.032 C329.808991,714.08 334.416991,714.592 339.536991,714.592 C349.776991,714.592 358.992991,709.472 366.160991,700.256 L561.744991,419.168 C569.936991,407.904 570.960991,395.104 564.304991,382.304 C557.648991,369.504 547.408991,363.36 533.072991,363.36 L432.208991,363.36 L463.952991,199.008 C464.464991,196.448 464.976991,193.376 464.976991,190.816 C464.976991,171.872 449.104991,156 431.184991,156 L429.648991,156 L429.648991,156 Z"
id="flash" fill="#FFFFFF"></path>
</g>
</svg>
</h1>
<h2>{:__('Installing FastAdmin')}</h2>
<div>
<form method="post">
{if $errInfo}
<div class="error">
{$errInfo}
</div>
{/if}
<div id="error" style="display:none"></div>
<div id="success" style="display:none"></div>
<div id="warmtips" style="display:none"></div>
<div class="form-group">
<div class="form-field">
<label>{:__('Mysql Hostname')}</label>
<input type="text" name="mysqlHostname" value="127.0.0.1" required="">
</div>
<div class="form-field">
<label>{:__('Mysql Database')}</label>
<input type="text" name="mysqlDatabase" value="" required="">
</div>
<div class="form-field">
<label>{:__('Mysql Username')}</label>
<input type="text" name="mysqlUsername" value="root" required="">
</div>
<div class="form-field">
<label>{:__('Mysql Password')}</label>
<input type="password" name="mysqlPassword">
</div>
<div class="form-field">
<label>{:__('Mysql Prefix')}</label>
<input type="text" name="mysqlPrefix" value="fa_">
</div>
<div class="form-field">
<label>{:__('Mysql Hostport')}</label>
<input type="number" name="mysqlHostport" value="3306">
</div>
</div>
<div class="form-group">
<div class="form-field">
<label>{:__('Admin Username')}</label>
<input name="adminUsername" value="admin" required=""/>
</div>
<div class="form-field">
<label>{:__('Admin Email')}</label>
<input name="adminEmail" value="admin@admin.com" required="">
</div>
<div class="form-field">
<label>{:__('Admin Password')}</label>
<input type="password" name="adminPassword" required="">
</div>
<div class="form-field">
<label>{:__('Repeat Password')}</label>
<input type="password" name="adminPasswordConfirmation" required="">
</div>
</div>
<div class="form-group">
<div class="form-field">
<label>{:__('Website')}</label>
<input type="text" name="siteName" value="{:__('My Website')}" required=""/>
</div>
</div>
<div class="form-buttons">
<!--@formatter:off-->
<button type="submit" {:$errInfo?'disabled':''}>{:__('Install now')}</button>
<!--@formatter:on-->
</div>
</form>
<!-- jQuery -->
<script src="__ROOT__/assets/libs/jquery/dist/jquery.min.js"></script>
<script>
$(function () {
$('form :input:first').select();
$('form').on('submit', function (e) {
e.preventDefault();
var form = this;
var $error = $("#error");
var $success = $("#success");
var $button = $(this).find('button')
.text("{:__('Installing')}")
.prop('disabled', true);
$.ajax({
url: "",
type: "POST",
dataType: "json",
data: $(this).serialize(),
success: function (ret) {
if (ret.code == 1) {
var data = ret.data;
$error.hide();
$(".form-group", form).remove();
$button.remove();
$("#success").text(ret.msg).show();
$buttons = $(".form-buttons", form);
$("<a class='btn' href='./'>{:__('Home')}</a>").appendTo($buttons);
if (typeof data.adminName !== 'undefined') {
var url = location.href.replace(/install\.php/, data.adminName);
$("#warmtips").html("{:__('Security tips')}" + '<a href="' + url + '">' + url + '</a>').show();
$('<a class="btn" href="' + url + '" id="btn-admin" style="background:#4e73df">' + "{:__('Dashboard')}" + '</a>').appendTo($buttons);
}
localStorage.setItem("fastep", "installed");
} else {
$error.show().text(ret.msg);
$button.prop('disabled', false).text("{:__('Install now')}");
$("html,body").animate({
scrollTop: 0
}, 500);
}
},
error: function (xhr) {
$error.show().text(xhr.responseText);
$button.prop('disabled', false).text("{:__('Install now')}");
$("html,body").animate({
scrollTop: 0
}, 500);
}
});
return false;
});
});
</script>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,35 @@
<?php
return [
'Warning' => '温馨提示',
'Installing FastAdmin' => '安装FastAdmin',
'Mysql Hostname' => 'MySQL 数据库地址',
'Mysql Database' => 'MySQL 数据库名',
'Mysql Username' => 'MySQL 用户名',
'Mysql Password' => 'MySQL 密码',
'Mysql Prefix' => 'MySQL 数据表前缀',
'Mysql Hostport' => 'MySQL 端口号',
'Admin Username' => '管理员用户名',
'Admin Email' => '管理员Email',
'Admin Password' => '管理员密码',
'Repeat Password' => '重复管理员密码',
'Website' => '网站名称',
'My Website' => '我的网站',
'Install now' => '点击安装',
'Installing' => '安装中...',
'Home' => '访问首页',
'Dashboard' => '进入后台',
'Go back' => '返回上一页',
'Install Successed' => '安装成功!',
'Security tips' => '温馨提示:请将以下后台登录入口添加到你的收藏夹,为了你的站点安全,不要泄漏或发送给他人!如有泄漏请及时修改!',
'Please input correct database' => '请输入正确的数据库名',
'Please input correct username' => '用户名只能由3-30位数字、字母、下划线组合',
'Please input correct password' => '密码长度必须在6-30位之间不能包含空格',
'Password is too weak' => '密码太简单,请重新输入',
'The two passwords you entered did not match' => '两次输入的密码不一致',
'Please input correct website' => '网站名称输入不正确',
'The current version %s is too low, please use PHP 7.4 or higher' => '当前版本%s过低请使用PHP7.4及以上版本',
'PDO is not currently installed and cannot be installed' => '当前未开启PDO无法进行安装',
'The current permissions are insufficient to write the file %s' => '当前权限不足,无法写入文件%s',
'Please go to the official website to download the full package or resource package and try to install' => '当前代码仅包含核心代码,请前往官网下载完整包或资源包覆盖后再尝试安装',
'The system has been installed. If you need to reinstall, please remove %s first' => '当前已经安装成功,如果需要重新安装,请手动移除%s文件',
];

267
application/admin/command/Menu.php 100644 → 100755
View File

@ -12,57 +12,79 @@ use think\console\Input;
use think\console\input\Option;
use think\console\Output;
use think\Exception;
use think\Loader;
class Menu extends Command
{
protected $model = null;
protected function configure()
{
$this
->setName('menu')
->addOption('controller', 'c', Option::VALUE_REQUIRED, 'controller name,use \'all-controller\' when build all menu', null)
->addOption('delete', 'd', Option::VALUE_OPTIONAL, 'delete the specified menu', '')
->setDescription('Build auth menu from controller');
->setName('menu')
->addOption('controller', 'c', Option::VALUE_REQUIRED | Option::VALUE_IS_ARRAY, 'controller name,use \'all-controller\' when build all menu', null)
->addOption('delete', 'd', Option::VALUE_OPTIONAL, 'delete the specified menu', '')
->addOption('force', 'f', Option::VALUE_OPTIONAL, 'force delete menu,without tips', null)
->addOption('equal', 'e', Option::VALUE_OPTIONAL, 'the controller must be equal', null)
->setDescription('Build auth menu from controller');
//要执行的controller必须一样不适用模糊查询
}
protected function execute(Input $input, Output $output)
{
$this->model = new AuthRule();
$adminPath = dirname(__DIR__) . DS;
$moduleName = 'admin';
//控制器名
$controller = $input->getOption('controller') ?: '';
if (!$controller)
{
if (!$controller) {
throw new Exception("please input controller name");
}
$force = $input->getOption('force');
//是否为删除模式
$delete = $input->getOption('delete');
if ($delete)
{
if ($controller == 'all-controller')
{
//是否控制器完全匹配
$equal = $input->getOption('equal');
if ($delete) {
if (in_array('all-controller', $controller)) {
throw new Exception("could not delete all menu");
}
$ids = [];
$list = $this->model->where('name', 'like', "/{$moduleName}/" . strtolower($controller) . "%")->select();
foreach ($list as $k => $v)
{
$list = $this->model->where(function ($query) use ($controller, $equal) {
foreach ($controller as $index => $item) {
if (stripos($item, '_') !== false) {
$item = Loader::parseName($item, 1);
}
if (stripos($item, '/') !== false) {
$controllerArr = explode('/', $item);
end($controllerArr);
$key = key($controllerArr);
$controllerArr[$key] = Loader::parseName($controllerArr[$key]);
} else {
$controllerArr = [Loader::parseName($item)];
}
$item = str_replace('_', '\_', implode('/', $controllerArr));
if ($equal) {
$query->whereOr('name', 'eq', $item);
} else {
$query->whereOr('name', 'like', strtolower($item) . "%");
}
}
})->select();
foreach ($list as $k => $v) {
$output->warning($v->name);
$ids[] = $v->id;
}
if (!$ids)
{
if (!$ids) {
throw new Exception("There is no menu to delete");
}
$readyMenu = [];
$output->info("Are you sure you want to delete all those menu? Type 'yes' to continue: ");
$line = fgets(STDIN);
if (trim($line) != 'yes')
{
throw new Exception("Operation is aborted!");
if (!$force) {
$output->info("Are you sure you want to delete all those menu? Type 'yes' to continue: ");
$line = fgets(defined('STDIN') ? STDIN : fopen('php://stdin', 'r'));
if (trim($line) != 'yes') {
throw new Exception("Operation is aborted!");
}
}
AuthRule::destroy($ids);
@ -71,23 +93,32 @@ class Menu extends Command
return;
}
if ($controller != 'all-controller')
{
$controllerArr = explode('/', $controller);
end($controllerArr);
$key = key($controllerArr);
$controllerArr[$key] = ucfirst($controllerArr[$key]);
$adminPath = dirname(__DIR__) . DS . 'controller' . DS . implode(DS, $controllerArr) . '.php';
if (!is_file($adminPath))
{
$output->error("controller not found");
return;
if (!in_array('all-controller', $controller)) {
foreach ($controller as $index => $item) {
if (stripos($item, '_') !== false) {
$item = Loader::parseName($item, 1);
}
if (stripos($item, '/') !== false) {
$controllerArr = explode('/', $item);
end($controllerArr);
$key = key($controllerArr);
$controllerArr[$key] = ucfirst($controllerArr[$key]);
} else {
$controllerArr = [ucfirst($item)];
}
$adminPath = dirname(__DIR__) . DS . 'controller' . DS . implode(DS, $controllerArr) . '.php';
if (!is_file($adminPath)) {
$output->error("controller not found");
return;
}
$this->importRule($item);
}
$this->importRule($controller);
}
else
{
$this->model->destroy([]);
} else {
$authRuleList = AuthRule::select();
//生成权限规则备份文件
file_put_contents(RUNTIME_PATH . 'authrule.json', json_encode(collection($authRuleList)->toArray()));
$this->model->where('id', '>', 0)->delete();
$controllerDir = $adminPath . 'controller' . DS;
// 扫描新的节点信息并导入
$treelist = $this->import($this->scandir($controllerDir));
@ -105,16 +136,11 @@ class Menu extends Command
{
$result = [];
$cdir = scandir($dir);
foreach ($cdir as $value)
{
if (!in_array($value, array(".", "..")))
{
if (is_dir($dir . DS . $value))
{
foreach ($cdir as $value) {
if (!in_array($value, array(".", ".."))) {
if (is_dir($dir . DS . $value)) {
$result[$value] = $this->scandir($dir . DS . $value);
}
else
{
} else {
$result[] = $value;
}
}
@ -131,19 +157,14 @@ class Menu extends Command
public function import($dirarr, $parentdir = [])
{
$menuarr = [];
foreach ($dirarr as $k => $v)
{
if (is_array($v))
{
foreach ($dirarr as $k => $v) {
if (is_array($v)) {
//当前是文件夹
$nowparentdir = array_merge($parentdir, [$k]);
$this->import($v, $nowparentdir);
}
else
{
} else {
//只匹配PHP文件
if (!preg_match('/^(\w+)\.php$/', $v, $matchone))
{
if (!preg_match('/^(\w+)\.php$/', $v, $matchone)) {
continue;
}
//导入文件
@ -157,10 +178,16 @@ class Menu extends Command
protected function importRule($controller)
{
$controllerArr = explode('/', $controller);
end($controllerArr);
$key = key($controllerArr);
$controllerArr[$key] = ucfirst($controllerArr[$key]);
$controller = str_replace('\\', '/', $controller);
if (stripos($controller, '/') !== false) {
$controllerArr = explode('/', $controller);
end($controllerArr);
$key = key($controllerArr);
$controllerArr[$key] = ucfirst($controllerArr[$key]);
} else {
$key = 0;
$controllerArr = [ucfirst($controller)];
}
$classSuffix = Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : '';
$className = "\\app\\admin\\controller\\" . implode("\\", $controllerArr) . $classSuffix;
@ -176,37 +203,48 @@ class Menu extends Command
$tempClassFile = __DIR__ . DS . $uniqueName . ".php";
file_put_contents($tempClassFile, $classContent);
$className = "\\app\\admin\\command\\" . $uniqueName;
//删除临时文件
register_shutdown_function(function () use ($tempClassFile) {
if ($tempClassFile) {
//删除临时文件
@unlink($tempClassFile);
}
});
//反射机制调用类的注释和方法名
$reflector = new ReflectionClass($className);
if (isset($tempClassFile))
{
//删除临时文件
@unlink($tempClassFile);
}
//只匹配公共的方法
$methods = $reflector->getMethods(ReflectionMethod::IS_PUBLIC);
$classComment = $reflector->getDocComment();
//判断是否有启用软删除
$softDeleteMethods = ['destroy', 'restore', 'recyclebin'];
$withSofeDelete = false;
$modelRegexArr = ["/\\\$this\->model\s*=\s*model\(['|\"](\w+)['|\"]\);/", "/\\\$this\->model\s*=\s*new\s+([a-zA-Z\\\]+);/"];
$modelRegex = preg_match($modelRegexArr[0], $classContent) ? $modelRegexArr[0] : $modelRegexArr[1];
preg_match_all($modelRegex, $classContent, $matches);
if (isset($matches[1]) && isset($matches[1][0]) && $matches[1][0]) {
\think\Request::instance()->module('admin');
$model = model($matches[1][0]);
if (in_array('trashed', get_class_methods($model))) {
$withSofeDelete = true;
}
}
//忽略的类
if (stripos($classComment, "@internal") !== FALSE)
{
if (stripos($classComment, "@internal") !== false) {
return;
}
preg_match_all('#(@.*?)\n#s', $classComment, $annotations);
$controllerIcon = 'fa fa-circle-o';
$controllerRemark = '';
//判断注释中是否设置了icon值
if (isset($annotations[1]))
{
foreach ($annotations[1] as $tag)
{
if (stripos($tag, '@icon') !== FALSE)
{
if (isset($annotations[1])) {
foreach ($annotations[1] as $tag) {
if (stripos($tag, '@icon') !== false) {
$controllerIcon = substr($tag, stripos($tag, ' ') + 1);
}
if (stripos($tag, '@remark') !== FALSE)
{
if (stripos($tag, '@remark') !== false) {
$controllerRemark = substr($tag, stripos($tag, ' ') + 1);
}
}
@ -217,56 +255,73 @@ class Menu extends Command
//导入中文语言包
\think\Lang::load(dirname(__DIR__) . DS . 'lang/zh-cn.php');
//先入菜单的数据
//先入菜单的数据
$pid = 0;
$name = "/admin";
foreach (explode('/', $controller) as $k => $v)
{
$name .= '/' . strtolower($v);
$title = (!isset($controllerArr[$k + 1]) ? $controllerTitle : '');
$icon = (!isset($controllerArr[$k + 1]) ? $controllerIcon : 'fa fa-list');
$remark = (!isset($controllerArr[$k + 1]) ? $controllerRemark : '');
$title = $title ? $title : __(ucfirst($v) . ' manager');
$rulemodel = $this->model->get(['name' => $name]);
if (!$rulemodel)
{
$this->model
->data(['pid' => $pid, 'name' => $name, 'title' => $title, 'icon' => $icon, 'remark' => $remark, 'ismenu' => 1, 'status' => 'normal'])
->isUpdate(false)
->save();
$pid = $this->model->id;
foreach ($controllerArr as $k => $v) {
$key = $k + 1;
//驼峰转下划线
$controllerNameArr = array_slice($controllerArr, 0, $key);
foreach ($controllerNameArr as &$val) {
$val = strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $val), "_"));
}
else
{
unset($val);
$name = implode('/', $controllerNameArr);
$title = (!isset($controllerArr[$key]) ? $controllerTitle : '');
$icon = (!isset($controllerArr[$key]) ? $controllerIcon : 'fa fa-list');
$remark = (!isset($controllerArr[$key]) ? $controllerRemark : '');
$title = $title ? $title : $v;
$rulemodel = $this->model->get(['name' => $name]);
if (!$rulemodel) {
$this->model
->data(['pid' => $pid, 'name' => $name, 'title' => $title, 'icon' => $icon, 'remark' => $remark, 'ismenu' => 1, 'status' => 'normal'])
->isUpdate(false)
->save();
$pid = $this->model->id;
} else {
$pid = $rulemodel->id;
}
}
$ruleArr = [];
foreach ($methods as $m => $n)
{
foreach ($methods as $m => $n) {
//过滤特殊的类
if (substr($n->name, 0, 2) == '__' || $n->name == '_initialize')
{
if (substr($n->name, 0, 2) == '__' || $n->name == '_initialize') {
continue;
}
//未启用软删除时过滤相关方法
if (!$withSofeDelete && in_array($n->name, $softDeleteMethods)) {
continue;
}
//只匹配符合的方法
if (!preg_match('/^(\w+)' . Config::get('action_suffix') . '/', $n->name, $matchtwo))
{
if (!preg_match('/^(\w+)' . Config::get('action_suffix') . '/', $n->name, $matchtwo)) {
unset($methods[$m]);
continue;
}
$comment = $reflector->getMethod($n->name)->getDocComment();
//忽略的方法
if (stripos($comment, "@internal") !== FALSE)
{
if (stripos($comment, "@internal") !== false) {
continue;
}
//过滤掉其它字符
$comment = preg_replace(array('/^\/\*\*(.*)[\n\r\t]/u', '/[\s]+\*\//u', '/\*\s@(.*)/u', '/[\s|\*]+/u'), '', $comment);
$ruleArr[] = array('pid' => $pid, 'name' => $name . "/" . strtolower($n->name), 'icon' => 'fa fa-circle-o', 'title' => $comment ? $comment : $n->name, 'ismenu' => 0, 'status' => 'normal');
$title = $comment ? $comment : ucfirst($n->name);
//获取主键作为AuthRule更新依据
$id = $this->getAuthRulePK($name . "/" . strtolower($n->name));
$ruleArr[] = array('id' => $id, 'pid' => $pid, 'name' => $name . "/" . strtolower($n->name), 'icon' => 'fa fa-circle-o', 'title' => $title, 'ismenu' => 0, 'status' => 'normal');
}
$this->model->saveAll($ruleArr);
$this->model->isUpdate(false)->saveAll($ruleArr);
}
//获取主键
protected function getAuthRulePK($name)
{
if (!empty($name)) {
$id = $this->model
->where('name', $name)
->value('id');
return $id ? $id : null;
}
}
}

View File

@ -27,20 +27,20 @@ class Min extends Command
->setName('min')
->addOption('module', 'm', Option::VALUE_REQUIRED, 'module name(frontend or backend),use \'all\' when build all modules', null)
->addOption('resource', 'r', Option::VALUE_REQUIRED, 'resource name(js or css),use \'all\' when build all resources', null)
->addOption('optimize', 'o', Option::VALUE_OPTIONAL, 'optimize type(uglify|closure|none)', 'none')
->setDescription('Compress js and css file');
}
protected function execute(Input $input, Output $output)
{
$module = $input->getOption('module') ? : '';
$resource = $input->getOption('resource') ? : '';
$module = $input->getOption('module') ?: '';
$resource = $input->getOption('resource') ?: '';
$optimize = $input->getOption('optimize') ?: 'none';
if (!$module || !in_array($module, ['frontend', 'backend', 'all']))
{
if (!$module || !in_array($module, ['frontend', 'backend', 'all'])) {
throw new Exception('Please input correct module name');
}
if (!$resource || !in_array($resource, ['js', 'css', 'all']))
{
if (!$resource || !in_array($resource, ['js', 'css', 'all'])) {
throw new Exception('Please input correct resource name');
}
@ -51,62 +51,60 @@ class Min extends Command
$publicPath = ROOT_PATH . 'public' . DS;
$tempFile = $minPath . 'temp.js';
// Winsows下请手动配置配置该值
$nodeExec = "";
$nodeExec = '';
if (!$nodeExec)
{
if (IS_WIN)
{
throw new Exception("node environment not found!please check http://doc.fastadmin.net/faq.html !");
}
try
{
$nodeExec = exec("which node");
if (!$nodeExec)
{
throw new Exception("node environment not found!please install node first!");
if (!$nodeExec) {
if (IS_WIN) {
// Winsows下请手动配置配置该值,一般将该值配置为 '"C:\Program Files\nodejs\node.exe"'除非你的Node安装路径有变更
$nodeExec = 'C:\Program Files\nodejs\node.exe';
if (file_exists($nodeExec)) {
$nodeExec = '"' . $nodeExec . '"';
} else {
// 如果 '"C:\Program Files\nodejs\node.exe"' 不存在可能是node安装路径有变更
// 但安装node会自动配置环境变量直接执行 '"node.exe"' 提高第一次使用压缩打包的成功率
$nodeExec = '"node.exe"';
}
} else {
try {
$nodeExec = exec("which node");
if (!$nodeExec) {
throw new Exception("node environment not found!please install node first!");
}
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
}
catch (Exception $e)
{
throw new Exception($e->getMessage());
}
}
foreach ($moduleArr as $mod)
{
foreach ($resourceArr as $res)
{
foreach ($moduleArr as $mod) {
foreach ($resourceArr as $res) {
$data = [
'publicPath' => $publicPath,
'jsBaseName' => str_replace('{module}', $mod, $this->options['jsBaseName']),
'jsBaseUrl' => $this->options['jsBaseUrl'],
'cssBaseName' => str_replace('{module}', $mod, $this->options['cssBaseName']),
'cssBaseUrl' => $this->options['cssBaseUrl'],
'jsBasePath' => ROOT_PATH . $this->options['jsBaseUrl'],
'cssBasePath' => ROOT_PATH . $this->options['cssBaseUrl'],
'jsBasePath' => str_replace(DS, '/', ROOT_PATH . $this->options['jsBaseUrl']),
'cssBasePath' => str_replace(DS, '/', ROOT_PATH . $this->options['cssBaseUrl']),
'optimize' => $optimize,
'ds' => DS,
];
//源文件
$from = $data["{$res}BasePath"] . $data["{$res}BaseName"] . '.' . $res;
if (!is_file($from))
{
if (!is_file($from)) {
$output->error("{$res} source file not found!file:{$from}");
continue;
}
if ($res == "js")
{
if ($res == "js") {
$content = file_get_contents($from);
preg_match("/require\.config\(\{[\n]+(.*?)\n\}\);/is", $content, $matches);
if (!isset($matches[1]))
{
preg_match("/require\.config\(\{[\r\n]?[\n]?+(.*?)[\r\n]?[\n]?}\);/is", $content, $matches);
if (!isset($matches[1])) {
$output->error("js config not found!");
continue;
}
$config = preg_replace("/(urlArgs|baseUrl):(.*)\n/", '', $matches[1]);
$config = preg_replace("/('tableexport'):(.*)\,\n/", "'tableexport': 'empty:',\n", $config);
$data['config'] = $config;
}
// 生成压缩文件
@ -115,11 +113,17 @@ class Min extends Command
$output->info("Compress " . $data["{$res}BaseName"] . ".{$res}");
// 执行压缩
echo exec("{$nodeExec} {$minPath}r.js -o {$tempFile} >> {$minPath}node.log");
$command = "{$nodeExec} \"{$minPath}r.js\" -o \"{$tempFile}\" >> \"{$minPath}node.log\"";
if ($output->isDebug()) {
$output->warning($command);
}
echo exec($command);
}
}
@unlink($tempFile);
if (!$output->isDebug()) {
@unlink($tempFile);
}
$output->info("Build Successed!");
}
@ -134,16 +138,14 @@ class Min extends Command
protected function writeToFile($name, $data, $pathname)
{
$search = $replace = [];
foreach ($data as $k => $v)
{
foreach ($data as $k => $v) {
$search[] = "{%{$k}%}";
$replace[] = $v;
}
$stub = file_get_contents($this->getStub($name));
$content = str_replace($search, $replace, $stub);
if (!is_dir(dirname($pathname)))
{
if (!is_dir(dirname($pathname))) {
mkdir(strtolower(dirname($pathname)), 0755, true);
}
return file_put_contents($pathname, $content);
@ -156,7 +158,6 @@ class Min extends Command
*/
protected function getStub($name)
{
return __DIR__ . '/Min/stubs/' . $name . '.stub';
return __DIR__ . DS . 'Min' . DS . 'stubs' . DS . $name . '.stub';
}
}

View File

@ -1,5 +1,6 @@
({
cssIn: "{%cssBasePath%}{%cssBaseName%}.css",
out: "{%cssBasePath%}{%cssBaseName%}.min.css",
optimizeCss: "default"
optimizeCss: "default",
optimize: "{%optimize%}"
})

View File

@ -2,7 +2,8 @@
{%config%}
,
optimizeCss: "standard",
optimize: "none", //可使用uglify|closure|none
optimize: "{%optimize%}", //可使用uglify|closure|none
preserveLicenseComments: false,
removeCombined: false,
baseUrl: "{%jsBasePath%}", //JS文件所在的基础目录
name: "{%jsBaseName%}", //来源文件,不包含后缀

286
application/admin/common.php 100644 → 100755
View File

@ -4,134 +4,194 @@ use app\common\model\Category;
use fast\Form;
use fast\Tree;
use think\Db;
use think\Loader;
/**
* 生成下拉列表
* @param string $name
* @param mixed $options
* @param mixed $selected
* @param mixed $attr
* @return string
*/
function build_select($name, $options, $selected = [], $attr = [])
{
$options = is_array($options) ? $options : explode(',', $options);
$selected = is_array($selected) ? $selected : explode(',', $selected);
return Form::select($name, $options, $selected, $attr);
}
if (!function_exists('build_select')) {
/**
* 生成单选按钮组
* @param string $name
* @param array $list
* @param mixed $selected
* @return string
*/
function build_radios($name, $list = [], $selected = null)
{
$html = [];
$selected = is_null($selected) ? key($list) : $selected;
$selected = is_array($selected) ? $selected : explode(',', $selected);
foreach ($list as $k => $v)
/**
* 生成下拉列表
* @param string $name
* @param mixed $options
* @param mixed $selected
* @param mixed $attr
* @return string
*/
function build_select($name, $options, $selected = [], $attr = [])
{
$html[] = sprintf(Form::label("{$name}-{$k}", "%s {$v}"), Form::radio($name, $k, in_array($k, $selected), ['id' => "{$name}-{$k}"]));
$options = is_array($options) ? $options : explode(',', $options ?? '');
$selected = is_array($selected) ? $selected : explode(',', $selected ?? '');
return Form::select($name, $options, $selected, $attr);
}
return implode(' ', $html);
}
/**
* 生成复选按钮组
* @param string $name
* @param array $list
* @param mixed $selected
* @return string
*/
function build_checkboxs($name, $list = [], $selected = null)
{
$html = [];
$selected = is_null($selected) ? [] : $selected;
$selected = is_array($selected) ? $selected : explode(',', $selected);
foreach ($list as $k => $v)
{
$html[] = sprintf(Form::label("{$name}-{$k}", "%s {$v}"), Form::checkbox($name, $k, in_array($k, $selected), ['id' => "{$name}-{$k}"]));
}
return implode(' ', $html);
}
if (!function_exists('build_radios')) {
/**
* 生成分类下拉列表框
* @param string $name
* @param string $type
* @param mixed $selected
* @param array $attr
* @return string
*/
function build_category_select($name, $type, $selected = null, $attr = [], $header = [])
{
$tree = Tree::instance();
$tree->init(Category::getCategoryArray($type), 'pid');
$categorylist = $tree->getTreeList($tree->getTreeArray(0), 'name');
$categorydata = $header ? $header : [];
foreach ($categorylist as $k => $v)
/**
* 生成单选按钮组
* @param string $name
* @param array $list
* @param mixed $selected
* @return string
*/
function build_radios($name, $list = [], $selected = null)
{
$categorydata[$v['id']] = $v['name'];
}
$attr = array_merge(['id' => "c-{$name}", 'class' => 'form-control selectpicker'], $attr);
return build_select($name, $categorydata, $selected, $attr);
}
/**
* 生成表格操作按钮栏
* @param array $btns 按钮组
* @param array $attr 按钮属性值
* @return string
*/
function build_toolbar($btns = NULL, $attr = [])
{
$btns = $btns ? $btns : ['refresh', 'add', 'edit', 'delete'];
$btns = is_array($btns) ? $btns : explode(',', $btns);
$btnAttr = [
'refresh' => ['javascript:;', 'btn btn-primary btn-refresh', 'fa fa-refresh', ''],
'add' => ['javascript:;', 'btn btn-success btn-add', 'fa fa-plus', __('Add')],
'edit' => ['javascript:;', 'btn btn-success btn-edit btn-disabled disabled', 'fa fa-pencil', __('Edit')],
'delete' => ['javascript:;', 'btn btn-danger btn-del btn-disabled disabled', 'fa fa-trash', __('Delete')],
];
$btnAttr = array_merge($btnAttr, $attr);
$html = [];
foreach ($btns as $k => $v)
{
if (!isset($btnAttr[$v]))
{
continue;
$html = [];
$selected = is_null($selected) ? key($list) : $selected;
$selected = is_array($selected) ? $selected : explode(',', $selected);
foreach ($list as $k => $v) {
$html[] = sprintf(Form::label("{$name}-{$k}", "%s " . str_replace('%', '%%', $v)), Form::radio($name, $k, in_array($k, $selected), ['id' => "{$name}-{$k}"]));
}
list($href, $class, $icon, $text) = $btnAttr[$v];
$html[] = '<a href="' . $href . '" class="' . $class . '" ><i class="' . $icon . '"></i> ' . $text . '</a>';
return '<div class="radio">' . implode(' ', $html) . '</div>';
}
return implode(' ', $html);
}
/**
* 生成页面Heading
*
* @param string $title
* @param string $content
* @return string
*/
function build_heading($title = NULL, $content = NULL)
{
if (is_null($title) && is_null($content))
if (!function_exists('build_checkboxs')) {
/**
* 生成复选按钮组
* @param string $name
* @param array $list
* @param mixed $selected
* @return string
*/
function build_checkboxs($name, $list = [], $selected = null)
{
$path = request()->pathinfo();
$path = $path[0] == '/' ? $path : '/' . $path;
$html = [];
$selected = is_null($selected) ? [] : $selected;
$selected = is_array($selected) ? $selected : explode(',', $selected);
foreach ($list as $k => $v) {
$html[] = sprintf(Form::label("{$name}-{$k}", "%s " . str_replace('%', '%%', $v)), Form::checkbox($name, $k, in_array($k, $selected), ['id' => "{$name}-{$k}"]));
}
return '<div class="checkbox">' . implode(' ', $html) . '</div>';
}
}
if (!function_exists('build_category_select')) {
/**
* 生成分类下拉列表框
* @param string $name
* @param string $type
* @param mixed $selected
* @param array $attr
* @param array $header
* @return string
*/
function build_category_select($name, $type, $selected = null, $attr = [], $header = [])
{
$tree = Tree::instance();
$tree->init(Category::getCategoryArray($type), 'pid');
$categorylist = $tree->getTreeList($tree->getTreeArray(0), 'name');
$categorydata = $header ? $header : [];
foreach ($categorylist as $k => $v) {
$categorydata[$v['id']] = $v['name'];
}
$attr = array_merge(['id' => "c-{$name}", 'class' => 'form-control selectpicker'], $attr);
return build_select($name, $categorydata, $selected, $attr);
}
}
if (!function_exists('build_toolbar')) {
/**
* 生成表格操作按钮栏
* @param array $btns 按钮组
* @param array $attr 按钮属性值
* @return string
*/
function build_toolbar($btns = null, $attr = [])
{
$auth = \app\admin\library\Auth::instance();
$controller = str_replace('.', '/', Loader::parseName(request()->controller()));
$btns = $btns ? $btns : ['refresh', 'add', 'edit', 'del', 'import'];
$btns = is_array($btns) ? $btns : explode(',', $btns);
$index = array_search('delete', $btns);
if ($index !== false) {
$btns[$index] = 'del';
}
$btnAttr = [
'refresh' => ['javascript:;', 'btn btn-primary btn-refresh', 'fa fa-refresh', '', __('Refresh')],
'add' => ['javascript:;', 'btn btn-success btn-add', 'fa fa-plus', __('Add'), __('Add')],
'edit' => ['javascript:;', 'btn btn-success btn-edit btn-disabled disabled', 'fa fa-pencil', __('Edit'), __('Edit')],
'del' => ['javascript:;', 'btn btn-danger btn-del btn-disabled disabled', 'fa fa-trash', __('Delete'), __('Delete')],
'import' => ['javascript:;', 'btn btn-info btn-import', 'fa fa-upload', __('Import'), __('Import')],
];
$btnAttr = array_merge($btnAttr, $attr);
$html = [];
foreach ($btns as $k => $v) {
//如果未定义或没有权限
if (!isset($btnAttr[$v]) || ($v !== 'refresh' && !$auth->check("{$controller}/{$v}", $auth->id))) {
continue;
}
list($href, $class, $icon, $text, $title) = $btnAttr[$v];
//$extend = $v == 'import' ? 'id="btn-import-file" data-url="ajax/upload" data-mimetype="csv,xls,xlsx" data-multiple="false"' : '';
//$html[] = '<a href="' . $href . '" class="' . $class . '" title="' . $title . '" ' . $extend . '><i class="' . $icon . '"></i> ' . $text . '</a>';
if ($v == 'import') {
$template = str_replace('/', '_', $controller);
$download = '';
if (file_exists("./template/{$template}.xlsx")) {
$download .= "<li><a href=\"/template/{$template}.xlsx\" target=\"_blank\">XLSX模版</a></li>";
}
if (file_exists("./template/{$template}.xls")) {
$download .= "<li><a href=\"/template/{$template}.xls\" target=\"_blank\">XLS模版</a></li>";
}
if (file_exists("./template/{$template}.csv")) {
$download .= empty($download) ? '' : "<li class=\"divider\"></li>";
$download .= "<li><a href=\"/template/{$template}.csv\" target=\"_blank\">CSV模版</a></li>";
}
$download .= empty($download) ? '' : "\n ";
if (!empty($download)) {
$html[] = <<<EOT
<div class="btn-group">
<button type="button" href="{$href}" class="btn btn-info btn-import" title="{$title}" id="btn-import-file" data-url="ajax/upload" data-mimetype="csv,xls,xlsx" data-multiple="false"><i class="{$icon}"></i> {$text}</button>
<button type="button" class="btn btn-info dropdown-toggle" data-toggle="dropdown" title="下载批量导入模版">
<span class="caret"></span>
<span class="sr-only">Toggle Dropdown</span>
</button>
<ul class="dropdown-menu" role="menu">{$download}</ul>
</div>
EOT;
} else {
$html[] = '<a href="' . $href . '" class="' . $class . '" title="' . $title . '" id="btn-import-file" data-url="ajax/upload" data-mimetype="csv,xls,xlsx" data-multiple="false"><i class="' . $icon . '"></i> ' . $text . '</a>';
}
} else {
$html[] = '<a href="' . $href . '" class="' . $class . '" title="' . $title . '"><i class="' . $icon . '"></i> ' . $text . '</a>';
}
}
return implode(' ', $html);
}
}
if (!function_exists('build_heading')) {
/**
* 生成页面Heading
*
* @param string $path 指定的path
* @return string
*/
function build_heading($path = null, $container = true)
{
$title = $content = '';
if (is_null($path)) {
$action = request()->action();
$controller = str_replace('.', '/', Loader::parseName(request()->controller()));
$path = strtolower($controller . ($action && $action != 'index' ? '/' . $action : ''));
}
// 根据当前的URI自动匹配父节点的标题和备注
$data = Db::name('auth_rule')->where('name', $path)->field('title,remark')->find();
if ($data)
{
$title = $data['title'];
$content = $data['remark'];
if ($data) {
$title = __($data['title']);
$content = __($data['remark']);
}
if (!$content) {
return '';
}
$result = '<div class="panel-lead"><em>' . $title . '</em>' . $content . '</div>';
if ($container) {
$result = '<div class="panel-heading">' . $result . '</div>';
}
return $result;
}
if (!$content)
return '';
return '<div class="panel-heading"><div class="panel-lead"><em>' . $title . '</em>' . $content . '</div></div>';
}

View File

@ -0,0 +1,462 @@
<?php
namespace app\admin\controller;
use app\common\controller\Backend;
use fast\Http;
use think\addons\AddonException;
use think\addons\Service;
use think\Cache;
use think\Config;
use think\Db;
use think\Exception;
/**
* 插件管理
*
* @icon fa fa-cube
* @remark 可在线安装、卸载、禁用、启用、配置、升级插件,插件升级前请做好备份。
*/
class Addon extends Backend
{
protected $model = null;
protected $noNeedRight = ['get_table_list'];
public function _initialize()
{
parent::_initialize();
if (!$this->auth->isSuperAdmin() && in_array($this->request->action(), ['install', 'uninstall', 'local', 'upgrade', 'authorization', 'testdata'])) {
$this->error(__('Access is allowed only to the super management group'));
}
}
/**
* 插件列表
*/
public function index()
{
$addons = get_addon_list();
foreach ($addons as $k => &$v) {
$config = get_addon_config($v['name']);
$v['config'] = $config ? 1 : 0;
$v['url'] = str_replace($this->request->server('SCRIPT_NAME'), '', $v['url']);
}
$this->assignconfig(['addons' => $addons, 'api_url' => config('fastadmin.api_url'), 'faversion' => config('fastadmin.version'), 'domain' => request()->host(true)]);
return $this->view->fetch();
}
/**
* 配置
*/
public function config($name = null)
{
$name = $name ? $name : $this->request->get("name");
if (!$name) {
$this->error(__('Parameter %s can not be empty', 'name'));
}
if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
$this->error(__('Addon name incorrect'));
}
$info = get_addon_info($name);
$config = get_addon_fullconfig($name);
if (!$info) {
$this->error(__('Addon not exists'));
}
if ($this->request->isPost()) {
$params = $this->request->post("row/a", [], 'trim');
if ($params) {
foreach ($config as $k => &$v) {
if (isset($params[$v['name']])) {
if ($v['type'] == 'array') {
$params[$v['name']] = is_array($params[$v['name']]) ? $params[$v['name']] : (array)json_decode($params[$v['name']], true);
$value = $params[$v['name']];
} else {
$value = is_array($params[$v['name']]) ? implode(',', $params[$v['name']]) : $params[$v['name']];
}
$v['value'] = $value;
}
}
try {
$addon = get_addon_instance($name);
//插件自定义配置实现逻辑
if (method_exists($addon, 'config')) {
$addon->config($name, $config);
} else {
//更新配置文件
set_addon_fullconfig($name, $config);
Service::refresh();
}
} catch (Exception $e) {
$this->error(__($e->getMessage()));
}
$this->success();
}
$this->error(__('Parameter %s can not be empty', ''));
}
$tips = [];
$groupList = [];
$ungroupList = [];
foreach ($config as $index => &$item) {
//如果有设置分组
if (isset($item['group']) && $item['group']) {
if (!in_array($item['group'], $groupList)) {
$groupList["custom" . (count($groupList) + 1)] = $item['group'];
}
} elseif ($item['name'] != '__tips__') {
$ungroupList[] = $item['name'];
}
if ($item['name'] == '__tips__') {
$tips = $item;
unset($config[$index]);
}
}
if ($ungroupList) {
$groupList['other'] = '其它';
}
$this->view->assign("groupList", $groupList);
$this->view->assign("addon", ['info' => $info, 'config' => $config, 'tips' => $tips]);
$configFile = ADDON_PATH . $name . DS . 'config.html';
$viewFile = is_file($configFile) ? $configFile : '';
return $this->view->fetch($viewFile);
}
/**
* 安装
*/
public function install()
{
$name = $this->request->post("name");
$force = (int)$this->request->post("force");
if (!$name) {
$this->error(__('Parameter %s can not be empty', 'name'));
}
if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
$this->error(__('Addon name incorrect'));
}
$info = [];
try {
$uid = $this->request->post("uid");
$token = $this->request->post("token");
$version = $this->request->post("version");
$faversion = $this->request->post("faversion");
$extend = [
'uid' => $uid,
'token' => $token,
'version' => $version,
'faversion' => $faversion
];
$info = Service::install($name, $force, $extend);
} catch (AddonException $e) {
$this->result($e->getData(), $e->getCode(), __($e->getMessage()));
} catch (Exception $e) {
$this->error(__($e->getMessage()), $e->getCode());
}
$this->success(__('Install successful'), '', ['addon' => $info]);
}
/**
* 卸载
*/
public function uninstall()
{
$name = $this->request->post("name");
$force = (int)$this->request->post("force");
$droptables = (int)$this->request->post("droptables");
if (!$name) {
$this->error(__('Parameter %s can not be empty', 'name'));
}
if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
$this->error(__('Addon name incorrect'));
}
//只有开启调试且为超级管理员才允许删除相关数据库
$tables = [];
if ($droptables && Config::get("app_debug") && $this->auth->isSuperAdmin()) {
$tables = get_addon_tables($name);
}
try {
Service::uninstall($name, $force);
if ($tables) {
$prefix = Config::get('database.prefix');
//删除插件关联表
foreach ($tables as $index => $table) {
//忽略非插件标识的表名
if (!preg_match("/^{$prefix}{$name}/", $table)) {
continue;
}
Db::execute("DROP TABLE IF EXISTS `{$table}`");
}
}
} catch (AddonException $e) {
$this->result($e->getData(), $e->getCode(), __($e->getMessage()));
} catch (Exception $e) {
$this->error(__($e->getMessage()));
}
$this->success(__('Uninstall successful'));
}
/**
* 禁用启用
*/
public function state()
{
$name = $this->request->post("name");
$action = $this->request->post("action");
$force = (int)$this->request->post("force");
if (!$name) {
$this->error(__('Parameter %s can not be empty', 'name'));
}
if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
$this->error(__('Addon name incorrect'));
}
try {
$action = $action == 'enable' ? $action : 'disable';
//调用启用、禁用的方法
Service::$action($name, $force);
Cache::rm('__menu__');
} catch (AddonException $e) {
$this->result($e->getData(), $e->getCode(), __($e->getMessage()));
} catch (Exception $e) {
$this->error(__($e->getMessage()));
}
$this->success(__('Operate successful'));
}
/**
* 本地上传
*/
public function local()
{
Config::set('default_return_type', 'json');
$info = [];
$file = $this->request->file('file');
try {
$uid = $this->request->post("uid");
$token = $this->request->post("token");
$faversion = $this->request->post("faversion");
$force = $this->request->post("force");
if (!$uid || !$token) {
throw new Exception(__('Please login and try to install'));
}
$extend = [
'uid' => $uid,
'token' => $token,
'faversion' => $faversion
];
$info = Service::local($file, $extend, $force);
} catch (AddonException $e) {
$this->result($e->getData(), $e->getCode(), __($e->getMessage()));
} catch (Exception $e) {
$this->error(__($e->getMessage()));
}
$this->success(__('Offline installed tips'), '', ['addon' => $info]);
}
/**
* 更新插件
*/
public function upgrade()
{
$name = $this->request->post("name");
$addonTmpDir = RUNTIME_PATH . 'addons' . DS;
if (!$name) {
$this->error(__('Parameter %s can not be empty', 'name'));
}
if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
$this->error(__('Addon name incorrect'));
}
if (!is_dir($addonTmpDir)) {
@mkdir($addonTmpDir, 0755, true);
}
$info = [];
try {
$info = get_addon_info($name);
$uid = $this->request->post("uid");
$token = $this->request->post("token");
$version = $this->request->post("version");
$faversion = $this->request->post("faversion");
$extend = [
'uid' => $uid,
'token' => $token,
'version' => $version,
'oldversion' => $info['version'] ?? '',
'faversion' => $faversion
];
//调用更新的方法
$info = Service::upgrade($name, $extend);
Cache::rm('__menu__');
} catch (AddonException $e) {
$this->result($e->getData(), $e->getCode(), __($e->getMessage()));
} catch (Exception $e) {
$this->error(__($e->getMessage()));
}
$this->success(__('Operate successful'), '', ['addon' => $info]);
}
/**
* 测试数据
*/
public function testdata()
{
$name = $this->request->post("name");
if (!$name) {
$this->error(__('Parameter %s can not be empty', 'name'));
}
if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
$this->error(__('Addon name incorrect'));
}
try {
Service::importsql($name, 'testdata.sql');
} catch (AddonException $e) {
$this->result($e->getData(), $e->getCode(), __($e->getMessage()));
} catch (Exception $e) {
$this->error(__($e->getMessage()), $e->getCode());
}
$this->success(__('Import successful'), '');
}
/**
* 已装插件
*/
public function downloaded()
{
$offset = (int)$this->request->get("offset");
$limit = (int)$this->request->get("limit");
$filter = $this->request->get("filter", '');
$search = $this->request->get("search", '', 'strip_tags,htmlspecialchars');
$onlineaddons = $this->getAddonList();
$filter = (array)json_decode($filter, true);
$addons = get_addon_list();
$list = [];
foreach ($addons as $k => $v) {
if ($search && stripos($v['name'], $search) === false && stripos($v['title'], $search) === false && stripos($v['intro'], $search) === false) {
continue;
}
if (isset($onlineaddons[$v['name']])) {
$v = array_merge($v, $onlineaddons[$v['name']]);
$v['price'] = '-';
} else {
$v['category_id'] = 0;
$v['flag'] = '';
$v['banner'] = '';
$v['image'] = '';
$v['demourl'] = '';
$v['price'] = __('None');
$v['screenshots'] = [];
$v['releaselist'] = [];
$v['url'] = addon_url($v['name']);
$v['url'] = str_replace($this->request->server('SCRIPT_NAME'), '', $v['url']);
}
$v['createtime'] = filemtime(ADDON_PATH . $v['name']);
if ($filter && isset($filter['category_id']) && is_numeric($filter['category_id']) && $filter['category_id'] != $v['category_id']) {
continue;
}
$list[] = $v;
}
$total = count($list);
if ($limit) {
$list = array_slice($list, $offset, $limit);
}
$result = array("total" => $total, "rows" => $list);
$callback = $this->request->get('callback') ? "jsonp" : "json";
return $callback($result);
}
/**
* 检测
*/
public function isbuy()
{
$name = $this->request->post("name");
$uid = $this->request->post("uid");
$token = $this->request->post("token");
$version = $this->request->post("version");
$faversion = $this->request->post("faversion");
$extend = [
'uid' => $uid,
'token' => $token,
'version' => $version,
'faversion' => $faversion
];
try {
$result = Service::isBuy($name, $extend);
} catch (Exception $e) {
$this->error(__($e->getMessage()));
}
return json($result);
}
/**
* 刷新授权
*/
public function authorization()
{
$params = [
'uid' => $this->request->post('uid'),
'token' => $this->request->post('token'),
'faversion' => $this->request->post('faversion'),
];
try {
Service::authorization($params);
} catch (Exception $e) {
$this->error(__($e->getMessage()));
}
$this->success(__('Operate successful'));
}
/**
* 获取插件相关表
*/
public function get_table_list()
{
$name = $this->request->post("name");
if (!preg_match("/^[a-zA-Z0-9]+$/", $name)) {
$this->error(__('Addon name incorrect'));
}
$tables = get_addon_tables($name);
$prefix = Config::get('database.prefix');
foreach ($tables as $index => $table) {
//忽略非插件标识的表名
if (!preg_match("/^{$prefix}{$name}/", $table)) {
unset($tables[$index]);
}
}
$tables = array_values($tables);
$this->success('', null, ['tables' => $tables]);
}
protected function getAddonList()
{
$onlineaddons = Cache::get("onlineaddons");
if (!is_array($onlineaddons) && config('fastadmin.api_url')) {
$onlineaddons = [];
$params = [
'uid' => $this->request->post('uid'),
'token' => $this->request->post('token'),
'version' => config('fastadmin.version'),
'faversion' => config('fastadmin.version'),
];
$json = [];
try {
$json = Service::addons($params);
} catch (\Exception $e) {
}
$rows = $json['rows'] ?? [];
foreach ($rows as $index => $row) {
if (!isset($row['name'])) {
continue;
}
$onlineaddons[$row['name']] = $row;
}
Cache::set("onlineaddons", $onlineaddons, 600);
}
return $onlineaddons;
}
}

View File

@ -1,308 +1,327 @@
<?php
namespace app\admin\controller;
use app\common\controller\Backend;
use fast\Random;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use think\Cache;
use think\Config;
use think\Db;
use think\Lang;
/**
* Ajax异步请求接口
* @internal
*/
class Ajax extends Backend
{
protected $noNeedLogin = ['lang'];
protected $noNeedRight = ['*'];
protected $layout = '';
public function _initialize()
{
parent::_initialize();
//设置过滤方法
$this->request->filter(['strip_tags', 'htmlspecialchars']);
}
/**
* 加载语言包
*/
public function lang()
{
header('Content-Type: application/javascript');
$callback = $this->request->get('callback');
$controllername = input("controllername");
$this->loadlang($controllername);
//强制输出JSON Object
$result = 'define(' . json_encode(Lang::get(), JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE) . ');';
return $result;
}
/**
* 上传文件
*/
public function upload()
{
$this->code = -1;
$file = $this->request->file('file');
if (empty($file))
{
$this->msg = "未上传文件或超出服务器上传限制";
return;
}
//判断是否已经存在附件
$sha1 = $file->hash();
$uploaded = model("attachment")->where('sha1', $sha1)->find();
if ($uploaded)
{
$this->code = 1;
$this->data = [
'url' => $uploaded['url']
];
return;
}
$upload = Config::get('upload');
preg_match('/(\d+)(\w+)/', $upload['maxsize'], $matches);
$type = strtolower($matches[2]);
$typeDict = ['b' => 0, 'k' => 1, 'kb' => 1, 'm' => 2, 'mb' => 2, 'gb' => 3, 'g' => 3];
$size = (int) $upload['maxsize'] * pow(1024, isset($typeDict[$type]) ? $typeDict[$type] : 0);
$fileInfo = $file->getInfo();
$suffix = strtolower(pathinfo($fileInfo['name'], PATHINFO_EXTENSION));
$suffix = $suffix ? $suffix : 'file';
$replaceArr = [
'{year}' => date("Y"),
'{mon}' => date("m"),
'{day}' => date("d"),
'{hour}' => date("H"),
'{min}' => date("i"),
'{sec}' => date("s"),
'{random}' => Random::alnum(16),
'{random32}' => Random::alnum(32),
'{filename}' => $suffix ? substr($fileInfo['name'], 0, strripos($fileInfo['name'], '.')) : $fileInfo['name'],
'{suffix}' => $suffix,
'{.suffix}' => $suffix ? '.' . $suffix : '',
'{filemd5}' => md5_file($fileInfo['tmp_name']),
];
$savekey = $upload['savekey'];
$savekey = str_replace(array_keys($replaceArr), array_values($replaceArr), $savekey);
$uploadDir = substr($savekey, 0, strripos($savekey, '/') + 1);
$fileName = substr($savekey, strripos($savekey, '/') + 1);
//
$splInfo = $file->validate(['size' => $size])->move(ROOT_PATH . '/public' . $uploadDir, $fileName);
if ($splInfo)
{
$imagewidth = $imageheight = 0;
if (in_array($suffix, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf']))
{
$imgInfo = getimagesize($splInfo->getPathname());
$imagewidth = isset($imgInfo[0]) ? $imgInfo[0] : $imagewidth;
$imageheight = isset($imgInfo[1]) ? $imgInfo[1] : $imageheight;
}
$params = array(
'filesize' => $fileInfo['size'],
'imagewidth' => $imagewidth,
'imageheight' => $imageheight,
'imagetype' => $suffix,
'imageframes' => 0,
'mimetype' => $fileInfo['type'],
'url' => $uploadDir . $splInfo->getSaveName(),
'uploadtime' => time(),
'storage' => 'local',
'sha1' => $sha1,
);
model("attachment")->create(array_filter($params));
$this->code = 1;
$this->data = [
'url' => $uploadDir . $splInfo->getSaveName()
];
}
else
{
// 上传失败获取错误信息
$this->data = $file->getError();
}
}
/**
* 通用排序
*/
public function weigh()
{
//排序的数组
$ids = $this->request->post("ids");
//拖动的记录ID
$changeid = $this->request->post("changeid");
//操作字段
$field = $this->request->post("field");
//操作的数据表
$table = $this->request->post("table");
//排序的方式
$orderway = $this->request->post("orderway", 'strtolower');
$orderway = $orderway == 'asc' ? 'ASC' : 'DESC';
$sour = $weighdata = [];
$ids = explode(',', $ids);
$prikey = 'id';
$pid = $this->request->post("pid");
//限制更新的字段
$field = in_array($field, ['weigh']) ? $field : 'weigh';
// 如果设定了pid的值,此时只匹配满足条件的ID,其它忽略
if ($pid !== '')
{
$hasids = [];
$list = Db::name($table)->where($prikey, 'in', $ids)->where('pid', 'in', $pid)->field('id,pid')->select();
foreach ($list as $k => $v)
{
$hasids[] = $v['id'];
}
$ids = array_values(array_intersect($ids, $hasids));
}
//直接修复排序
$one = Db::name($table)->field("{$field},COUNT(*) AS nums")->group($field)->having('nums > 1')->find();
if ($one)
{
$list = Db::name($table)->field("$prikey,$field")->order($field, $orderway)->select();
foreach ($list as $k => $v)
{
Db::name($table)->where($prikey, $v[$prikey])->update([$field => $k + 1]);
}
$this->code = 1;
}
else
{
$list = Db::name($table)->field("$prikey,$field")->where($prikey, 'in', $ids)->order($field, $orderway)->select();
foreach ($list as $k => $v)
{
$sour[] = $v[$prikey];
$weighdata[$v[$prikey]] = $v[$field];
}
$position = array_search($changeid, $ids);
$desc_id = $sour[$position]; //移动到目标的ID值,取出所处改变前位置的值
$sour_id = $changeid;
$desc_value = $weighdata[$desc_id];
$sour_value = $weighdata[$sour_id];
//echo "移动的ID:{$sour_id}\n";
//echo "替换的ID:{$desc_id}\n";
$weighids = array();
$temp = array_values(array_diff_assoc($ids, $sour));
foreach ($temp as $m => $n)
{
if ($n == $sour_id)
{
$offset = $desc_id;
}
else
{
if ($sour_id == $temp[0])
{
$offset = isset($temp[$m + 1]) ? $temp[$m + 1] : $sour_id;
}
else
{
$offset = isset($temp[$m - 1]) ? $temp[$m - 1] : $sour_id;
}
}
$weighids[$n] = $weighdata[$offset];
Db::name($table)->where($prikey, $n)->update([$field => $weighdata[$offset]]);
}
$this->code = 1;
}
}
/**
* 清空系统缓存
*/
public function wipecache()
{
$wipe_cache_type = ['TEMP_PATH', 'LOG_PATH', 'CACHE_PATH'];
foreach ($wipe_cache_type as $item)
{
$dir = constant($item);
if (!is_dir($dir))
continue;
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($files as $fileinfo)
{
$todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
$todo($fileinfo->getRealPath());
}
//rmdir($dir);
}
Cache::clear();
$this->code = 1;
}
/**
* 读取分类数据,联动列表
*/
public function category()
{
$type = $this->request->get('type');
$pid = $this->request->get('pid');
$where = ['status' => 'normal'];
$categorylist = null;
if ($pid !== '')
{
if ($type)
{
$where['type'] = $type;
}
if ($pid)
{
$where['pid'] = $pid;
}
$categorylist = Db::name('category')->where($where)->field('id as value,name')->order('weigh desc,id desc')->select();
}
$this->code = 1;
$this->data = $categorylist;
return;
}
/**
* 读取省市区数据,联动列表
*/
public function area()
{
$province = $this->request->get('province');
$city = $this->request->get('city');
$where = ['pid' => 0, 'level' => 1];
$provincelist = null;
if ($province !== '')
{
if ($province)
{
$where['pid'] = $province;
$where['level'] = 2;
}
if ($city !== '')
{
if ($city)
{
$where['pid'] = $city;
$where['level'] = 3;
}
$provincelist = Db::name('area')->where($where)->field('id as value,name')->select();
}
}
$this->code = 1;
$this->data = $provincelist;
return;
}
}
<?php
namespace app\admin\controller;
use app\common\controller\Backend;
use app\common\exception\UploadException;
use app\common\library\Upload;
use fast\Random;
use think\addons\Service;
use think\Cache;
use think\Config;
use think\Db;
use think\Lang;
use think\Loader;
use think\Response;
use think\Validate;
/**
* Ajax异步请求接口
* @internal
*/
class Ajax extends Backend
{
protected $noNeedLogin = ['lang'];
protected $noNeedRight = ['*'];
protected $layout = '';
public function _initialize()
{
parent::_initialize();
//设置过滤方法
$this->request->filter(['trim', 'strip_tags', 'htmlspecialchars']);
}
/**
* 加载语言包
*/
public function lang()
{
$this->request->get(['callback' => 'define']);
$header = ['Content-Type' => 'application/javascript'];
if (!config('app_debug')) {
$offset = 30 * 60 * 60 * 24; // 缓存一个月
$header['Cache-Control'] = 'public';
$header['Pragma'] = 'cache';
$header['Expires'] = gmdate("D, d M Y H:i:s", time() + $offset) . " GMT";
}
$controllername = $this->request->get('controllername');
$lang = $this->request->get('lang');
if (!$lang || !in_array($lang, config('allow_lang_list')) || !$controllername || !preg_match("/^[a-z0-9_\.]+$/i", $controllername)) {
return jsonp(['errmsg' => '参数错误'], 200, [], ['json_encode_param' => JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE]);
}
$controllername = input("controllername");
$className = Loader::parseClass($this->request->module(), 'controller', $controllername, false);
//存在对应的类才加载
if (class_exists($className)) {
$this->loadlang($controllername);
}
return jsonp(Lang::get(), 200, $header, ['json_encode_param' => JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE]);
}
/**
* 上传文件
*/
public function upload()
{
Config::set('default_return_type', 'json');
//必须还原upload配置,否则分片及cdnurl函数计算错误
Config::load(APP_PATH . 'extra/upload.php', 'upload');
$chunkid = $this->request->post("chunkid");
if ($chunkid) {
if (!Config::get('upload.chunking')) {
$this->error(__('Chunk file disabled'));
}
$action = $this->request->post("action");
$chunkindex = $this->request->post("chunkindex/d");
$chunkcount = $this->request->post("chunkcount/d");
$filename = $this->request->post("filename");
$method = $this->request->method(true);
if ($action == 'merge') {
$attachment = null;
//合并分片文件
try {
$upload = new Upload();
$attachment = $upload->merge($chunkid, $chunkcount, $filename);
} catch (UploadException $e) {
$this->error($e->getMessage());
}
$this->success(__('Uploaded successful'), '', ['url' => $attachment->url, 'fullurl' => cdnurl($attachment->url, true)]);
} elseif ($method == 'clean') {
//删除冗余的分片文件
try {
$upload = new Upload();
$upload->clean($chunkid);
} catch (UploadException $e) {
$this->error($e->getMessage());
}
$this->success();
} else {
//上传分片文件
//默认普通上传文件
$file = $this->request->file('file');
try {
$upload = new Upload($file);
$upload->chunk($chunkid, $chunkindex, $chunkcount);
} catch (UploadException $e) {
$this->error($e->getMessage());
}
$this->success();
}
} else {
$attachment = null;
//默认普通上传文件
$file = $this->request->file('file');
try {
$upload = new Upload($file);
$attachment = $upload->upload();
} catch (UploadException $e) {
$this->error($e->getMessage());
}
$this->success(__('Uploaded successful'), '', ['url' => $attachment->url, 'fullurl' => cdnurl($attachment->url, true)]);
}
}
/**
* 通用排序
*/
public function weigh()
{
//排序的数组
$ids = $this->request->post("ids");
//拖动的记录ID
$changeid = $this->request->post("changeid");
//操作字段
$field = $this->request->post("field");
//操作的数据表
$table = $this->request->post("table");
if (!Validate::is($table, "alphaDash")) {
$this->error();
}
//主键
$pk = $this->request->post("pk");
//排序的方式
$orderway = strtolower($this->request->post("orderway", ""));
$orderway = $orderway == 'asc' ? 'ASC' : 'DESC';
$sour = $weighdata = [];
$ids = explode(',', $ids);
$prikey = $pk && preg_match("/^[a-z0-9\-_]+$/i", $pk) ? $pk : (Db::name($table)->getPk() ?: 'id');
$pid = $this->request->post("pid", "");
//限制更新的字段
$field = in_array($field, ['weigh']) ? $field : 'weigh';
// 如果设定了pid的值,此时只匹配满足条件的ID,其它忽略
if ($pid !== '') {
$hasids = [];
$list = Db::name($table)->where($prikey, 'in', $ids)->where('pid', 'in', $pid)->field("{$prikey},pid")->select();
foreach ($list as $k => $v) {
$hasids[] = $v[$prikey];
}
$ids = array_values(array_intersect($ids, $hasids));
}
$list = Db::name($table)->field("$prikey,$field")->where($prikey, 'in', $ids)->order($field, $orderway)->select();
foreach ($list as $k => $v) {
$sour[] = $v[$prikey];
$weighdata[$v[$prikey]] = $v[$field];
}
$position = array_search($changeid, $ids);
$desc_id = isset($sour[$position]) ? $sour[$position] : end($sour); //移动到目标的ID值,取出所处改变前位置的值
$sour_id = $changeid;
$weighids = array();
$temp = array_values(array_diff_assoc($ids, $sour));
foreach ($temp as $m => $n) {
if ($n == $sour_id) {
$offset = $desc_id;
} else {
if ($sour_id == $temp[0]) {
$offset = isset($temp[$m + 1]) ? $temp[$m + 1] : $sour_id;
} else {
$offset = isset($temp[$m - 1]) ? $temp[$m - 1] : $sour_id;
}
}
if (!isset($weighdata[$offset])) {
continue;
}
$weighids[$n] = $weighdata[$offset];
Db::name($table)->where($prikey, $n)->update([$field => $weighdata[$offset]]);
}
$this->success();
}
/**
* 清空系统缓存
*/
public function wipecache()
{
try {
$type = $this->request->request("type");
switch ($type) {
case 'all':
// no break
case 'content':
//内容缓存
rmdirs(CACHE_PATH, false);
Cache::clear();
if ($type == 'content') {
break;
}
case 'template':
// 模板缓存
rmdirs(TEMP_PATH, false);
if ($type == 'template') {
break;
}
case 'addons':
// 插件缓存
Service::refresh();
if ($type == 'addons') {
break;
}
case 'browser':
// 浏览器缓存
// 只有生产环境下才修改
if (!config('app_debug')) {
$version = config('site.version');
$newversion = preg_replace_callback("/(.*)\.([0-9]+)\$/", function ($match) {
return $match[1] . '.' . ($match[2] + 1);
}, $version);
if ($newversion && $newversion != $version) {
Db::startTrans();
try {
\app\common\model\Config::where('name', 'version')->update(['value' => $newversion]);
\app\common\model\Config::refreshFile();
Db::commit();
} catch (\Exception $e) {
Db::rollback();
exception($e->getMessage());
}
}
}
if ($type == 'browser') {
break;
}
}
} catch (\Exception $e) {
$this->error($e->getMessage());
}
\think\Hook::listen("wipecache_after");
$this->success();
}
/**
* 读取分类数据,联动列表
*/
public function category()
{
$type = $this->request->get('type', '');
$pid = $this->request->get('pid', '');
$where = ['status' => 'normal'];
$categorylist = null;
if ($pid || $pid === '0') {
$where['pid'] = $pid;
}
if ($type) {
$where['type'] = $type;
}
$categorylist = Db::name('category')->where($where)->field('id as value,name')->order('weigh desc,id desc')->select();
$this->success('', '', $categorylist);
}
/**
* 读取省市区数据,联动列表
*/
public function area()
{
$params = $this->request->get("row/a");
if (!empty($params)) {
$province = isset($params['province']) ? $params['province'] : null;
$city = isset($params['city']) ? $params['city'] : null;
} else {
$province = $this->request->get('province');
$city = $this->request->get('city');
}
$where = ['pid' => 0, 'level' => 1];
$provincelist = null;
if ($province !== null) {
$where['pid'] = $province;
$where['level'] = 2;
if ($city !== null) {
$where['pid'] = $city;
$where['level'] = 3;
}
}
$provincelist = Db::name('area')->where($where)->field('id as value,name')->select();
$this->success('', '', $provincelist);
}
/**
* 生成后缀图标
*/
public function icon()
{
$suffix = $this->request->request("suffix");
$suffix = $suffix ? $suffix : "FILE";
$data = build_suffix_image($suffix);
$header = ['Content-Type' => 'image/svg+xml'];
$offset = 30 * 60 * 60 * 24; // 缓存一个月
$header['Cache-Control'] = 'public';
$header['Pragma'] = 'cache';
$header['Expires'] = gmdate("D, d M Y H:i:s", time() + $offset) . " GMT";
$response = Response::create($data, '', 200, $header);
return $response;
}
}

View File

@ -1,69 +1,158 @@
<?php
namespace app\admin\controller;
use app\common\controller\Backend;
use app\common\model\Category as CategoryModel;
use fast\Tree;
/**
* 分类管理
*
* @icon fa fa-list
* @remark 用于统一管理网站的所有分类,分类可进行无限级分类
*/
class Category extends Backend
{
protected $model = null;
protected $categorylist = [];
protected $noNeedRight = ['selectpage'];
public function _initialize()
{
parent::_initialize();
$this->request->filter(['strip_tags']);
$this->model = model('Category');
$tree = Tree::instance();
$tree->init($this->model->order('weigh desc,id desc')->select(), 'pid');
$this->categorylist = $tree->getTreeList($tree->getTreeArray(0), 'name');
$categorydata = [0 => __('None')];
foreach ($this->categorylist as $k => $v)
{
$categorydata[$v['id']] = $v['name'];
}
$this->view->assign("flagList", $this->model->getFlagList());
$this->view->assign("typeList", CategoryModel::getTypeList());
$this->view->assign("parentList", $categorydata);
}
/**
* 查看
*/
public function index()
{
if ($this->request->isAjax())
{
//构造父类select列表选项数据
$list = $this->categorylist;
$total = count($list);
$result = array("total" => 1, "rows" => $list);
return json($result);
}
return $this->view->fetch();
}
/**
* Selectpage搜索
*
* @internal
*/
public function selectpage()
{
return parent::selectpage();
}
}
<?php
namespace app\admin\controller;
use app\common\controller\Backend;
use app\common\model\Category as CategoryModel;
use fast\Tree;
/**
* 分类管理
*
* @icon fa fa-list
* @remark 用于管理网站的所有分类,分类可进行无限级分类,分类类型请在常规管理->系统配置->字典配置中添加
*/
class Category extends Backend
{
/**
* @var \app\common\model\Category
*/
protected $model = null;
protected $categorylist = [];
protected $noNeedRight = ['selectpage'];
public function _initialize()
{
parent::_initialize();
$this->model = model('app\common\model\Category');
$tree = Tree::instance();
$tree->init(collection($this->model->order('weigh desc,id desc')->select())->toArray(), 'pid');
$this->categorylist = $tree->getTreeList($tree->getTreeArray(0), 'name');
$categorydata = [0 => ['type' => 'all', 'name' => __('None')]];
foreach ($this->categorylist as $k => $v) {
$categorydata[$v['id']] = $v;
}
$typeList = CategoryModel::getTypeList();
$this->view->assign("flagList", $this->model->getFlagList());
$this->view->assign("typeList", $typeList);
$this->view->assign("parentList", $categorydata);
$this->assignconfig('typeList', $typeList);
}
/**
* 查看
*/
public function index()
{
//设置过滤方法
$this->request->filter(['strip_tags']);
if ($this->request->isAjax()) {
$search = $this->request->request("search");
$type = $this->request->request("type");
//构造父类select列表选项数据
$list = [];
foreach ($this->categorylist as $k => $v) {
if ($search) {
if ($v['type'] == $type && stripos($v['name'], $search) !== false || stripos($v['nickname'], $search) !== false) {
if ($type == "all" || $type == null) {
$list = $this->categorylist;
} else {
$list[] = $v;
}
}
} else {
if ($type == "all" || $type == null) {
$list = $this->categorylist;
} elseif ($v['type'] == $type) {
$list[] = $v;
}
}
}
$total = count($list);
$result = array("total" => $total, "rows" => $list);
return json($result);
}
return $this->view->fetch();
}
/**
* 添加
*/
public function add()
{
if ($this->request->isPost()) {
$this->token();
}
return parent::add();
}
/**
* 编辑
*/
public function edit($ids = null)
{
$row = $this->model->get($ids);
if (!$row) {
$this->error(__('No Results were found'));
}
$adminIds = $this->getDataLimitAdminIds();
if (is_array($adminIds)) {
if (!in_array($row[$this->dataLimitField], $adminIds)) {
$this->error(__('You have no permission'));
}
}
if ($this->request->isPost()) {
$this->token();
$params = $this->request->post("row/a");
if ($params) {
$params = $this->preExcludeFields($params);
if ($params['pid'] != $row['pid']) {
$childrenIds = Tree::instance()->init(collection(\app\common\model\Category::select())->toArray())->getChildrenIds($row['id'], true);
if (in_array($params['pid'], $childrenIds)) {
$this->error(__('Can not change the parent to child or itself'));
}
}
try {
//是否采用模型验证
if ($this->modelValidate) {
$name = str_replace("\\model\\", "\\validate\\", get_class($this->model));
$validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.edit' : $name) : $this->modelValidate;
$row->validate($validate);
}
$result = $row->allowField(true)->save($params);
if ($result !== false) {
$this->success();
} else {
$this->error($row->getError());
}
} catch (\think\exception\PDOException $e) {
$this->error($e->getMessage());
} catch (\think\Exception $e) {
$this->error($e->getMessage());
}
}
$this->error(__('Parameter %s can not be empty', ''));
}
$this->view->assign("row", $row);
return $this->view->fetch();
}
/**
* Selectpage搜索
*
* @internal
*/
public function selectpage()
{
return parent::selectpage();
}
}

View File

@ -1,48 +1,84 @@
<?php
namespace app\admin\controller;
use app\common\controller\Backend;
/**
* 控制台
*
* @icon fa fa-dashboard
* @remark 用于展示当前系统中的统计数据、统计报表及重要实时数据
*/
class Dashboard extends Backend
{
/**
* 查看
*/
public function index()
{
$seventtime = \fast\Date::unixtime('day', -7);
$paylist = $createlist = [];
for ($i = 0; $i < 7; $i++)
{
$day = date("Y-m-d", $seventtime + ($i * 86400));
$createlist[$day] = mt_rand(20, 200);
$paylist[$day] = mt_rand(1, mt_rand(1, $createlist[$day]));
}
$this->view->assign([
'totaluser' => 35200,
'totalviews' => 219390,
'totalorder' => 32143,
'totalorderamount' => 174800,
'todayuserlogin' => 321,
'todayusersignup' => 430,
'todayorder' => 2324,
'todayunsettleorder' => 132,
'sevendnu' => '80%',
'sevendau' => '32%',
'paylist' => $paylist,
'createlist' => $createlist,
]);
return $this->view->fetch();
}
}
<?php
namespace app\admin\controller;
use app\admin\model\Admin;
use app\admin\model\User;
use app\common\controller\Backend;
use app\common\model\Attachment;
use fast\Date;
use think\Db;
/**
* 控制台
*
* @icon fa fa-dashboard
* @remark 用于展示当前系统中的统计数据、统计报表及重要实时数据
*/
class Dashboard extends Backend
{
/**
* 查看
*/
public function index()
{
try {
\think\Db::execute("SET @@sql_mode='';");
} catch (\Exception $e) {
}
$column = [];
$starttime = Date::unixtime('day', -6);
$endtime = Date::unixtime('day', 0, 'end');
$joinlist = Db("user")->where('jointime', 'between time', [$starttime, $endtime])
->field('jointime, status, COUNT(*) AS nums, DATE_FORMAT(FROM_UNIXTIME(jointime), "%Y-%m-%d") AS join_date')
->group('join_date')
->select();
for ($time = $starttime; $time <= $endtime;) {
$column[] = date("Y-m-d", $time);
$time += 86400;
}
$userlist = array_fill_keys($column, 0);
foreach ($joinlist as $k => $v) {
$userlist[$v['join_date']] = $v['nums'];
}
$dbTableList = Db::query("SHOW TABLE STATUS");
$addonList = get_addon_list();
$totalworkingaddon = 0;
$totaladdon = count($addonList);
foreach ($addonList as $index => $item) {
if ($item['state']) {
$totalworkingaddon += 1;
}
}
$this->view->assign([
'totaluser' => User::count(),
'totaladdon' => $totaladdon,
'totaladmin' => Admin::count(),
'totalcategory' => \app\common\model\Category::count(),
'todayusersignup' => User::whereTime('jointime', 'today')->count(),
'todayuserlogin' => User::whereTime('logintime', 'today')->count(),
'sevendau' => User::whereTime('jointime|logintime|prevtime', '-7 days')->count(),
'thirtydau' => User::whereTime('jointime|logintime|prevtime', '-30 days')->count(),
'threednu' => User::whereTime('jointime', '-3 days')->count(),
'sevendnu' => User::whereTime('jointime', '-7 days')->count(),
'dbtablenums' => count($dbTableList),
'dbsize' => array_sum(array_map(function ($item) {
return $item['Data_length'] + $item['Index_length'];
}, $dbTableList)),
'totalworkingaddon' => $totalworkingaddon,
'attachmentnums' => Attachment::count(),
'attachmentsize' => Attachment::sum('filesize'),
'picturenums' => Attachment::where('mimetype', 'like', 'image/%')->count(),
'picturesize' => Attachment::where('mimetype', 'like', 'image/%')->sum('filesize'),
]);
$this->assignconfig('column', array_keys($userlist));
$this->assignconfig('userdata', array_values($userlist));
return $this->view->fetch();
}
}

View File

@ -1,109 +1,141 @@
<?php
namespace app\admin\controller;
use app\common\controller\Backend;
use think\Validate;
/**
* 后台首页
* @internal
*/
class Index extends Backend
{
protected $noNeedLogin = ['login'];
protected $noNeedRight = ['index', 'logout'];
protected $layout = '';
public function _initialize()
{
parent::_initialize();
}
/**
* 后台首页
*/
public function index()
{
//
$menulist = $this->auth->getSidebar([
'dashboard' => 'hot',
'auth' => ['new', 'red', 'badge'],
'auth/admin' => 12,
'auth/rule' => 4,
'general' => ['18', 'purple'],
], $this->view->site['fixedpage']);
$this->view->assign('menulist', $menulist);
$this->view->assign('title', __('Home'));
return $this->view->fetch();
}
/**
* 管理员登录
*/
public function login()
{
$url = $this->request->get('url', 'index/index');
if ($this->auth->isLogin())
{
$this->error(__("You've logged in, do not login again"), $url);
return;
}
if ($this->request->isPost())
{
$username = $this->request->post('username');
$password = $this->request->post('password');
$keeplogin = $this->request->post('keeplogin');
$token = $this->request->post('__token__');
$rule = [
'username' => 'require|length:3,30',
'password' => 'require|length:3,30',
'__token__' => 'token',
];
$data = [
'username' => $username,
'password' => $password,
'__token__' => $token,
];
$validate = new Validate($rule);
$result = $validate->check($data);
if (!$result)
{
$this->error($validate->getError(), $url, ['token' => $this->request->token()]);
return;
}
\app\admin\model\AdminLog::setTitle(__('Login'));
$result = $this->auth->login($username, $password, $keeplogin ? 86400 : 0);
if ($result === true)
{
$this->success(__('Login successful'), $url, ['url' => $url, 'id' => $this->auth->id, 'username' => $username, 'avatar' => $this->auth->avatar]);
return;
}
else
{
$this->error(__('Username or password is incorrect'), $url, ['token' => $this->request->token()]);
}
return;
}
// 根据客户端的cookie,判断是否可以自动登录
if ($this->auth->autologin())
{
$this->redirect($url);
}
$this->view->assign('title', __('Login'));
return $this->view->fetch();
}
/**
* 注销登录
*/
public function logout()
{
$this->auth->logout();
$this->success(__('Logout successful'), 'index/login');
return;
}
}
<?php
namespace app\admin\controller;
use app\admin\model\AdminLog;
use app\common\controller\Backend;
use think\Config;
use think\Hook;
use think\Session;
use think\Validate;
/**
* 后台首页
* @internal
*/
class Index extends Backend
{
protected $noNeedLogin = ['login'];
protected $noNeedRight = ['index', 'logout'];
protected $layout = '';
public function _initialize()
{
parent::_initialize();
//移除HTML标签
$this->request->filter('trim,strip_tags,htmlspecialchars');
}
/**
* 后台首页
*/
public function index()
{
$cookieArr = ['adminskin' => "/^skin\-([a-z\-]+)\$/i", 'multiplenav' => "/^(0|1)\$/", 'multipletab' => "/^(0|1)\$/", 'show_submenu' => "/^(0|1)\$/"];
foreach ($cookieArr as $key => $regex) {
$cookieValue = $this->request->cookie($key);
if (!is_null($cookieValue) && preg_match($regex, $cookieValue)) {
config('fastadmin.' . $key, $cookieValue);
}
}
//左侧菜单
list($menulist, $navlist, $fixedmenu, $referermenu) = $this->auth->getSidebar([
'dashboard' => 'hot',
'addon' => ['new', 'red', 'badge'],
'auth/rule' => __('Menu'),
], $this->view->site['fixedpage']);
$action = $this->request->request('action');
if ($this->request->isPost()) {
if ($action == 'refreshmenu') {
$this->success('', null, ['menulist' => $menulist, 'navlist' => $navlist]);
}
}
$this->assignconfig('cookie', ['prefix' => config('cookie.prefix')]);
$this->view->assign('menulist', $menulist);
$this->view->assign('navlist', $navlist);
$this->view->assign('fixedmenu', $fixedmenu);
$this->view->assign('referermenu', $referermenu);
$this->view->assign('title', __('Home'));
return $this->view->fetch();
}
/**
* 管理员登录
*/
public function login()
{
$url = $this->request->get('url', '', 'url_clean');
$url = $url ?: 'index/index';
if ($this->auth->isLogin()) {
$this->success(__("You've logged in, do not login again"), $url);
}
//保持会话有效时长,单位:小时
$keeyloginhours = 24;
if ($this->request->isPost()) {
$username = $this->request->post('username');
$password = $this->request->post('password', '', null);
$keeplogin = $this->request->post('keeplogin');
$token = $this->request->post('__token__');
$rule = [
'username' => 'require|length:3,30',
'password' => 'require|length:3,30',
'__token__' => 'require|token',
];
$data = [
'username' => $username,
'password' => $password,
'__token__' => $token,
];
if (Config::get('fastadmin.login_captcha')) {
$rule['captcha'] = 'require|captcha';
$data['captcha'] = $this->request->post('captcha');
}
$validate = new Validate($rule, [], ['username' => __('Username'), 'password' => __('Password'), 'captcha' => __('Captcha')]);
$result = $validate->check($data);
if (!$result) {
$this->error($validate->getError(), $url, ['token' => $this->request->token()]);
}
AdminLog::setTitle(__('Login'));
$result = $this->auth->login($username, $password, $keeplogin ? $keeyloginhours * 3600 : 0);
if ($result === true) {
Hook::listen("admin_login_after", $this->request);
$this->success(__('Login successful'), $url, ['url' => $url, 'id' => $this->auth->id, 'username' => $username, 'avatar' => $this->auth->avatar]);
} else {
$msg = $this->auth->getError();
$msg = $msg ? $msg : __('Username or password is incorrect');
$this->error($msg, $url, ['token' => $this->request->token()]);
}
}
// 根据客户端的cookie,判断是否可以自动登录
if ($this->auth->autologin()) {
Session::delete("referer");
$this->redirect($url);
}
$background = Config::get('fastadmin.login_background');
$background = $background ? (stripos($background, 'http') === 0 ? $background : config('site.cdnurl') . $background) : '';
$this->view->assign('keeyloginhours', $keeyloginhours);
$this->view->assign('background', $background);
$this->view->assign('title', __('Login'));
Hook::listen("admin_login_init", $this->request);
return $this->view->fetch();
}
/**
* 退出登录
*/
public function logout()
{
if ($this->request->isPost()) {
$this->auth->logout();
Hook::listen("admin_logout_after", $this->request);
$this->success(__('Logout successful'), 'index/login');
}
$html = "<form id='logout_submit' name='logout_submit' action='' method='post'>" . token() . "<input type='submit' value='ok' style='display:none;'></form>";
$html .= "<script>document.forms['logout_submit'].submit();</script>";
return $html;
}
}

View File

@ -1,51 +0,0 @@
<?php
namespace app\admin\controller;
use app\common\controller\Backend;
/**
* 单页管理
*
* @icon fa fa-circle-o
* @remark 用于管理普通的单页面,通常用于关于我们、联系我们、商务合作等单一页面
*/
class Page extends Backend
{
protected $model = null;
protected $relationSearch = true;
public function _initialize()
{
parent::_initialize();
$this->model = model('Page');
}
/**
* 查看
*/
public function index()
{
if ($this->request->isAjax())
{
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$total = $this->model
->with("category")
->where($where)
->order($sort, $order)
->count();
$list = $this->model
->with("category")
->where($where)
->order($sort, $order)
->limit($offset, $limit)
->select();
$result = array("total" => $total, "rows" => $list);
return json($result);
}
return $this->view->fetch();
}
}

View File

@ -1,27 +0,0 @@
<?php
namespace app\admin\controller;
use app\common\controller\Backend;
/**
* 会员管理
*
* @icon fa fa-circle-o
* @internal
*/
class User extends Backend
{
/**
* User模型对象
*/
protected $model = null;
public function _initialize()
{
parent::_initialize();
$this->model = model('User');
}
}

View File

@ -1,26 +0,0 @@
<?php
namespace app\admin\controller;
use app\common\controller\Backend;
use think\Controller;
use think\Request;
/**
* 版本管理
*
* @icon fa fa-circle-o
*/
class Version extends Backend
{
protected $model = null;
public function _initialize()
{
parent::_initialize();
$this->model = model('Version');
}
}

View File

@ -2,47 +2,65 @@
namespace app\admin\controller\auth;
use app\admin\model\AuthGroup;
use app\admin\model\AuthGroupAccess;
use app\common\controller\Backend;
use fast\Random;
use fast\Tree;
use think\Db;
use think\Validate;
/**
* 管理员管理
*
* @icon fa fa-users
* @icon fa fa-users
* @remark 一个管理员可以有多个角色组,左侧的菜单根据管理员所拥有的权限进行生成
*/
class Admin extends Backend
{
/**
* @var \app\admin\model\Admin
*/
protected $model = null;
//当前登录管理员所有子节点组别
protected $childrenIds = [];
protected $selectpageFields = 'id,username,nickname,avatar';
protected $searchFields = 'id,username,nickname';
protected $childrenGroupIds = [];
protected $childrenAdminIds = [];
public function _initialize()
{
parent::_initialize();
$this->model = model('Admin');
$groups = $this->auth->getGroups();
$this->childrenAdminIds = $this->auth->getChildrenAdminIds($this->auth->isSuperAdmin());
$this->childrenGroupIds = $this->auth->getChildrenGroupIds($this->auth->isSuperAdmin());
// 取出所有分组
$grouplist = model('AuthGroup')->all(['status' => 'normal']);
$objlist = [];
foreach ($groups as $K => $v)
{
// 取出包含自己的所有子节点
$childrenlist = Tree::instance()->init($grouplist)->getChildren($v['id'], TRUE);
$obj = Tree::instance()->init($childrenlist)->getTreeArray($v['pid']);
$objlist = array_merge($objlist, Tree::instance()->getTreeList($obj));
}
$groupList = collection(AuthGroup::where('id', 'in', $this->childrenGroupIds)->select())->toArray();
Tree::instance()->init($groupList);
$groupdata = [];
foreach ($objlist as $k => $v)
{
$groupdata[$v['id']] = $v['name'];
if ($this->auth->isSuperAdmin()) {
$result = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0));
foreach ($result as $k => $v) {
$groupdata[$v['id']] = $v['name'];
}
} else {
$result = [];
$groups = $this->auth->getGroups();
foreach ($groups as $m => $n) {
$childlist = Tree::instance()->getTreeList(Tree::instance()->getTreeArray($n['id']));
$temp = [];
foreach ($childlist as $k => $v) {
$temp[$v['id']] = $v['name'];
}
$result[__($n['name'])] = $temp;
}
$groupdata = $result;
}
$this->childrenIds = array_keys($groupdata);
$this->view->assign('groupdata', $groupdata);
$this->assignconfig("admin", ['id' => $this->auth->id]);
}
/**
@ -50,44 +68,46 @@ class Admin extends Backend
*/
public function index()
{
if ($this->request->isAjax())
{
$groupData = model('AuthGroup')->where('status', 'normal')->column('id,name');
//设置过滤方法
$this->request->filter(['strip_tags', 'trim']);
if ($this->request->isAjax()) {
//如果发送的来源是Selectpage则转发到Selectpage
if ($this->request->request('keyField')) {
return $this->selectpage();
}
$childrenGroupIds = $this->childrenGroupIds;
$groupName = AuthGroup::where('id', 'in', $childrenGroupIds)
->column('id,name');
$authGroupList = AuthGroupAccess::where('group_id', 'in', $childrenGroupIds)
->field('uid,group_id')
->select();
$childrenAdminIds = [];
$authGroupList = model('AuthGroupAccess')
->field('uid,group_id')
->where('group_id', 'in', $this->childrenIds)
->select();
$adminGroupName = [];
foreach ($authGroupList as $k => $v)
{
$childrenAdminIds[] = $v['uid'];
if (isset($groupData[$v['group_id']]))
$adminGroupName[$v['uid']][$v['group_id']] = $groupData[$v['group_id']];
foreach ($authGroupList as $k => $v) {
if (isset($groupName[$v['group_id']])) {
$adminGroupName[$v['uid']][$v['group_id']] = $groupName[$v['group_id']];
}
}
$groups = $this->auth->getGroups();
foreach ($groups as $m => $n) {
$adminGroupName[$this->auth->id][$n['id']] = $n['name'];
}
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$total = $this->model
->where($where)
->where('id', 'in', $childrenAdminIds)
->order($sort, $order)
->count();
$list = $this->model
->where($where)
->where('id', 'in', $childrenAdminIds)
->field(['password', 'salt', 'token'], true)
->order($sort, $order)
->limit($offset, $limit)
->select();
foreach ($list as $k => &$v)
{
->where($where)
->where('id', 'in', $this->childrenAdminIds)
->field(['password', 'salt', 'token'], true)
->order($sort, $order)
->paginate($limit);
foreach ($list as $k => &$v) {
$groups = isset($adminGroupName[$v['id']]) ? $adminGroupName[$v['id']] : [];
$v['groups'] = implode(',', array_keys($groups));
$v['groups_text'] = implode(',', array_values($groups));
}
$result = array("total" => $total, "rows" => $list);
unset($v);
$result = array("total" => $list->total(), "rows" => $list->items());
return json($result);
}
@ -99,31 +119,43 @@ class Admin extends Backend
*/
public function add()
{
if ($this->request->isPost())
{
$this->code = -1;
if ($this->request->isPost()) {
$this->token();
$params = $this->request->post("row/a");
if ($params)
{
$params['salt'] = Random::alnum();
$params['password'] = md5(md5($params['password']) . $params['salt']);
$params['avatar'] = '/assets/img/avatar.png'; //设置新管理员默认头像。
if ($params) {
Db::startTrans();
try {
if (!Validate::is($params['password'], '\S{6,30}')) {
exception(__("Please input correct password"));
}
$params['salt'] = Random::alnum();
$params['password'] = $this->auth->getEncryptPassword($params['password'], $params['salt']);
$params['avatar'] = '/assets/img/avatar.png'; //设置新管理员默认头像。
$result = $this->model->validate('Admin.add')->save($params);
if ($result === false) {
exception($this->model->getError());
}
$group = $this->request->post("group/a");
$admin = $this->model->create($params);
$group = $this->request->post("group/a");
//过滤不允许的组别,避免越权
$group = array_intersect($this->childrenGroupIds, $group);
if (!$group) {
exception(__('The parent group exceeds permission limit'));
}
//过滤不允许的组别,避免越权
$group = array_intersect($this->childrenIds, $group);
$dataset = [];
foreach ($group as $value)
{
$dataset[] = ['uid' => $admin->id, 'group_id' => $value];
$dataset = [];
foreach ($group as $value) {
$dataset[] = ['uid' => $this->model->id, 'group_id' => $value];
}
model('AuthGroupAccess')->saveAll($dataset);
Db::commit();
} catch (\Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
model('AuthGroupAccess')->saveAll($dataset);
$this->code = 1;
$this->success();
}
return;
$this->error(__('Parameter %s can not be empty', ''));
}
return $this->view->fetch();
}
@ -131,51 +163,71 @@ class Admin extends Backend
/**
* 编辑
*/
public function edit($ids = NULL)
public function edit($ids = null)
{
$row = $this->model->get(['id' => $ids]);
if (!$row)
if (!$row) {
$this->error(__('No Results were found'));
if ($this->request->isPost())
{
$this->code = -1;
}
if (!in_array($row->id, $this->childrenAdminIds)) {
$this->error(__('You have no permission'));
}
if ($this->request->isPost()) {
$this->token();
$params = $this->request->post("row/a");
if ($params)
{
if ($params['password'])
{
$params['salt'] = Random::alnum();
$params['password'] = md5(md5($params['password']) . $params['salt']);
if ($params) {
Db::startTrans();
try {
if ($params['password']) {
if (!Validate::is($params['password'], '\S{6,30}')) {
exception(__("Please input correct password"));
}
$params['salt'] = Random::alnum();
$params['password'] = $this->auth->getEncryptPassword($params['password'], $params['salt']);
} else {
unset($params['password'], $params['salt']);
}
//这里需要针对username和email做唯一验证
$adminValidate = \think\Loader::validate('Admin');
$adminValidate->rule([
'username' => 'require|regex:\w{3,30}|unique:admin,username,' . $row->id,
'email' => 'require|email|unique:admin,email,' . $row->id,
'mobile' => 'regex:1[3-9]\d{9}|unique:admin,mobile,' . $row->id,
'password' => 'regex:\S{32}',
]);
$result = $row->validate('Admin.edit')->save($params);
if ($result === false) {
exception($row->getError());
}
// 先移除所有权限
model('AuthGroupAccess')->where('uid', $row->id)->delete();
$group = $this->request->post("group/a");
// 过滤不允许的组别,避免越权
$group = array_intersect($this->childrenGroupIds, $group);
if (!$group) {
exception(__('The parent group exceeds permission limit'));
}
$dataset = [];
foreach ($group as $value) {
$dataset[] = ['uid' => $row->id, 'group_id' => $value];
}
model('AuthGroupAccess')->saveAll($dataset);
Db::commit();
} catch (\Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
else
{
unset($params['password'], $params['salt']);
}
$row->save($params);
// 先移除所有权限
model('AuthGroupAccess')->where('uid', $row->id)->delete();
$group = $this->request->post("group/a");
// 过滤不允许的组别,避免越权
$group = array_intersect($this->childrenIds, $group);
$dataset = [];
foreach ($group as $value)
{
$dataset[] = ['uid' => $row->id, 'group_id' => $value];
}
model('AuthGroupAccess')->saveAll($dataset);
$this->code = 1;
$this->success();
}
return;
$this->error(__('Parameter %s can not be empty', ''));
}
$grouplist = $this->auth->getGroups($row['id']);
$groupids = [];
foreach ($grouplist as $k => $v)
{
foreach ($grouplist as $k => $v) {
$groupids[] = $v['id'];
}
$this->view->assign("row", $row);
@ -188,32 +240,39 @@ class Admin extends Backend
*/
public function del($ids = "")
{
$this->code = -1;
if ($ids)
{
if (!$this->request->isPost()) {
$this->error(__("Invalid parameters"));
}
$ids = $ids ? $ids : $this->request->post("ids");
if ($ids) {
$ids = array_intersect($this->childrenAdminIds, array_filter(explode(',', $ids)));
// 避免越权删除管理员
$childrenGroupIds = $this->childrenIds;
$adminList = $this->model->where('id', 'in', $ids)->where('id', 'in', function($query) use($childrenGroupIds) {
$query->name('auth_group_access')->where('group_id', 'in', $childrenGroupIds)->field('uid');
})->select();
if ($adminList)
{
$childrenGroupIds = $this->childrenGroupIds;
$adminList = $this->model->where('id', 'in', $ids)->where('id', 'in', function ($query) use ($childrenGroupIds) {
$query->name('auth_group_access')->where('group_id', 'in', $childrenGroupIds)->field('uid');
})->select();
if ($adminList) {
$deleteIds = [];
foreach ($adminList as $k => $v)
{
foreach ($adminList as $k => $v) {
$deleteIds[] = $v->id;
}
$deleteIds = array_diff($deleteIds, [$this->auth->id]);
if ($deleteIds)
{
$this->model->destroy($deleteIds);
model('AuthGroupAccess')->where('uid', 'in', $deleteIds)->delete();
$this->code = 1;
$deleteIds = array_values(array_diff($deleteIds, [$this->auth->id]));
if ($deleteIds) {
Db::startTrans();
try {
$this->model->destroy($deleteIds);
model('AuthGroupAccess')->where('uid', 'in', $deleteIds)->delete();
Db::commit();
} catch (\Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
$this->success();
}
$this->error(__('No rows were deleted'));
}
}
return;
$this->error(__('You have no permission'));
}
/**
@ -223,7 +282,16 @@ class Admin extends Backend
public function multi($ids = "")
{
// 管理员禁止批量操作
$this->code = -1;
$this->error();
}
/**
* 下拉搜索
*/
public function selectpage()
{
$this->dataLimit = 'auth';
$this->dataLimitField = 'id';
return parent::selectpage();
}
}

View File

@ -2,46 +2,37 @@
namespace app\admin\controller\auth;
use app\admin\model\AuthGroup;
use app\common\controller\Backend;
use fast\Tree;
/**
* 管理员日志
*
* @icon fa fa-users
* @icon fa fa-users
* @remark 管理员可以查看自己所拥有的权限的管理员日志
*/
class Adminlog extends Backend
{
/**
* @var \app\admin\model\AdminLog
*/
protected $model = null;
//当前登录管理员所有子节点组别
protected $childrenIds = [];
protected $childrenGroupIds = [];
protected $childrenAdminIds = [];
public function _initialize()
{
parent::_initialize();
$this->model = model('AdminLog');
$groups = $this->auth->getGroups();
$this->childrenAdminIds = $this->auth->getChildrenAdminIds(true);
$this->childrenGroupIds = $this->auth->getChildrenGroupIds(true);
// 取出所有分组
$grouplist = model('AuthGroup')->all(['status' => 'normal']);
$objlist = [];
foreach ($groups as $K => $v)
{
// 取出包含自己的所有子节点
$childrenlist = Tree::instance()->init($grouplist)->getChildren($v['id'], TRUE);
$obj = Tree::instance()->init($childrenlist)->getTreeArray($v['pid']);
$objlist = array_merge($objlist, Tree::instance()->getTreeList($obj));
}
$groupdata = [];
foreach ($objlist as $k => $v)
{
$groupdata[$v['id']] = $v['name'];
}
$this->childrenIds = array_keys($groupdata);
$this->view->assign('groupdata', $groupdata);
$groupName = AuthGroup::where('id', 'in', $this->childrenGroupIds)
->column('id,name');
$this->view->assign('groupdata', $groupName);
}
/**
@ -49,40 +40,44 @@ class Adminlog extends Backend
*/
public function index()
{
if ($this->request->isAjax())
{
$childrenAdminIds = model('AuthGroupAccess')
->field('uid')
->where('group_id', 'in', $this->childrenIds)
->column('uid');
//设置过滤方法
$this->request->filter(['strip_tags', 'trim']);
if ($this->request->isAjax()) {
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$total = $this->model
->where($where)
->where('admin_id', 'in', $childrenAdminIds)
->order($sort, $order)
->count();
$isSuperAdmin = $this->auth->isSuperAdmin();
$childrenAdminIds = $this->childrenAdminIds;
$list = $this->model
->where($where)
->where('admin_id', 'in', $childrenAdminIds)
->order($sort, $order)
->limit($offset, $limit)
->select();
$result = array("total" => $total, "rows" => $list);
->where($where)
->where(function ($query) use ($isSuperAdmin, $childrenAdminIds) {
if (!$isSuperAdmin) {
$query->where('admin_id', 'in', $childrenAdminIds);
}
})
->field('content,useragent', true)
->order($sort, $order)
->paginate($limit);
$result = array("total" => $list->total(), "rows" => $list->items());
return json($result);
}
return $this->view->fetch();
}
/**
* 详情
*/
public function detail($ids)
{
$row = $this->model->get(['id' => $ids]);
if (!$row)
if (!$row) {
$this->error(__('No Results were found'));
}
if (!$this->auth->isSuperAdmin()) {
if (!$row['admin_id'] || !in_array($row['admin_id'], $this->childrenAdminIds)) {
$this->error(__('You have no permission'));
}
}
$this->view->assign("row", $row->toArray());
return $this->view->fetch();
}
@ -93,16 +88,16 @@ class Adminlog extends Backend
*/
public function add()
{
$this->code = -1;
$this->error();
}
/**
* 编辑
* @internal
*/
public function edit($ids = NULL)
public function edit($ids = null)
{
$this->code = -1;
$this->error();
}
/**
@ -110,29 +105,32 @@ class Adminlog extends Backend
*/
public function del($ids = "")
{
$this->code = -1;
if ($ids)
{
$childrenGroupIds = $this->childrenIds;
$adminList = $this->model->where('id', 'in', $ids)->where('admin_id', 'in', function($query) use($childrenGroupIds) {
$query->name('auth_group_access')->field('uid');
})->select();
if ($adminList)
{
if (!$this->request->isPost()) {
$this->error(__("Invalid parameters"));
}
$ids = $ids ? $ids : $this->request->post("ids");
if ($ids) {
$isSuperAdmin = $this->auth->isSuperAdmin();
$childrenAdminIds = $this->childrenAdminIds;
$adminList = $this->model->where('id', 'in', $ids)
->where(function ($query) use ($isSuperAdmin, $childrenAdminIds) {
if (!$isSuperAdmin) {
$query->where('admin_id', 'in', $childrenAdminIds);
}
})
->select();
if ($adminList) {
$deleteIds = [];
foreach ($adminList as $k => $v)
{
foreach ($adminList as $k => $v) {
$deleteIds[] = $v->id;
}
if ($deleteIds)
{
if ($deleteIds) {
$this->model->destroy($deleteIds);
$this->code = 1;
$this->success();
}
}
}
return;
$this->error();
}
/**
@ -142,7 +140,7 @@ class Adminlog extends Backend
public function multi($ids = "")
{
// 管理员禁止批量操作
$this->code = -1;
$this->error();
}
}

View File

@ -2,22 +2,29 @@
namespace app\admin\controller\auth;
use app\admin\model\AuthGroup;
use app\common\controller\Backend;
use fast\Tree;
use think\Db;
use think\Exception;
/**
* 角色组
*
* @icon fa fa-group
* @icon fa fa-group
* @remark 角色组可以有多个,角色有上下级层级关系,如果子角色有角色组和管理员的权限则可以派生属于自己组别下级的角色组或管理员
*/
class Group extends Backend
{
/**
* @var \app\admin\model\AuthGroup
*/
protected $model = null;
//当前登录管理员所有子节点组别
protected $childrenIds = [];
//当前登录管理员所有子组别
protected $childrenGroupIds = [];
//当前组别列表数据
protected $grouplist = [];
protected $groupdata = [];
//无需要权限判断的方法
protected $noNeedRight = ['roletree'];
@ -27,27 +34,37 @@ class Group extends Backend
parent::_initialize();
$this->model = model('AuthGroup');
$groups = $this->auth->getGroups();
$this->childrenGroupIds = $this->auth->getChildrenGroupIds(true);
// 取出所有分组
$grouplist = model('AuthGroup')->all(['status' => 'normal']);
$objlist = [];
foreach ($groups as $K => $v)
{
// 取出包含自己的所有子节点
$childrenlist = Tree::instance()->init($grouplist)->getChildren($v['id'], TRUE);
$obj = Tree::instance()->init($childrenlist)->getTreeArray($v['pid']);
$objlist = array_merge($objlist, Tree::instance()->getTreeList($obj));
$groupList = collection(AuthGroup::where('id', 'in', $this->childrenGroupIds)->select())->toArray();
Tree::instance()->init($groupList);
$groupList = [];
if ($this->auth->isSuperAdmin()) {
$groupList = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0));
} else {
$groups = $this->auth->getGroups();
$groupIds = [];
foreach ($groups as $m => $n) {
if (in_array($n['id'], $groupIds) || in_array($n['pid'], $groupIds)) {
continue;
}
$groupList = array_merge($groupList, Tree::instance()->getTreeList(Tree::instance()->getTreeArray($n['pid'])));
foreach ($groupList as $index => $item) {
$groupIds[] = $item['id'];
}
}
}
$groupName = [];
foreach ($groupList as $k => $v) {
$groupName[$v['id']] = $v['name'];
}
$groupdata = [];
foreach ($objlist as $k => $v)
{
$groupdata[$v['id']] = $v['name'];
}
$this->groupdata = $groupdata;
$this->childrenIds = array_keys($groupdata);
$this->view->assign('groupdata', $groupdata);
$this->grouplist = $groupList;
$this->groupdata = $groupName;
$this->assignconfig("admin", ['id' => $this->auth->id, 'group_ids' => $this->auth->getGroupIds()]);
$this->view->assign('groupdata', $this->groupdata);
}
/**
@ -55,15 +72,8 @@ class Group extends Backend
*/
public function index()
{
if ($this->request->isAjax())
{
$list = [];
foreach ($this->groupdata as $k => $v)
{
$data = $this->model->get($k);
$data->name = $v;
$list[] = $data;
}
if ($this->request->isAjax()) {
$list = $this->grouplist;
$total = count($list);
$result = array("total" => $total, "rows" => $list);
@ -77,22 +87,16 @@ class Group extends Backend
*/
public function add()
{
if ($this->request->isPost())
{
$this->code = -1;
if ($this->request->isPost()) {
$this->token();
$params = $this->request->post("row/a", [], 'strip_tags');
$params['rules'] = explode(',', $params['rules']);
if (!in_array($params['pid'], $this->childrenIds))
{
$this->code = -1;
$this->msg = __('');
return;
if (!in_array($params['pid'], $this->childrenGroupIds)) {
$this->error(__('The parent group exceeds permission limit'));
}
$parentmodel = model("AuthGroup")->get($params['pid']);
if (!$parentmodel)
{
$this->msg = __('The parent group can not found');
return;
if (!$parentmodel) {
$this->error(__('The parent group can not found'));
}
// 父级别的规则节点
$parentrules = explode(',', $parentmodel->rules);
@ -104,13 +108,11 @@ class Group extends Backend
// 如果当前组别不是超级管理员则需要过滤规则节点,不能超当前组别的权限
$rules = in_array('*', $currentrules) ? $rules : array_intersect($currentrules, $rules);
$params['rules'] = implode(',', $rules);
if ($params)
{
if ($params) {
$this->model->create($params);
$this->code = 1;
$this->success();
}
return;
$this->error();
}
return $this->view->fetch();
}
@ -118,28 +120,31 @@ class Group extends Backend
/**
* 编辑
*/
public function edit($ids = NULL)
public function edit($ids = null)
{
if (!in_array($ids, $this->childrenGroupIds)) {
$this->error(__('You have no permission'));
}
$row = $this->model->get(['id' => $ids]);
if (!$row)
if (!$row) {
$this->error(__('No Results were found'));
if ($this->request->isPost())
{
$this->code = -1;
}
if ($this->request->isPost()) {
$this->token();
$params = $this->request->post("row/a", [], 'strip_tags');
// 父节点不能是它自身的子节点
if (!in_array($params['pid'], $this->childrenIds))
{
$this->msg = __('The parent group can not be its own child');
return;
//父节点不能是非权限内节点
if (!in_array($params['pid'], $this->childrenGroupIds)) {
$this->error(__('The parent group exceeds permission limit'));
}
// 父节点不能是它自身的子节点或自己本身
if (in_array($params['pid'], Tree::instance()->getChildrenIds($row->id, true))) {
$this->error(__('The parent group can not be its own child or itself'));
}
$params['rules'] = explode(',', $params['rules']);
$parentmodel = model("AuthGroup")->get($params['pid']);
if (!$parentmodel)
{
$this->msg = __('The parent group can not found');
return;
if (!$parentmodel) {
$this->error(__('The parent group can not found'));
}
// 父级别的规则节点
$parentrules = explode(',', $parentmodel->rules);
@ -151,12 +156,25 @@ class Group extends Backend
// 如果当前组别不是超级管理员则需要过滤规则节点,不能超当前组别的权限
$rules = in_array('*', $currentrules) ? $rules : array_intersect($currentrules, $rules);
$params['rules'] = implode(',', $rules);
if ($params)
{
$row->save($params);
$this->code = 1;
if ($params) {
Db::startTrans();
try {
$row->save($params);
$children_auth_groups = model("AuthGroup")->all(['id' => ['in', implode(',', (Tree::instance()->getChildrenIds($row->id)))]]);
$childparams = [];
foreach ($children_auth_groups as $key => $children_auth_group) {
$childparams[$key]['id'] = $children_auth_group->id;
$childparams[$key]['rules'] = implode(',', array_intersect(explode(',', $children_auth_group->rules), $rules));
}
model("AuthGroup")->saveAll($childparams);
Db::commit();
$this->success();
} catch (Exception $e) {
Db::rollback();
$this->error($e->getMessage());
}
}
$this->error();
return;
}
$this->view->assign("row", $row);
@ -168,12 +186,14 @@ class Group extends Backend
*/
public function del($ids = "")
{
$this->code = -1;
if ($ids)
{
if (!$this->request->isPost()) {
$this->error(__("Invalid parameters"));
}
$ids = $ids ? $ids : $this->request->post("ids");
if ($ids) {
$ids = explode(',', $ids);
$grouplist = $this->auth->getGroups();
$group_ids = array_map(function($group) {
$group_ids = array_map(function ($group) {
return $group['id'];
}, $grouplist);
// 移除掉当前管理员所在组别
@ -182,35 +202,29 @@ class Group extends Backend
// 循环判断每一个组别是否可删除
$grouplist = $this->model->where('id', 'in', $ids)->select();
$groupaccessmodel = model('AuthGroupAccess');
foreach ($grouplist as $k => $v)
{
foreach ($grouplist as $k => $v) {
// 当前组别下有管理员
$groupone = $groupaccessmodel->get(['group_id' => $v['id']]);
if ($groupone)
{
if ($groupone) {
$ids = array_diff($ids, [$v['id']]);
continue;
}
// 当前组别下有子组别
$groupone = $this->model->get(['pid' => $v['id']]);
if ($groupone)
{
if ($groupone) {
$ids = array_diff($ids, [$v['id']]);
continue;
}
}
if (!$ids)
{
$this->msg = __('You can not delete group that contain child group and administrators');
return;
if (!$ids) {
$this->error(__('You can not delete group that contain child group and administrators'));
}
$count = $this->model->where('id', 'in', $ids)->delete();
if ($count)
{
$this->code = 1;
if ($count) {
$this->success();
}
}
return;
$this->error();
}
/**
@ -220,13 +234,12 @@ class Group extends Backend
public function multi($ids = "")
{
// 组别禁止批量操作
$this->code = -1;
return;
$this->error();
}
/**
* 读取角色权限树
*
*
* @internal
*/
public function roletree()
@ -236,75 +249,69 @@ class Group extends Backend
$model = model('AuthGroup');
$id = $this->request->post("id");
$pid = $this->request->post("pid");
$parentgroupmodel = $model->get($pid);
$currentgroupmodel = NULL;
if ($id)
{
$currentgroupmodel = $model->get($id);
$parentGroupModel = $model->get($pid);
$currentGroupModel = null;
if ($id) {
$currentGroupModel = $model->get($id);
}
if (($pid || $parentgroupmodel) && (!$id || $currentgroupmodel))
{
$id = $id ? $id : NULL;
$ruleList = collection(model('AuthRule')->order('weigh', 'desc')->select())->toArray();
if (($pid || $parentGroupModel) && (!$id || $currentGroupModel)) {
$id = $id ? $id : null;
$ruleList = collection(model('AuthRule')->order('weigh', 'desc')->order('id', 'asc')->select())->toArray();
//读取父类角色所有节点列表
$parentRuleList = [];
if (in_array('*', explode(',', $parentgroupmodel->rules)))
{
if (in_array('*', explode(',', $parentGroupModel->rules))) {
$parentRuleList = $ruleList;
}
else
{
$parent_rule_ids = explode(',', $parentgroupmodel->rules);
foreach ($ruleList as $k => $v)
{
if (in_array($v['id'], $parent_rule_ids))
{
} else {
$parentRuleIds = explode(',', $parentGroupModel->rules);
foreach ($ruleList as $k => $v) {
if (in_array($v['id'], $parentRuleIds)) {
$parentRuleList[] = $v;
}
}
}
$ruleTree = new Tree();
$groupTree = new Tree();
//当前所有正常规则列表
Tree::instance()->init($ruleList);
$ruleTree->init($parentRuleList);
//角色组列表
$groupTree->init(collection(model('AuthGroup')->where('id', 'in', $this->childrenGroupIds)->select())->toArray());
//读取当前角色下规则ID集合
$admin_rule_ids = $this->auth->getRuleIds();
$adminRuleIds = $this->auth->getRuleIds();
//是否是超级管理员
$superadmin = $this->auth->isSuperAdmin();
//当前拥有的规则ID集合
$current_rule_ids = $id ? explode(',', $currentgroupmodel->rules) : [];
$currentRuleIds = $id ? explode(',', $currentGroupModel->rules) : [];
if (!$id || !in_array($pid, Tree::instance()->getChildrenIds($id, TRUE)))
{
$ruleList = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0), 'name');
if (!$id || !in_array($pid, $this->childrenGroupIds) || !in_array($pid, $groupTree->getChildrenIds($id, true))) {
$parentRuleList = $ruleTree->getTreeList($ruleTree->getTreeArray(0), 'name');
$hasChildrens = [];
foreach ($ruleList as $k => $v)
{
if ($v['haschild'])
foreach ($parentRuleList as $k => $v) {
if ($v['haschild']) {
$hasChildrens[] = $v['id'];
}
}
$nodelist = [];
foreach ($parentRuleList as $k => $v)
{
if (!$superadmin && !in_array($v['id'], $admin_rule_ids))
$parentRuleIds = array_map(function ($item) {
return $item['id'];
}, $parentRuleList);
$nodeList = [];
foreach ($parentRuleList as $k => $v) {
if (!$superadmin && !in_array($v['id'], $adminRuleIds)) {
continue;
$state = array('selected' => in_array($v['id'], $current_rule_ids) && !in_array($v['id'], $hasChildrens));
$nodelist[] = array('id' => $v['id'], 'parent' => $v['pid'] ? $v['pid'] : '#', 'text' => $v['title'], 'type' => 'menu', 'state' => $state);
}
if ($v['pid'] && !in_array($v['pid'], $parentRuleIds)) {
continue;
}
$state = array('selected' => in_array($v['id'], $currentRuleIds) && !in_array($v['id'], $hasChildrens));
$nodeList[] = array('id' => $v['id'], 'parent' => $v['pid'] ? $v['pid'] : '#', 'text' => __($v['title']), 'type' => 'menu', 'state' => $state);
}
$this->code = 1;
$this->data = $nodelist;
$this->success('', null, $nodeList);
} else {
$this->error(__('Can not change the parent to child'));
}
else
{
$this->code = -1;
$this->data = __('Can not change the parent to child');
}
}
else
{
$this->code = -1;
$this->data = __('Group not found');
} else {
$this->error(__('Group not found'));
}
}
}

View File

@ -2,6 +2,7 @@
namespace app\admin\controller\auth;
use app\admin\model\AuthRule;
use app\common\controller\Backend;
use fast\Tree;
use think\Cache;
@ -9,12 +10,15 @@ use think\Cache;
/**
* 规则管理
*
* @icon fa fa-list
* @icon fa fa-list
* @remark 规则通常对应一个控制器的方法,同时左侧的菜单栏数据也从规则中体现,通常建议通过控制台进行生成规则节点
*/
class Rule extends Backend
{
/**
* @var \app\admin\model\AuthRule
*/
protected $model = null;
protected $rulelist = [];
protected $multiFields = 'ismenu,status';
@ -22,18 +26,29 @@ class Rule extends Backend
public function _initialize()
{
parent::_initialize();
if (!$this->auth->isSuperAdmin()) {
$this->error(__('Access is allowed only to the super management group'));
}
$this->model = model('AuthRule');
// 必须将结果集转换为数组
Tree::instance()->init(collection($this->model->order('weigh', 'desc')->select())->toArray());
$ruleList = \think\Db::name("auth_rule")->field('type,condition,remark,createtime,updatetime', true)->order('weigh DESC,id ASC')->select();
foreach ($ruleList as $k => &$v) {
$v['title'] = __($v['title']);
}
unset($v);
Tree::instance()->init($ruleList)->icon = ['&nbsp;&nbsp;&nbsp;&nbsp;', '&nbsp;&nbsp;&nbsp;&nbsp;', '&nbsp;&nbsp;&nbsp;&nbsp;'];
$this->rulelist = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0), 'title');
$ruledata = [0 => __('None')];
foreach ($this->rulelist as $k => $v)
{
if (!$v['ismenu'])
foreach ($this->rulelist as $k => &$v) {
if (!$v['ismenu']) {
continue;
}
$ruledata[$v['id']] = $v['title'];
unset($v['spacer']);
}
unset($v);
$this->view->assign('ruledata', $ruledata);
$this->view->assign("menutypeList", $this->model->getMenutypeList());
}
/**
@ -41,11 +56,9 @@ class Rule extends Backend
*/
public function index()
{
if ($this->request->isAjax())
{
if ($this->request->isAjax()) {
$list = $this->rulelist;
$total = count($this->rulelist);
$result = array("total" => $total, "rows" => $list);
return json($result);
@ -58,23 +71,21 @@ class Rule extends Backend
*/
public function add()
{
if ($this->request->isPost())
{
$this->code = -1;
if ($this->request->isPost()) {
$this->token();
$params = $this->request->post("row/a", [], 'strip_tags');
if ($params)
{
if (!$params['ismenu'] && !$params['pid'])
{
$this->msg = __('The non-menu rule must have parent');
return;
if ($params) {
if (!$params['ismenu'] && !$params['pid']) {
$this->error(__('The non-menu rule must have parent'));
}
$result = $this->model->validate()->save($params);
if ($result === false) {
$this->error($this->model->getError());
}
$this->model->create($params);
Cache::rm('__menu__');
$this->code = 1;
$this->success();
}
return;
$this->error();
}
return $this->view->fetch();
}
@ -82,28 +93,41 @@ class Rule extends Backend
/**
* 编辑
*/
public function edit($ids = NULL)
public function edit($ids = null)
{
$row = $this->model->get(['id' => $ids]);
if (!$row)
if (!$row) {
$this->error(__('No Results were found'));
if ($this->request->isPost())
{
$this->code = -1;
}
if ($this->request->isPost()) {
$this->token();
$params = $this->request->post("row/a", [], 'strip_tags');
if ($params)
{
if (!$params['ismenu'] && !$params['pid'])
{
$this->msg = __('The non-menu rule must have parent');
return;
if ($params) {
if (!$params['ismenu'] && !$params['pid']) {
$this->error(__('The non-menu rule must have parent'));
}
if ($params['pid'] == $row['id']) {
$this->error(__('Can not change the parent to self'));
}
if ($params['pid'] != $row['pid']) {
$childrenIds = Tree::instance()->init(collection(AuthRule::select())->toArray())->getChildrenIds($row['id']);
if (in_array($params['pid'], $childrenIds)) {
$this->error(__('Can not change the parent to child'));
}
}
//这里需要针对name做唯一验证
$ruleValidate = \think\Loader::validate('AuthRule');
$ruleValidate->rule([
'name' => 'require|unique:AuthRule,name,' . $row->id,
]);
$result = $row->validate()->save($params);
if ($result === false) {
$this->error($row->getError());
}
$row->save($params);
Cache::rm('__menu__');
$this->code = 1;
$this->success();
}
return;
$this->error();
}
$this->view->assign("row", $row);
return $this->view->fetch();
@ -114,24 +138,22 @@ class Rule extends Backend
*/
public function del($ids = "")
{
$this->code = -1;
if ($ids)
{
if (!$this->request->isPost()) {
$this->error(__("Invalid parameters"));
}
$ids = $ids ? $ids : $this->request->post("ids");
if ($ids) {
$delIds = [];
foreach (explode(',', $ids) as $k => $v)
{
$delIds = array_merge($delIds, Tree::instance()->getChildrenIds($v, TRUE));
foreach (explode(',', $ids) as $k => $v) {
$delIds = array_merge($delIds, Tree::instance()->getChildrenIds($v, true));
}
$delIds = array_unique($delIds);
$count = $this->model->where('id', 'in', $delIds)->delete();
if ($count)
{
if ($count) {
Cache::rm('__menu__');
$this->code = 1;
$this->success();
}
}
return;
$this->error();
}
}

View File

@ -1,70 +0,0 @@
<?php
namespace app\admin\controller\example;
use app\common\controller\Backend;
/**
* 表格完整示例
*
* @icon fa fa-table
* @remark 在使用Bootstrap-table中的常用方式,更多使用方式可查看:http://bootstrap-table.wenzhixin.net.cn/zh-cn/
*/
class Bootstraptable extends Backend
{
protected $model = null;
protected $noNeedRight = ['change', 'detail'];
public function _initialize()
{
parent::_initialize();
$this->model = model('AdminLog');
}
/**
* 查看
*/
public function index()
{
if ($this->request->isAjax())
{
list($where, $sort, $order, $offset, $limit) = $this->buildparams(NULL);
$total = $this->model
->where($where)
->order($sort, $order)
->count();
$list = $this->model
->where($where)
->order($sort, $order)
->limit($offset, $limit)
->select();
$result = array("total" => $total, "rows" => $list);
return json($result);
}
return $this->view->fetch();
}
/**
* 详情
*/
public function detail($ids)
{
$row = $this->model->get(['id' => $ids]);
if (!$row)
$this->error(__('No Results were found'));
$this->view->assign("row", $row->toArray());
return $this->view->fetch();
}
/**
* 变更
* @internal
*/
public function change()
{
$this->code = 1;
}
}

View File

@ -1,24 +0,0 @@
<?php
namespace app\admin\controller\example;
use app\common\controller\Backend;
/**
* 彩色角标
*
* @icon fa fa-table
* @remark 在JS端控制角标的显示与隐藏,请注意左侧菜单栏角标的数值变化
*/
class Colorbadge extends Backend
{
protected $model = null;
public function _initialize()
{
parent::_initialize();
$this->model = model('AdminLog');
}
}

View File

@ -1,24 +0,0 @@
<?php
namespace app\admin\controller\example;
use app\common\controller\Backend;
/**
* 控制器间跳转
*
* @icon fa fa-table
* @remark FastAdmin支持在控制器间跳转,点击后将切换到另外一个TAB中,无需刷新当前页面
*/
class Controllerjump extends Backend
{
protected $model = null;
public function _initialize()
{
parent::_initialize();
$this->model = model('AdminLog');
}
}

View File

@ -1,23 +0,0 @@
<?php
namespace app\admin\controller\example;
use app\common\controller\Backend;
/**
* 多级联动
*
* @icon fa fa-table
* @remark FastAdmin使用了jQuery-cxselect实现多级联动,更多文档请参考https://github.com/karsonzhang/cxSelect
*/
class Cxselect extends Backend
{
protected $model = null;
public function _initialize()
{
parent::_initialize();
}
}

View File

@ -1,33 +0,0 @@
<?php
namespace app\admin\controller\example;
use app\common\controller\Backend;
/**
* 多表格示例
*
* @icon fa fa-table
* @remark 当一个页面上存在多个Bootstrap-table时该如何控制按钮和表格
*/
class Multitable extends Backend
{
protected $model = null;
public function _initialize()
{
parent::_initialize();
}
/**
* 查看
*/
public function index()
{
$this->loadlang('general/attachment');
$this->loadlang('general/crontab');
return $this->view->fetch();
}
}

View File

@ -1,52 +0,0 @@
<?php
namespace app\admin\controller\example;
use app\common\controller\Backend;
/**
* 多模型关联
*
* @icon fa fa-table
* @remark 当使用到关联模型时需要重载index方法
*/
class Relationmodel extends Backend
{
protected $model = null;
public function _initialize()
{
parent::_initialize();
$this->model = model('AdminLog');
}
/**
* 查看
*/
public function index()
{
$this->relationSearch = true;
$this->searchFields = "admin.username,id";
if ($this->request->isAjax())
{
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$total = $this->model
->with("admin")
->where($where)
->order($sort, $order)
->count();
$list = $this->model
->with("admin")
->where($where)
->order($sort, $order)
->limit($offset, $limit)
->select();
$result = array("total" => $total, "rows" => $list);
return json($result);
}
return $this->view->fetch();
}
}

View File

@ -1,60 +0,0 @@
<?php
namespace app\admin\controller\example;
use app\common\controller\Backend;
/**
* 表格模板示例
*
* @icon fa fa-table
* @remark 可以通过使用表格模板将表格中的行渲染成一样的展现方式,基于此功能可以任意定制自己想要的展示列表
*/
class Tabletemplate extends Backend
{
protected $model = null;
public function _initialize()
{
parent::_initialize();
$this->model = model('AdminLog');
}
/**
* 查看
*/
public function index()
{
if ($this->request->isAjax())
{
list($where, $sort, $order, $offset, $limit) = $this->buildparams(NULL);
$total = $this->model
->where($where)
->order($sort, $order)
->count();
$list = $this->model
->where($where)
->order($sort, $order)
->limit($offset, $limit)
->select();
$result = array("total" => $total, "rows" => $list);
return json($result);
}
return $this->view->fetch();
}
/**
* 详情
*/
public function detail($ids)
{
$row = $this->model->get(['id' => $ids]);
if (!$row)
$this->error(__('No Results were found'));
$this->view->assign("row", $row->toArray());
return $this->view->fetch();
}
}

View File

@ -1,79 +1,160 @@
<?php
namespace app\admin\controller\general;
use app\common\controller\Backend;
/**
* 附件管理
*
* @icon fa fa-circle-o
* @remark 主要用于管理上传到又拍云的数据或上传至本服务的上传数据
*/
class Attachment extends Backend
{
protected $model = null;
public function _initialize()
{
parent::_initialize();
$this->model = model('Attachment');
}
/**
* 查看
*/
public function index()
{
if ($this->request->isAjax())
{
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$total = $this->model
->where($where)
->order($sort, $order)
->count();
$list = $this->model
->where($where)
->order($sort, $order)
->limit($offset, $limit)
->select();
$cdnurl = preg_replace("/\/(\w+)\.php$/i", '', $this->request->root());
foreach ($list as $k => &$v)
{
$v['fullurl'] = ($v['storage'] == 'local' ? $cdnurl : $this->view->config['upload']['cdnurl']) . $v['url'];
}
unset($v);
$result = array("total" => $total, "rows" => $list);
return json($result);
}
return $this->view->fetch();
}
/**
* 选择附件
*/
public function select()
{
if ($this->request->isAjax())
{
return $this->index();
}
return $this->view->fetch();
}
/**
* 添加
*/
public function add()
{
if ($this->request->isAjax())
{
$this->code = -1;
}
return $this->view->fetch();
}
}
<?php
namespace app\admin\controller\general;
use app\common\controller\Backend;
/**
* 附件管理
*
* @icon fa fa-circle-o
* @remark 主要用于管理上传到服务器或第三方存储的数据
*/
class Attachment extends Backend
{
/**
* @var \app\common\model\Attachment
*/
protected $model = null;
protected $searchFields = 'id,filename,url';
protected $noNeedRight = ['classify'];
public function _initialize()
{
parent::_initialize();
$this->model = model('Attachment');
$this->view->assign("mimetypeList", \app\common\model\Attachment::getMimetypeList());
$this->view->assign("categoryList", \app\common\model\Attachment::getCategoryList());
$this->assignconfig("categoryList", \app\common\model\Attachment::getCategoryList());
}
/**
* 查看
*/
public function index()
{
//设置过滤方法
$this->request->filter(['strip_tags', 'trim']);
if ($this->request->isAjax()) {
$mimetypeQuery = [];
$filter = $this->request->request('filter');
$filterArr = (array)json_decode($filter, true);
if (isset($filterArr['category']) && $filterArr['category'] == 'unclassed') {
$filterArr['category'] = ',unclassed';
$this->request->get(['filter' => json_encode(array_diff_key($filterArr, ['category' => '']))]);
}
if (isset($filterArr['mimetype']) && preg_match("/(\/|\,|\*)/", $filterArr['mimetype'])) {
$mimetype = $filterArr['mimetype'];
$filterArr = array_diff_key($filterArr, ['mimetype' => '']);
$mimetypeQuery = function ($query) use ($mimetype) {
$mimetypeArr = array_filter(explode(',', $mimetype));
foreach ($mimetypeArr as $index => $item) {
$query->whereOr('mimetype', 'like', '%' . str_replace("/*", "/", $item) . '%');
}
};
}
$this->request->get(['filter' => json_encode($filterArr)]);
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$list = $this->model
->where($mimetypeQuery)
->where($where)
->order($sort, $order)
->paginate($limit);
$cdnurl = preg_replace("/\/(\w+)\.php$/i", '', $this->request->root());
foreach ($list as $k => &$v) {
$v['fullurl'] = ($v['storage'] == 'local' ? $cdnurl : $this->view->config['upload']['cdnurl']) . $v['url'];
}
unset($v);
$result = array("total" => $list->total(), "rows" => $list->items());
return json($result);
}
return $this->view->fetch();
}
/**
* 选择附件
*/
public function select()
{
if ($this->request->isAjax()) {
return $this->index();
}
$mimetype = $this->request->get('mimetype', '');
$mimetype = substr($mimetype, -1) === '/' ? $mimetype . '*' : $mimetype;
$this->view->assign('mimetype', $mimetype);
return $this->view->fetch();
}
/**
* 添加
*/
public function add()
{
if ($this->request->isAjax()) {
$this->error();
}
return $this->view->fetch();
}
/**
* 删除附件
* @param array $ids
*/
public function del($ids = "")
{
if (!$this->request->isPost()) {
$this->error(__("Invalid parameters"));
}
$ids = $ids ? $ids : $this->request->post("ids");
if ($ids) {
\think\Hook::add('upload_delete', function ($params) {
if ($params['storage'] == 'local') {
$attachmentFile = ROOT_PATH . '/public' . $params['url'];
if (is_file($attachmentFile)) {
@unlink($attachmentFile);
}
}
});
$attachmentlist = $this->model->where('id', 'in', $ids)->select();
foreach ($attachmentlist as $attachment) {
\think\Hook::listen("upload_delete", $attachment);
$attachment->delete();
}
$this->success();
}
$this->error(__('Parameter %s can not be empty', 'ids'));
}
/**
* 归类
*/
public function classify()
{
if (!$this->auth->check('general/attachment/edit')) {
\think\Hook::listen('admin_nopermission', $this);
$this->error(__('You have no permission'), '');
}
if (!$this->request->isPost()) {
$this->error(__("Invalid parameters"));
}
$category = $this->request->post('category', '');
$ids = $this->request->post('ids');
if (!$ids) {
$this->error(__('Parameter %s can not be empty', 'ids'));
}
$categoryList = \app\common\model\Attachment::getCategoryList();
if ($category && !isset($categoryList[$category])) {
$this->error(__('Category not found'));
}
$category = $category == 'unclassed' ? '' : $category;
\app\common\model\Attachment::where('id', 'in', $ids)->update(['category' => $category]);
$this->success();
}
}

View File

@ -1,263 +1,311 @@
<?php
namespace app\admin\controller\general;
use app\common\controller\Backend;
use app\common\library\Email;
use app\common\model\Config as ConfigModel;
use think\Exception;
/**
* 系统配置
*
* @icon fa fa-circle-o
*/
class Config extends Backend
{
protected $model = null;
protected $noNeedRight = ['check'];
public function _initialize()
{
parent::_initialize();
$this->model = model('Config');
}
public function index()
{
$siteList = [];
$groupList = ConfigModel::getGroupList();
foreach ($groupList as $k => $v)
{
$siteList[$k]['name'] = $k;
$siteList[$k]['title'] = $v;
$siteList[$k]['list'] = [];
}
foreach ($this->model->all() as $k => $v)
{
if (!isset($siteList[$v['group']]))
{
continue;
}
$value = $v->toArray();
if (in_array($value['type'], ['select', 'selects', 'checkbox', 'radio']))
{
$value['value'] = explode(',', $value['value']);
}
if ($value['type'] == 'array')
{
$value['value'] = (array) json_decode($value['value'], TRUE);
}
$value['content'] = json_decode($value['content'], TRUE);
$siteList[$v['group']]['list'][] = $value;
}
$index = 0;
foreach ($siteList as $k => &$v)
{
$v['active'] = !$index ? true : false;
$index++;
}
$this->view->assign('siteList', $siteList);
$this->view->assign('typeList', ConfigModel::getTypeList());
$this->view->assign('groupList', ConfigModel::getGroupList());
return $this->view->fetch();
}
/**
* 添加
*/
public function add()
{
if ($this->request->isPost())
{
$this->code = -1;
$params = $this->request->post("row/a");
if ($params)
{
foreach ($params as $k => &$v)
{
$v = is_array($v) ? implode(',', $v) : $v;
}
try
{
if ($params['content'] && in_array($params['type'], ['select', 'selects', 'checkbox', 'radio']))
{
$content = explode("\r\n", $params['content']);
$arr = [];
foreach ($content as $k => &$v)
{
if (stripos($v, "|") !== false)
{
$item = explode('|', $v);
$arr[$item[0]] = $item[1];
}
}
$params['content'] = $arr ? json_encode($arr, JSON_UNESCAPED_UNICODE) : '';
}
else
{
$params['content'] = '';
}
$result = $this->model->create($params);
if ($result !== false)
{
try
{
$this->refreshFile();
$this->code = 1;
}
catch (Exception $e)
{
$this->msg = $e->getMessage();
}
}
else
{
$this->msg = $this->model->getError();
}
}
catch (Exception $e)
{
$this->msg = $e->getMessage();
}
}
else
{
$this->msg = __('Parameter %s can not be empty', '');
}
return;
}
return $this->view->fetch();
}
public function edit($ids = NULL)
{
$this->code = -1;
if ($this->request->isPost())
{
$params = $this->request->post("row/a");
if ($params)
{
$configList = [];
foreach ($this->model->all() as $k => $v)
{
if (isset($params[$v['name']]))
{
if ($v['type'] == 'array')
{
$fieldarr = $valuearr = [];
$field = $params[$v['name']]['field'];
$value = $params[$v['name']]['value'];
foreach ($field as $m => $n)
{
if ($n != '')
{
$fieldarr[] = $field[$m];
$valuearr[] = $value[$m];
}
}
$params[$v['name']] = array_combine($fieldarr, $valuearr);
$value = json_encode($params[$v['name']], JSON_UNESCAPED_UNICODE);
}
else
{
$value = is_array($params[$v['name']]) ? implode(',', $params[$v['name']]) : $params[$v['name']];
}
$configList[] = ['id' => $v['id'], 'value' => $value];
}
}
$this->model->saveAll($configList);
try
{
$this->refreshFile();
$this->code = 1;
}
catch (Exception $e)
{
$this->msg = $e->getMessage();
}
}
else
{
$this->msg = __('Parameter %s can not be empty', '');
}
return;
}
}
protected function refreshFile()
{
$config = [];
foreach ($this->model->all() as $k => $v)
{
$value = $v->toArray();
if (in_array($value['type'], ['selects', 'checkbox', 'images', 'files']))
{
$value['value'] = explode(',', $value['value']);
}
if ($value['type'] == 'array')
{
$value['value'] = (array) json_decode($value['value'], TRUE);
}
$config[$value['name']] = $value['value'];
}
file_put_contents(APP_PATH . 'extra' . DS . 'site.php', '<?php' . "\n\nreturn " . var_export($config, true) . ";");
}
/**
* @internal
*/
public function check()
{
$params = $this->request->post("row/a");
if ($params)
{
$config = $this->model->get($params);
if (!$config)
{
return json(['ok' => '']);
}
else
{
return json(['error' => __('Name already exist')]);
}
}
else
{
return json(['error' => __('Invalid parameters')]);
}
}
/**
* 发送测试邮件
* @internal
*/
public function emailtest()
{
$receiver = $this->request->request("receiver");
$email = new Email;
$result = $email
->to($receiver)
->subject(__("This is a test mail"))
->message('<div style="min-height:550px; padding: 100px 55px 200px;">' . __('This is a test mail content') . '</div>')
->send();
if ($result)
{
$this->code = 1;
}
else
{
$this->code = -1;
$this->msg = $email->getError();
}
}
}
<?php
namespace app\admin\controller\general;
use app\common\controller\Backend;
use app\common\library\Email;
use app\common\model\Config as ConfigModel;
use think\Cache;
use think\Db;
use think\Exception;
use think\Validate;
/**
* 系统配置
*
* @icon fa fa-cogs
* @remark 可以在此增改系统的变量和分组,也可以自定义分组和变量,如果需要删除请从数据库中删除
*/
class Config extends Backend
{
/**
* @var \app\common\model\Config
*/
protected $model = null;
protected $noNeedRight = ['check', 'rulelist', 'selectpage', 'get_fields_list'];
public function _initialize()
{
parent::_initialize();
// $this->model = model('Config');
$this->model = new ConfigModel;
ConfigModel::event('before_write', function ($row) {
if (isset($row['name']) && $row['name'] == 'name' && preg_match("/fast" . "admin/i", $row['value'])) {
throw new Exception(__("Site name incorrect"));
}
});
}
/**
* 查看
*/
public function index()
{
$siteList = [];
$groupList = ConfigModel::getGroupList();
foreach ($groupList as $k => $v) {
$siteList[$k]['name'] = $k;
$siteList[$k]['title'] = $v;
$siteList[$k]['list'] = [];
}
foreach ($this->model->all() as $k => $v) {
if (!isset($siteList[$v['group']])) {
continue;
}
$value = $v->toArray();
$value['title'] = __($value['title']);
if (in_array($value['type'], ['select', 'selects', 'checkbox', 'radio'])) {
$value['value'] = explode(',', $value['value']);
}
$value['content'] = json_decode($value['content'], true);
if (in_array($value['name'], ['categorytype', 'configgroup', 'attachmentcategory'])) {
$dictValue = (array)json_decode($value['value'], true);
foreach ($dictValue as $index => &$item) {
$item = __($item);
}
unset($item);
$value['value'] = json_encode($dictValue, JSON_UNESCAPED_UNICODE);
}
$value['tip'] = htmlspecialchars($value['tip']);
if ($value['name'] == 'cdnurl') {
//cdnurl不支持在线修改
continue;
}
$siteList[$v['group']]['list'][] = $value;
}
$index = 0;
foreach ($siteList as $k => &$v) {
$v['active'] = !$index ? true : false;
$index++;
}
$this->view->assign('siteList', $siteList);
$this->view->assign('typeList', ConfigModel::getTypeList());
$this->view->assign('ruleList', ConfigModel::getRegexList());
$this->view->assign('groupList', ConfigModel::getGroupList());
return $this->view->fetch();
}
/**
* 添加
*/
public function add()
{
if (!config('app_debug')) {
$this->error(__('Only work at development environment'));
}
if ($this->request->isPost()) {
$this->token();
$params = $this->request->post("row/a", [], 'trim');
if ($params) {
foreach ($params as $k => &$v) {
$v = is_array($v) && $k !== 'setting' ? implode(',', $v) : $v;
}
if (in_array($params['type'], ['select', 'selects', 'checkbox', 'radio', 'array'])) {
$params['content'] = json_encode(ConfigModel::decode($params['content']), JSON_UNESCAPED_UNICODE);
} else {
$params['content'] = '';
}
try {
$result = $this->model->create($params);
} catch (Exception $e) {
$this->error($e->getMessage());
}
if ($result !== false) {
try {
ConfigModel::refreshFile();
} catch (Exception $e) {
$this->error($e->getMessage());
}
$this->success();
} else {
$this->error($this->model->getError());
}
}
$this->error(__('Parameter %s can not be empty', ''));
}
return $this->view->fetch();
}
/**
* 编辑
* @param null $ids
*/
public function edit($ids = null)
{
if ($this->request->isPost()) {
$this->token();
$row = $this->request->post("row/a", [], 'trim');
if ($row) {
$configList = [];
foreach ($this->model->all() as $v) {
if (isset($row[$v['name']])) {
$value = $row[$v['name']];
if (is_array($value) && isset($value['field'])) {
$value = json_encode(ConfigModel::getArrayData($value), JSON_UNESCAPED_UNICODE);
} else {
$value = is_array($value) ? implode(',', $value) : $value;
}
$v['value'] = $value;
$configList[] = $v->toArray();
}
}
try {
$this->model->allowField(true)->saveAll($configList);
} catch (Exception $e) {
$this->error($e->getMessage());
}
try {
ConfigModel::refreshFile();
} catch (Exception $e) {
$this->error($e->getMessage());
}
$this->success();
}
$this->error(__('Parameter %s can not be empty', ''));
}
}
/**
* 删除
* @param string $ids
*/
public function del($ids = "")
{
if (!config('app_debug')) {
$this->error(__('Only work at development environment'));
}
$name = $this->request->post('name');
$config = ConfigModel::getByName($name);
if ($name && $config) {
try {
$config->delete();
ConfigModel::refreshFile();
} catch (Exception $e) {
$this->error($e->getMessage());
}
$this->success();
} else {
$this->error(__('Invalid parameters'));
}
}
/**
* 检测配置项是否存在
* @internal
*/
public function check()
{
$params = $this->request->post("row/a");
if ($params) {
$config = $this->model->get($params);
if (!$config) {
$this->success();
} else {
$this->error(__('Name already exist'));
}
} else {
$this->error(__('Invalid parameters'));
}
}
/**
* 规则列表
* @internal
*/
public function rulelist()
{
//主键
$primarykey = $this->request->request("keyField");
//主键值
$keyValue = $this->request->request("keyValue", "");
$keyValueArr = array_filter(explode(',', $keyValue));
$regexList = \app\common\model\Config::getRegexList();
$list = [];
foreach ($regexList as $k => $v) {
if ($keyValueArr) {
if (in_array($k, $keyValueArr)) {
$list[] = ['id' => $k, 'name' => $v];
}
} else {
$list[] = ['id' => $k, 'name' => $v];
}
}
return json(['list' => $list]);
}
/**
* 发送测试邮件
* @internal
*/
public function emailtest()
{
$row = $this->request->post('row/a');
$receiver = $this->request->post("receiver");
if ($receiver) {
if (!Validate::is($receiver, "email")) {
$this->error(__('Please input correct email'));
}
\think\Config::set('site', array_merge(\think\Config::get('site'), $row));
$email = new Email;
$result = $email
->to($receiver)
->subject(__("This is a test mail", config('site.name')))
->message('<div style="min-height:550px; padding: 100px 55px 200px;">' . __('This is a test mail content', config('site.name')) . '</div>')
->send();
if ($result) {
$this->success();
} else {
$this->error($email->getError());
}
} else {
$this->error(__('Invalid parameters'));
}
}
public function selectpage()
{
$id = $this->request->get("id/d");
$config = \app\common\model\Config::get($id);
if (!$config) {
$this->error(__('Invalid parameters'));
}
$setting = $config['setting'];
//自定义条件
$custom = isset($setting['conditions']) ? (array)json_decode($setting['conditions'], true) : [];
$custom = array_filter($custom);
$this->request->request(['showField' => $setting['field'], 'keyField' => $setting['primarykey'], 'custom' => $custom, 'searchField' => [$setting['field'], $setting['primarykey']]]);
$this->model = \think\Db::connect()->setTable($setting['table']);
return parent::selectpage();
}
/**
* 获取表列表
* @internal
*/
public function get_table_list()
{
$tableList = [];
$dbname = \think\Config::get('database.database');
$tableList = \think\Db::query("SELECT `TABLE_NAME` AS `name`,`TABLE_COMMENT` AS `title` FROM `information_schema`.`TABLES` where `TABLE_SCHEMA` = '{$dbname}';");
$this->success('', null, ['tableList' => $tableList]);
}
/**
* 获取表字段列表
* @internal
*/
public function get_fields_list()
{
$table = $this->request->request('table');
$dbname = \think\Config::get('database.database');
//从数据库中获取表字段信息
$sql = "SELECT `COLUMN_NAME` AS `name`,`COLUMN_COMMENT` AS `title`,`DATA_TYPE` AS `type` FROM `information_schema`.`columns` WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION";
//加载主表的列
$fieldList = Db::query($sql, [$dbname, $table]);
$this->success("", null, ['fieldList' => $fieldList]);
}
}

View File

@ -1,100 +0,0 @@
<?php
namespace app\admin\controller\general;
use app\common\controller\Backend;
use Cron\CronExpression;
/**
* 定时任务
*
* @icon fa fa-tasks
* @remark 类似于Linux的Crontab定时任务,可以按照设定的时间进行任务的执行,目前仅支持三种任务:请求URL、执行SQL、执行Shell
*/
class Crontab extends Backend
{
protected $model = null;
protected $noNeedRight = ['check_schedule', 'get_schedule_future'];
public function _initialize()
{
parent::_initialize();
$this->model = model('Crontab');
$this->view->assign('typedata', \app\common\model\Crontab::getTypeList());
}
/**
* 查看
*/
public function index()
{
if ($this->request->isAjax())
{
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$total = $this->model
->where($where)
->order($sort, $order)
->count();
$list = $this->model
->where($where)
->order($sort, $order)
->limit($offset, $limit)
->select();
foreach ($list as $k => &$v)
{
$cron = CronExpression::factory($v['schedule']);
$v['nexttime'] = $cron->getNextRunDate()->getTimestamp();
}
$result = array("total" => $total, "rows" => $list);
return json($result);
}
return $this->view->fetch();
}
/**
* 判断Crontab格式是否正确
* @internal
*/
public function check_schedule()
{
$row = $this->request->post("row/a");
$schedule = isset($row['schedule']) ? $row['schedule'] : '';
if (CronExpression::isValidExpression($schedule))
{
return json(['ok' => '']);
}
else
{
return json(['error' => __('Crontab format invalid')]);
}
}
/**
* 根据Crontab表达式读取未来七次的时间
* @internal
*/
public function get_schedule_future()
{
$time = [];
$schedule = $this->request->post('schedule');
$days = (int) $this->request->post('days');
try
{
$cron = CronExpression::factory($schedule);
for ($i = 0; $i < $days; $i++)
{
$time[] = $cron->getNextRunDate(null, $i)->format('Y-m-d H:i:s');
}
}
catch (\Exception $e)
{
}
return json(['futuretime' => $time]);
}
}

View File

@ -1,211 +0,0 @@
<?php
namespace app\admin\controller\general;
use app\common\controller\Backend;
use think\Db;
use think\Debug;
/**
* 数据库管理
*
* @icon fa fa-database
* @remark 可在线进行一些简单的数据库表优化或修复,查看表结构和数据。也可以进行SQL语句的操作
*/
class Database extends Backend
{
/**
* 查看
*/
function index()
{
$tables_data_length = $tables_index_length = $tables_free_length = $tables_data_count = 0;
$tables = $list = [];
$list = Db::query("SHOW TABLES");
foreach ($list as $key => $row)
{
$tables[] = ['name' => reset($row), 'rows' => 0];
}
$data['tables'] = $tables;
$data['saved_sql'] = [];
$this->view->assign($data);
return $this->view->fetch();
}
/**
* SQL查询
*/
public function query()
{
$do_action = $this->request->post('do_action');
echo '<style type="text/css">
xmp,body{margin:0;padding:0;line-height:18px;font-size:12px;font-family:"Helvetica Neue", Helvetica, Microsoft Yahei, Hiragino Sans GB, WenQuanYi Micro Hei, sans-serif;}
hr{height:1px;margin:5px 1px;background:#e3e3e3;border:none;}
</style>';
if ($do_action == '')
exit(__('Invalid parameters'));
$tablename = $this->request->post("tablename/a");
if (in_array($do_action, array('doquery', 'optimizeall', 'repairall')))
{
$this->$do_action();
}
else if (count($tablename) == 0)
{
exit(__('Invalid parameters'));
}
else
{
foreach ($tablename as $table)
{
$this->$do_action($table);
echo "<br />";
}
}
}
private function viewinfo($name)
{
$row = Db::query("SHOW CREATE TABLE `{$name}`");
$row = array_values($row[0]);
$info = $row[1];
echo "<xmp>{$info};</xmp>";
}
private function viewdata($name = '')
{
$sqlquery = "SELECT * FROM `{$name}`";
$this->doquery($sqlquery);
}
private function optimize($name = '')
{
if (Db::execute("OPTIMIZE TABLE `{$name}`"))
{
echo __('Optimize table %s done', $name);
}
else
{
echo __('Optimize table %s fail', $name);
}
}
private function optimizeall($name = '')
{
$list = Db::query("SHOW TABLES");
foreach ($list as $key => $row)
{
$name = reset($row);
if (Db::execute("OPTIMIZE TABLE {$name}"))
{
echo __('Optimize table %s done', $name);
}
else
{
echo __('Optimize table %s fail', $name);
}
echo "<br />";
}
}
private function repair($name = '')
{
if (Db::execute("REPAIR TABLE `{$name}`"))
{
echo __('Repair table %s done', $name);
}
else
{
echo __('Repair table %s fail', $name);
}
}
private function repairall($name = '')
{
$list = Db::query("SHOW TABLES");
foreach ($list as $key => $row)
{
$name = reset($row);
if (Db::execute("REPAIR TABLE {$name}"))
{
echo __('Repair table %s done', $name);
}
else
{
echo __('Repair table %s fail', $name);
}
echo "<br />";
}
}
private function doquery($sql = null)
{
$sqlquery = $sql ? $sql : $this->request->post('sqlquery');
if ($sqlquery == '')
exit(__('SQL can not be empty'));
$sqlquery = str_replace("\r", "", $sqlquery);
$sqls = preg_split("/;[ \t]{0,}\n/i", $sqlquery);
$maxreturn = 100;
$r = '';
foreach ($sqls as $key => $val)
{
if (trim($val) == '')
continue;
$val = rtrim($val, ';');
$r .= "SQL<span style='color:green;'>{$val}</span> ";
if (preg_match("/^(select|explain)(.*)/i ", $val))
{
Debug::remark("begin");
$limit = stripos(strtolower($val), "limit") !== false ? true : false;
$count = Db::execute($val);
if ($count > 0)
{
$resultlist = Db::query($val . (!$limit && $count > $maxreturn ? ' LIMIT ' . $maxreturn : ''));
}
else
{
$resultlist = [];
}
Debug::remark("end");
$time = Debug::getRangeTime('begin', 'end', 4);
$usedseconds = __('Query took %s seconds', $time) . "<br />";
if ($count <= 0)
{
$r .= __('Query returned an empty result');
}
else
{
$r .= (__('Total:%s', $count) . (!$limit && $count > $maxreturn ? ',' . __('Max output:%s', $maxreturn) : ""));
}
$r = $r . ',' . $usedseconds;
$j = 0;
foreach ($resultlist as $m => $n)
{
$j++;
if (!$limit && $j > $maxreturn)
break;
$r .= "<hr/>";
$r .= "<font color='red'>" . __('Row:%s', $j) . "</font><br />";
foreach ($n as $k => $v)
{
$r .= "<font color='blue'>{$k}</font>{$v}<br/>\r\n";
}
}
}
else
{
Debug::remark("begin");
$count = Db::execute($val);
Debug::remark("end");
$time = Debug::getRangeTime('begin', 'end', 4);
$r .= __('Query affected %s rows and took %s seconds', $count, $time) . "<br />";
}
}
echo $r;
}
}

View File

@ -1,80 +1,84 @@
<?php
namespace app\admin\controller\general;
use think\Session;
use app\admin\model\AdminLog;
use app\common\controller\Backend;
use fast\Random;
/**
* 个人配置
*
* @icon fa fa-user
*/
class Profile extends Backend
{
/**
* 查看
*/
public function index()
{
if ($this->request->isAjax())
{
$model = model('AdminLog');
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$total = $model
->where($where)
->where('admin_id', $this->auth->id)
->order($sort, $order)
->count();
$list = $model
->where($where)
->where('admin_id', $this->auth->id)
->order($sort, $order)
->limit($offset, $limit)
->select();
$result = array("total" => $total, "rows" => $list);
return json($result);
}
return $this->view->fetch();
}
/**
* 更新个人信息
*/
public function update()
{
if ($this->request->isPost())
{
$this->code = -1;
$params = $this->request->post("row/a");
$params = array_filter(array_intersect_key($params, array_flip(array('email', 'nickname', 'password', 'avatar'))));
unset($v);
if (isset($params['password']))
{
$params['salt'] = Random::alnum();
$params['password'] = md5(md5($params['password']) . $params['salt']);
}
if ($params)
{
model('admin')->where('id', $this->auth->id)->update($params);
//因为个人资料面板读取的Session显示修改自己资料后同时更新Session
$admin = Session::get('admin');
$admin_id = $admin ? $admin->id : 0;
if($this->auth->id==$admin_id){
$admin = model('admin')->get(['id' => $admin_id]);
Session::set("admin", $admin);
}
$this->code = 1;
}
}
return;
}
}
<?php
namespace app\admin\controller\general;
use app\admin\model\Admin;
use app\common\controller\Backend;
use fast\Random;
use think\Session;
use think\Validate;
/**
* 个人配置
*
* @icon fa fa-user
*/
class Profile extends Backend
{
protected $searchFields = 'id,title';
/**
* 查看
*/
public function index()
{
//设置过滤方法
$this->request->filter(['strip_tags', 'trim']);
if ($this->request->isAjax()) {
$this->model = model('AdminLog');
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$list = $this->model
->where($where)
->where('admin_id', $this->auth->id)
->order($sort, $order)
->paginate($limit);
$result = array("total" => $list->total(), "rows" => $list->items());
return json($result);
}
return $this->view->fetch();
}
/**
* 更新个人信息
*/
public function update()
{
if ($this->request->isPost()) {
$this->token();
$params = $this->request->post("row/a");
$params = array_filter(array_intersect_key(
$params,
array_flip(array('email', 'nickname', 'password', 'avatar'))
));
unset($v);
if (!Validate::is($params['email'], "email")) {
$this->error(__("Please input correct email"));
}
if (isset($params['password'])) {
if (!Validate::is($params['password'], "/^[\S]{6,30}$/")) {
$this->error(__("Please input correct password"));
}
$params['salt'] = Random::alnum();
$params['password'] = md5(md5($params['password']) . $params['salt']);
}
$exist = Admin::where('email', $params['email'])->where('id', '<>', $this->auth->id)->find();
if ($exist) {
$this->error(__("Email already exists"));
}
if ($params) {
$admin = Admin::get($this->auth->id);
$admin->save($params);
//因为个人资料面板读取的Session显示修改自己资料后同时更新Session
Session::set("admin", $admin->toArray());
Session::set("admin.safecode", $this->auth->getEncryptSafecode($admin));
$this->success();
}
$this->error();
}
return;
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace app\admin\controller\user;
use app\common\controller\Backend;
/**
* 会员组管理
*
* @icon fa fa-users
*/
class Group extends Backend
{
/**
* @var \app\admin\model\UserGroup
*/
protected $model = null;
public function _initialize()
{
parent::_initialize();
$this->model = model('UserGroup');
$this->view->assign("statusList", $this->model->getStatusList());
}
public function add()
{
if ($this->request->isPost()) {
$this->token();
}
$nodeList = \app\admin\model\UserRule::getTreeList();
$this->assign("nodeList", $nodeList);
return parent::add();
}
public function edit($ids = null)
{
if ($this->request->isPost()) {
$this->token();
}
$row = $this->model->get($ids);
if (!$row) {
$this->error(__('No Results were found'));
}
$rules = explode(',', $row['rules']);
$nodeList = \app\admin\model\UserRule::getTreeList($rules);
$this->assign("nodeList", $nodeList);
return parent::edit($ids);
}
}

View File

@ -0,0 +1,108 @@
<?php
namespace app\admin\controller\user;
use app\common\controller\Backend;
use fast\Tree;
/**
* 会员规则管理
*
* @icon fa fa-circle-o
*/
class Rule extends Backend
{
/**
* @var \app\admin\model\UserRule
*/
protected $model = null;
protected $rulelist = [];
protected $multiFields = 'ismenu,status';
public function _initialize()
{
parent::_initialize();
$this->model = model('UserRule');
$this->view->assign("statusList", $this->model->getStatusList());
// 必须将结果集转换为数组
$ruleList = collection($this->model->order('weigh', 'desc')->select())->toArray();
foreach ($ruleList as $k => &$v) {
$v['title'] = __($v['title']);
$v['remark'] = __($v['remark']);
}
unset($v);
Tree::instance()->init($ruleList)->icon = ['&nbsp;&nbsp;&nbsp;&nbsp;', '&nbsp;&nbsp;&nbsp;&nbsp;', '&nbsp;&nbsp;&nbsp;&nbsp;'];
$this->rulelist = Tree::instance()->getTreeList(Tree::instance()->getTreeArray(0), 'title');
$ruledata = [0 => __('None')];
foreach ($this->rulelist as $k => &$v) {
if (!$v['ismenu']) {
continue;
}
$ruledata[$v['id']] = $v['title'];
}
$this->view->assign('ruledata', $ruledata);
}
/**
* 查看
*/
public function index()
{
if ($this->request->isAjax()) {
$list = $this->rulelist;
$total = count($this->rulelist);
$result = array("total" => $total, "rows" => $list);
return json($result);
}
return $this->view->fetch();
}
/**
* 添加
*/
public function add()
{
if ($this->request->isPost()) {
$this->token();
}
return parent::add();
}
/**
* 编辑
*/
public function edit($ids = null)
{
if ($this->request->isPost()) {
$this->token();
}
return parent::edit($ids);
}
/**
* 删除
*/
public function del($ids = "")
{
if (!$this->request->isPost()) {
$this->error(__("Invalid parameters"));
}
$ids = $ids ? $ids : $this->request->post("ids");
if ($ids) {
$delIds = [];
foreach (explode(',', $ids) as $k => $v) {
$delIds = array_merge($delIds, Tree::instance()->getChildrenIds($v, true));
}
$delIds = array_unique($delIds);
$count = $this->model->where('id', 'in', $delIds)->delete();
if ($count) {
$this->success();
}
}
$this->error();
}
}

View File

@ -0,0 +1,105 @@
<?php
namespace app\admin\controller\user;
use app\common\controller\Backend;
use app\common\library\Auth;
/**
* 会员管理
*
* @icon fa fa-user
*/
class User extends Backend
{
protected $relationSearch = true;
protected $searchFields = 'id,username,nickname';
/**
* @var \app\admin\model\User
*/
protected $model = null;
public function _initialize()
{
parent::_initialize();
$this->model = new \app\admin\model\User;
}
/**
* 查看
*/
public function index()
{
//设置过滤方法
$this->request->filter(['strip_tags', 'trim']);
if ($this->request->isAjax()) {
//如果发送的来源是Selectpage则转发到Selectpage
if ($this->request->request('keyField')) {
return $this->selectpage();
}
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
$list = $this->model
->with('group')
->where($where)
->order($sort, $order)
->paginate($limit);
foreach ($list as $k => $v) {
$v->avatar = $v->avatar ? cdnurl($v->avatar, true) : letter_avatar($v->nickname);
$v->hidden(['password', 'salt']);
}
$result = array("total" => $list->total(), "rows" => $list->items());
return json($result);
}
return $this->view->fetch();
}
/**
* 添加
*/
public function add()
{
if ($this->request->isPost()) {
$this->token();
}
return parent::add();
}
/**
* 编辑
*/
public function edit($ids = null)
{
if ($this->request->isPost()) {
$this->token();
}
$row = $this->model->get($ids);
$this->modelValidate = true;
if (!$row) {
$this->error(__('No Results were found'));
}
$this->view->assign('groupList', build_select('row[group_id]', \app\admin\model\UserGroup::column('id,name'), $row['group_id'], ['class' => 'form-control selectpicker']));
return parent::edit($ids);
}
/**
* 删除
*/
public function del($ids = "")
{
if (!$this->request->isPost()) {
$this->error(__("Invalid parameters"));
}
$ids = $ids ? $ids : $this->request->post("ids");
$row = $this->model->get($ids);
$this->modelValidate = true;
if (!$row) {
$this->error(__('No Results were found'));
}
Auth::instance()->delete($row['id']);
$this->success();
}
}

View File

@ -1,74 +0,0 @@
<?php
namespace app\admin\controller\wechat;
use app\common\controller\Backend;
use app\common\model\WechatResponse;
/**
* 微信自动回复管理
*
* @icon fa fa-circle-o
*/
class Autoreply extends Backend
{
protected $model = null;
protected $noNeedRight = ['check_text_unique'];
public function _initialize()
{
parent::_initialize();
$this->model = model('WechatAutoreply');
}
/**
* 编辑
*/
public function edit($ids = NULL)
{
$row = $this->model->get(['id' => $ids]);
if (!$row)
$this->error(__('No Results were found'));
if ($this->request->isPost())
{
$this->code = -1;
$params = $this->request->post("row/a");
if ($params)
{
$row->save($params);
$this->code = 1;
}
return;
}
$response = WechatResponse::get(['eventkey' => $row['eventkey']]);
$this->view->assign("response", $response);
$this->view->assign("row", $row);
return $this->view->fetch();
}
/**
* 判断文本是否唯一
* @internal
*/
public function check_text_unique()
{
$row = $this->request->post("row/a");
$except = $this->request->post("except");
$text = isset($row['text']) ? $row['text'] : '';
if ($this->model->where('text', $text)->where(function($query) use($except) {
if ($except)
{
$query->where('text', '<>', $except);
}
})->count() == 0)
{
return json(['ok' => '']);
}
else
{
return json(['error' => __('Text already exists')]);
}
}
}

View File

@ -1,164 +0,0 @@
<?php
namespace app\admin\controller\wechat;
use app\common\controller\Backend;
use think\Controller;
use think\Request;
/**
* 微信配置管理
*
* @icon fa fa-circle-o
*/
class Config extends Backend
{
protected $model = null;
public function _initialize()
{
parent::_initialize();
$this->model = model('WechatConfig');
}
/**
* 添加
*/
public function add()
{
if ($this->request->isPost())
{
$this->code = -1;
$params = $this->request->post("row/a");
if ($params)
{
foreach ($params as $k => &$v)
{
$v = is_array($v) ? implode(',', $v) : $v;
}
if ($params['mode'] == 'json')
{
//JSON字段
$fieldarr = $valuearr = [];
$field = $this->request->post('field/a');
$value = $this->request->post('value/a');
foreach ($field as $k => $v)
{
if ($v != '')
{
$fieldarr[] = $field[$k];
$valuearr[] = $value[$k];
}
}
$params['value'] = json_encode(array_combine($fieldarr, $valuearr), JSON_UNESCAPED_UNICODE);
}
unset($params['mode']);
try
{
//是否采用模型验证
if ($this->modelValidate)
{
$name = basename(str_replace('\\', '/', get_class($this->model)));
$validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.add' : true) : $this->modelValidate;
$this->model->validate($validate);
}
$result = $this->model->save($params);
if ($result !== false)
{
$this->code = 1;
}
else
{
$this->msg = $this->model->getError();
}
}
catch (\think\exception\PDOException $e)
{
$this->msg = $e->getMessage();
}
}
else
{
$this->msg = __('Parameter %s can not be empty', '');
}
return;
}
return $this->view->fetch();
}
/**
* 编辑
*/
public function edit($ids = NULL)
{
$row = $this->model->get($ids);
if (!$row)
$this->error(__('No Results were found'));
if ($this->request->isPost())
{
$this->code = -1;
$params = $this->request->post("row/a");
if ($params)
{
foreach ($params as $k => &$v)
{
$v = is_array($v) ? implode(',', $v) : $v;
}
if ($params['mode'] == 'json')
{
//JSON字段
$fieldarr = $valuearr = [];
$field = $this->request->post('field/a');
$value = $this->request->post('value/a');
foreach ($field as $k => $v)
{
if ($v != '')
{
$fieldarr[] = $field[$k];
$valuearr[] = $value[$k];
}
}
$params['value'] = json_encode(array_combine($fieldarr, $valuearr), JSON_UNESCAPED_UNICODE);
}
unset($params['mode']);
try
{
//是否采用模型验证
if ($this->modelValidate)
{
$name = basename(str_replace('\\', '/', get_class($this->model)));
$validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.add' : true) : $this->modelValidate;
$row->validate($validate);
}
$result = $row->save($params);
if ($result !== false)
{
$this->code = 1;
}
else
{
$this->msg = $row->getError();
}
}
catch (think\exception\PDOException $e)
{
$this->msg = $e->getMessage();
}
}
else
{
$this->msg = __('Parameter %s can not be empty', '');
}
return;
}
$this->view->assign("row", $row);
$this->view->assign("value", (array) json_decode($row->value, true));
return $this->view->fetch();
}
}

View File

@ -1,111 +0,0 @@
<?php
namespace app\admin\controller\wechat;
use app\common\controller\Backend;
use app\common\model\WechatResponse;
use EasyWeChat\Foundation\Application;
use think\Config;
use think\Exception;
/**
* 菜单管理
*
* @icon fa fa-list-alt
*/
class Menu extends Backend
{
protected $wechatcfg = NULL;
public function _initialize()
{
parent::_initialize();
$this->wechatcfg = \app\common\model\WechatConfig::get(['name' => 'menu']);
}
/**
* 查看
*/
public function index()
{
$responselist = array();
$all = WechatResponse::all();
foreach ($all as $k => $v)
{
$responselist[$v['eventkey']] = $v['title'];
}
$this->view->assign('responselist', $responselist);
$this->view->assign('menu', (array) json_decode($this->wechatcfg->value, TRUE));
return $this->view->fetch();
}
/**
* 修改
*/
public function edit($ids = NULL)
{
$menu = $this->request->post("menu");
$menu = (array) json_decode($menu, TRUE);
$this->wechatcfg->value = json_encode($menu, JSON_UNESCAPED_UNICODE);
$this->wechatcfg->save();
$this->code = 1;
return;
}
/**
* 同步
*/
public function sync($ids = NULL)
{
$this->code = -1;
$app = new Application(Config::get('wechat'));
try
{
$hasError = false;
$menu = json_decode($this->wechatcfg->value, TRUE);
foreach ($menu as $k => $v)
{
if (isset($v['sub_button']))
{
foreach ($v['sub_button'] as $m => $n)
{
if (isset($n['key']) && !$n['key'])
{
$hasError = true;
break 2;
}
}
}
else if (isset($v['key']) && !$v['key'])
{
$hasError = true;
break;
}
}
if (!$hasError)
{
$ret = $app->menu->add($menu);
if ($ret->errcode == 0)
{
$this->code = 1;
}
else
{
$this->msg = $ret->errmsg;
}
}
else
{
$this->msg = __('Invalid parameters');
}
}
catch (Exception $e)
{
$this->msg = $e->getMessage();
}
return;
}
}

View File

@ -1,86 +0,0 @@
<?php
namespace app\admin\controller\wechat;
use app\common\controller\Backend;
use fast\service\Wechat;
/**
* 资源管理
*
* @icon fa fa-list-alt
*/
class Response extends Backend
{
protected $model = null;
protected $searchFields = 'id,title';
public function _initialize()
{
parent::_initialize();
$this->model = model('WechatResponse');
}
/**
* 选择素材
*/
public function select()
{
return $this->view->fetch();
}
/**
* 添加
*/
public function add()
{
if ($this->request->isPost())
{
$this->code = -1;
$params = $this->request->post("row/a");
$params['eventkey'] = isset($params['eventkey']) && $params['eventkey'] ? $params['eventkey'] : uniqid();
$params['content'] = json_encode($params['content']);
$params['createtime'] = time();
if ($params)
{
$this->model->save($params);
$this->code = 1;
$this->content = $params;
}
return;
}
$appConfig = Wechat::appConfig();
$this->view->applist = $appConfig;
return $this->view->fetch();
}
/**
* 编辑
*/
public function edit($ids = NULL)
{
$row = $this->model->get($ids);
if (!$row)
$this->error(__('No Results were found'));
if ($this->request->isPost())
{
$this->code = -1;
$params = $this->request->post("row/a");
$params['eventkey'] = isset($params['eventkey']) && $params['eventkey'] ? $params['eventkey'] : uniqid();
$params['content'] = json_encode($params['content']);
if ($params)
{
$row->save($params);
$this->code = 1;
}
return;
}
$this->view->assign("row", $row);
$appConfig = Wechat::appConfig();
$this->view->applist = $appConfig;
return $this->view->fetch();
}
}

139
application/admin/lang/zh-cn.php 100644 → 100755
View File

@ -4,17 +4,26 @@ return [
'User id' => '会员ID',
'Username' => '用户名',
'Nickname' => '昵称',
'Mobile' => '手机',
'Email' => '邮箱',
'Password' => '密码',
'Sign up' => '注 册',
'Sign in' => '登 录',
'Sign out' => '注 销',
'Sign out' => '退 出',
'Keep login' => '保持会话',
'Guest' => '游客',
'Welcome' => '%s你好',
'View' => '查看',
'Add' => '添加',
'Edit' => '编辑',
'Del' => '删除',
'Delete' => '删除',
'Import' => '导入',
'Export' => '导出',
'All' => '全部',
'Detail' => '详情',
'Multi' => '批量更新',
'Setting' => '配置',
'Move' => '移动',
'Name' => '名称',
'Status' => '状态',
@ -25,8 +34,19 @@ return [
'Article' => '文章',
'Page' => '单页',
'OK' => '确定',
'Apply' => '应用',
'Cancel' => '取消',
'Clear' => '清空',
'Custom Range' => '自定义',
'Today' => '今天',
'Yesterday' => '昨天',
'Last 7 days' => '最近7天',
'Last 30 days' => '最近30天',
'Last month' => '上月',
'This month' => '本月',
'Loading' => '加载中',
'Money' => '余额',
'Score' => '积分',
'More' => '更多',
'Yes' => '是',
'No' => '否',
@ -38,8 +58,11 @@ return [
'Execute' => '执行',
'Close' => '关闭',
'Choose' => '选择',
'Go' => '跳转',
'Search' => '搜索',
'Refresh' => '刷新',
'Install' => '安装',
'Uninstall' => '卸载',
'First' => '首页',
'Previous' => '上一页',
'Next' => '下一页',
@ -48,25 +71,21 @@ return [
'Home' => '主页',
'Online' => '在线',
'Login' => '登录',
'Logout' => '注销',
'Logout' => '退出',
'Profile' => '个人资料',
'Index' => '首页',
'Hot' => '热门',
'Recommend' => '推荐',
'Dashboard' => '控制台',
'Upload' => '上传',
'Uploading' => '上传中',
'Code' => '编号',
'Message' => '内容',
'Line' => '行号',
'File' => '文件',
'Menu' => '菜单',
'Name' => '名称',
'Weigh' => '权重',
'Type' => '类型',
'Title' => '标题',
'Content' => '内容',
'Status' => '状态',
'Operate' => '操作',
'Append' => '追加',
'Select' => '选择',
'Memo' => '备注',
@ -79,18 +98,17 @@ return [
'End time' => '结束时间',
'Create time' => '创建时间',
'Update time' => '更新时间',
'Createtime' => '创建时间',
'Updatetime' => '更新时间',
'Deletetime' => '删除时间',
'Flag' => '标志',
'Drag to sort' => '拖动进行排序',
'Redirect now' => '立即跳转',
'Operation completed' => '操作成功!',
'Operation failed' => '操作失败!',
'Unknown data format' => '未知的数据格式!',
'Network error' => '网络错误!',
'Auth manager' => '权限管理',
'General manager' => '常规管理',
'Example manager' => '示例管理',
'Wechat manager' => '微信管理',
'Key' => '键',
'Value' => '值',
'Common search' => '普通搜索',
'Search %s' => '搜索 %s',
'View %s' => '查看 %s',
'%d second%s ago' => '%d秒前',
'%d minute%s ago' => '%d分钟前',
'%d hour%s ago' => '%d小时前',
@ -98,19 +116,108 @@ return [
'%d week%s ago' => '%d周前',
'%d month%s ago' => '%d月前',
'%d year%s ago' => '%d年前',
//
'%d second%s after' => '%d秒后',
'%d minute%s after' => '%d分钟后',
'%d hour%s after' => '%d小时后',
'%d day%s after' => '%d天后',
'%d week%s after' => '%d周后',
'%d month%s after' => '%d月后',
'%d year%s after' => '%d年后',
'Set to normal' => '设为正常',
'Set to hidden' => '设为隐藏',
'Set status to normal' => '设为正常',
'Set status to hidden' => '设为隐藏',
'Recycle bin' => '回收站',
'Restore' => '还原',
'Restore all' => '还原全部',
'Destroy' => '销毁',
'Destroy all' => '清空回收站',
'Nothing need restore' => '没有需要还原的数据',
//提示
'Go back' => '返回首页',
'Jump now' => '立即跳转',
'Click to search %s' => '点击搜索 %s',
'Click to toggle' => '点击切换',
'Operation completed' => '操作成功!',
'Operation failed' => '操作失败!',
'Unknown data format' => '未知的数据格式!',
'Network error' => '网络错误!',
'Invalid parameters' => '未知参数',
'No results were found' => '记录未找到',
'No rows were inserted' => '未插入任何行',
'No rows were deleted' => '未删除任何行',
'No rows were updated' => '未更新任何行',
'Parameter %s can not be empty' => '参数%s不能为空',
'Are you sure you want to delete the %s selected item?' => '确定删除选中的 %s 项?',
'Are you sure you want to delete this item?' => '确定删除此项?',
'Are you sure you want to delete or turncate?' => '确定删除或清空?',
'Are you sure you want to truncate?' => '确定清空?',
'Token verification error' => 'Token验证错误',
'You have no permission' => '你没有权限访问',
'Please enter your username' => '请输入你的用户名',
'Please enter your password' => '请输入你的密码',
'Please login first' => '请登录后操作',
'Uploaded successful' => '上传成功',
'You can upload up to %d file%s' => '你最多还可以上传%d个文件',
'You can choose up to %d file%s' => '你最多还可以选择%d个文件',
'Chunk file write error' => '分片写入失败',
'Chunk file info error' => '分片文件错误',
'Chunk file merge error' => '分片合并错误',
'Chunk file disabled' => '未开启分片上传功能',
'Cancel upload' => '取消上传',
'Upload canceled' => '上传已取消',
'No file upload or server upload limit exceeded' => '未上传文件或超出服务器上传限制',
'Uploaded file format is limited' => '上传文件格式受限制',
'Uploaded file is not a valid image' => '上传文件不是有效的图片文件',
'Are you sure you want to cancel this upload?' => '确定取消上传?',
'Remove file' => '移除文件',
'You can only upload a maximum of %s files' => '你最多允许上传 %s 个文件',
'You can\'t upload files of this type' => '不允许上传的文件类型',
'Server responded with %s code' => '服务端响应(Code:%s)',
'File is too big (%sMiB), Max filesize: %sMiB' => '当前上传(%sM),最大允许上传文件大小:%sM',
'An unexpected error occurred' => '发生了一个意外错误,程序猿正在紧急处理中',
'This page will be re-directed in %s seconds' => '页面将在 %s 秒后自动跳转',
'Click to uncheck all' => '点击取消全部',
'Multiple selection mode: %s checked' => '跨页选择模式,已选 %s 项',
//菜单
'Dashboard' => '控制台',
'General' => '常规管理',
'Category' => '分类管理',
'Addon' => '插件管理',
'Auth' => '权限管理',
'Config' => '系统配置',
'Attachment' => '附件管理',
'Admin' => '管理员管理',
'Admin log' => '管理员日志',
'Group' => '角色组',
'Rule' => '菜单规则',
'User' => '会员管理',
'User group' => '会员分组',
'User rule' => '会员规则',
'Select attachment' => '选择附件',
'Update profile' => '更新个人信息',
'Local install' => '本地安装',
'Update state' => '禁用启用',
'Admin group' => '超级管理组',
'Second group' => '二级管理组',
'Third group' => '三级管理组',
'Second group 2' => '二级管理组2',
'Third group 2' => '三级管理组2',
'Dashboard tips' => '用于展示当前系统中的统计数据、统计报表及重要实时数据',
'Config tips' => '可以在此增改系统的变量和分组,也可以自定义分组和变量',
'Category tips' => '分类类型请在常规管理->系统配置->字典配置中添加',
'Attachment tips' => '主要用于管理上传到服务器或第三方存储的数据',
'Addon tips' => '可在线安装、卸载、禁用、启用、配置、升级插件,插件升级前请做好备份。',
'Admin tips' => '一个管理员可以有多个角色组,左侧的菜单根据管理员所拥有的权限进行生成',
'Admin log tips' => '管理员可以查看自己所拥有的权限的管理员日志',
'Group tips' => '角色组可以有多个,角色有上下级层级关系,如果子角色有角色组和管理员的权限则可以派生属于自己组别的下级角色组或管理员',
'Rule tips' => '菜单规则通常对应一个控制器的方法,同时菜单栏数据也从规则中获取',
'Access is allowed only to the super management group' => '仅超级管理组能访问',
'Local addon' => '本地插件',
// 前台菜单
'Frontend' => '前台',
'API Interface' => 'API接口',
'User Module' => '会员模块',
'Register' => '注册',
'User Center' => '会员中心',
];

View File

@ -0,0 +1,114 @@
<?php
return [
'Id' => 'ID',
'Title' => '名称',
'Value' => '配置值',
'Array key' => '键',
'Array value' => '值',
'File' => '文件',
'Donate' => '打赏作者',
'Warmtips' => '温馨提示',
'Pay now' => '立即支付',
'Local install' => '本地安装',
'Refresh addon cache' => '刷新插件缓存',
'Userinfo' => '会员信息',
'Reload authorization' => '刷新授权',
'Local addon' => '本地插件',
'Conflict tips' => '此插件中发现和现有系统中部分文件发现冲突!以下文件将会被影响,请备份好相关文件后再继续操作',
'Pay tips' => '扫码支付后如果仍然无法安装,请不要重复支付,请稍后再重试安装!',
'Pay successful tips' => '购买成功!请点击继续安装按钮完成安装!',
'Pay click tips' => '请点击这里在新窗口中进行支付!',
'Pay new window tips' => '请在新弹出的窗口中进行支付,支付完成后再重新点击安装按钮进行安装!',
'Upgrade tips' => '确认升级<b>《%s》</b><p class="text-danger">1、请务必做好代码和数据库备份备份备份<br>2、升级后如出现冗余数据请根据需要移除即可!<br>3、不建议在生产环境升级请在本地完成升级测试</p>如有重要数据请备份后再操作!',
'Offline installed tips' => '安装成功!清除浏览器缓存和框架缓存后生效!',
'Online installed tips' => '安装成功!清除浏览器缓存和框架缓存后生效!',
'Please login and try to install' => '请登录FastAdmin后再进行本地安装',
'Not installed tips' => '请安装后再访问插件前台页面!',
'Not enabled tips' => '插件已经禁用,请启用后再访问插件前台页面!',
'New version tips' => '发现新版本:%s 点击查看更新日志',
'Testdata tips' => '你还可以继续导入测试数据!',
'Import testdata' => '导入测试数据',
'Skip testdata' => '暂不导入',
'Store not available tips' => '插件市场暂不可用,是否切换到本地插件?',
'Switch to the local' => '切换到本地插件',
'try to reload' => '重新尝试加载',
'Please disable addon first' => '请先禁用插件再进行操作',
'Please disable the add before trying to upgrade' => '请先禁用插件再进行升级',
'Please disable the add before trying to uninstall' => '请先禁用插件再进行卸载',
'Login now' => '立即登录',
'Continue install' => '继续安装',
'View addon home page' => '查看插件介绍和帮助',
'View addon index page' => '查看插件前台首页',
'View addon screenshots' => '点击查看插件截图',
'Click to toggle status' => '点击切换插件状态',
'Click to contact developer' => '点击与插件开发者取得联系',
'Continue installation' => '继续安装',
'My addons' => '我购买的插件',
'Index' => '前台',
'All' => '全部',
'Uncategoried' => '未归类',
'Recommend' => '推荐',
'Hot' => '热门',
'New' => '新',
'Paying' => '付费',
'Free' => '免费',
'Sale' => '折扣',
'No image' => '暂无缩略图',
'Price' => '价格',
'Downloads' => '下载',
'Author' => '作者',
'Identify' => '标识',
'Homepage' => '主页',
'Intro' => '介绍',
'Version' => '版本',
'New version' => '新版本',
'Createtime' => '添加时间',
'Releasetime' => '更新时间',
'Detail' => '插件详情',
'Document' => '文档',
'Demo' => '演示',
'Feedback' => '反馈BUG',
'Install' => '安装',
'Uninstall' => '卸载',
'Upgrade' => '升级',
'Setting' => '配置',
'Disable' => '禁用',
'Enable' => '启用',
'Your username or email' => '你的手机号、用户名或邮箱',
'Your password' => '你的密码',
'Login' => '登录',
'Logout' => '退出登录',
'Register' => '注册账号',
'You\'re not login' => '当前未登录',
'Continue uninstall' => '继续卸载',
'Continue operate' => '继续操作',
'Install successful' => '安装成功',
'Uninstall successful' => '卸载成功',
'Operate successful' => '操作成功',
'Import successful' => '导入测试数据成功!清除浏览器缓存和框架缓存后生效!',
'Initialize successful' => '初始化成功',
'Initialize template not found' => '初始化模板未找到',
'Addon name incorrect' => '插件名称不正确',
'Addon info file was not found' => '插件配置文件未找到',
'Addon info file data incorrect' => '插件配置信息不正确',
'Addon already exists' => '插件已经存在',
'Addon not exists' => '插件不存在',
'Addon package download failed' => '插件下载失败',
'Conflicting file found' => '发现冲突文件',
'Invalid addon package' => '未验证的插件',
'No initialize method' => '未找到初始化方法',
'No permission to write temporary files' => '没有权限写入临时文件',
'The addon file does not exist' => '插件主启动程序不存在',
'The configuration file content is incorrect' => '配置文件不完整',
'Unable to open the zip file' => '无法打开ZIP文件',
'Unable to extract the file' => '无法解压ZIP文件',
'Unable to open file \'%s\' for writing' => '文件(%s)没有写入权限',
'Are you sure you want to unstall %s?' => '确认卸载<b>《%s》</b>?',
'Are you sure you want to refresh authorization?' => '确认刷新应用插件授权?',
'Delete all the addon file and cannot be recovered!' => '卸载将会删除所有插件文件且不可找回!!!',
'Delete all the addon database and cannot be recovered!' => '删除所有插件相关数据表且不可找回!!!',
'Please backup important data manually before uninstall!' => '如有重要数据请备份后再操作!!!',
'The following data tables will be deleted' => '以下插件数据表将会被删除',
'The Addon did not create a data table' => '插件未创建任何数据表',
];

View File

@ -0,0 +1,3 @@
<?php
return [];

View File

@ -1,6 +1,14 @@
<?php
return [
'Group' => '所属组别',
'Login time' => '最后登录',
'Email' => '电子邮箱',
'Mobile' => '手机号',
'Group' => '所属组别',
'Loginfailure' => '登录失败次数',
'Login time' => '最后登录',
'The parent group exceeds permission limit' => '父组别超出权限范围',
'Please input correct username' => '用户名只能由3-30位数字、字母、下划线组合',
'Username must be 3 to 30 characters' => '用户名只能由3-30位数字、字母、下划线组合',
'Please input correct password' => '密码长度必须在6-30位之间不能包含空格',
'Password must be 6 to 30 characters' => '密码长度必须在6-30位之间不能包含空格',
];

View File

@ -5,6 +5,8 @@ return [
'The parent group can not found' => '父组别未找到',
'Group not found' => '组别未找到',
'Can not change the parent to child' => '父组别不能是它的子组别',
'Can not change the parent to self' => '父组别不能是它的子组别',
'Can not change the parent to self' => '父组别不能是它自己',
'You can not delete group that contain child group and administrators' => '你不能删除含有子组和管理员的组',
'The parent group exceeds permission limit' => '父组别超出权限范围',
'The parent group can not be its own child or itself' => '父组别不能是它的子组别及本身',
];

View File

@ -1,13 +1,28 @@
<?php
return [
'Toggle all' => '显示全部',
'Condition' => '规则条件',
'Remark' => '备注',
'Icon' => '图标',
'Alert' => '警告',
'Name' => '规则URL',
'Ismenu' => '菜单',
'The non-menu rule must have parent' => '非菜单规则节点必须有父级',
'If not necessary, use the command line to build rule' => '非必要情况下请直接使用命令行php think menu来生成',
'Toggle all' => '显示全部',
'Condition' => '规则条件',
'Remark' => '备注',
'Icon' => '图标',
'Alert' => '警告',
'Name' => '规则',
'Controller/Action' => '控制器名/方法名',
'Ismenu' => '菜单',
'Menutype' => '菜单类型',
'Addtabs' => '选项卡(默认)',
'Dialog' => '弹窗',
'Ajax' => 'Ajax请求',
'Blank' => '链接',
'Extend' => '扩展属性',
'Search icon' => '搜索图标',
'Toggle menu visible' => '点击切换菜单显示',
'Toggle sub menu' => '点击切换子菜单',
'Menu tips' => '父级菜单无需匹配控制器和方法,子级菜单请使用控制器名',
'Node tips' => '控制器/方法名,如果有目录请使用 目录名/控制器名/方法名',
'Url tips' => '一般情况下留空即可,如果是外部链接或相对链接请输入',
'The non-menu rule must have parent' => '非菜单规则节点必须有父级',
'Can not change the parent to child' => '父级不能是它的子级',
'Can not change the parent to self' => '父级不能是它自己',
'Name only supports letters, numbers, underscore and slash' => 'URL规则只能是小写字母、数字、下划线和/组成',
];

View File

@ -1,15 +1,18 @@
<?php
return [
'Id' => 'ID',
'Pid' => '父ID',
'Type' => '栏目类型',
'Image' => '图片',
'Keywords' => '关键字',
'Description' => '描述',
'Diyname' => '自定义名称',
'Createtime' => '创建时间',
'Updatetime' => '更新时间',
'Weigh' => '权重',
'Status' => '状态'
'Id' => 'ID',
'Pid' => '父ID',
'Type' => '类型',
'All' => '全部',
'Image' => '图片',
'Keywords' => '关键字',
'Description' => '描述',
'Diyname' => '自定义名称',
'Createtime' => '创建时间',
'Updatetime' => '更新时间',
'Weigh' => '权重',
'Category warmtips' => '温馨提示:栏目类型请前往<b>常规管理</b>-><b>系统配置</b>-><b>字典配置</b>中进行管理',
'Can not change the parent to child or itself' => '父组别不能是它的子组别或它自己',
'Status' => '状态'
];

View File

@ -0,0 +1,50 @@
<?php
return [
'Custom' => '自定义',
'Pid' => '父ID',
'Type' => '栏目类型',
'Image' => '图片',
'Total user' => '总会员数',
'Total addon' => '总插件数',
'Total category' => '总分类数',
'Total attachment' => '总附件数',
'Total admin' => '总管理员数',
'Today user signup' => '今日注册',
'Today user login' => '今日登录',
'Today order' => '今日订单',
'Unsettle order' => '未处理订单',
'Three dnu' => '三日新增',
'Seven dnu' => '七日新增',
'Seven dau' => '七日活跃',
'Thirty dau' => '月活跃',
'Custom zone' => '这里是你的自定义数据',
'Register user' => '注册用户数',
'Real time' => '实时',
'Category count' => '分类统计',
'Working addon count' => '运行中的插件',
'Category count tips' => '当前分类总记录数',
'Working addon count tips' => '当前运行中的插件数',
'Database count' => '数据库统计',
'Database table nums' => '数据表数量',
'Database size' => '占用空间',
'Attachment count' => '附件统计',
'Attachment nums' => '附件数量',
'Attachment size' => '附件大小',
'Attachment count tips' => '当前上传的附件数量',
'Picture count' => '图片统计',
'Picture nums' => '图片数量',
'Picture size' => '图片大小',
'Server info' => '服务器信息',
'PHP version' => 'PHP版本',
'Sapi name' => '运行方式',
'Debug mode' => '调试模式',
'Software' => '环境信息',
'Upload mode' => '上传模式',
'Upload url' => '上传URL',
'Upload cdn url' => '上传CDN',
'Cdn url' => '静态资源CDN',
'Timezone' => '时区',
'Language' => '语言',
'View more' => '查看更多',
];

View File

@ -1,17 +1,41 @@
<?php
return [
'Url' => '物理路径',
'Imagewidth' => '宽度',
'Imageheight' => '宽度',
'Imagetype' => '图片类型',
'Imageframes' => '图片帧数',
'Preview' => '预览',
'Filesize' => '文件大小',
'Mimetype' => 'Mime类型',
'Extparam' => '透传数据',
'Createtime' => '创建日期',
'Uploadtime' => '上传时间',
'Storage' => '存储引擎',
'Upload by summernote' => '从编辑器上传'
'Id' => 'ID',
'Admin_id' => '管理员ID',
'User_id' => '会员ID',
'Url' => '物理路径',
'Imagewidth' => '宽度',
'Imageheight' => '高度',
'Imagetype' => '图片类型',
'Imageframes' => '图片帧数',
'Preview' => '预览',
'Filename' => '文件名',
'Filesize' => '文件大小',
'Mimetype' => 'Mime类型',
'Image' => '图片',
'Audio' => '音频',
'Video' => '视频',
'Text' => '文档',
'Application' => '应用',
'Zip' => '压缩包',
'Extparam' => '透传数据',
'Createtime' => '创建日期',
'Uploadtime' => '上传时间',
'Storage' => '存储引擎',
'Category1' => '分类一',
'Category2' => '分类二',
'Custom' => '自定义',
'Unclassed' => '未归类',
'Category' => '类别',
'Classify' => '归类',
'Filter Type' => '类型筛选',
'Upload to third' => '上传到第三方',
'Upload to local' => '上传到本地',
'Upload to third by chunk' => '上传到第三方(分片模式)',
'Upload to local by chunk' => '上传到本地(分片模式)',
'Please enter a new name' => '请输入新的类别名称',
'Please select category' => '请选择一个类别',
'Category not found' => '指定的类别未找到',
'Upload from editor' => '从编辑器上传'
];

View File

@ -1,39 +1,83 @@
<?php
return [
'Name' => '变量名',
'Tip' => '提示信息',
'Group' => '分组',
'Type' => '类型',
'Title' => '变量标题',
'Value' => '变量值',
'Basic' => '基础配置',
'Email' => '邮件配置',
'Attachment' => '附件配置',
'User' => '会员配置',
'Example' => '示例分组',
'Extend' => '扩展属性',
'String' => '字符',
'Text' => '文本',
'Number' => '数字',
'Date' => '日期',
'Time' => '时间',
'Datetime' => '日期时间',
'Image' => '图片',
'Images' => '图片(多)',
'File' => '文件',
'Files' => '文件(多)',
'Select' => '列表',
'Selects' => '列表(多选)',
'Checkbox' => '复选',
'Radio' => '单选',
'Array' => '数组',
'Array key' => '键名',
'Array value' => '键值',
'Content' => '数据列表',
'Rule' => '校验规则',
'Name already exist' => '变量名称已经存在',
'Send a test message' => '发送测试邮件',
'This is a test mail content' => '这是一封测试邮件,用于测试邮件配置是否正常!',
'This is a test mail' => '这是一封测试邮件',
'Name' => '变量名',
'Tip' => '提示信息',
'Group' => '分组',
'Type' => '类型',
'Title' => '变量标题',
'Value' => '变量值',
'Basic' => '基础配置',
'Email' => '邮件配置',
'Attachment' => '附件配置',
'Dictionary' => '字典配置',
'User' => '会员配置',
'Example' => '示例分组',
'Extend' => '扩展属性',
'String' => '字符',
'Password' => '密码',
'Text' => '文本',
'Editor' => '编辑器',
'Number' => '数字',
'Date' => '日期',
'Time' => '时间',
'Datetime' => '日期时间',
'Datetimerange' => '日期时间区间',
'Image' => '图片',
'Images' => '图片(多)',
'File' => '文件',
'Files' => '文件(多)',
'Select' => '列表',
'Selects' => '列表(多选)',
'Switch' => '开关',
'Checkbox' => '复选',
'Radio' => '单选',
'Array' => '数组',
'Array key' => '键名',
'Array value' => '键值',
'City' => '城市地区',
'Selectpage' => '关联表',
'Selectpages' => '关联表(多选)',
'Custom' => '自定义',
'Please select table' => '关联表',
'Selectpage table' => '关联表',
'Selectpage primarykey' => '存储字段',
'Selectpage field' => '显示字段',
'Selectpage conditions' => '筛选条件',
'Field title' => '字段名',
'Field value' => '字段值',
'Content' => '数据列表',
'Rule' => '校验规则',
'Visible condition' => '可见条件',
'Site name' => '站点名称',
'Beian' => '备案号',
'Cdn url' => 'CDN地址',
'Version' => '版本号',
'Timezone' => '时区',
'Forbidden ip' => '禁止IP',
'Languages' => '语言',
'Fixed page' => '后台固定页',
'Category type' => '分类类型',
'Config group' => '配置分组',
'Attachment category' => '附件类别',
'Category1' => '分类一',
'Category2' => '分类二',
'Rule tips' => '校验规则使用请参考Nice-validator文档',
'Extend tips' => '扩展属性支持{id}、{name}、{group}、{title}、{value}、{content}、{rule}替换',
'Mail type' => '邮件发送方式',
'Mail smtp host' => 'SMTP服务器',
'Mail smtp port' => 'SMTP端口',
'Mail smtp user' => 'SMTP用户名',
'Mail smtp password' => 'SMTP密码',
'Mail vertify type' => 'SMTP验证方式',
'Mail from' => '发件人邮箱',
'Site name incorrect' => '网站名称错误',
'Name already exist' => '变量名称已经存在',
'Add new config' => '点击添加新的配置',
'Send a test message' => '发送测试邮件',
'Only work at development environment' => '只允许在开发环境开操作',
'This is a test mail content' => '这是一封来自%s的校验邮件,用于校验邮件配置是否正常!',
'This is a test mail' => '这是一封来自%s的邮件',
'Please input your email' => '请输入测试接收者邮箱',
'Please input correct email' => '请输入正确的邮箱地址',
];

Some files were not shown because too many files have changed in this diff Show More