222 Commits

Author SHA1 Message Date
wolfcode
e4ae29fed2 fix(admin): update password hashing method- Replace password function with password_hash for secure password storage- Use PASSWORD_DEFAULT algorithm for hashing
- Improve password security in admin controller

Signed-off-by: wolfcode <wolfcode@88.com>
2025-06-28 10:15:07 +08:00
wolfcode
c82e1c8ea3 fix(curd): improve form element rendering and validation
- Add length validation for images form type
- Update radio and checkbox view generation to use correct syntax- Improve select option view generation with more accurate conditions
2025-06-25 18:55:43 +08:00
wolfcode
3f718beacb fix(admin): update password hashing method-Replace custom password function with PHP's built-in password_hash
- Improve password security in admin controller
2025-06-23 11:24:27 +08:00
wolfcode
af44a9e7b8 🚀 Layui v2.11.3 2025-06-19 09:40:41 +08:00
wolfcode
4ed8237a00 refactor(auth): upgrade password hashing to PHP's password_hash
- Replace custom password hashing function with PHP's built-in password_hash
- Update password verification to use password_verify
- Adjust database schema to accommodate new password hash length
- Modify installation and login controllers to use new hashing method
2025-06-18 11:51:12 +08:00
wolfcode
216ca6e697 fix(install): update PDO extension check to pdo_mysql
- Change extension check from PDO to pdo_mysql for MySQL database support
- Improve error message for better user understanding
2025-06-12 15:21:35 +08:00
wolfcode
969a7a5ce5 fix(easy-admin): try-catch onInitElemStyle and hide theme switch on mobile
- Add try-catch block around onInitElemStyle function to handle potential errors
- Hide theme switch option on mobile devices to improve user experience
2025-06-09 14:37:58 +08:00
wolfcode
5593a20009 feat(layuimini): improve menu rendering and add keyboard event handling
- Add border-radius to layuimini-logo for rounded corners
- Implement Enter key event handling for login button- Enhance miniMenu rendering logic for better menu display
2025-06-04 11:14:55 +08:00
wolfcode
8a33a4fed3 fix(easy-admin): improve select component rendering and initialization
- Update select component initialization to properly set selected values
- Modify search value comparison to use loose equality for broader compatibility
- Enhance xmSelect rendering with pre-selected values
2025-05-20 10:22:50 +08:00
wolfcode
a4e8a86045 🚀 Layui v2.11.2 2025-05-19 18:30:34 +08:00
wolfcode
61e622d2ad refactor(controller): instantiate model class instead of assigning model name
- Change {{modelFilename}}::class to new {{modelFilename}}() in controller constructor- This modification allows direct access to model properties and methods
2025-05-14 12:32:02 +08:00
wolfcode
1b3265aeb5 feat(menu): add toggle buttons for menu folding and unfolding
- Add a new button for toggling menu folding and unfolding
- Implement functionality to fold and unfold all menu items
- Update button text and icon based on current state (folded/unfolded)
2025-05-13 14:06:20 +08:00
wolfcode
e1c0f6c881 feat(admin): add MIME type for logo upload
- Add image MIME type to the logo upload input
- This change improves file type restriction for logo uploads
2025-05-09 11:37:38 +08:00
wolfcode
3891cf8898 🚀 Layui v2.11.1 2025-05-06 15:59:24 +08:00
wolfcode
4ade618657 fix: correct class selector for tab item in refresh function
- Update the class selector from "layui-tab-item" to "layui-tabs-item" in the refresh function
- This change ensures that the correct tab item is targeted for refreshing
2025-04-30 11:10:29 +08:00
wolfcode
e7253e7de0 fix(layuimini): improve tab context menu positioning and behavior
- Update tab context menu CSS to use fixed positioning with higher z-index
- Modify JavaScript to prevent default context menu and use correct left position
2025-04-28 15:33:50 +08:00
wolfcode
063108a846 feat(easy-admin): dynamic theme color for xmSelect
- Replace static color codes with dynamic theme color retrieval
- Use getComputedStyle to fetch the '--ea8-theme-main-color' variable
- Update xmSelect instances in easy-admin.js and goods.js to use dynamic color
2025-04-22 18:16:26 +08:00
wolfcode
f41943320b Update welcome.html 2025-04-22 16:04:04 +08:00
wolfcode
1f0064743e 🚀 Layui v2.11.0 2025-04-22 15:46:10 +08:00
wolfcode
115573a88c 🚀 Layui v2.11.0 2025-04-22 15:26:25 +08:00
wolfcode
253379f0c6 feat(theme): implement dynamic theme color change
- Add functionality to change the main theme color dynamically
- Update CSS to use a custom property for the main theme color
- Modify JavaScript to set the main theme color based on user preference Adjust form select styles to match the new theme color
2025-04-21 18:03:39 +08:00
wolfcode
9a0ff912b5 feat(mall):新增表单中多选案例 add simulated multi-select feature
- Add simulated multi-select functionality to goods add and edit pages
- Integrate xm-select library for multi-select implementation
- Add random color theme to the multi-select dropdown
- Include predefined options for demonstration purposes
2025-04-18 15:16:06 +08:00
wolfcode
517fd191d3 refactor(curd): improve relation handling and query methods
- Replace with() with withJoin() for more efficient left joins
- Fix foreign key and primary key assignments in relations
- Update index page queries to use relation index method-Modify relation method generation in model to use belongsTo instead of hasOne- Adjust table column definitions for relation fields
2025-04-17 17:25:06 +08:00
wolfcode
d1dfa8b49b Update app.php 2025-04-16 18:40:37 +08:00
wolfcode
c819751a66 chore: update .gitignore files
- Extend and runtime .gitignore: add space after '!' in .gitignore
- Root .gitignore: remove .project file
2025-04-16 18:38:53 +08:00
wolfcode
3a2ee69d0f feat(curd): 关联表优化支持 add relation support and optimize model associations
- Add support for handling relations in CURD operations
- Optimize model associations to use hasOne instead of belongsTo
- Implement field selection for relations
- Update controller to handle AJAX requests and return JSON data Modify model to include relation methods
2025-04-16 15:28:41 +08:00
wolfcode
d513177c74 feat(easy-admin): improve AI suggestion display and typing effect
- Adjust the width of AI suggestion popup for mobile devices
- Implement a more realistic typing effect for AI-generated content
- Remove the 'fluid' class from the 'AI optimization' button in goods edit page
2025-04-11 15:23:15 +08:00
wolfcode
0705b9a38d Update public.css 2025-04-10 18:30:57 +08:00
wolfcode
feb26660e8 style(admin): adjust table cell height and line height
- Set table cell height to 47px instead of100%
- Set table cell line-height to 40px
2025-04-10 16:51:57 +08:00
wolfcode
b8ccf1542b feat(curd):支持CURD自动生成回收站功能 add recycle functionality for soft deleted items
- Add recycle method to Curd trait for restoring or permanently deleting items
- Implement recycle view and update index view to include recycle button-Add recycle URL and functionality to JavaScript table initialization
- Create new recycle template files for view and JavaScript
2025-04-08 10:43:27 +08:00
wolfcode
74122885f1 feat(mall): 新增回收站功能 add recycle functionality for goods
- Add recycle feature to goods management
- Implement recycle button in toolbar
- Create recycle page for deleted goods
- Add functionality to restore or permanently delete goods
2025-04-07 13:44:12 +08:00
wolfcode
f5813dec99 perf(cache): update system configuration and version caching logic
- Add cache update logic for system configurations in Config.php
- Modify version retrieval and caching logic in ConfigService.php
2025-04-02 11:18:42 +08:00
wolfcode
db0ac015f0 feat(easy-admin): add filtering functionality when sorting tables
- Implement filter logic in the table sorting event
- Iterate through columns to check for filter conditions-Construct filter and operator objects based on selected values
- Update the defaultWhere object with filter and op parameters
- Reload the table with the combined sorting and filtering conditions
2025-04-01 17:35:49 +08:00
wolfcode
bdabac7cff Update curd_generate.js 2025-03-31 17:11:12 +08:00
wolfcode
bd9cb6a3af Update Menu.php 2025-03-29 20:39:25 +08:00
wolfcode
3aaf030b89 feat(export): replace php-excel with PhpSpreadsheet for Excel export-Remove php-excel package and related usage
- Add PhpSpreadsheet package and update to latest version
- Implement new exportExcel function using PhpSpreadsheet
- Update admin controller to use new exportExcel function
- Remove redundant code and improve error handling
2025-03-28 12:22:05 +08:00
wolfcode
16975c4ee8 Update log.md 2025-03-28 10:18:00 +08:00
wolfcode
666598cd30 Update log.md 2025-03-27 18:43:31 +08:00
wolfcode
b9f764e4d0 refactor(admin): 重构控制器和模型的使用方式
- https://github.com/top-think/think-orm/issues/704
2025-03-27 18:38:27 +08:00
wolfcode
bc03616e43 refactor(admin): refactor model generation and improve array string handling
- Update CommonTool to fix array string formatting issue- Refactor model generation template to use getOptions method
2025-03-27 14:58:32 +08:00
wolfcode
8aba56c8c2 🚀 Layui v2.10.1 2025-03-27 11:22:43 +08:00
wolfcode
150e0ecd23 Update composer.json 2025-03-26 16:09:01 +08:00
wolfcode
e9ed0cd8f6 Merge branch 'main' of https://github.com/easyadmin8/EasyAdmin8 2025-03-26 14:03:22 +08:00
wolfcode
187d4343b3 refactor(admin): remove unused 'where' option in system module
- Remove unnecessary 'where' option from various models in system module
- Simplify query methods by removing redundant 'where' calls
- Affected models: Config, Node, SystemAdmin, SystemMenu, SystemNode
2025-03-26 14:00:58 +08:00
wolfcode
9bc0185b6b refactor(admin): improve error handling in Admin controller
- Enhance error messages by appending exception details
- Remove unnecessary password field handling in update scenario
2025-03-26 11:20:06 +08:00
wolfcode
652b17d6a6 refactor(admin): move auth_ids explode logic to model
- Remove auth_ids explode logic from Admin controller
- Add getAuthIdsAttr method to SystemAdmin model for auth_ids parsing
- This change improves code organization and reusability
2025-03-26 10:03:59 +08:00
wolfcode
ed8c3d545b refactor(admin):适配新版 think-orm remove console.log and refactor MallGoods model
- Remove unnecessary console.log statement from log.js
- Refactor MallGoods model to use getOptions method for deleteTime
2025-03-25 18:38:07 +08:00
wolfcode
12b38c7bf5 refactor(admin): optimize log data processing and display
- Update log data serialization and deserialization method
- Improve log data display format in the admin interface- Refactor log model initialization and table suffix handling
- Optimize time model configuration for better timestamp management
2025-03-25 18:08:35 +08:00
wolfcode
fc202be987 build(deps): update topthink/think-orm to 4.0.3
- Specify version 4.0.3 for topthink/think-orm in composer.json
- This change pins the ORM package to a specific version for stability and compatibility
2025-03-25 15:02:33 +08:00
wolfcode
71e069712a style(easy-admin): adjust image height in admin table
- Change image height from 40 to 30 in the admin table
2025-03-19 18:32:26 +08:00
wolfcode
ca4080d5e6 feat(config-admin): integrate lazyload for image optimization
- Add lazyload plugin to config-admin.js
- Implement lazyload functionality in easy-admin.js
- Add lazyload.min.js file to project
2025-03-18 17:10:46 +08:00
wolfcode
4bbe287626 refactor(system): upgrade tabs functionality and improve error handling
- Replace element.on with tabs.on for better tab management Add success and error handling for form submission
- Update HTML structure to use layui-tabs for improved UI
- Remove unnecessary Vue import
2025-03-17 18:02:06 +08:00
wolfcode
e316cd40e0 🚀 Layui v2.10.0 2025-03-17 18:01:02 +08:00
wolfcode
a7a3ddef8b refactor(easy-admin): improve AI optimization feature and enhance user experience
- Remove line-height style from AI optimization content div
- Add smooth scrolling to the end of the content when streaming output Move input validation to the beginning of the aiOptimization function
2025-03-10 11:54:41 +08:00
wolfcode
51f2cbc0f4 feat(mall): 新增AI对话支持 add AI optimization function for product titles
- Integrate AI functionality to optimize product titles
- Add AI optimization button in product add and edit pages
- Implement AI chat service for generating optimized titles
- Update backend to support AI optimization requests
- Add error handling and loading state for AI optimization process
2025-03-07 17:58:11 +08:00
wolfcode
8acf9f3f6c Update .example.env 2025-03-06 11:02:49 +08:00
wolfcode
d7b23f305d Update RateLimiting.php 2025-03-05 18:31:45 +08:00
wolfcode
fdfe2a542a Update RateLimiting.php 2025-03-05 15:25:04 +08:00
wolfcode
3d412d9ec6 Update index.html 2025-03-05 09:18:41 +08:00
wolfcode
f75ebffa5d feat(admin): 新增限流器 add rate limiting middleware for backend
- Add rate limiting functionality to the admin panel
- Implement rate limiting middleware to control API request frequency
- Update login controller to use rate limiting Add helper functions for getting IP and admin UID
- Update route configuration to include rate limiting middleware
- Add Redis configuration to .env file
- Update composer.json to include rate limiting package dependency
2025-03-04 17:26:24 +08:00
wolfcode
77881a27ed fix(curd): resolve select option value binding issue
- Update buildOptionView method to use correct field parameter
- Add conditional check for templateValue before appending to indexCols
2025-03-03 17:58:35 +08:00
wolfcode
07e11a1c45 fix(curd): improve CURD generation logic
- Update relation handling in BuildCurd.php:
  - Modify how foreign key relationships are constructed
  - Remove unnecessary template generation for non-editable fields

- Enhance command processing in CurdGenerate.php:
  - Filter and re-index command arguments to improve flexibility
2025-03-03 17:42:52 +08:00
wolfcode
8531ec89ad fix(admin): handle exceptions when reflecting on controller methods
- Add try-catch block around ReflectionMethod usage
- Catch and ignore any Throwable that occurs during reflection
- Improve error handling and prevent potential fatal errors
2025-02-25 10:50:18 +08:00
wolfcode
ff2e842c24 🚀 Layui v2.9.23 2025-02-21 11:11:24 +08:00
wolfcode
3d767643c8 Update BuildCurd.php 2025-02-20 15:40:47 +08:00
wolfcode
08fb6ef4d0 feat(log): 新增支持删除部分日志 add function to delete logs older than specified months
- Add deleteMonthLog functionality to log system
- Create new route and controller method for handling log deletion
- Implement frontend UI and logic for selecting and confirming log deletion-Add necessary database queries to safely delete logs
2025-02-19 11:23:46 +08:00
wolfcode
32d94bb3e0 feat(log): 新增支持删除部分日志 add function to delete logs older than specified months
- Add deleteMonthLog functionality to log system
- Create new route and controller method for handling log deletion
- Implement frontend UI and logic for selecting and confirming log deletion-Add necessary database queries to safely delete logs
2025-02-19 11:07:06 +08:00
wolfcode
d93483d9bf Update README.md 2025-02-18 13:44:24 +08:00
wolfcode
973e9cb24c feat(orm): upgrade think-orm to version 4.0 and add BaseEntity class
- Upgrade topthink/think-orm from ^3.0 to ^4.0 in composer.json
- Add new BaseEntity class in app/common/entity to handle common entity options
2025-02-14 16:12:12 +08:00
wolfcode
b510042323 Update Goods.php 2025-02-14 11:42:40 +08:00
wolfcode
b55dd8f67a feat(auth): add support for ignoring node authentication via annotation
- Add checkNodeAnnotationAttrAuth method to AuthService for annotation-based auth control
- Update checkAuth method to use the new annotation check-Modify Goods controller to use NodeAnnotation for specifying auth requirements
- Remove unused library imports in config-admin.js
2025-02-14 11:36:07 +08:00
wolfcode
8e488fb46c 🚀 Layui v2.9.21 2025-02-10 10:40:14 +08:00
wolfcode
d99e168583 Update Install.php 2025-01-15 09:54:11 +08:00
wolfcode
d6bb1456fa build(dependencies): update composer dependencies and adjust view rendering
- Update topthink/think-view dependency to version ^2.0
- Modify view rendering in Install controller to use empty template path
2025-01-15 09:45:07 +08:00
wolfcode
91eac36371 refactor(view): remove redundant template path generation
- Remove unnecessary code that generates a template path based on the request pathinfo
- Simplify the render function by removing the conditional block that sets the template path
2025-01-15 09:28:15 +08:00
wolfcode
1e4486989a refactor(admin): improve login check middleware
- Rename $ignoreAuth to $ignoreLogin for better clarity
- Update comments for better code readability
- Modify Login controller to use $ignoreLogin instead of $ignoreAuth
2025-01-14 10:09:58 +08:00
wolfcode
f9f25b76dd Merge branch 'main' of https://github.com/easyadmin8/EasyAdmin8 2025-01-12 18:13:17 +08:00
wolfcode
7603cdfa7e feat(admin): add middleware annotation for login exemption
- Add MiddlewareAnnotation to CheckLogin middleware for login exemption
- Implement IGNORE_LOGIN constant in MiddlewareAnnotation
- Use MiddlewareAnnotation in Goods controller for login exemption

Signed-off-by: wolfcode <37436228+wolf-leo@users.noreply.github.com>
2025-01-12 18:12:28 +08:00
wolfcode
2e0cc85966 build(deps): update topthink/think-view to 2.0.0
- Change topthink/think-view dependency from ^2.0 to 2.0.0
- This update specifies an exact version to ensure compatibility and stability
2025-01-09 11:48:38 +08:00
wolfcode
5814fed0da Update Install.php 2025-01-09 11:32:25 +08:00
wolfcode
40f7ee82cd refactor(install): update view path delimiter
- Change the view path delimiter from '/' to '@' for the installation view
- This modification ensures consistency with the naming convention used in other parts of the application
2025-01-09 11:21:02 +08:00
wolfcode
936cb56c7f Update public.css 2025-01-08 21:38:48 +08:00
wolfcode
2d1940522c fix(View): automatically determine the template path if it is empty
- Add logic to automatically set the template path based on the controller and action if not explicitly defined
- Improve consistency in the 'iframeOpenTop' configuration by adding missing commas
2025-01-08 14:35:54 +08:00
wolfcode
264cf56ae4 fix(View): automatically determine the template path if it is empty
- Add logic to automatically set the template path based on the controller and action if not explicitly defined
- Improve consistency in the 'iframeOpenTop' configuration by adding missing commas
2025-01-08 14:25:54 +08:00
wolfcode
57ea9a3f47 feat(install): optimize installation page and add .env configuration support
- Update installation page layout and styling
- Add support for reading database configuration from .env file
- Improve error handling and user feedback during installation process
- Refactor JavaScript code for better readability and performance
2025-01-08 13:53:24 +08:00
wolfcode
31c06cff69 feat: use localStorage instead of sessionStorage and adjust styles for dark mode
- Replace sessionStorage with localStorage for saving background color ID
- Update CSS to improve dark mode styling:
  - Remove background color from layuimini-main
  - Add background color and other styles for tableSearch-xmSelect in dark mode
2025-01-08 10:47:37 +08:00
wolfcode
4c8d21fccf Merge pull request #21 from 1rmb/main
xmselect搜索独立样式
2025-01-07 20:41:16 +08:00
淘青松
817582bed5 xmselect搜索独立样式
表单item中可以自定义最小高度适配layui了
2025-01-07 18:13:51 +08:00
wolfcode
3a0c0c1624 Update public.css 2025-01-07 15:27:42 +08:00
wolfcode
30c7615e53 feat(auth): use attribute for ignore node
- Add NodeAnnotation attribute to filter ignore nodes
- Update Goods controller to use NodeAnnotation for ignore nodes
- Modify Node service to handle new attribute-Enhance AdminController with ignoreNode property
2025-01-07 11:12:16 +08:00
wolfcode
9d58da3cc3 Merge pull request #19 from QiuYiBin/main
feta(admin):数字输入框添加动态点缀,样式更加好看
2025-01-07 09:52:46 +08:00
wolfcode
1f5ca39226 style(admin): update login page background styling
- Set body background color to #333333
- Move background image from container to body element
- Remove background color from container element

Signed-off-by: wolfcode <37436228+wolf-leo@users.noreply.github.com>
2025-01-06 20:25:37 +08:00
wolfcode
82f17301a7 style(layuimini): adjust header and menu colors
- Darken header right colors for better legibility-Adjust left menu colors for consistency- Update font family in public.css for improved typography
2025-01-06 18:30:59 +08:00
QiuYiBin
4ef7879ae3 feta(admin):数字输入框添加动态点缀,样式更加好看
feta(admin):CURL 生成排序使用数字输入框
2025-01-06 18:01:25 +08:00
wolfcode
0c9b369e2e build: integrate xmSelect plugin into admin static files
- Add xmSelect plugin to config-admin.js
- Remove separate script tag for xmSelect in default.html
- Update easy-admin.js to include xmSelect as a dependency
- Modify xm-select.js to support different module systems
2025-01-06 16:55:24 +08:00
wolfcode
e981852422 feat(admin): add custom favicon support Add favicon link to the head section of the default layout
- Use site_ico configuration or fallback to default favicon.ico
2025-01-06 16:29:18 +08:00
wolfcode
1d258621c8 feat(theme): 支持日夜模式切换 add dark mode support and enhance theme switching functionality
- Add dark mode support to the admin panel
- Implement smooth theme switching animation
- Update theme settings to use local storage
- Refactor theme initialization and rendering logic-Improve styling for dark mode
2025-01-06 14:26:12 +08:00
wolfcode
9534f2c2fe style(admin): update welcome module layout-Add max-height and overflow properties to welcome-module class
- Improve layout consistency for the welcome module in the admin panel
2025-01-03 11:24:53 +08:00
wolfcode
9fb8dd022a refactor(admin): improve system log middleware
- Remove unused imports and annotations- Simplify annotation reading process
- Use ReflectionClass and ReflectionMethod directly-Handle annotations using getAttributes() method
- Improve code readability and performance
2025-01-02 17:23:13 +08:00
wolfcode
fc264630ec refactor(admin):重构注解获取 upgrade annotations
- Replace Doctrine annotations with PHP 8.1 attributes
- Update annotation classes to use Attribute interface
- Modify attribute usage across multiple controller files
- Update composer.json to use doctrine/annotations ^2.0.0
2025-01-02 17:11:28 +08:00
wolfcode
3d19c8d337 refactor(admin):重构注解获取 upgrade annotations
- Replace Doctrine annotations with PHP 8.1 attributes
- Update annotation classes to use Attribute interface
- Modify attribute usage across multiple controller files
- Update composer.json to use doctrine/annotations ^2.0.0
2025-01-02 17:04:10 +08:00
wolfcode
4baa4e185d feat(admin): add system log switch and optimize log middleware
- Add APP_ADMIN_SYSTEM_LOG environment variable to control admin system logs
- Implement log switch check in SystemLog middleware
- Update .example.env file with new environment variable
2025-01-02 09:58:08 +08:00
wolfcode
88bc6441e5 Update README.md 2025-01-01 11:37:24 +08:00
wolfcode
0efb70b3b7 build: update minimum PHP version to8.1
- Update composer.json to require PHP >= 8.1.0
- Add log entry for PHP version update in log.md

Signed-off-by: wolfcode <37436228+wolf-leo@users.noreply.github.com>
2025-01-01 11:31:43 +08:00
wolfcode
c22be8ac76 Merge pull request #17 from 1rmb/main
隐藏搜索框提示
2024-12-31 11:06:00 +08:00
淘青松
ebce7f3b0e 隐藏搜索框提示
将注释属性添加到table标签上即可
2024-12-31 10:56:13 +08:00
wolfcode
f8b6d847d5 fix(admin): 阿里云对象存储签名版本 V4, upgrade OSS SDK and implement signature version 4
- Upgrade aliyuncs/oss-sdk-php to v2.7.2 or above
- Implement signature version 4 for OSS client
- Add region and environment variable credentials provider
- Update error handling and code formatting
2024-12-27 11:25:02 +08:00
wolfcode
55a9b13e69 refactor(curd): optimize relation binding and code generation
- Move relation binding logic from model constructor to controller
- Generate controller code for binding select fields-Remove unnecessary code and templates
- Improve code readability and maintainability
2024-12-20 17:09:04 +08:00
wolfcode
5d58248cf4 feat(curd): add relation data binding and optimize model construction
- Add relation data binding in model construction
- Update model template to include new relation construction logic
- Remove unused index method template
2024-12-20 16:26:56 +08:00
wolfcode
bbd0320a35 Update README.md 2024-12-19 10:40:01 +08:00
wolfcode
d62a67aafe refactor(admin): use param instead of post to get request parameters
- In both Admin and Menu controllers, change $request->post('id') to $request->param('id')
- This change improves code consistency and allows for more flexible parameter retrieval
2024-12-17 11:22:01 +08:00
wolfcode
c6cc9d4164 feat(common): add support for absolute URLs in __url function
- Implement a check for absolute URLs using filter_var with FILTER_VALIDATE_URL
- If the provided URL is an absolute URL, it is returned as is
- This enhancement allows the __url function to handle both relative and absolute URLs
2024-12-16 13:51:27 +08:00
wolfcode
add8c5144c fix(easymde): update font-awesome css link
- Replace external font-awesome link with local static resource
- Update font-awesome CSS path to local version
2024-12-13 10:44:24 +08:00
wolfcode
62f591045e refactor(admin): enhance delete method to support custom primary keys
- Update delete method in Curd trait to accept Request parameter
- Add support for deleting multiple records using 'id' parameter
- Improve error handling for non-existent data
2024-12-11 17:00:34 +08:00
wolfcode
7470790657 refactor(admin): optimize system log middleware
- Adjust class name construction logic in SystemLog middleware
- Simplify condition for checking controller and action existence
2024-12-09 13:55:24 +08:00
wolfcode
11fa69afaf Merge pull request #13 from Rodots/main
路由配置新增操作方法的参数绑定方式
2024-12-02 16:04:23 +08:00
Rodots
8983705ce4 feat: Parameter Binding Method of New Operation Method for Routing Configuration 2024-12-02 16:01:49 +08:00
wolfcode
95ccd4071b feat(easy-admin): add support for custom template functions in operat column-Implement the ability to use custom template functions in the operat column of easy-admin tables
- Add a new button in the goods list page to demonstrate the usage of custom templates
2024-12-02 11:03:26 +08:00
wolfcode
09f3ea7e54 feat(search): add date type search support
- Implement search functionality for date type fields
- Add new case for 'date' in the search HTML generation logic
- Include date-specific input field with appropriate attributes
2024-11-29 18:50:02 +08:00
wolfcode
9cadb27c9e 🚀Layui v2.9.20 2024-11-29 16:38:46 +08:00
wolfcode
2772034a93 fix(admin): add type attribute to login button
- Add 'type="button"' attribute to the login button in admin login page
- This change prevents the button from being submitted as a form Reset button
2024-11-28 18:19:12 +08:00
wolfcode
08ea79033c feat(admin): add ignoreNode property to skip unnecessary node generation
- Add $ignoreNode property to Goods controller to specify methods to ignore
- Update Node service to check and skip ignored methods during node generation
- This change helps to filter out unnecessary nodes, improving system performance and readability
2024-11-28 10:54:15 +08:00
wolfcode
e7f09d9c68 refactor(install):使用PDO初始化安装 use PDO instead of mysqli for database operations
- Replace mysqli with PDO for database connection and queries
- Create a separate method for generating PDO DSN
- Update error handling to use PDOException
- Remove unnecessary mysqli_set_charset and mysqli_select_db calls
2024-11-27 12:33:04 +08:00
wolfcode
e2effb762c build(deps): update dependency wolfcode/authenticator to v0.0.6
- Updated wolfcode/authenticator from version 0.0.5 to 0.0.6 in composer.json
2024-11-22 16:59:07 +08:00
wolfcode
0e18825808 fix(authenticator): update dependency and adjust QR code generation
- Update wolfcode/authenticator from 0.0.3 to 0.0.5
- Modify QR code generation method in Index controller
2024-11-22 16:51:36 +08:00
wolfcode
c031b09422 build(deps): update topthink/think-multi-app to ^1.1.0
- Replace dev-master with stable version ^1.1.0 for topthink/think-multi-app- This change ensures compatibility and stability in dependency management
2024-11-22 15:16:44 +08:00
wolfcode
231fd48e2f build(deps): update topthink/think-multi-app to dev-master
- Change the version of topthink/think-multi-app from ^1.0 to dev-master in composer.json
2024-11-22 09:32:06 +08:00
wolfcode
53772badd4 Merge pull request #12 from Rodots/main
fix: 修正命名空间,移除无用类的引用
2024-11-14 16:45:09 +08:00
wolfcode
3e329a4ea3 fix(install): update ea_system_admin insert statement
- Add missing column for 'status' in ea_system_admin insert statement
- Set 'status' column to 1 for the admin user
2024-11-13 16:30:15 +08:00
wolfcode
f015a90b89 style(layuimini): remove redundant CSS rules for nav more icons- Removed specific margin-top rules for .layuimini-menu-left and .layuimini-menu-left-zoom classes
- Kept the general rule for .layuimini-menu-left .layui-nav .layui-nav-mored and .layuimini-menu-left .layui-nav-itemed > a .layui-nav-more
2024-11-12 17:21:04 +08:00
wolfcode
75c668b966 feat(auth): 新增谷歌验证码登录 implement Google Authenticator support for two-factor authentication
- Add Google Authenticator integration for enhanced login security-Update admin edit page to include login type selection
- Modify login process to support two-factor authentication
- Add new database fields for login type and GA secret
- Update client-side JavaScript to handle GA code input and validation
2024-11-12 10:51:48 +08:00
wolfcode
f3e5a041d1 feat(ueditor): add custom upload service and other enhancements
- Add custom upload service support
- Implement server response preparation function
- Enable loading config from server
- Add content import button to toolbar
- Update image alignment buttons order
- Add tip error function for better error handling
- Remove unused comments and simplify configuration options
2024-11-11 11:22:47 +08:00
Rodots
34354837d5 fix: 修正命名空间,移除无用类的引用 2024-11-06 14:51:15 +08:00
wolfcode
3b219cfe7f feat(install): add official website tutorial and common questions links
- Add links to the official website tutorial and common questions in the installation page
- Update the logo image style for better visual presentation
2024-11-04 11:53:39 +08:00
wolfcode
ed99fe79fd feat(admin): 快捷操作支持翻页滚动显示 and optimize UI for better user experience
- Add swiper plugin support for carousel functionality
- Optimize UI for better user experience by using swiper
- Update index.js to support swiper initialization
- Modify welcome.html to include swiper HTML structure
- Adjust welcome.css for better styling of swiper elements
2024-10-29 17:24:54 +08:00
wolfcode
846b8ffafc feat(easy-admin): update window open URL for new tab button
- Change the URL for the "New tab open" button to use the iframe's content URL
- Add fallback to current window URL if iframe content URL cannot be accessed
- Improve user experience by opening the correct URL in a new tab

Signed-off-by: wolfcode <37436228+wolf-leo@users.noreply.github.com>
2024-10-28 18:26:26 +08:00
wolfcode
47ef5d9f10 feat(auth): 改用 zTree 替换 layui 的 tree 组件 use zTree for node authorization
- Replace layui tree with zTree for better performance and flexibility
- Update auth.js to use zTree for node rendering and checking
- Add zTree CSS and JS files to project

Signed-off-by: wolfcode <37436228+wolf-leo@users.noreply.github.com>
2024-10-28 16:35:06 +08:00
wolfcode
70ec12f406 feat(admin): 新增搜索表单多选功能 add xmSelect plugin support for multiple selection
- Add xmSelect case handling in AdminController for 'in' operation
- Include xmSelect.js in admin layout
- Update easy-admin.js to support xmSelect initialization and data binding
- Modify goods.js to demonstrate xmSelect usage with simulated data
- Adjust public.css for better styling of xmSelect elements

Signed-off-by: wolfcode <37436228+wolf-leo@users.noreply.github.com>
2024-10-25 11:44:19 +08:00
wolfcode
e15b56ecc9 Update composer.json 2024-10-24 09:37:09 +08:00
wolfcode
af275e1c6a fix(install): check for .env file existence before installation
- Add a check for the presence of the .env file in the root directory
- Update the index method to redirect to the admin page with a default value

Signed-off-by: wolfcode <37436228+wolf-leo@users.noreply.github.com>
2024-10-23 10:27:06 +08:00
wolfcode
20d4038159 Update public.css 2024-10-22 16:08:23 +08:00
wolfcode
475a1ef84d refactor(admin): update login.js to include jQuery dependency
- Add jQuery as a dependency in the define function
- Update the function parameters to include jQuery ($)

Signed-off-by: wolfcode <37436228+wolf-leo@users.noreply.github.com>
2024-10-22 14:43:50 +08:00
wolfcode
9307e724a9 refactor(SystemLogService): optimize detectTable method and improve code quality
- Remove unnecessary return type annotation in instance method
- Add type hint for SystemLogService instance
- Use variable for cache key to improve code readability
- Update cache key name for consistency

Signed-off-by: wolfcode <37436228+wolf-leo@users.noreply.github.com>
2024-10-22 11:54:59 +08:00
wolfcode
6616e96724 perf(middleware): optimize system log middleware for performance
- Remove redundant code for checking ignoreLog property
- Consolidate logic to check ignoreLog property in a single location
- Improve readability and maintainability of the code

Signed-off-by: wolfcode <37436228+wolf-leo@users.noreply.github.com>
2024-10-22 11:31:06 +08:00
wolfcode
963febc15c feat(admin): 新增弹框是否允许在新标签页中打开 add option to open iframe in new tab
- Add 'iframe_open_top' configuration to admin site settings
- Implement functionality to open iframe in new tab if configured
- Update admin layout to include new configuration option
- Modify easy-admin.js to support new tab opening for iframes

Signed-off-by: wolfcode <37436228+wolf-leo@users.noreply.github.com>
2024-10-21 16:27:48 +08:00
wolfcode
f78edd2fd5 refactor(css): simplify table styles and remove redundant code
- Remove duplicate styles for .layui-table-tool
- Delete unused styles for .layui-table-view and .layui-table-box
- Simplify border styles for light and dark themes- Remove unnecessary padding and positioning styles- Improve readability of table header text

Signed-off-by: wolfcode <37436228+wolf-leo@users.noreply.github.com>
2024-10-16 22:21:20 +08:00
wolfcode
83aa399f95 feat(easy-admin): prevent rapid form submission
Add a mechanism to handle quick consecutive form submissions by displaying a message when a user tries to submit the form before the previous submission is completed. This
update introduces a wait period during which subsequent submissions are prevented,
enhancing the user experience and preventing potential errors.

Signed-off-by: wolfcode <37436228+wolf-leo@users.noreply.github.com>
2024-10-16 11:05:26 +08:00
wolfcode
3096aa8985 🚀Layui v2.9.18 2024-10-15 11:08:50 +08:00
wolfcode
c7f7ca9af7 Update README.md 2024-10-14 18:00:48 +08:00
wolfcode
352484c69e fix(layout): adjust column layout for better responsiveness
Changed the column layout in both goods add and edit pages to ensure better responsiveness across different screen sizes. The left column is now fixed at 5 units, while the right column is set to 12 units for improved flexibility on smaller screens.

Signed-off-by: wolfcode <37436228+wolf-leo@users.noreply.github.com>
2024-10-14 17:31:51 +08:00
wolfcode
dfe15f7e88 feat(admin): 新增过滤不需要记录后台日志的方法 add ignoreLog property to exclude log recording for specific actions
- Add 'ignoreLog' property to the Log controller to specify actions that should not be logged
- Implement logic in SystemLog middleware to check if the current action is in the ignoreLog list
- Skip logging for actions listed in ignoreLog, improving performance and reducing log clutter

Signed-off-by: wolfcode <37436228+wolf-leo@users.noreply.github.com>
2024-10-12 17:21:56 +08:00
wolfcode
4d7365921e chore(deps): bump wolf-leo/phplogviewer from 0.08.0 to 0.10.0
Update the wolf-leo/phplogviewer package from version 0.08.0 to 0.10.0 in composer.json.
This update may include new features, bug fixes, and performance improvements in the PHP log viewer library.

- Update wolf-leo/phplogviewer from ^0.08.0 to ^0.10.0 in composer.json
- Modify return type of System\Log\record() method to Json|string for improved type hinting

Signed-off-by: wolfcode <37436228+wolf-leo@users.noreply.github.com>
2024-10-12 14:00:14 +08:00
wolfcode
d613f3c26f Update composer.json 2024-10-10 15:01:51 +08:00
wolfcode
74c21d2397 Update public.css 2024-10-09 13:37:46 +08:00
wolfcode
f1d049fc0a Update logviewer.php 2024-10-09 11:12:27 +08:00
wolfcode
a6ec0f143a feat(log): 新增框架日志查看器,integrate log viewer and improve log management
- Add log viewer functionality using wolf-leo/phplogviewer package
- Implement log record feature in admin system
- Update log table to include log record button
- Configure default and common log modules in logviewer config

Signed-off-by: wolfcode <37436228+wolf-leo@users.noreply.github.com>
2024-10-09 10:02:42 +08:00
wolfcode
a1d4aa97d5 feat(log): 新增框架日志查看器,integrate log viewer and improve log management
- Add log viewer functionality using wolf-leo/phplogviewer package
- Implement log record feature in admin system
- Update log table to include log record button
- Configure default and common log modules in logviewer config

Signed-off-by: wolfcode <37436228+wolf-leo@users.noreply.github.com>
2024-10-09 09:51:25 +08:00
wolfcode
4b8e163454 feat(goods): optimize goods add and edit pages
- Adjust layout of goods add and edit forms for better readability- Enhance editor visibility on the right side of the page
- Increase dialog size for adding and editing goods to improve usability

Signed-off-by: wolfcode <37436228+wolf-leo@users.noreply.github.com>
2024-10-07 13:26:49 +08:00
wolfcode
c2762fa2ad feat(goods): optimize goods add and edit pages
- Adjust layout of goods add and edit forms for better readability- Enhance editor visibility on the right side of the page
- Increase dialog size for adding and editing goods to improve usability

Signed-off-by: wolfcode <37436228+wolf-leo@users.noreply.github.com>
2024-10-07 13:07:17 +08:00
wolfcode
de92299a4f Update README.md 2024-09-27 16:59:48 +08:00
wolfcode
a4782ad38e 🚀Layui v2.9.17 2024-09-25 10:05:01 +08:00
wolfcode
36038516e0 fix(SystemLog): limit response content in admin middleware
Implement a limitation on the amount of response content recorded in the system logs
to avoid excessively large entries. The response is now truncated to 3000 characters,
enhancing performance and readability while still capturing essential data.
2024-09-20 09:51:17 +08:00
wolfcode
aeb3b28184 fix(SystemLog): limit response content in admin middleware
Implement a limitation on the amount of response content recorded in the system logs
to avoid excessively large entries. The response is now truncated to 3000 characters,
enhancing performance and readability while still capturing essential data.
2024-09-20 09:49:26 +08:00
wolfcode
cb34bf1758 fix(log): allow text wrapping in table cells
Modified the log table's line style to enable 'word-break: break-all' property, which permits text to break and wrap within table cells. This adjustment addresses the issue
where long strings of text would overflow or extend beyond the cell's boundary, now
ensuring that all content remains neatly contained within the structure of the table.
2024-09-19 16:34:24 +08:00
wolfcode
84f0bdcc77 refactor(admin/middleware): update request type to app\Request
Change the type of the request parameter in middleware handle functions to use
app\Request instead of the plain $request. This update clarifies the expected
request object type for better type checking and autocompletion support.
2024-09-14 17:33:07 +08:00
wolfcode
ef40aa27b7 fix(goods): 添加属性显示与否演示 add visible check for stock button in admin mallImplement a conditional 'visible' attribute for the 'stock' button within the goods
management section of the admin mall interface, ensuring the button only appears
when the goods status is set to 1 (active). This change streamlines the UI by
hiding irrelevant options for inactive goods items.
2024-09-13 10:56:27 +08:00
wolfcode
e69fddd2f7 feat(table): 新增 visible 参数值控制属性显示与否 pass data param to buildOperatHtml for visibility logic
Pass the `data` parameter to the `buildOperatHtml` function to support dynamic visibility checks for table operations. This allows operation visibility to be conditionally determined
based on the provided data.
2024-09-13 10:51:11 +08:00
wolfcode
229c74fbea feat(admin): add DEBUG mode button to welcome page
Display the status of the DEBUG mode on the welcome page with a button that changes color
and text based on the value of `APP_DEBUG` environment variable. Include a grey badge
提示 to prompt users to switch off DEBUG mode in production environments.
2024-09-12 18:38:56 +08:00
wolfcode
336ca82729 feat(search-form): 新增默认关闭搜索表单自动补全功能 add autocomplete control and other improvements
- Add an attribute 'searchTableAutocomplete' to enable/disable form autocomplete.
- Set 'searchTableShow' attribute to control the visibility of the search form.
- Remove redundant form initialization code for a cleaner setup.
2024-09-11 16:46:09 +08:00
wolfcode
5b444f7fc1 fix(admin): remove version param from require.js to use cache
Removed the version parameter from the require.js src attribute to enable browser caching
of the script. This change will improve load times and performance by leveraging cached
versions of the script when users revisit the site.
2024-09-11 09:45:17 +08:00
wolfcode
7375b7cbf0 fix(admin): remove version param from require.js to use cache
Removed the version parameter from the require.js src attribute to enable browser caching
of the script. This change will improve load times and performance by leveraging cached
versions of the script when users revisit the site.
2024-09-11 09:43:44 +08:00
wolfcode
62a3d80fa3 fix(wangEditor): correctly access wangEditor instances
Correct the way wangEditor instances are accessed and stored in the window object. The previous
implementation using `eval` could lead to security risks and is removed in favor of direct
window property access. This change improves code security and reliability by eliminating `eval`
and clarifying the object property name construction.
2024-09-05 09:33:38 +08:00
wolfcode
ea733d0acb feat(admin): 新增 markdown 编辑器支持,integrate EasyMDE markdown editor for admin interface
Introduce the EasyMDE markdown editor into the admin interface, providing enhanced
markdown editing capabilities. This integration caters to users preferring a dedicated
markdown editor with additional features and a streamlined user experience.
2024-09-02 16:09:36 +08:00
wolfcode
857533704e fix(admin-controller): ensure page and limit are integers
Cast page and limit values to integers in AdminController's filter method to prevent
type-related errors. This change guarantees that these parameters are whole numbers,
which is essential for consistent database pagination and filtering operations.
2024-08-27 18:49:26 +08:00
wolfcode
af419ff25b feat(cache): set default cache prefix to 'EA8TP'
To prevent cache data residuals between different versions, the default cache prefix
has been changed from an empty string to 'EA8TP'. This ensures each version's cache
is distinctly separated.

BREAKING CHANGE: The cache prefix 'EA8TP' is now used by default. If your application
depends on the default cache prefix being empty, you will need to explicitly set the
prefix to an empty string.
2024-08-26 18:03:16 +08:00
wolfcode
dcbb944fbf 🚀 Layui v2.9.16 2024-08-26 10:39:44 +08:00
wolfcode
5081a21a0b fix(easy-admin): 表格 toolbar 支持元素传入
The renderToolbar function in easy-admin now checks if the provided data is an object before
attempting to iterate over it. This fix prevents errors when the data parameter is not an object,
ensuring that the function does not break down and correctly returns the provided data.
2024-08-23 17:20:19 +08:00
wolfcode
92c75e52ba feat(admin-view): update welcome page layout and information display
Admin welcome page has been redesigned to improve the layout and readability of displayed
information. Real-time statistics panels have been adjusted with new color schemes, and the
information presentation has been refined with appropriate heading sizes. Additionally, the
page now links to the layui documentation and includes a reminder about the open-source
nature of the framework.

- Redesign the overall layout of the welcome page.
- Refactor real-time statistics panels with updated styles and content.
- Improve the display of quick entry modules.
- Add links to layui documentation and policy reminders.
- Ensure consistent use of spaces and clean up the footer section.
2024-08-22 10:42:30 +08:00
wolfcode
2b7f57cc9f Update echarts.min.js 2024-08-22 10:25:55 +08:00
wolfcode
0708435d91 feat(switch): add 'is_show' to switch field optionsInclude 'is_show' as an option in the switchFields array to enable the use of a switch
component for toggling the visibility of fields in the admin panel's build form.
2024-08-21 14:15:07 +08:00
wolfcode
c3a6118387 fix(admin-config): ensure required JS loads on DOM ready; add error handling
Moved the RequireJS dynamic load of the controller-specific JavaScript to execute upon
the 'load' event of the window, guaranteeing that all required DOM elements are available
before the script execution begins. Also, added error handling to log any require failures
to the console for easier debugging.
2024-08-20 17:41:32 +08:00
wolfcode
d7dcfb95b6 fix(admin-config): ensure required JS loads on DOM ready; add error handling
Moved the RequireJS dynamic load of the controller-specific JavaScript to execute upon
the 'load' event of the window, guaranteeing that all required DOM elements are available
before the script execution begins. Also, added error handling to log any require failures
to the console for easier debugging.
2024-08-20 17:22:54 +08:00
wolfcode
55c4743417 🚀 更新CRUD/CRUD可视化操作,新增支持可视化命令行
Extend the CURD generation functionality to support command line operations, supplementing
the existing visual generation method. This update includes modifications to the admin interface, enabling the system to process both types of CURD generation commands effectively.
2024-08-20 09:40:34 +08:00
wolfcode
f56e3630b6 🚀 Layui v2.9.15 2024-08-20 09:39:31 +08:00
wolfcode
6dfdffeab9 feat(console): add CRUD support and update usage warning
Extend the console command configuration to include 'crud' alongside the existing 'curd'
command for better supporting CRUD operations in the application. Additionally, update the
user warning message to recommend using the visual generation features for CRUD functionality
within the system, indicating that further support for command-line CURD/CRUD will be phased out.Adjust the related fields handling in the Curd command class methods to use 'onlyField' for
consistency with the singular form of field manipulations, and ensure correct method parameter
names are reflected in the relation setup.
2024-08-16 11:32:13 +08:00
wolfcode
ed0e14cb32 fix(admin): use direct IP retrieval for system logs
Direct IP retrieval method has been updated to improve accuracy in the admin system log.
This change involves modifying the IP address retrieval logic from considering the
`HTTP_X_FORWARDED_FOR` header to directly using the `ip()` method, potentially enhancing
the security and reliability of IP logging.
2024-08-12 15:58:23 +08:00
wolfcode
772ffc6328 fix(admin): correct admin login page redirect
After the admin session expires, the system now correctly redirects to the specified admin login URL, addressing the issue where the redirect would
sometimes point to an incorrect path.
2024-08-12 13:57:33 +08:00
wolfcode
c9f65c838e Update Curd.php 2024-08-06 19:49:24 +08:00
wolfcode
7f3a3a85f3 CURD生成器:增强字段类型设置和模板调整
本次更新增强了CURD生成器的字段类型自定义功能,允许开发者通过Web界面设置特定字段的类型,例如忽略字段、下拉字段、单选字段、多选字段、图片字段、多选图片字段、日期字段、日期时间字段和编辑器字段。这些设置会直接影响生成的控制器、模型和视图文件,从而提供更高的灵活性和定制化能力。此外,还对代码模板进行了调整,引入了`$notes`变量来存储字段定义,简化了视图中的脚本处理,并优化了控制器和模型中的代码结构。这些改动旨在改善代码的可读性和可维护性,同时使CURD生成器的使用更加直观和便捷。

通过这次更新,我们希望进一步提升CURD生成器的实用性和用户体验,减少开发者在日常 CRUD操作中重复编写代码的工作量。相关的代码改动包括对`BuildCurd.php`文件的多处调整,以实现新的字段类型设置功能;对`CommonTool.php`的修改,以支持新的数组字符串处理逻辑;对`controller.code`、`curd_generate.js`、`curd_generate.php`、`index.code`和`index.html`等文件的修改,以确保生成的代码与新的设置逻辑兼容,并改善前端交互体验。
2024-07-31 14:47:24 +08:00
wolfcode
c5a091c732 移除Vue实例并优化配置项加载逻辑
删除了`config.js`中的Vue实例创建过程,简化了上传类型的数据绑定。通过直接操作`app.upload_type`来响应上传类型的变化。
2024-07-26 10:20:18 +08:00
wolfcode
4f184abb08 移除Vue实例并优化配置项加载逻辑
删除了`config.js`中的Vue实例创建过程,简化了上传类型的数据绑定。通过直接操作`app.upload_type`来响应上传类型的变化。
2024-07-26 10:08:09 +08:00
wolfcode
1365fd08a0 fix(system/config): correct upload button reference in admin background config
Update the data-upload attribute on the upload button in the admin background
configuration page to correctly reference 'admin_background' instead of
'site_ico', ensuring the button functions as intended when uploading a new
background image.
2024-07-26 09:38:54 +08:00
wolfcode
71aee61345 Merge remote-tracking branch 'origin/main' 2024-07-19 21:14:39 +08:00
wolfcode
efbf557cc0 fix(admin): ensure CheckAuth is enabled for route verification
Enable CheckAuth in the admin route configuration which was previously
commented out, ensuring that node permissions are validated as expected.
2024-07-19 21:14:28 +08:00
wolfcode
96d162d11f 切换编辑器类型时修正了common.php中编辑器样式的分配问题。
根据编辑器类型(ckeditor、wangEditor、ueditor),现在正确地分配了相应的HTML结构。
之前默认分配给wangEditor的div结构,在编辑器类型为ueditor时会出现问题,现在已将其修正为正确的script标签结构。
同时,也对ckeditor的textarea标签进行了样式修正。
2024-07-18 12:40:18 +08:00
wolfcode
27b718da3c fix(composer): update alibabacloud/client dependency to support PHP 8.1
The Alibaba Cloud client dependency has been updated to version 1.6.0 to add
PHP 8.1 compatibility. This change is reflected in the composer.json file under
the `require` section.

Additionally, the ordering of the `ext-mysqli` and `ext-pdo` dependencies has
been corrected for clarity and consistency.
2024-07-17 11:32:15 +08:00
wolfcode
e49b747f43 style: adjust width of search input in easy-admin
Set a fixed width for the search input field in the easy-admin plugin to
enhance the consistency and readability of the search section. The update
modifies the easy-admin.js and public.css files to enforce the new styling.
2024-07-16 16:02:19 +08:00
wolfcode
a2942e5589 feat(easy-admin): add laySearch support for search boxes in lists
Introduce the `laySearch` option to enable the search functionality on list
page search boxes. This improvement allows users to have a more intuitive
and interactive way of searching through the list data by providing a search
box with clear and concise feedback.

When `laySearch` is set to true, the corresponding search box will now
be equipped with the `lay-search` class which enhances the user interface
and experience.
2024-07-10 14:30:16 +08:00
wolfcode
f121d3c8b3 feat(easy-admin): add laySearch support for search boxes in lists
Introduce the `laySearch` option to enable the search functionality on list
page search boxes. This improvement allows users to have a more intuitive
and interactive way of searching through the lists by providing a search
input alongside the dropdown options.
2024-07-10 14:29:49 +08:00
wolfcode
a069704803 🚀 Layui v2.9.14 2024-07-10 14:20:48 +08:00
wolfcode
58db4f4277 fix(common): correct spelling of 'selectFields' in Curd.php
Fix misspelling of 'selectFields' option throughout Curd.php, ensuring consistency and
correctness in the command's configuration options.
2024-07-10 11:53:07 +08:00
wolfcode
332ededb3a update README.md 2024-07-10 11:19:01 +08:00
wolfcode
214b3e9f19 update README.md 2024-07-10 11:16:35 +08:00
wolfcode
6b97ca12de feat(admin): add table order sorting functionality
初始化表格排序,js排序操作传递到服务端
2024-07-08 14:57:18 +08:00
wolfcode
11aa723d75 fix(easy-admin): prevent js error when operat is not object
When the operat parameter passed to the easy-admin.js plugin is not an object,it previously caused a JavaScript error. This fix adds a check to ensure that operat is indeed an object before processing it, thus preventing the error.
2024-07-08 10:25:40 +08:00
wolfcode
df69c2aea4 refactor(admin-model): change MallGoods cate relation from BelongsTo to HasOne
The relationship between MallGoods and MallCate has been updated to reflect a HasOne
association rather than a BelongsTo. This change is reflected in the cate() method
of the MallGoods model, enhancing the flexibility of the model relationships.
2024-07-01 13:51:58 +08:00
wolfcode
5855a97255 Improve category selection in goods management - Simplify HTML structure and recommend a concise coding method for
category selection in both add and edit pages of the mall goods module.
- Modify the corresponding JS file to support the new category display format.
- Refactor controller code to assign categories using a more efficient method.
- Ensure consistent category data processing across all related views.

Note: This change updates the way categories are presented to the admin user,
enhancing usability and maintainability of the goods management system.
2024-06-20 10:23:27 +08:00
wolfcode
88035326a4 refactor(admin): change default editor from ueditor to wangEditor
Due to the recommendation for better performance and user experience,
the default editor in both configuration files and view templates has been
switched from ueditor to wangEditor. The code changes reflect this update by
marking ueditor as '(不建议使用)' (not recommended) and WangEditor as
'(推荐使用)' (recommended). Additionally, the relevant controller file has
been updated to ensure that the correct default editor is loaded based on
the new configuration setting.
2024-06-19 15:30:03 +08:00
wolfcode
df3571534c Adjust the editor_textarea function to allow $detail parameter as nullable
The function signature for `editor_textarea` in common.php has been updated
to accept `$detail` as a nullable string. This change allows the function to handle cases
where `$detail` might not be provided or could be null.

```php
-    function editor_textarea(string $detail, string $name = 'desc', string $placeholder = '请输入'): string
+ function editor_textarea(?string $detail, string $name = 'desc', string $placeholder = '请输入'): string
```
2024-06-18 17:36:23 +08:00
wolfcode
417a834593 feat(curd): 初始化下拉、单选、复选字段的后缀和字段类型定义
在BuildCurd.php中,新增了下拉字段的后缀定义及相关的字段类型数组初始化,同时对单选框和复选框的后缀及字段类型进行了初始化配置。这些改动将有助于更精确地识别和处理不同类型的表单字段。
2024-06-14 15:15:34 +08:00
wolfcode
070d9494e7 🚀 Layui V2.9.11 2024-06-07 13:38:33 +08:00
wolfcode
6dcd0c8d1f fix: 修复自动生成页面时获取表字段注释匹配失效 2024-06-07 11:35:46 +08:00
wolfcode
09beccc30b fix: 当修改后台路径时百度编辑器配置报错 2024-06-07 09:41:07 +08:00
wolfcode
41a444a49d Update LICENSE 2024-06-06 18:42:20 +08:00
wolfcode
1071c7e1d2 Update Ajax.php 2024-06-06 16:58:41 +08:00
wolfcode
8b1e9e3744 Update README.md 2024-05-23 17:38:40 +08:00
wolfcode
40256d3d4b Update welcome.html 2024-05-23 17:38:37 +08:00
wolfcode
4974ba7c79 🚀 Layui V2.9.10 2024-05-23 17:34:15 +08:00
wolfcode
9d61503ea6 Update Curd.php 2024-05-14 15:23:40 +08:00
wolfcode
2ec046de07 Update console.php 2024-05-14 13:33:30 +08:00
wolfcode
0c68f2c1ad Update session.php 2024-05-13 18:30:14 +08:00
wolfcode
a6fd81b0ed 🚀 202405重置版 2024-05-13 11:16:20 +08:00
354 changed files with 77175 additions and 35921 deletions

View File

@@ -1,24 +1,32 @@
APP_DEBUG=true
[APP]
# 后台系统日志开关
APP_ADMIN_SYSTEM_LOG=true
DEFAULT_TIMEZONE=Asia/Shanghai
[DATABASE]
TYPE=mysql
HOSTNAME=127.0.0.1
DATABASE=easyadmin8
USERNAME=root
PASSWORD=root
HOSTPORT=3306
CHARSET=utf8mb4
DEBUG=true
PREFIX=ea8_
DB_TYPE=mysql
DB_HOST=127.0.0.1
DB_NAME=easyadmin8
DB_USER=root
DB_PASS=root
DB_PORT=3306
DB_CHARSET=utf8mb4
DB_PREFIX=ea8_
[LANG]
default_lang=zh-cn
# 限流器开关 若启动需要配置 Redis 服务
RATE_LIMITING_STATUS=false
# Redis配置
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_PREFIX=
REDIS_DATABASE=0
# 后台配置项组
[EASYADMIN]
# 后台地址后缀名称
ADMIN=admin

27
.gitignore vendored
View File

@@ -1,21 +1,12 @@
/.idea
/.vscode
*.log
.env
config/install/lock/install.lock
/public/upload
/public/storage
/public/docs
runtime/admin
runtime/index
runtime/log
runtime/session
runtime/temp
runtime/cache
public/conf
public/WowOss.exe
app/index/controller/Test.php
composer.phar
app/test/
vendor
composer.lock
composer.lock
.DS_Store
Thumbs.db
/.idea
/.vscode
/vendor
/.settings
/.buildpath
/.project

66
CURD.md
View File

@@ -1,66 +0,0 @@
# CURD命令大全
`EasyAdmin8`框架以内置快速生成CURD的命令, 包括控制器、视图、模型、JS文件。能够使开发者效率得到进一步提升。
# 常用命令
```shell
# 生成ea8_test_goods表的CURD
php think curd -t test_goods
# 生成ea8_test_goods表的CURD, 文件冲突时强制覆盖
php think curd -t test_goods -f 1
# 删除ea8_test_goods表的CURD
php think curd -t test_goods -d 1
# 生成ea8_test_goods表的CURD, 控制器在目录demo下的Goods.php文件
php think curd -t test_goods -c demo/Goods
# 生成ea8_test_goods表的CURD, 模型在目录demo下的Goods.php文件
php think curd -t test_goods -m demo/Goods
# 生成ea8_test_goods表的CURD, 并关联ea8_test_cate表, 并设置外键为cate_id
php think curd -t test_goods -r test_cate --foreignKey=cate_id --primaryKey=id
# 生成ea8_test_goods表的CURD, 并关联ea8_test_cate表, 并设置只显示title,image两个字段
php think curd -t test_goods -r test_cate --foreignKey=cate_id --relationOnlyFileds=title,image
# 生成ea8_test_goods表的CURD, 并关联ea8_test_cate表, 并设置主表外键cate_id在表单的下拉选择显示的关联表的title字段
php think curd -t test_goods -r test_cate --foreignKey=cate_id --relationBindSelect=title
# 生成ea8_test_goods表的CURD, 并设置logo字段后缀为单图片
php think curd -t test_goods --imageFieldSuffix=logo
# 生成ea8_test_goods表的CURD, 并设置忽略remark, stock字段
php think curd -t test_goods --ignoreFields=remark --ignoreFields=stock
```
# 参数介绍
| 短参 | 长参 | 说明 |
| --- | --- |--- |
| -t | --table=VALUE | 主表名 |
| -c | --controllerFilename=VALUE | 控制器文件名 |
| -m | --modelFilename=VALUE | 主表模型文件名 |
| -f | --force=VALUE | 强制覆盖模式 |
| -d | --delete=VALUE | 删除模式 |
| | --checkboxFieldSuffix=VALUE | 复选框字段后缀 |
| | --radioFieldSuffix=VALUE | 单选框字段后缀 |
| | --imageFieldSuffix=VALUE | 单图片字段后缀 |
| | --imagesFieldSuffix=VALUE | 多图片字段后缀 |
| | --fileFieldSuffix=VALUE | 单文件字段后缀 |
| | --filesFieldSuffix=VALUE | 多文件字段后缀 |
| | --dateFieldSuffix=VALUE | 时间字段后缀 |
| | --switchFields=VALUE | 开关的字段 |
| | --selectFileds=VALUE | 下拉的字段 |
| | --editorFields=VALUE | 富文本的字段 |
| | --sortFields=VALUE | 排序的字段 |
| | --ignoreFields=VALUE | 忽略的字段 |
| -r | --relationTable=VALUE | 关联表名 |
| | --foreignKey=VALUE | 关联外键 |
| | --primaryKey=VALUE | 关联主键 |
| | --relationOnlyFileds=VALUE | 关联模型中只显示的字段 |
| | --relationBindSelect=VALUE | 关联模型中的字段用于主表外键的表单下拉选择 |
| | --relationModelFilename=VALUE | 关联模型文件名 |

View File

@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.

View File

@@ -1 +0,0 @@
# EasyAdmin8

View File

@@ -1,16 +1,31 @@
<div align="center" dir="auto">
<img alt="log" src="public/static/common/images/logo-8.png" />
<p>
<img src="https://img.shields.io/badge/php-%3E=8.1.0-brightgreen.svg?style=for-the-badge&logo=php&colorB=ff69b4" alt="php">
<img src="https://img.shields.io/badge/mysql-%3E=5.7-brightgreen.svg?style=for-the-badge&logo=mysql&colorB=blue" alt="MySQL">
<img src="https://img.shields.io/badge/thinkphp-%3E=8.0.0-brightgreen.svg?style=for-the-badge&logo=thinkphp" alt="ThinkPHP">
<img src="https://img.shields.io/badge/layui-%3E=2.9.0-brightgreen.svg?style=for-the-badge&logo=layui&colorB=orange" alt="layui">
<img src="https://img.shields.io/badge/license-MIT-green?style=for-the-badge&logo=license&colorB=purple" alt="License">
</p>
</div>
## `EasyAdmin8`所有版本 (当前项目为`ThinkPHP`版本)
| | Github | Gitee |
|----------|:--------------------------------------------------------------------:|:-----------------------------------------------------------------:|
| ThinkPHP | [EasyAdmin8](https://github.com/wolf-leo/EasyAdmin8) | [EasyAdmin8](https://gitee.com/wolf18/EasyAdmin8) |
| Laravel | [EasyAdmin8-Laravel](https://github.com/wolf-leo/EasyAdmin8-Laravel) | [EasyAdmin8-Laravel](https://gitee.com/wolf18/EasyAdmin8-Laravel) |
| webman | [EasyAdmin8-webman](https://github.com/wolf-leo/EasyAdmin8-webman) | [EasyAdmin8-webman](https://gitee.com/wolf18/EasyAdmin8-webman) |
| | Github | Gitee |
|----------|:----------------------------------------------------------------------:|:---------------------------------------------------------------------:|
| ThinkPHP | [EasyAdmin8](https://github.com/EasyAdmin8/EasyAdmin8) | [EasyAdmin8](https://gitee.com/EasyAdmin8/EasyAdmin8) |
| Laravel | [EasyAdmin8-Laravel](https://github.com/EasyAdmin8/EasyAdmin8-Laravel) | [EasyAdmin8-Laravel](https://gitee.com/EasyAdmin8/EasyAdmin8-Laravel) |
| webman | [EasyAdmin8-webman](https://github.com/EasyAdmin8/EasyAdmin8-webman) | [EasyAdmin8-webman](https://gitee.com/EasyAdmin8/EasyAdmin8-webman) |
## 项目介绍
> `EasyAdmin8` 在 [`EasyAdmin`](https://gitee.com/zhongshaofa/easyadmin) 的基础上更新 ThinkPHP 框架到 8.0 PHP 最低版本要求不低于 8.0
> `EasyAdmin8` 在 [`EasyAdmin`](https://gitee.com/zhongshaofa/easyadmin) 的基础上更新 ThinkPHP 框架到 8.1+ PHP 最低版本要求不低于 8.1
>
> 2025年起 `PHP` 版本要求提升到 `8.1+`, 如果需要 `8.0` 到分支 `v8.0` 中下载
>
> ThinkPHP v8.0 和 Layui v2.9.x 的快速开发的后台管理系统。
> ThinkPHP v8.1+ 和 Layui v2.9.x 的快速开发的后台管理系统。
>
> 项目地址:[http://easyadmin8.top](http://easyadmin8.top)
>
@@ -20,22 +35,32 @@
>
>【如果不能访问,可以自行本地搭建预览或参考下方界面预览图】
## 大版本更新记录:
[更新记录](log.md)
## 安装教程
> EasyAdmin8 使用 Composer 来管理项目依赖。因此,在使用 EasyAdmin8 之前,请确保你的机器已经安装了 Composer。
### 通过git下载安装包composer安装依赖包
### 通过一键安装命令
```
if [ -f /usr/bin/curl ];then curl -sSO https://easyadmin8.top/auto-install-EasyAdmin8.sh;else wget -O auto-install-EasyAdmin8.sh https://easyadmin8.top/auto-install-EasyAdmin8.sh;fi;bash auto-install-EasyAdmin8.sh
```
### 通过`git`下载安装包,`composer`安装依赖包
```
1.下载安装包
git clone https://github.com/wolf-leo/EasyAdmin8
git clone https://github.com/EasyAdmin8/EasyAdmin8
或者
git clone https://gitee.com/wolf18/EasyAdmin8
git clone https://gitee.com/EasyAdmin8/EasyAdmin8
2.安装依赖包(确保 PHP 版本 >= 8.0
2.安装依赖包(确保 PHP 版本 >= 8.1
在根目录下 composer install ,如果有报错信息可以使用命令 composer install --ignore-platform-reqs
@@ -54,7 +79,7 @@
## CURD命令大全
> 参考 [CURD命令大全](CURD.md)
> 参考 [CURD命令大全](https://edocs.easyadmin8.top/curd/command.html)
## 常见问题
@@ -76,11 +101,11 @@
## 相关文档
* [ThinkPHP 8.0](https://doc.thinkphp.cn/v8_0)
* [ThinkPHP 8.1](https://doc.thinkphp.cn)
* [EasyAdmin](http://easyadmin.99php.cn/docs)
* [Layui 2.8.x](https://layui.dev/docs/2.8/)
* [Layui 2.9.x](https://layui.dev/docs)
* [Layuimini](https://github.com/zhongshaofa/layuimini)

22
app/AppService.php Normal file
View File

@@ -0,0 +1,22 @@
<?php
declare (strict_types = 1);
namespace app;
use think\Service;
/**
* 应用服务类
*/
class AppService extends Service
{
public function register()
{
// 服务注册
}
public function boot()
{
// 服务启动
}
}

View File

@@ -64,7 +64,7 @@ abstract class BaseController
* @return array|string|true
* @throws ValidateException
*/
protected function validate(array $data, $validate, array $message = [], bool $batch = false)
protected function validate(array $data, string|array $validate, array $message = [], bool $batch = false)
{
if (is_array($validate)) {
$v = new Validate();
@@ -72,7 +72,7 @@ abstract class BaseController
} else {
if (strpos($validate, '.')) {
// 支持场景
list($validate, $scene) = explode('.', $validate);
[$validate, $scene] = explode('.', $validate);
}
$class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate);
$v = new $class();

View File

@@ -1,11 +1,8 @@
<?php
namespace app;
// 应用请求对象类
class Request extends \think\Request
{
protected $filter = ['htmlspecialchars'];
}

View File

@@ -1,33 +1,24 @@
<?php
return [
// 不需要验证登录的控制器
'no_login_controller' => [
'login',
],
// 不需要验证登录的节点
'no_login_node' => [
'login/index',
'login/out',
],
// 后台路径地址 默认 admin
'alias_name' => env('EASYADMIN.ADMIN'),
// 不需要验证权限的控制器
'no_auth_controller' => [
'no_auth_controller' => [
'ajax',
'login',
'index',
],
// 不需要验证权限的节点
'no_auth_node' => [
'no_auth_node' => [
'login/index',
'login/out',
],
//上传类型
'upload_types' => [
'upload_types' => [
'local' => '本地存储',
'oss' => '阿里云oss',
'cos' => '腾讯云cos',
@@ -35,9 +26,11 @@ return [
],
// 默认编辑器
'editor_types' => [
'ueditor' => '百度编辑器',
'editor_types' => [
'ueditor' => '百度编辑器(不建议使用)',
'ckeditor' => 'CK编辑器',
'wangEditor' => 'wangEditor',
'wangEditor' => 'wangEditor(推荐使用)',
'EasyMDE' => 'EasyMDE(markdown)',
],
];

View File

@@ -1,10 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | 应用设置
// +----------------------------------------------------------------------
use think\facade\Env;
return [
];

View File

@@ -1,10 +0,0 @@
<?php
// +----------------------------------------------------------------------
// | 控制台配置
// +----------------------------------------------------------------------
return [
// 指令定义
'commands' => [
'alioss' => 'addons\alioss\command\Alioss',
],
];

View File

@@ -1,19 +1,31 @@
<?php
use app\admin\middleware\CheckInstall;
use app\admin\middleware\CheckLogin;
use app\admin\middleware\CheckAuth;
use app\admin\middleware\SystemLog;
use app\admin\middleware\RateLimiting;
// 你可以在这里继续写你需要的路由
// +----------------------------------------------------------------------
// | 路由设置
// | 这里只是路由的中间件
// | 至于为什么要把中间件配置写在这里呢??? Why???
// | 因为 ThinkPHP官方最新版本 已经不支持在中间件获取 controller 和 action 了
// +----------------------------------------------------------------------
return [
// 路由中间件
'middleware' => [
// // 后台视图初始化
// \app\admin\middleware\ViewInit::class,
// 检测用户是否登录
// \app\admin\middleware\CheckAdmin::class,
// 限流中间件
RateLimiting::class,
// 判断是否已经安装后台系统
CheckInstall::class,
// 检测是否登录
CheckLogin::class,
// 操作日志
SystemLog::class,
// 验证节点权限
CheckAuth::class,
],
];
];

View File

@@ -6,6 +6,7 @@ use app\admin\model\SystemUploadfile;
use app\admin\service\UploadService;
use app\common\controller\AdminController;
use app\common\service\MenuService;
use app\Request;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
@@ -25,28 +26,28 @@ class Ajax extends AdminController
*/
public function initAdmin(): Json
{
$cacheData = Cache::get('initAdmin_' . session('admin.id'));
$cacheData = Cache::get('initAdmin_' . $this->adminUid);
if (!empty($cacheData)) {
return json($cacheData);
}
$menuService = new MenuService(session('admin.id'));
$menuService = new MenuService($this->adminUid);
$data = [
'logoInfo' => [
'title' => sysconfig('site', 'logo_title'),
'image' => sysconfig('site', 'logo_image'),
'title' => sysConfig('site', 'logo_title'),
'image' => sysConfig('site', 'logo_image'),
'href' => __url('index/index'),
],
'homeInfo' => $menuService->getHomeInfo(),
'menuInfo' => $menuService->getMenuTree(),
];
Cache::tag('initAdmin')->set('initAdmin_' . session('admin.id'), $data);
Cache::tag('initAdmin')->set('initAdmin_' . $this->adminUid, $data);
return json($data);
}
/**
* 清理缓存接口
*/
public function clearCache()
public function clearCache(): void
{
Cache::clear();
$this->success('清理缓存成功');
@@ -54,17 +55,19 @@ class Ajax extends AdminController
/**
* 上传文件
* @param Request $request
* @return Json|null
*/
public function upload()
public function upload(Request $request): Json|null
{
$this->isDemo && $this->error('演示环境下不允许修改');
$this->checkPostRequest();
$type = $this->request->param('type', '');
$type = $request->param('type', '');
$data = [
'upload_type' => $this->request->post('upload_type'),
'file' => $this->request->file($type == 'editor' ? 'upload' : 'file'),
'upload_type' => $request->post('upload_type'),
'file' => $request->file($type == 'editor' ? 'upload' : 'file'),
];
$uploadConfig = sysconfig('upload');
$uploadConfig = sysConfig('upload');
empty($data['upload_type']) && $data['upload_type'] = $uploadConfig['upload_type'];
$rule = [
'upload_type|指定上传类型有误' => "in:{$uploadConfig['upload_allow_type']}",
@@ -81,44 +84,46 @@ class Ajax extends AdminController
if ($code == 0) {
$this->error($upload['data'] ?? '');
}else {
$type == 'editor' ? json(
[
'error' => ['message' => '上传成功', 'number' => 201,],
'fileName' => '',
'uploaded' => 1,
'url' => $upload['data']['url'] ?? '',
]
) : $this->success('上传成功', $upload['data'] ?? '');
if ($type == 'editor') {
return json(
[
'error' => ['message' => '上传成功', 'number' => 201,],
'fileName' => '',
'uploaded' => 1,
'url' => $upload['data']['url'] ?? '',
]
);
}else {
$this->success('上传成功', $upload['data'] ?? '');
}
}
}
/**
* 获取上传文件列表
* @param Request $request
* @return Json
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function getUploadFiles(): Json
public function getUploadFiles(Request $request): Json
{
$get = $this->request->get();
$page = isset($get['page']) && !empty($get['page']) ? $get['page'] : 1;
$limit = isset($get['limit']) && !empty($get['limit']) ? $get['limit'] : 10;
$title = isset($get['title']) && !empty($get['title']) ? $get['title'] : null;
$this->model = new SystemUploadfile();
$count = $this->model
->where(function (Query $query) use ($title) {
!empty($title) && $query->where('original_name', 'like', "%{$title}%");
})
$get = $request->get();
$page = !empty($get['page']) ? $get['page'] : 1;
$limit = !empty($get['limit']) ? $get['limit'] : 10;
$title = !empty($get['title']) ? $get['title'] : null;
$count = SystemUploadfile::where(function(Query $query) use ($title) {
!empty($title) && $query->where('original_name', 'like', "%{$title}%");
})
->count();
$list = $this->model
->where(function (Query $query) use ($title) {
!empty($title) && $query->where('original_name', 'like', "%{$title}%");
})
$list = SystemUploadfile::where(function(Query $query) use ($title) {
!empty($title) && $query->where('original_name', 'like', "%{$title}%");
})
->page($page, $limit)
->order($this->sort)
->select();
$data = [
->select()->toArray();
$data = [
'code' => 0,
'msg' => '',
'count' => $count,
@@ -129,18 +134,19 @@ class Ajax extends AdminController
/**
* 百度编辑器上传
* @param Request $request
* @return Json
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function uploadUEditor(): Json
public function uploadUEditor(Request $request): Json
{
$uploadConfig = sysconfig('upload');
$uploadConfig = sysConfig('upload');
$upload_allow_size = $uploadConfig['upload_allow_size'];
$_upload_allow_ext = explode(',', $uploadConfig['upload_allow_ext']);
$upload_allow_ext = [];
array_map(function ($value) use (&$upload_allow_ext) {
array_map(function($value) use (&$upload_allow_ext) {
$upload_allow_ext[] = '.' . $value;
}, $_upload_allow_ext);
$config = [
@@ -171,8 +177,8 @@ class Ajax extends AdminController
"fileMaxSize" => $upload_allow_size,
"fileAllowFiles" => $upload_allow_ext,
];
$action = $this->request->param('action/s', '');
$file = $this->request->file('file');
$action = $request->param('action/s', '');
$file = $request->file('file');
$upload_type = $uploadConfig['upload_type'];
switch ($action) {
case 'image':
@@ -204,4 +210,23 @@ class Ajax extends AdminController
return json($config);
}
}
public function composerInfo(): Json
{
$lockFilePath = root_path() . '/composer.lock';
$list = [];
if (file_exists($lockFilePath)) {
$lockFileContent = file_get_contents($lockFilePath);
if ($lockFileContent !== false) {
$lockData = json_decode($lockFileContent, true);
if (!empty($lockData['packages'])) {
foreach ($lockData['packages'] as $package) {
$list[] = ['name' => $package['name'], 'version' => $package['version']];
}
}
}
}
$this->success('success', $list);
}
}

View File

@@ -5,27 +5,24 @@ namespace app\admin\controller;
use app\admin\model\SystemAdmin;
use app\admin\model\SystemQuick;
use app\common\controller\AdminController;
use app\Request;
use Exception;
use think\App;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\facade\Db;
use think\facade\Env;
class Index extends AdminController
{
/**
* 后台主页
* @param Request $request
* @return string
* @throws Exception
*/
public function index(): string
public function index(Request $request): string
{
return $this->fetch('', [
'admin' => session('admin'),
]);
return $this->fetch('', ['admin' => $request->adminUserInfo,]);
}
/**
@@ -39,65 +36,67 @@ class Index extends AdminController
$mysqlVersion = Db::query("select version() as version")[0]['version'] ?? '未知';
$phpVersion = phpversion();
$versions = compact('tpVersion', 'mysqlVersion', 'phpVersion');
$quicks = SystemQuick::field('id,title,icon,href')
->where(['status' => 1])
->order('sort', 'desc')
->limit(8)
->select();
$quick_list = SystemQuick::field('id,title,icon,href')
->where(['status' => 1])->order('sort', 'desc')->limit(50)->select()->toArray();
$quicks = array_chunk($quick_list, 8);
$this->assign(compact('quicks', 'versions'));
return $this->fetch();
}
/**
* 修改管理员信息
* @param Request $request
* @return string
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function editAdmin(): string
public function editAdmin(Request $request): string
{
$id = session('admin.id');
$id = $this->adminUid;
$row = (new SystemAdmin())
->withoutField('password')
->find($id);
empty($row) && $this->error('用户信息不存在');
if ($this->request->isPost()) {
$post = $this->request->post();
if ($request->isPost()) {
$post = $request->post();
$this->isDemo && $this->error('演示环境下不允许修改');
$rule = [];
$this->validate($post, $rule);
try {
$save = $row
->allowField(['head_img', 'phone', 'remark', 'update_time'])
->save($post);
} catch (Exception $e) {
$login_type = $post['login_type'] ?? 1;
if ($login_type == 2) {
$ga_secret = (new SystemAdmin())->where('id', $id)->value('ga_secret');
if (empty($ga_secret)) $this->error('请先绑定谷歌验证器');
}
$save = $row->allowField(['head_img', 'phone', 'remark', 'update_time', 'login_type'])->save($post);
}catch (\PDOException $e) {
$this->error('保存失败');
}
$save ? $this->success('保存成功') : $this->error('保存失败');
}
$this->assign('row', $row);
$notes = (new SystemAdmin())->notes;
$this->assign('notes', $notes);
return $this->fetch();
}
/**
* 修改密码
* @param Request $request
* @return string
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function editPassword(): string
public function editPassword(Request $request): string
{
$id = session('admin.id');
$id = $this->adminUid;
$row = (new SystemAdmin())
->withoutField('password')
->find($id);
if (!$row) {
$this->error('用户信息不存在');
}
if ($this->request->isPost()) {
$post = $this->request->post();
if ($request->isPost()) {
$post = $request->post();
$this->isDemo && $this->error('演示环境下不允许修改');
$rule = [
'password|登录密码' => 'require',
@@ -110,14 +109,14 @@ class Index extends AdminController
try {
$save = $row->save([
'password' => password($post['password']),
]);
} catch (Exception $e) {
'password' => password_hash($post['password'], PASSWORD_DEFAULT),
]);
}catch (Exception $e) {
$this->error('保存失败');
}
if ($save) {
$this->success('保存成功');
} else {
}else {
$this->error('保存失败');
}
}
@@ -125,4 +124,37 @@ class Index extends AdminController
return $this->fetch();
}
/**
* 设置谷歌验证码
* @param Request $request
* @return string
* @throws Exception
*/
public function set2fa(Request $request): string
{
$id = $this->adminUid;
$row = (new SystemAdmin())->withoutField('password')->find($id);
if (!$row) $this->error('用户信息不存在');
// You can see: https://gitee.com/wolf-code/authenticator
$ga = new \Wolfcode\Authenticator\google\PHPGangstaGoogleAuthenticator();
if (!$request->isAjax()) {
$old_secret = $row->ga_secret;
$secret = $ga->createSecret(32);
$ga_title = $this->isDemo ? 'EasyAdmin8演示环境' : '可自定义修改显示标题';
$dataUri = $ga->getQRCode($ga_title, $secret);
$this->assign(compact('row', 'dataUri', 'old_secret', 'secret'));
return $this->fetch();
}
$this->isDemo && $this->error('演示环境下不允许修改');
$post = $request->post();
$ga_secret = $post['ga_secret'] ?? '';
$ga_code = $post['ga_code'] ?? '';
if (empty($ga_code)) $this->error('请输入验证码');
if (!$ga->verifyCode($ga_secret, $ga_code)) $this->error('验证码错误');
$row->ga_secret = $ga_secret;
$row->login_type = 2;
$row->save();
$this->success('操作成功');
}
}

View File

@@ -4,74 +4,79 @@ namespace app\admin\controller;
use app\admin\model\SystemAdmin;
use app\common\controller\AdminController;
use app\common\utils\Helper;
use think\captcha\facade\Captcha;
use think\facade\Env;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use app\Request;
use think\Response;
use Wolfcode\RateLimiting\Attributes\RateLimitingMiddleware;
/**
* Class Login
* @package app\admin\controller
*/
class Login extends AdminController
{
/**
* 初始化方法
*/
public function initialize()
protected bool $ignoreLogin = true;
public function initialize(): void
{
parent::initialize();
$action = $this->request->action();
if (!empty(session('admin')) && !in_array($action, ['out'])) {
$adminModuleName = config('app.admin_alias_name');
if (!empty($this->adminUid) && !in_array($action, ['out'])) {
$adminModuleName = config('admin.alias_name');
$this->success('已登录,无需再次登录', [], __url("@{$adminModuleName}"));
}
}
/**
* 用户登录
* @param Request $request
* @return string
* @throws \Exception
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function index(): string
#[RateLimitingMiddleware(key: [Helper::class, 'getIp'], seconds: 1, limit: 1, message: '请求过于频繁')]
public function index(Request $request): string
{
$captcha = Env::get('EASYADMIN.CAPTCHA', 1);
if ($this->request->isPost()) {
$post = $this->request->post();
$rule = [
'username|用户名' => 'require',
'password|密码' => 'require',
'keep_login|是否保持登录' => 'require',
];
$captcha == 1 && $rule['captcha|验证码'] = 'require|captcha';
$this->validate($post, $rule);
$admin = SystemAdmin::where(['username' => $post['username']])->find();
if (empty($admin)) {
$this->error('用户不存在');
}
if (password($post['password']) != $admin->password) {
$this->error('密码输入有误');
}
if ($admin->status == 0) {
$this->error('账号已被禁用');
}
$admin->login_num += 1;
$admin->save();
$admin = $admin->toArray();
unset($admin['password']);
$admin['expire_time'] = $post['keep_login'] == 1 ? true : time() + 7200;
session('admin', $admin);
$this->success('登录成功');
$captcha = env('EASYADMIN.CAPTCHA', 1);
if (!$request->isPost()) return $this->fetch('', compact('captcha'));
$post = $request->post();
$rule = [
'username|用户名' => 'require',
'password|密码' => 'require',
'keep_login|是否保持登录' => 'require',
];
$captcha == 1 && $rule['captcha|验证码'] = 'require|captcha';
$this->validate($post, $rule);
$admin = SystemAdmin::where(['username' => $post['username']])->find();
if (empty($admin)) {
$this->error('用户不存在');
}
$this->assign('captcha', $captcha);
$this->assign('demo', $this->isDemo);
return $this->fetch();
if (!password_verify($post['password'], $admin->password)) {
$this->error('密码输入有误');
}
if ($admin->status == 0) {
$this->error('账号已被禁用');
}
if ($admin->login_type == 2) {
if (empty($post['ga_code'])) $this->error('请输入谷歌验证码', ['is_ga_code' => true]);
$ga = new \Wolfcode\Authenticator\google\PHPGangstaGoogleAuthenticator();
if (!$ga->verifyCode($admin->ga_secret, $post['ga_code'])) $this->error('谷歌验证码错误');;
}
$admin->login_num += 1;
$admin->save();
$admin = $admin->toArray();
unset($admin['password']);
$admin['expire_time'] = $post['keep_login'] == 1 ? true : time() + 7200;
session('admin', $admin);
$this->success('登录成功');
}
/**
* 用户退出
*/
public function out()
public function out(): void
{
session('admin', null);
$this->success('退出登录成功');
@@ -83,6 +88,6 @@ class Login extends AdminController
*/
public function captcha(): Response
{
return Captcha::create();
return Captcha::instance()->create();
}
}

View File

@@ -3,26 +3,19 @@
namespace app\admin\controller\mall;
use app\admin\model\MallCate;
use app\admin\traits\Curd;
use app\common\controller\AdminController;
use app\admin\service\annotation\ControllerAnnotation;
use app\admin\service\annotation\NodeAnnotation;
use think\App;
/**
* Class Admin
* @package app\admin\controller\system
* @ControllerAnnotation(title="商品分类管理")
*/
#[ControllerAnnotation(title: '商品分类管理')]
class Cate extends AdminController
{
use Curd;
public function __construct(App $app)
{
parent::__construct($app);
$this->model = new MallCate();
self::$model = MallCate::class;
}
}

View File

@@ -2,51 +2,40 @@
namespace app\admin\controller\mall;
use app\admin\model\MallCate;
use app\admin\model\MallGoods;
use app\admin\traits\Curd;
use app\admin\service\annotation\MiddlewareAnnotation;
use app\common\controller\AdminController;
use app\admin\service\annotation\ControllerAnnotation;
use app\admin\service\annotation\NodeAnnotation;
use app\Request;
use think\App;
use think\response\Json;
use Wolfcode\Ai\Enum\AiType;
use Wolfcode\Ai\Service\AiChatService;
/**
* Class Goods
* @package app\admin\controller\mall
* @ControllerAnnotation(title="商城商品管理")
*/
#[ControllerAnnotation(title: '商城商品管理')]
class Goods extends AdminController
{
use Curd;
protected bool $relationSearch = true;
#[NodeAnnotation(ignore: ['export'])] // 过滤不需要生成的权限节点 默认 CURD 中会自动生成部分节点 可以在此处过滤
protected array $ignoreNode;
public function __construct(App $app)
{
parent::__construct($app);
$this->model = new MallGoods();
self::$model = new MallGoods();
$this->assign('cate', MallCate::column('title', 'id'));
}
/**
* @NodeAnnotation(title="列表")
*/
public function index()
#[NodeAnnotation(title: '列表', auth: true)]
public function index(Request $request): Json|string
{
if ($this->request->isAjax()) {
if (input('selectFields')) {
return $this->selectList();
}
if ($request->isAjax()) {
if (input('selectFields')) return $this->selectList();
list($page, $limit, $where) = $this->buildTableParams();
$count = $this->model
->withJoin('cate', 'LEFT')
->where($where)
->count();
$list = $this->model
->withJoin('cate', 'LEFT')
->where($where)
->page($page, $limit)
->order($this->sort)
->select();
$count = self::$model::where($where)->count();
$list = self::$model::with(['cate'])->where($where)->page($page, $limit)->order($this->sort)->select()->toArray();
$data = [
'code' => 0,
'msg' => '',
@@ -58,22 +47,20 @@ class Goods extends AdminController
return $this->fetch();
}
/**
* @NodeAnnotation(title="入库")
*/
public function stock($id)
#[NodeAnnotation(title: '入库', auth: true)]
public function stock(Request $request, $id): string
{
$row = $this->model->find($id);
$row = self::$model::find($id);
empty($row) && $this->error('数据不存在');
if ($this->request->isPost()) {
$post = $this->request->post();
if ($request->isPost()) {
$post = $request->post();
$rule = [];
$this->validate($post, $rule);
try {
$post['total_stock'] = $row->total_stock + $post['stock'];
$post['stock'] = $row->stock + $post['stock'];
$save = $row->save($post);
} catch (\Exception $e) {
}catch (\Exception $e) {
$this->error('保存失败');
}
$save ? $this->success('保存成功') : $this->error('保存失败');
@@ -82,4 +69,67 @@ class Goods extends AdminController
return $this->fetch();
}
#[MiddlewareAnnotation(ignore: MiddlewareAnnotation::IGNORE_LOGIN)]
public function no_check_login(Request $request): string
{
return '这里演示方法不需要经过登录验证';
}
#[NodeAnnotation(title: 'AI优化', auth: true)]
public function aiOptimization(Request $request): void
{
$message = $request->post('message');
if (empty($message)) $this->error('请输入内容');
// 演示环境下 默认返回的内容
if ($this->isDemo) {
$content = <<<EOF
演示环境中 默认返回的内容
我来帮你优化这个标题,让它更有吸引力且更符合电商平台的搜索逻辑:
"商务男士高端定制马克杯 | 办公室精英必备 | 优质陶瓷防烫手柄"
这个优化后的标题:
1. 突出了目标用户群体(商务男士)
2. 强调了产品定位(高端定制)
3. 点明了使用场景(办公室)
4. 添加了材质和功能特点(优质陶瓷、防烫手柄)
5. 使用了吸引人的关键词(精英必备)
这样的标题不仅更具体,也更容易被搜索引擎识别,同时能精准触达目标客户群。您觉得这个版本如何?
EOF;
$choices = [['message' => [
'role' => 'assistant',
'content' => $content,
]]];
$this->success('success', compact('choices'));
}
try {
$result = AiChatService::instance()
// 当使用推理模型时,可能存在超时的情况,所以需要设置超时时间为 0
// ->setTimeLimit(0)
// 请替换为您需要的模型类型
->setAiType(AiType::QWEN)
// 如果需要指定模型的 API 地址,可自行设置
// ->setAiUrl('https://xxx.com')
// 请替换为您的模型
->setAiModel('qwen-plus')
// 请替换为您的 API KEY
->setAiKey('sk-1234567890')
// 此内容会作为系统提示,会影响到回答的内容 当前仅作为测试使用
->setSystemContent('你现在是一位资深的海外电商产品经理')
->chat($message);
$choices = $result['choices'];
}catch (\Throwable $exception) {
$choices = [['message' => [
'role' => 'assistant',
'content' => $exception->getMessage(),
]]];
}
$this->success('success', compact('choices'));
}
}

View File

@@ -8,18 +8,14 @@ use app\common\constants\AdminConstant;
use app\common\controller\AdminController;
use app\admin\service\annotation\ControllerAnnotation;
use app\admin\service\annotation\NodeAnnotation;
use app\Request;
use think\App;
use think\response\Json;
/**
* Class Admin
* @package app\admin\controller\system
* @ControllerAnnotation(title="管理员管理")
*/
#[ControllerAnnotation(title: '管理员管理')]
class Admin extends AdminController
{
use \app\admin\traits\Curd;
protected array $sort = [
'sort' => 'desc',
'id' => 'desc',
@@ -28,29 +24,24 @@ class Admin extends AdminController
public function __construct(App $app)
{
parent::__construct($app);
$this->model = new SystemAdmin();
$this->assign('auth_list', $this->model->getAuthList());
self::$model = SystemAdmin::class;
$this->assign('auth_list', self::$model::getAuthList());
}
/**
* @NodeAnnotation(title="列表")
*/
public function index()
#[NodeAnnotation(title: '列表', auth: true)]
public function index(Request $request): Json|string
{
if ($this->request->isAjax()) {
if ($request->isAjax()) {
if (input('selectFields')) {
return $this->selectList();
}
list($page, $limit, $where) = $this->buildTableParams();
$count = $this->model
->where($where)
->count();
$list = $this->model
->withoutField('password')
$count = self::$model::where($where)->count();
$list = self::$model::withoutField('password')
->where($where)
->page($page, $limit)
->order($this->sort)
->select();
->select()->toArray();
$data = [
'code' => 0,
'msg' => '',
@@ -62,67 +53,57 @@ class Admin extends AdminController
return $this->fetch();
}
/**
* @NodeAnnotation(title="添加")
*/
public function add()
#[NodeAnnotation(title: '添加', auth: true)]
public function add(Request $request): string
{
if ($this->request->isPost()) {
$post = $this->request->post();
$authIds = $this->request->post('auth_ids', []);
if ($request->isPost()) {
$post = $request->post();
$authIds = $request->post('auth_ids', []);
$post['auth_ids'] = implode(',', array_keys($authIds));
$rule = [];
$this->validate($post, $rule);
if (empty($post['password'])) $post['password'] = '123456';
$post['password'] = password($post['password']);
$post['password'] = password_hash($post['password'],PASSWORD_DEFAULT);
try {
$save = $this->model->save($post);
} catch (\Exception $e) {
$this->error('保存失败');
$save = self::$model::create($post);
}catch (\Exception $e) {
$this->error('保存失败' . $e->getMessage());
}
$save ? $this->success('保存成功') : $this->error('保存失败');
}
return $this->fetch();
}
/**
* @NodeAnnotation(title="编辑")
*/
public function edit($id)
#[NodeAnnotation(title: '编辑', auth: true)]
public function edit(Request $request, $id = 0): string
{
$row = $this->model->find($id);
$row = self::$model::find($id);
empty($row) && $this->error('数据不存在');
if ($this->request->isPost()) {
$post = $this->request->post();
$authIds = $this->request->post('auth_ids', []);
if ($request->isPost()) {
$post = $request->post();
$authIds = $request->post('auth_ids', []);
$post['auth_ids'] = implode(',', array_keys($authIds));
$rule = [];
$this->validate($post, $rule);
if (isset($row['password'])) {
unset($row['password']);
}
try {
$save = $row->save($post);
TriggerService::updateMenu($id);
} catch (\Exception $e) {
$this->error('保存失败');
}catch (\Exception $e) {
$this->error('保存失败' . $e->getMessage());
}
$save ? $this->success('保存成功') : $this->error('保存失败');
}
$row->auth_ids = explode(',', $row->auth_ids ?: '');
$this->assign('row', $row);
return $this->fetch();
}
/**
* @NodeAnnotation(title="编辑")
*/
public function password($id)
#[NodeAnnotation(title: '设置密码', auth: true)]
public function password(Request $request, $id): string
{
$row = $this->model->find($id);
$row = self::$model::find($id);
empty($row) && $this->error('数据不存在');
if ($this->request->isAjax()) {
$post = $this->request->post();
if ($request->isAjax()) {
$post = $request->post();
$rule = [
'password|登录密码' => 'require',
'password_again|确认密码' => 'require',
@@ -133,25 +114,23 @@ class Admin extends AdminController
}
try {
$save = $row->save([
'password' => password($post['password']),
]);
} catch (\Exception $e) {
'password' => password_hash($post['password'], PASSWORD_DEFAULT),
]);
}catch (\Exception $e) {
$this->error('保存失败');
}
$save ? $this->success('保存成功') : $this->error('保存失败');
}
$row->auth_ids = explode(',', $row->auth_ids ?: '');
$this->assign('row', $row);
return $this->fetch();
}
/**
* @NodeAnnotation(title="删除")
*/
public function delete($id)
#[NodeAnnotation(title: '删除', auth: true)]
public function delete(Request $request): void
{
$this->checkPostRequest();
$row = $this->model->whereIn('id', $id)->select();
$id = $request->param('id');
$row = self::$model::whereIn('id', $id)->select();
$row->isEmpty() && $this->error('数据不存在');
$id == AdminConstant::SUPER_ADMIN_ID && $this->error('超级管理员不允许修改');
if (is_array($id)) {
@@ -161,23 +140,21 @@ class Admin extends AdminController
}
try {
$save = $row->delete();
} catch (\Exception $e) {
}catch (\Exception $e) {
$this->error('删除失败');
}
$save ? $this->success('删除成功') : $this->error('删除失败');
}
/**
* @NodeAnnotation(title="属性修改")
*/
public function modify()
#[NodeAnnotation(title: '属性修改', auth: true)]
public function modify(Request $request): void
{
$this->checkPostRequest();
$post = $this->request->post();
$post = $request->post();
$rule = [
'id|ID' => 'require',
'id|ID' => 'require',
'field|字段' => 'require',
'value|值' => 'require',
'value|值' => 'require',
];
$this->validate($post, $rule);
if (!in_array($post['field'], $this->allowModifyFields)) {
@@ -186,13 +163,13 @@ class Admin extends AdminController
if ($post['id'] == AdminConstant::SUPER_ADMIN_ID && $post['field'] == 'status') {
$this->error('超级管理员状态不允许修改');
}
$row = $this->model->find($post['id']);
$row = self::$model::find($post['id']);
empty($row) && $this->error('数据不存在');
try {
$row->save([
$post['field'] => $post['value'],
]);
} catch (\Exception $e) {
$post['field'] => $post['value'],
]);
}catch (\Exception $e) {
$this->error($e->getMessage());
}
$this->success('保存成功');

View File

@@ -8,18 +8,13 @@ use app\admin\service\TriggerService;
use app\common\controller\AdminController;
use app\admin\service\annotation\ControllerAnnotation;
use app\admin\service\annotation\NodeAnnotation;
use app\Request;
use think\App;
/**
* @ControllerAnnotation(title="角色权限管理")
* Class Auth
* @package app\admin\controller\system
*/
#[ControllerAnnotation(title: '角色权限管理', auth: true)]
class Auth extends AdminController
{
use \app\admin\traits\Curd;
protected array $sort = [
'sort' => 'desc',
'id' => 'desc',
@@ -28,34 +23,30 @@ class Auth extends AdminController
public function __construct(App $app)
{
parent::__construct($app);
$this->model = new SystemAuth();
self::$model = SystemAuth::class;
}
/**
* @NodeAnnotation(title="授权")
*/
public function authorize($id)
#[NodeAnnotation(title: '授权', auth: true)]
public function authorize(Request $request, $id): string
{
$row = $this->model->find($id);
$row = self::$model::find($id);
empty($row) && $this->error('数据不存在');
if ($this->request->isAjax()) {
$list = $this->model->getAuthorizeNodeListByAdminId($id);
if ($request->isAjax()) {
$list = self::$model::getAuthorizeNodeListByAdminId($id);
$this->success('获取成功', $list);
}
$this->assign('row', $row);
return $this->fetch();
}
/**
* @NodeAnnotation(title="授权保存")
*/
public function saveAuthorize()
#[NodeAnnotation(title: '授权保存', auth: true)]
public function saveAuthorize(Request $request): void
{
$this->checkPostRequest();
$id = $this->request->post('id');
$node = $this->request->post('node', "[]");
$id = $request->post('id');
$node = $request->post('node', "[]");
$node = json_decode($node, true);
$row = $this->model->find($id);
$row = self::$model::find($id);
empty($row) && $this->error('数据不存在');
try {
$authNode = new SystemAuthNode();
@@ -71,7 +62,7 @@ class Auth extends AdminController
$authNode->saveAll($saveAll);
}
TriggerService::updateMenu();
} catch (\Exception $e) {
}catch (\Exception $e) {
$this->error('保存失败');
}
$this->success('保存成功');

View File

@@ -7,39 +7,34 @@ use app\admin\service\TriggerService;
use app\common\controller\AdminController;
use app\admin\service\annotation\ControllerAnnotation;
use app\admin\service\annotation\NodeAnnotation;
use app\Request;
use think\App;
use think\facade\Cache;
use think\response\Json;
/**
* Class Config
* @package app\admin\controller\system
* @ControllerAnnotation(title="系统配置管理")
*/
#[ControllerAnnotation(title: '系统配置管理')]
class Config extends AdminController
{
public function __construct(App $app)
{
parent::__construct($app);
$this->model = new SystemConfig();
self::$model = SystemConfig::class;
$this->assign('upload_types', config('admin.upload_types'));
$this->assign('editor_types', config('admin.editor_types'));
}
/**
* @NodeAnnotation(title="列表")
*/
public function index()
#[NodeAnnotation(title: '列表', auth: true)]
public function index(Request $request): Json|string
{
return $this->fetch();
}
/**
* @NodeAnnotation(title="保存")
*/
public function save()
#[NodeAnnotation(title: '保存', auth: true)]
public function save(Request $request): void
{
$this->checkPostRequest();
$post = $this->request->post();
$post = $request->post();
$notAddFields = ['_token', 'file', 'group'];
try {
$group = $post['group'] ?? '';
@@ -47,25 +42,26 @@ class Config extends AdminController
if ($group == 'upload') {
$upload_types = config('admin.upload_types');
// 兼容旧版本
$this->model->where('name', 'upload_allow_type')->update(['value' => implode(',', array_keys($upload_types))]);
self::$model::where('name', 'upload_allow_type')->update(['value' => implode(',', array_keys($upload_types))]);
}
foreach ($post as $key => $val) {
if (in_array($key, $notAddFields)) continue;
if ($this->model->where('name', $key)->count()) {
$this->model->where('name', $key)->update(['value' => $val,]);
} else {
$this->model->create(
if (self::$model::where('name', $key)->count()) {
self::$model::where('name', $key)->update(['value' => $val,]);
}else {
self::$model::create(
[
'name' => $key,
'value' => $val,
'group' => $group,
]);
}
if (Cache::has($key)) Cache::set($key, $val);
}
TriggerService::updateMenu();
TriggerService::updateSysconfig();
} catch (\Exception $e) {
$this->error('保存失败');
TriggerService::updateSysConfig();
}catch (\Exception $e) {
$this->error('保存失败' . $e->getMessage());
}
$this->success('保存成功');
}

View File

@@ -3,43 +3,36 @@
namespace app\admin\controller\system;
use app\admin\service\curd\BuildCurd;
use app\admin\service\curd\exceptions\TableException;
use app\common\controller\AdminController;
use app\admin\service\annotation\ControllerAnnotation;
use app\admin\service\annotation\NodeAnnotation;
use app\Request;
use think\db\exception\PDOException;
use think\exception\FileException;
use think\facade\Console;
use think\facade\Db;
use think\helper\Str;
use think\response\Json;
/**
* @ControllerAnnotation(title="CURD可视化管理")
* @package app\admin\controller\system
*/
#[ControllerAnnotation(title: 'CURD可视化管理')]
class CurdGenerate extends AdminController
{
/**
* @NodeAnnotation(title="列表")
*/
public function index()
#[NodeAnnotation(title: '列表', auth: true)]
public function index(Request $request): Json|string
{
return $this->fetch();
}
/**
* @NodeAnnotation(title="操作")
* @throws TableException
*/
#[NodeAnnotation(title: '操作', auth: true)]
public function save(Request $request, string $type = ''): ?Json
{
if (!$request->isAjax()) $this->error();
$tb_prefix = $request->param('tb_prefix/s', '');
$tb_name = $request->param('tb_name/s', '');
if (empty($tb_name)) $this->error('参数错误');
switch ($type) {
case "search":
$tb_prefix = $request->param('tb_prefix/s', '');
$tb_name = $request->param('tb_name/s', '');
if (empty($tb_name)) $this->error('参数错误');
try {
$list = Db::query("SHOW FULL COLUMNS FROM {$tb_prefix}{$tb_name}");
$data = [];
@@ -59,10 +52,53 @@ class CurdGenerate extends AdminController
}
break;
case "add":
$force = $request->post('force/d', 0);
$tb_prefix = $request->param('tb_prefix/s', '');
$tb_name = $request->param('tb_name/s', '');
if (empty($tb_name)) $this->error('参数错误');
$tb_fields = $request->param('tb_fields');
$force = $request->post('force/d', 0);
try {
$build = (new BuildCurd())->setTablePrefix($tb_prefix)->setTable($tb_name);
$build->setForce($force); // 强制覆盖
// 新增字段类型
if ($tb_fields) {
foreach ($tb_fields as $tk => $tf) {
if (empty($tf)) continue;
$tf = array_values($tf);
switch ($tk) {
case 'ignore':
$build->setIgnoreFields($tf, true);
break;
case 'select':
$build->setSelectFields($tf, true);
break;
case 'radio':
$build->setRadioFieldSuffix($tf, true);
break;
case 'checkbox':
$build->setCheckboxFieldSuffix($tf, true);
break;
case 'image':
$build->setImageFieldSuffix($tf, true);
break;
case 'images':
$build->setImagesFieldSuffix($tf, true);
break;
case 'date':
$build->setDateFieldSuffix($tf, true);
break;
case 'datetime':
$build->setDatetimeFieldSuffix($tf, true);
break;
case 'editor':
$build->setEditorFields($tf, true);
break;
default:
break;
}
}
}
$build = $build->render();
$fileList = $build->getFileList();
if (empty($fileList)) $this->error('这里什么都没有');
@@ -74,7 +110,7 @@ class CurdGenerate extends AdminController
$_fileExp_last = array_slice($_fileExp, -2);
$_fileExp_last_0 = $_fileExp_last[0] . '.';
if ($_fileExp_last[0] == 'controller') $_fileExp_last_0 = '';
$link = '/' . env('EASYADMIN.ADMIN', 'admin') . '/' . $_fileExp_last_0 . Str::snake(explode('.php', end($_fileExp_last))[0] ?? '') . '/index';
$link = '/' . config('admin.alias_name') . '/' . $_fileExp_last_0 . Str::snake(explode('.php', end($_fileExp_last))[0] ?? '') . '/index';
}
$this->success('生成成功', compact('result', 'link'));
}catch (FileException $exception) {
@@ -82,6 +118,10 @@ class CurdGenerate extends AdminController
}
break;
case "delete":
$tb_prefix = $request->param('tb_prefix/s', '');
$tb_name = $request->param('tb_name/s', '');
if (empty($tb_name)) $this->error('参数错误');
try {
$build = (new BuildCurd())->setTablePrefix($tb_prefix)->setTable($tb_name);
$build = $build->render();
@@ -93,6 +133,20 @@ class CurdGenerate extends AdminController
return json(['code' => -1, 'msg' => $exception->getMessage()]);
}
break;
case 'console':
$command = $request->post('command', '');
if (empty($command)) $this->error('请输入命令');
$commandExp = explode(' ', $command);
$commandExp = array_values(array_filter($commandExp));
try {
$output = Console::call('curd', [...$commandExp]);
}catch (\Throwable $exception) {
$this->error($exception->getMessage() . $exception->getLine());
}
if (empty($output)) $this->error('设置错误');
$this->success($output->fetch());
break;
default:
$this->error('参数错误');
break;

View File

@@ -3,48 +3,42 @@
namespace app\admin\controller\system;
use app\admin\model\SystemLog;
use app\admin\service\annotation\MiddlewareAnnotation;
use app\admin\service\tool\CommonTool;
use app\admin\traits\Curd;
use app\common\controller\AdminController;
use app\admin\service\annotation\ControllerAnnotation;
use app\admin\service\annotation\NodeAnnotation;
use app\Request;
use jianyan\excel\Excel;
use think\App;
use think\db\exception\DbException;
use think\db\exception\PDOException;
use think\facade\Db;
use think\response\Json;
/**
* @ControllerAnnotation(title="操作日志管理")
* Class Auth
* @package app\admin\controller\system
*/
#[ControllerAnnotation(title: '操作日志管理')]
class Log extends AdminController
{
use Curd;
public function __construct(App $app)
{
parent::__construct($app);
$this->model = new SystemLog();
self::$model = SystemLog::class;
}
/**
* @NodeAnnotation(title="列表")
*/
public function index()
#[NodeAnnotation(title: '列表', auth: true)]
public function index(Request $request): Json|string
{
if ($this->request->isAjax()) {
if ($request->isAjax()) {
if (input('selectFields')) {
return $this->selectList();
}
[$page, $limit, $where, $excludeFields] = $this->buildTableParams(['month']);
$month = !empty($excludeFields['month']) ? date('Ym', strtotime($excludeFields['month'])) : date('Ym');
$model = $this->model->setMonth($month)->with('admin')->where($where);
$model = (new self::$model)->setSuffix("_$month")->with('admin')->where($where);
try {
$count = $model->count();
$list = $model->page($page, $limit)->order($this->sort)->select();
} catch (PDOException|DbException $exception) {
}catch (PDOException|DbException $exception) {
$count = 0;
$list = [];
}
@@ -59,16 +53,15 @@ class Log extends AdminController
return $this->fetch();
}
/**
* @NodeAnnotation(title="导出")
*/
#[NodeAnnotation(title: '导出', auth: true)]
public function export()
{
if (env('EASYADMIN.IS_DEMO', false)) {
$this->error('演示环境下不允许操作');
}
[$page, $limit, $where, $excludeFields] = $this->buildTableParams(['month']);
$tableName = $this->model->getName();
$month = !empty($excludeFields['month']) ? date('Ym', strtotime($excludeFields['month'])) : date('Ym');
$tableName = (new self::$model)->setSuffix("_$month")->getName();
$tableName = CommonTool::humpToLine(lcfirst($tableName));
$prefix = config('database.connections.mysql.prefix');
$dbList = Db::query("show full columns from {$prefix}{$tableName}");
@@ -79,20 +72,67 @@ class Log extends AdminController
$header[] = [$comment, $vo['Field']];
}
}
$month = !empty($excludeFields['month']) ? date('Ym', strtotime($excludeFields['month'])) : date('Ym');
$model = $this->model->setMonth($month)->with('admin')->where($where);
$model = (new self::$model)->setSuffix("_$month")->with('admin')->where($where);
try {
$list = $model
->where($where)
->limit(100000)
->limit(10000)
->order('id', 'desc')
->select()
->toArray();
} catch (PDOException|DbException $exception) {
foreach ($list as &$vo) {
$vo['content'] = json_encode($vo['content'], JSON_UNESCAPED_UNICODE);
$vo['response'] = json_encode($vo['response'], JSON_UNESCAPED_UNICODE);
}
exportExcel($header, $list, '操作日志');
}catch (\Throwable $exception) {
$this->error($exception->getMessage());
}
$fileName = time();
return Excel::exportData($list, $header, $fileName, 'xlsx');
}
#[NodeAnnotation(title: '删除指定日志', auth: true)]
public function deleteMonthLog(Request $request)
{
if (!$request->isAjax()) {
return $this->fetch();
}
if ($this->isDemo) $this->error('演示环境下不允许操作');
$monthsAgo = $request->param('month/d', 0);
if ($monthsAgo < 1) $this->error('月份错误');
$currentDate = new \DateTime();
$currentDate->modify("-$monthsAgo months");
$dbPrefix = env('DB_PREFIX');
$dbLike = "{$dbPrefix}system_log_";
$tables = Db::query("SHOW TABLES LIKE '$dbLike%'");
$threshold = date('Ym', strtotime("-$monthsAgo month"));
$tableNames = [];
try {
foreach ($tables as $table) {
$tableName = current($table);
if (!preg_match("/^$dbLike\d{6}$/", $tableName)) continue;
$datePart = substr($tableName, -6);
$issetTable = Db::query("SHOW TABLES LIKE '$tableName'");
if (!$issetTable) continue;
if ($datePart - $threshold <= 0) {
Db::execute("DROP TABLE `$tableName`");
$tableNames[] = $tableName;
}
}
}catch (PDOException) {
}
if (empty($tableNames)) $this->error('没有需要删除的表');
$this->success('操作成功 - 共删除 ' . count($tableNames) . ' 张表<br/>' . implode('<br>', $tableNames));
}
#[MiddlewareAnnotation(ignore: MiddlewareAnnotation::IGNORE_LOG)]
#[NodeAnnotation(title: '框架日志', auth: true, ignore: NodeAnnotation::IGNORE_NODE)]
public function record(): Json|string
{
return (new \Wolfcode\PhpLogviewer\thinkphp\LogViewer())->fetch();
}
}

View File

@@ -5,23 +5,18 @@ namespace app\admin\controller\system;
use app\admin\model\SystemMenu;
use app\admin\model\SystemNode;
use app\admin\service\TriggerService;
use app\admin\traits\Curd;
use app\common\constants\MenuConstant;
use app\admin\service\annotation\ControllerAnnotation;
use app\admin\service\annotation\NodeAnnotation;
use app\common\controller\AdminController;
use app\Request;
use think\App;
use think\response\Json;
/**
* Class Menu
* @package app\admin\controller\system
* @ControllerAnnotation(title="菜单管理",auth=true)
*/
#[ControllerAnnotation(title: '菜单管理')]
class Menu extends AdminController
{
use Curd;
protected array $sort = [
'sort' => 'desc',
'id' => 'asc',
@@ -30,20 +25,18 @@ class Menu extends AdminController
public function __construct(App $app)
{
parent::__construct($app);
$this->model = new SystemMenu();
self::$model = SystemMenu::class;
}
/**
* @NodeAnnotation(title="列表")
*/
public function index()
#[NodeAnnotation(title: '列表', auth: true)]
public function index(Request $request): Json|string
{
if ($this->request->isAjax()) {
if ($request->isAjax()) {
if (input('selectFields')) {
return $this->selectList();
}
$count = $this->model->count();
$list = $this->model->order($this->sort)->select();
$count = self::$model::count();
$list = self::$model::order($this->sort)->select()->toArray();
$data = [
'code' => 0,
'msg' => '',
@@ -55,17 +48,16 @@ class Menu extends AdminController
return $this->fetch();
}
/**
* @NodeAnnotation(title="添加")
*/
public function add($id = null)
#[NodeAnnotation(title: '添加', auth: true)]
public function add(Request $request): string
{
$homeId = $this->model->where(['pid' => MenuConstant::HOME_PID,])->value('id');
$id = $request->param('id');
$homeId = self::$model::where(['pid' => MenuConstant::HOME_PID,])->value('id');
if ($id == $homeId) {
$this->error('首页不能添加子菜单');
}
if ($this->request->isPost()) {
$post = $this->request->post();
if ($request->isPost()) {
$post = $request->post();
$rule = [
'pid|上级菜单' => 'require',
'title|菜单名称' => 'require',
@@ -73,32 +65,30 @@ class Menu extends AdminController
];
$this->validate($post, $rule);
try {
$save = $this->model->save($post);
} catch (\Exception $e) {
$save = self::$model::create($post);
}catch (\Exception $e) {
$this->error('保存失败');
}
if ($save) {
TriggerService::updateMenu();
$this->success('保存成功');
} else {
}else {
$this->error('保存失败');
}
}
$pidMenuList = $this->model->getPidMenuList();
$pidMenuList = self::$model::getPidMenuList();
$this->assign('id', $id);
$this->assign('pidMenuList', $pidMenuList);
return $this->fetch();
}
/**
* @NodeAnnotation(title="编辑")
*/
public function edit($id)
#[NodeAnnotation(title: '编辑', auth: true)]
public function edit(Request $request, $id = 0): string
{
$row = $this->model->find($id);
$row = self::$model::find($id);
empty($row) && $this->error('数据不存在');
if ($this->request->isPost()) {
$post = $this->request->post();
if ($request->isPost()) {
$post = $request->post();
$rule = [
'pid|上级菜单' => 'require',
'title|菜单名称' => 'require',
@@ -108,100 +98,94 @@ class Menu extends AdminController
if ($row->pid == MenuConstant::HOME_PID) $post['pid'] = MenuConstant::HOME_PID;
try {
$save = $row->save($post);
} catch (\Exception $e) {
}catch (\Exception $e) {
$this->error('保存失败');
}
if (!empty($save)) {
TriggerService::updateMenu();
$this->success('保存成功');
} else {
}else {
$this->error('保存失败');
}
}
$pidMenuList = $this->model->getPidMenuList();
$pidMenuList = self::$model::getPidMenuList();
$this->assign([
'id' => $id,
'pidMenuList' => $pidMenuList,
'row' => $row,
]);
'id' => $id,
'pidMenuList' => $pidMenuList,
'row' => $row,
]);
return $this->fetch();
}
/**
* @NodeAnnotation(title="删除")
*/
public function delete($id)
#[NodeAnnotation(title: '删除', auth: true)]
public function delete(Request $request): void
{
$this->checkPostRequest();
$row = $this->model->whereIn('id', $id)->select();
$id = $request->param('id');
$row = self::$model::whereIn('id', $id)->select();
empty($row) && $this->error('数据不存在');
try {
$save = $row->delete();
} catch (\Exception $e) {
}catch (\Exception $e) {
$this->error('删除失败');
}
if ($save) {
TriggerService::updateMenu();
$this->success('删除成功');
} else {
}else {
$this->error('删除失败');
}
}
/**
* @NodeAnnotation(title="属性修改")
*/
public function modify()
#[NodeAnnotation(title: '属性修改', auth: true)]
public function modify(Request $request): void
{
$this->checkPostRequest();
$post = $this->request->post();
$post = $request->post();
$rule = [
'id|ID' => 'require',
'id|ID' => 'require',
'field|字段' => 'require',
'value|值' => 'require',
'value|值' => 'require',
];
$this->validate($post, $rule);
$row = $this->model->find($post['id']);
$row = self::$model::find($post['id']);
if (!$row) {
$this->error('数据不存在');
}
if (!in_array($post['field'], $this->allowModifyFields)) {
$this->error('该字段不允许修改:' . $post['field']);
}
$homeId = $this->model
->where([
'pid' => MenuConstant::HOME_PID,
])
$homeId = self::$model::where([
'pid' => MenuConstant::HOME_PID,
])
->value('id');
if ($post['id'] == $homeId && $post['field'] == 'status') {
$this->error('首页状态不允许关闭');
}
try {
$row->save([
$post['field'] => $post['value'],
]);
} catch (\Exception $e) {
$post['field'] => $post['value'],
]);
}catch (\Exception $e) {
$this->error($e->getMessage());
}
TriggerService::updateMenu();
$this->success('保存成功');
}
/**
* @NodeAnnotation(title="添加菜单提示")
*/
public function getMenuTips()
#[NodeAnnotation(title: '添加菜单提示', auth: true)]
public function getMenuTips(): Json
{
$node = input('get.keywords');
$list = SystemNode::whereLike('node', "%{$node}%")
->field('node,title')
->limit(10)
->select();
->select()->toArray();
return json([
'code' => 0,
'content' => $list,
'type' => 'success',
]);
'code' => 0,
'content' => $list,
'type' => 'success',
]);
}
}

View File

@@ -8,37 +8,32 @@ use app\common\controller\AdminController;
use app\admin\service\annotation\ControllerAnnotation;
use app\admin\service\annotation\NodeAnnotation;
use app\admin\service\NodeService;
use app\Request;
use think\App;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\response\Json;
/**
* @ControllerAnnotation(title="系统节点管理")
* Class Node
* @package app\admin\controller\system
*/
#[ControllerAnnotation(title: '系统节点管理')]
class Node extends AdminController
{
use \app\admin\traits\Curd;
public function __construct(App $app)
{
parent::__construct($app);
$this->model = new SystemNode();
self::$model = SystemNode::class;
}
/**
* @NodeAnnotation(title="列表")
*/
public function index()
#[NodeAnnotation(title: '列表', auth: true)]
public function index(Request $request): Json|string
{
if ($this->request->isAjax()) {
if ($request->isAjax()) {
if (input('selectFields')) {
return $this->selectList();
}
$count = $this->model
->count();
$list = $this->model
->getNodeTreeList();
$count = self::$model::count();
$list = self::$model::getNodeTreeList();
$data = [
'code' => 0,
'msg' => '',
@@ -50,24 +45,21 @@ class Node extends AdminController
return $this->fetch();
}
/**
* @NodeAnnotation(title="系统节点更新")
*/
#[NodeAnnotation(title: '系统节点更新', auth: true)]
public function refreshNode($force = 0): void
{
$this->checkPostRequest();
$nodeList = (new NodeService())->getNodeList();
empty($nodeList) && $this->error('暂无需要更新的系统节点');
$model = new SystemNode();
try {
if ($force == 1) {
$updateNodeList = $model->whereIn('node', array_column($nodeList, 'node'))->select();
$updateNodeList = self::$model::whereIn('node', array_column($nodeList, 'node'))->select();
$formatNodeList = array_format_key($nodeList, 'node');
foreach ($updateNodeList as $vo) {
isset($formatNodeList[$vo['node']])
&& $model->where('id', $vo['id'])->update(
&& self::$model::where('id', $vo['id'])->update(
[
'title' => $formatNodeList[$vo['node']]['title'],
'is_auth' => $formatNodeList[$vo['node']]['is_auth'],
@@ -75,7 +67,7 @@ class Node extends AdminController
);
}
}
$existNodeList = $model->field('node,title,type,is_auth')->select();
$existNodeList = self::$model::field('node,title,type,is_auth')->select();
foreach ($nodeList as $key => $vo) {
foreach ($existNodeList as $v) {
if ($vo['node'] == $v->node) {
@@ -84,30 +76,29 @@ class Node extends AdminController
}
}
}
$model->saveAll($nodeList);
TriggerService::updateNode();
} catch (\Exception $e) {
if (!empty($nodeList)) {
(new self::$model)->saveAll($nodeList);
TriggerService::updateNode();
}
}catch (\Exception $e) {
$this->error('节点更新失败');
}
$this->success('节点更新成功');
}
/**
* @NodeAnnotation(title="清除失效节点")
*/
#[NodeAnnotation(title: '清除失效节点', auth: true)]
public function clearNode(): void
{
$this->checkPostRequest();
$nodeList = (new NodeService())->getNodeList();
$model = new SystemNode();
try {
$existNodeList = $model->field('id,node,title,type,is_auth')->select()->toArray();
$existNodeList = self::$model::field('id,node,title,type,is_auth')->select()->toArray();
$formatNodeList = array_format_key($nodeList, 'node');
foreach ($existNodeList as $vo) {
!isset($formatNodeList[$vo['node']]) && $model->where('id', $vo['id'])->delete();
!isset($formatNodeList[$vo['node']]) && self::$model::where('id', $vo['id'])->delete();
}
TriggerService::updateNode();
} catch (\Exception $e) {
}catch (\Exception $e) {
$this->error('节点更新失败');
}
$this->success('节点更新成功');

View File

@@ -9,16 +9,10 @@ use app\admin\service\annotation\ControllerAnnotation;
use app\admin\service\annotation\NodeAnnotation;
use think\App;
/**
* @ControllerAnnotation(title="快捷入口管理")
* Class Quick
* @package app\admin\controller\system
*/
#[ControllerAnnotation(title: '快捷入口管理')]
class Quick extends AdminController
{
use \app\admin\traits\Curd;
protected array $sort = [
'sort' => 'desc',
'id' => 'desc',
@@ -27,7 +21,7 @@ class Quick extends AdminController
public function __construct(App $app)
{
parent::__construct($app);
$this->model = new SystemQuick();
self::$model = SystemQuick::class;
}
}

View File

@@ -8,20 +8,14 @@ use app\admin\service\annotation\ControllerAnnotation;
use app\admin\service\annotation\NodeAnnotation;
use think\App;
/**
* @ControllerAnnotation(title="上传文件管理")
* Class Uploadfile
* @package app\admin\controller\system
*/
#[ControllerAnnotation(title: '上传文件管理')]
class Uploadfile extends AdminController
{
use \app\admin\traits\Curd;
public function __construct(App $app)
{
parent::__construct($app);
$this->model = new SystemUploadfile();
self::$model = SystemUploadfile::class;
$this->assign('upload_types', config('admin.upload_types'));
}

12
app/admin/entity/Test.php Normal file
View File

@@ -0,0 +1,12 @@
<?php
namespace app\admin\entity;
use app\common\entity\BaseEntity;
/**
* ThinkORM 4.0 实体模型案例
* 可与 Model 并存 或者 单独使用
* @package app\admin\entity
*/
class Test extends BaseEntity {}

View File

@@ -1,21 +0,0 @@
<?php
// 事件定义文件
return [
'bind' => [
],
'listen' => [
'AppInit' => [
\app\admin\listener\ViewInitListener::class,
],
'HttpRun' => [
\app\admin\listener\ViewInitListener::class,
],
'HttpEnd' => [],
'LogLevel' => [],
'LogWrite' => [],
],
'subscribe' => [
],
];

View File

@@ -1,23 +1,5 @@
<?php
// 全局中间件定义文件
return [
// Session初始化
\think\middleware\SessionInit::class,
// 系统操作日志
\app\admin\middleware\SystemLog::class,
// Csrf安全校验
\app\admin\middleware\CsrfMiddleware::class,
// 后台视图初始化
// \app\admin\middleware\ViewInit::class,
// 检测用户是否登录
// \app\admin\middleware\CheckAdmin::class,
// 检测是否已经安装程序
\app\admin\middleware\CheckInstall::class,
// ...
];

View File

@@ -1,57 +0,0 @@
<?php
namespace app\admin\middleware;
use app\common\service\AuthService;
use think\Request;
/**
* @deprecated 废弃新版TP不支持在中间件获取控制器相关信息
* 检测用户登录和节点权限
* Class CheckAdmin
* @package app\admin\middleware
*/
class CheckAdmin
{
use \app\common\traits\JumpTrait;
public function handle(Request $request, \Closure $next)
{
$adminConfig = config('admin');
$adminId = session('admin.id');
$expireTime = session('admin.expire_time');
/** @var AuthService $authService */
$authService = app(AuthService::class, ['adminId' => $adminId]);
$currentNode = $authService->getCurrentNode();
$currentController = parse_name($request->controller());
// 验证登录
if (!in_array($currentController, $adminConfig['no_login_controller']) &&
!in_array($currentNode, $adminConfig['no_login_node'])) {
empty($adminId) && $this->error('请先登录后台', [], __url('admin/login/index'));
// 判断是否登录过期
if ($expireTime !== true && time() > $expireTime) {
session('admin', null);
$this->error('登录已过期,请重新登录', [], __url('admin/login/index'));
}
}
// 验证权限
if (!in_array($currentController, $adminConfig['no_auth_controller']) &&
!in_array($currentNode, $adminConfig['no_auth_node'])) {
$check = $authService->checkNode($currentNode);
!$check && $this->error('无权限访问');
// 判断是否为演示环境
if(env('EASYADMIN.IS_DEMO', false) && $request->isPost()){
$this->error('演示环境下不允许修改');
}
}
return $next($request);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace app\admin\middleware;
use app\common\service\AuthService;
use app\common\traits\JumpTrait;
use app\Request;
use Closure;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
class CheckAuth
{
use JumpTrait;
/**
* @throws ModelNotFoundException
* @throws DbException
* @throws DataNotFoundException
*/
public function handle(Request $request, Closure $next)
{
$adminUserInfo = $request->adminUserInfo;
if (empty($adminUserInfo)) return $next($request);
$adminConfig = config('admin');
$adminId = $adminUserInfo['id'];
$authService = app(AuthService::class, ['adminId' => $adminId]);
$currentNode = $authService->getCurrentNode();
$currentController = parse_name($request->controller());
if (!in_array($currentController, $adminConfig['no_auth_controller']) && !in_array($currentNode, $adminConfig['no_auth_node'])) {
$check = $authService->checkNode($currentNode);
!$check && $this->error('无权限访问');
// 判断是否为演示环境
if (env('EASYADMIN.IS_DEMO', false) && $request->isPost()) {
if (!in_array($currentNode, ['system.log/record', 'mall.goods/aiOptimization'])) $this->error('演示环境下不允许修改');
}
}
return $next($request);
}
}

View File

@@ -2,18 +2,18 @@
namespace app\admin\middleware;
use app\common\traits\JumpTrait;
use app\Request;
use Closure;
/**
* 检测是否安装成功
* 系统安装后可以在 config/route 中删除该中间件判定
*/
class CheckInstall
{
public function handle(Request $request, \Closure $next)
use JumpTrait;
public function handle(Request $request, Closure $next)
{
$controller = $request->controller();
if (!is_file(ROOT_PATH . 'config' . DS . 'install' . DS . 'lock' . DS . 'install.lock')) {
if (!is_file(root_path() . 'config' . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'lock' . DIRECTORY_SEPARATOR . 'install.lock')) {
if ($controller != 'Install') return redirect('/install');
}
return $next($request);

View File

@@ -0,0 +1,60 @@
<?php
namespace app\admin\middleware;
use app\common\traits\JumpTrait;
use app\Request;
use Closure;
use ReflectionClass;
use ReflectionException;
use app\admin\service\annotation\MiddlewareAnnotation;
class CheckLogin
{
use JumpTrait;
/**
* @throws ReflectionException
*/
public function handle(Request $request, Closure $next)
{
$controller = $request->controller();
if (empty($controller)) return $next($request);
if (str_contains($controller, '.')) $controller = str_replace('.', '\\', $controller);
$action = $request->action();
$controllerClass = 'app\\admin\\controller\\' . $controller;
$classObj = new ReflectionClass($controllerClass);
$properties = $classObj->getDefaultProperties();
// 整个控制器是否忽略登录
$ignoreLogin = $properties['ignoreLogin'] ?? false;
$adminUserInfo = session('admin');
if (!$ignoreLogin) {
$noNeedCheck = $properties['noNeedCheck'] ?? [];
if (in_array($action, $noNeedCheck)) {
return $next($request);
}
try {
$reflectionMethod = new \ReflectionMethod($controllerClass, $action);
$attributes = $reflectionMethod->getAttributes(MiddlewareAnnotation::class);
foreach ($attributes as $attribute) {
$annotation = $attribute->newInstance();
$_ignore = (array)$annotation->ignore;
// 控制器中的某个方法忽略登录
if (in_array('LOGIN', $_ignore)) return $next($request);
}
}catch (\Throwable) {
}
if (empty($adminUserInfo)) {
return redirect(__url('login/index'));
}
// 判断是否登录过期
$expireTime = $adminUserInfo['expire_time'];
if ($expireTime !== true && time() > $expireTime) {
session('admin', null);
$this->error('登录已过期,请重新登录', [], __url(env('EASYADMIN.ADMIN') . '/login/index'));
}
}
$request->adminUserInfo = $adminUserInfo ?: [];
return $next($request);
}
}

View File

@@ -1,40 +0,0 @@
<?php
namespace app\admin\middleware;
use app\Request;
use CsrfVerify\drive\ThinkphpCache;
use CsrfVerify\entity\CsrfVerifyEntity;
use CsrfVerify\interfaces\CsrfVerifyInterface;
use think\facade\Session;
class CsrfMiddleware
{
use \app\common\traits\JumpTrait;
public function handle(Request $request, \Closure $next)
{
if (env('EASYADMIN.IS_CSRF', true)) {
if (in_array($request->method(), ['POST', 'DELETE'])) {
// 跨域校验
$refererUrl = $request->header('REFERER', null);
$refererInfo = parse_url($refererUrl);
$host = $request->host(true);
if (!isset($refererInfo['host']) || $refererInfo['host'] != $host) {
$this->error('当前请求不合法!');
}
// CSRF校验
// @todo 兼容CK编辑器上传功能
$ckCsrfToken = $request->post('ckCsrfToken');
$data = !empty($ckCsrfToken) ? ['__token__' => $ckCsrfToken] : [];
$check = $request->checkToken('__token__', $data);
if (!$check) {
$this->error('请求验证失败,请重新刷新页面!');
}
}
}
return $next($request);
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace app\admin\middleware;
use app\common\traits\JumpTrait;
use app\Request;
use Closure;
use Wolfcode\RateLimiting\Bootstrap;
class RateLimiting
{
use JumpTrait;
/**
* 启用限流器需要开启Redis
* @param Request $request
* @param Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next): mixed
{
// 是否启用限流器
if (!env('RATE_LIMITING_STATUS', false)) return $next($request);
if ($request->method() == 'GET') return $next($request);
$controller = $request->controller();
$module = app('http')->getName();
$appNamespace = config('app.app_namespace');
$controllerClass = "app\\{$module}\\controller\\{$controller}{$appNamespace}";
$controllerClass = str_replace('.', '\\', $controllerClass);
$action = $request->action();
try {
Bootstrap::init($controllerClass, $action, [
# Redis 相关配置
'host' => env('REDIS_HOST', '127.0.0.1'),
'port' => (int)env('REDIS_PORT', 6379),
'password' => env('REDIS_PASSWORD', ''),
'prefix' => env('REDIS_PREFIX', ''),
'database' => (int)env('REDIS_DATABASE', 0),
]);
}catch (\Throwable $exception) {
$this->error($exception->getMessage());
}
return $next($request);
}
}

View File

@@ -3,38 +3,38 @@
namespace app\admin\middleware;
use app\admin\service\annotation\ControllerAnnotation;
use app\admin\service\annotation\MiddlewareAnnotation;
use app\admin\service\annotation\NodeAnnotation;
use app\admin\service\SystemLogService;
use app\common\traits\JumpTrait;
use app\Request;
use app\admin\service\tool\CommonTool;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\DocParser;
use Closure;
use ReflectionException;
/**
* 系统操作日志中间件
* Class SystemLog
* @package app\admin\middleware
*/
class SystemLog
{
use JumpTrait;
/**
* 敏感信息字段,日志记录时需要加密
* @var array
*/
protected $sensitiveParams = [
protected array $sensitiveParams = [
'password',
'password_again',
'phone',
'mobile',
];
public function handle(Request $request, \Closure $next)
/**
* @throws ReflectionException
*/
public function handle(Request $request, Closure $next)
{
$response = $next($request);
if (!env('APP_ADMIN_SYSTEM_LOG', true)) return $response;
$params = $request->param();
if (isset($params['s'])) {
unset($params['s']);
}
if (isset($params['s'])) unset($params['s']);
foreach ($params as $key => $val) {
in_array($key, $this->sensitiveParams) && $params[$key] = "***********";
}
@@ -44,9 +44,9 @@ class SystemLog
if (env('APP_DEBUG')) {
trace(['url' => $url, 'method' => $method, 'params' => $params,], 'requestDebugInfo');
}
if ($request->isAjax()) {
if (in_array($method, ['post', 'put', 'delete'])) {
$title = '';
try {
$pathInfo = $request->pathinfo();
@@ -55,20 +55,36 @@ class SystemLog
$pathInfoExp = explode('.', $pathInfoExp[0] ?? '');
$_name = $pathInfoExp[0] ?? '';
$_controller = ucfirst($pathInfoExp[1] ?? '');
if ($_name && $_controller && $_action) {
$className = "app\admin\controller\\{$_name}\\{$_controller}";
$reflectionClass = new \ReflectionClass($className);
$parser = new DocParser();
$parser->setIgnoreNotImportedAnnotations(true);
$reader = new AnnotationReader($parser);
$controllerAnnotation = $reader->getClassAnnotation($reflectionClass, ControllerAnnotation::class);
$reflectionAction = $reflectionClass->getMethod($_action);
$nodeAnnotation = $reader->getMethodAnnotation($reflectionAction, NodeAnnotation::class);
$title = $controllerAnnotation->title . ' - ' . $nodeAnnotation->title;
$className = $_controller ? "app\admin\controller\\{$_name}\\{$_controller}" : "app\admin\controller\\{$_name}";
if ($_name && $_action) {
$reflectionMethod = new \ReflectionMethod($className, $_action);
$attributes = $reflectionMethod->getAttributes(MiddlewareAnnotation::class);
foreach ($attributes as $attribute) {
$annotation = $attribute->newInstance();
$_ignore = (array)$annotation->ignore;
if (in_array('log', array_map('strtolower', $_ignore))) return $response;
}
$controllerTitle = $nodeTitle = '';
$controllerAttributes = (new \ReflectionClass($className))->getAttributes(ControllerAnnotation::class);
$actionAttributes = $reflectionMethod->getAttributes(NodeAnnotation::class);
foreach ($controllerAttributes as $controllerAttribute) {
$controllerAnnotation = $controllerAttribute->newInstance();
$controllerTitle = $controllerAnnotation->title ?? '';
}
foreach ($actionAttributes as $actionAttribute) {
$actionAnnotation = $actionAttribute->newInstance();
$nodeTitle = $actionAnnotation->title ?? '';
}
$title = $controllerTitle . ' - ' . $nodeTitle;
}
} catch (\Throwable $exception) {
}catch (\Throwable $exception) {
}
$ip = CommonTool::getRealIp();
$ip = $request->ip();
// 限制记录的响应内容,避免过大
$_response = json_encode($response->getData(), JSON_UNESCAPED_UNICODE);
$_response = mb_substr($_response, 0, 3000, 'utf-8');
$data = [
'admin_id' => session('admin.id'),
'title' => $title,
@@ -76,13 +92,13 @@ class SystemLog
'method' => $method,
'ip' => $ip,
'content' => json_encode($params, JSON_UNESCAPED_UNICODE),
'useragent' => $_SERVER['HTTP_USER_AGENT'],
'response' => $_response,
'useragent' => $request->server('HTTP_USER_AGENT'),
'create_time' => time(),
];
SystemLogService::instance()->save($data);
}
}
return $next($request);
return $response;
}
}

View File

@@ -1,47 +0,0 @@
<?php
namespace app\admin\middleware;
use app\admin\service\ConfigService;
use app\common\constants\AdminConstant;
use think\App;
use think\facade\Request;
use think\facade\View;
/**
* @deprecated 废弃新版TP不支持在中间件获取控制器相关信息
* Class ViewInit
* @package app\admin\middleware
*/
class ViewInit
{
public function handle(\app\Request $request, \Closure $next)
{
list($thisModule, $thisController, $thisAction) = [app('http')->getName(), Request::controller(), $request->action()];
list($thisControllerArr, $jsPath) = [explode('.', $thisController), null];
foreach ($thisControllerArr as $vo) {
empty($jsPath) ? $jsPath = parse_name($vo) : $jsPath .= '/' . parse_name($vo);
}
$autoloadJs = file_exists(root_path('public')."static/{$thisModule}/js/{$jsPath}.js") ? true : false;
$thisControllerJsPath = "{$thisModule}/js/{$jsPath}.js";
$adminModuleName = config('app.admin_alias_name');
$isSuperAdmin = session('admin.id') == AdminConstant::SUPER_ADMIN_ID ? true : false;
$data = [
'adminModuleName' => $adminModuleName,
'thisController' => parse_name($thisController),
'thisAction' => $thisAction,
'thisRequest' => parse_name("{$thisModule}/{$thisController}/{$thisAction}"),
'thisControllerJsPath' => "{$thisControllerJsPath}",
'autoloadJs' => $autoloadJs,
'isSuperAdmin' => $isSuperAdmin,
'version' => env('APP_DEBUG') ? time() : ConfigService::getVersion(),
];
View::assign($data);
$request->adminModuleName = $adminModuleName;
return $next($request);
}
}

View File

@@ -8,6 +8,11 @@ use app\common\model\TimeModel;
class MallCate extends TimeModel
{
protected $deleteTime = 'delete_time';
protected function getOptions(): array
{
return [
'deleteTime' => 'delete_time',
];
}
}

View File

@@ -2,19 +2,31 @@
namespace app\admin\model;
use app\common\model\TimeModel;
use think\model\relation\BelongsTo;
use think\model\relation\HasOne;
class MallGoods extends TimeModel
{
protected $table = "";
protected $deleteTime = 'delete_time';
public function cate()
protected function getOptions(): array
{
return $this->belongsTo('app\admin\model\MallCate', 'cate_id', 'id');
return [
'deleteTime' => 'delete_time',
];
}
// * +++++++++++++++++++++++++++
// | 以下两种写法适用于 with 关联
// * +++++++++++++++++++++++++
// public function cate(): BelongsTo
// {
// return $this->belongsTo('app\admin\model\MallCate', 'cate_id', 'id');
// }
public function cate(): HasOne
{
return $this->hasOne(MallCate::class, 'id', 'cate_id');
}
}

View File

@@ -8,14 +8,29 @@ use app\common\model\TimeModel;
class SystemAdmin extends TimeModel
{
protected $deleteTime = 'delete_time';
public function getAuthList()
protected function getOptions(): array
{
$list = (new SystemAuth())
->where('status', 1)
->column('title', 'id');
return $list;
return [
'deleteTime' => 'delete_time',
];
}
public array $notes = [
'login_type' => [
1 => '密码登录',
2 => '密码 + 谷歌验证码登录'
],
];
public static function getAuthIdsAttr($value): array
{
if (!$value) return [];
return explode(',', $value);
}
public static function getAuthList(): array
{
return SystemAuth::where('status', 1)->column('title', 'id');
}
}

View File

@@ -3,44 +3,52 @@
namespace app\admin\model;
use app\common\model\TimeModel;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
class SystemAuth extends TimeModel
{
protected $deleteTime = 'delete_time';
protected function getOptions(): array
{
return [
'deleteTime' => 'delete_time',
];
}
/**
* 根据角色ID获取授权节点
* @param $authId
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function getAuthorizeNodeListByAdminId($authId)
public static function getAuthorizeNodeListByAdminId($authId): array
{
$checkNodeList = (new SystemAuthNode())
->where('auth_id', $authId)
->column('node_id');
$systemNode = new SystemNode();
$nodelList = $systemNode
$systemNode = new SystemNode();
$nodeList = $systemNode
->where('is_auth', 1)
->field('id,node,title,type,is_auth')
->select()
->toArray();
$newNodeList = [];
foreach ($nodelList as $vo) {
$newNodeList = [];
foreach ($nodeList as $vo) {
if ($vo['type'] == 1) {
$vo = array_merge($vo, ['field' => 'node', 'spread' => true]);
$vo = array_merge($vo, ['field' => 'node', 'spread' => true]);
$vo['checked'] = false;
$vo['title'] = "{$vo['title']}{$vo['node']}";
$children = [];
foreach ($nodelList as $v) {
$vo['title'] = "{$vo['title']}{$vo['node']}";
$children = [];
foreach ($nodeList as $v) {
if ($v['type'] == 2 && strpos($v['node'], $vo['node'] . '/') !== false) {
$v = array_merge($v, ['field' => 'node', 'spread' => true]);
$v = array_merge($v, ['field' => 'node', 'spread' => true]);
$v['checked'] = in_array($v['id'], $checkNodeList) ? true : false;
$v['title'] = "{$v['title']}{$v['node']}";
$children[] = $v;
$v['title'] = "{$v['title']}{$v['node']}";
$children[] = $v;
}
}
!empty($children) && $vo['children'] = $children;

View File

@@ -4,24 +4,23 @@ namespace app\admin\model;
use app\admin\service\SystemLogService;
use app\common\model\TimeModel;
use think\model\relation\BelongsTo;
class SystemLog extends TimeModel
{
public function __construct(array $data = [])
{
parent::__construct($data);
$this->name = 'system_log_' . date('Ym');
}
protected array $type = [
'content' => 'json',
'response' => 'json',
];
public function setMonth($month)
protected function init(): void
{
SystemLogService::instance()->detectTable();
$this->name = 'system_log_' . $month;
return $this;
}
public function admin()
public function admin(): BelongsTo
{
return $this->belongsTo('app\admin\model\SystemAdmin', 'admin_id', 'id');
}

View File

@@ -4,31 +4,41 @@ namespace app\admin\model;
use app\common\constants\MenuConstant;
use app\common\model\TimeModel;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
class SystemMenu extends TimeModel
{
protected $deleteTime = 'delete_time';
public function getPidMenuList()
protected function getOptions(): array
{
$list = $this->field('id,pid,title')
->where([
['pid', '<>', MenuConstant::HOME_PID],
['status', '=', 1],
])
->select()
->toArray();
$pidMenuList = $this->buildPidMenu(0, $list);
$pidMenuList = array_merge([[
return [
'deleteTime' => 'delete_time',
];
}
/**
* @throws ModelNotFoundException
* @throws DbException
* @throws DataNotFoundException
*/
public static function getPidMenuList(): array
{
$list = self::field('id,pid,title')->where([
['pid', '<>', MenuConstant::HOME_PID],
['status', '=', 1],
])->select()->toArray();
$pidMenuList = self::buildPidMenu(0, $list);
return array_merge([[
'id' => 0,
'pid' => 0,
'title' => '顶级菜单',
]], $pidMenuList);
return $pidMenuList;
}
protected function buildPidMenu($pid, $list, $level = 0)
protected static function buildPidMenu($pid, $list, $level = 0): array
{
$newList = [];
foreach ($list as $vo) {
@@ -47,7 +57,7 @@ class SystemMenu extends TimeModel
$vo['title'] = $markString . $vo['title'];
}
$newList[] = $vo;
$childList = $this->buildPidMenu($vo['id'], $list, $level);
$childList = self::buildPidMenu($vo['id'], $list, $level);
!empty($childList) && $newList = array_merge($newList, $childList);
}

View File

@@ -7,22 +7,21 @@ use app\common\model\TimeModel;
class SystemNode extends TimeModel
{
public function getNodeTreeList()
public static function getNodeTreeList(): array
{
$list = $this->select()->toArray();
$list = $this->buildNodeTree($list);
return $list;
$list = self::select()->toArray();
return self::buildNodeTree($list);
}
protected function buildNodeTree($list)
protected static function buildNodeTree($list): array
{
$newList = [];
$newList = [];
$repeatString = "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
foreach ($list as $vo) {
if ($vo['type'] == 1) {
$newList[] = $vo;
foreach ($list as $v) {
if ($v['type'] == 2 && strpos($v['node'], $vo['node'] . '/') !== false) {
if ($v['type'] == 2 && str_contains($v['node'], $vo['node'] . '/')) {
$v['node'] = "{$repeatString}{$repeatString}" . $v['node'];
$newList[] = $v;
}

View File

@@ -7,6 +7,11 @@ use app\common\model\TimeModel;
class SystemQuick extends TimeModel
{
protected $deleteTime = 'delete_time';
protected function getOptions(): array
{
return [
'deleteTime' => 'delete_time',
];
}
}

View File

@@ -9,11 +9,10 @@ class ConfigService
public static function getVersion()
{
$version = cache('version');
$version = cache('site_version');
if (empty($version)) {
$version = sysconfig('site', 'site_version');
$version = sysConfig('site', 'site_version');
cache('site_version', $version);
Cache::set('version', $version, 3600);
}
return $version;
}

View File

@@ -15,29 +15,25 @@ use think\facade\Env;
class SystemLogService
{
/**
* 当前实例
* @var object
*/
protected static $instance;
protected static ?SystemLogService $instance = null;
/**
* 表前缀
* @var string
*/
protected $tablePrefix;
protected string $tablePrefix;
/**
* 表后缀
* @var string
*/
protected $tableSuffix;
protected string $tableSuffix;
/**
* 表名
* @var string
*/
protected $tableName;
protected string $tableName;
/**
* 构造方法
@@ -48,14 +44,13 @@ class SystemLogService
$this->tablePrefix = Config::get('database.connections.mysql.prefix');
$this->tableSuffix = date('Ym', time());
$this->tableName = "{$this->tablePrefix}system_log_{$this->tableSuffix}";
return $this;
}
/**
* 获取实例对象
* @return SystemLogService|object
* @return SystemLogService
*/
public static function instance()
public static function instance(): SystemLogService
{
if (is_null(self::$instance)) {
self::$instance = new static();
@@ -69,14 +64,14 @@ class SystemLogService
* @param $data
* @return bool|string
*/
public function save($data)
public function save($data): bool|string
{
Db::startTrans();
try {
$this->detectTable();
Db::table($this->tableName)->insert($data);
Db::table($this->tableName)->strict(false)->insert($data);
Db::commit();
} catch (\Exception $e) {
}catch (\Exception $e) {
Db::rollback();
return $e->getMessage();
}
@@ -89,15 +84,16 @@ class SystemLogService
*/
public function detectTable(): bool
{
$_key = "system_log_{$this->tableName}_table";
// 手动删除日志表时候 记得清除缓存
$isset = Cache::get("systemLog{$this->tableName}Table");
$isset = Cache::get($_key);
if ($isset) return true;
$check = Db::query("show tables like '{$this->tableName}'");
if (empty($check)) {
$sql = $this->getCreateSql();
Db::execute($sql);
}
Cache::set("system_log_{$this->tableName}_table", !empty($check));
Cache::set($_key, !empty($check));
return true;
}
@@ -119,7 +115,8 @@ CREATE TABLE `{$this->tableName}` (
`url` varchar(1500) NOT NULL DEFAULT '' COMMENT '操作页面',
`method` varchar(50) NOT NULL COMMENT '请求方法',
`title` varchar(100) DEFAULT '' COMMENT '日志标题',
`content` text NOT NULL COMMENT '内容',
`content` json NOT NULL COMMENT '请求数据',
`response` json DEFAULT NULL COMMENT '回调数据',
`ip` varchar(50) NOT NULL DEFAULT '' COMMENT 'IP',
`useragent` varchar(255) DEFAULT '' COMMENT 'User-Agent',
`create_time` int(10) DEFAULT NULL COMMENT '操作时间',

View File

@@ -41,9 +41,9 @@ class TriggerService
* 更新系统设置缓存
* @return bool
*/
public static function updateSysconfig()
public static function updateSysConfig(): bool
{
Cache::tag('sysconfig')->clear();
Cache::tag('sysConfig')->clear();
return true;
}

View File

@@ -4,6 +4,7 @@ namespace app\admin\service;
use app\admin\model\SystemUploadfile;
use OSS\Core\OssException;
use OSS\Credentials\EnvironmentVariableCredentialsProvider;
use OSS\OssClient;
use think\facade\Env;
use think\file\UploadedFile;
@@ -112,10 +113,21 @@ class UploadService
$accessKeySecret = $config['oss_access_key_secret'];
$endpoint = $config['oss_endpoint'];
$bucket = $config['oss_bucket'];
// 升级 aliyuncs/oss-sdk-php 到 v2.7.2 以上, 使用签名 v4 版本
putenv('OSS_ACCESS_KEY_ID=' . $accessKeyId);
putenv('OSS_ACCESS_KEY_SECRET=' . $accessKeySecret);
$region = str_replace(['http://oss-', 'https://oss-', 'oss-'], '', explode('.aliyuncs.com', $endpoint)[0] ?? '');
$provider = new EnvironmentVariableCredentialsProvider();
$args = [
"provider" => $provider,
"endpoint" => $endpoint,
"signatureVersion" => OssClient::OSS_SIGNATURE_VERSION_V4,
"region" => $region
];
if ($file->isValid()) {
$object = $this->setFilePath($file, Env::get('EASYADMIN.OSS_STATIC_PREFIX', 'easyadmin8') . '/');
try {
$ossClient = new OssClient($accessKeyId, $accessKeySecret, $endpoint);
$ossClient = new OssClient($args);
$_rs = $ossClient->putObject($bucket, $object, file_get_contents($file->getRealPath()));
$oss_request_url = $_rs['oss-request-url'] ?? '';
if (empty($oss_request_url)) return ['code' => 0, 'data' => '上传至OSS失败'];
@@ -164,7 +176,7 @@ class UploadService
if (empty($location)) return ['code' => 0, 'data' => '上传至COS失败'];
$location = 'https://' . $location;
$this->setSaveData($file);
} catch (Exception $e) {
}catch (Exception $e) {
return ['code' => 0, 'data' => $e->getMessage()];
}
$data = ['url' => $location];

View File

@@ -2,38 +2,20 @@
namespace app\admin\service\annotation;
use Doctrine\Common\Annotations\Annotation\Attributes;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use Attribute;
/**
* Class ControllerAnnotation
*
* @Annotation
* @Target("CLASS")
* @Attributes({
* @Attribute("title", type="string"),
* })
*
* @since 2.0
* controller 节点注解类
*/
#[Attribute]
final class ControllerAnnotation
{
/**
* Route group prefix for the controller
*
* @Required()
*
* @var string
* @param string $title
* @param bool $auth 是否需要权限
* @param string|array $ignore
*/
public $title = '';
/**
* 是否开启权限控制
* @Enum({true,false})
* @var bool
*/
public $auth = true;
public function __construct(public string $title = '', public bool $auth = true, public string|array $ignore = '')
{
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace app\admin\service\annotation;
use Attribute;
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_METHOD)]
final class MiddlewareAnnotation
{
/** 过滤日志 */
const IGNORE_LOG = 'LOG';
/** 免登录 */
const IGNORE_LOGIN = 'LOGIN';
public function __construct(public string $type = '', public string|array $ignore = '')
{
}
}

View File

@@ -2,32 +2,24 @@
namespace app\admin\service\annotation;
use Doctrine\Common\Annotations\Annotation\Attributes;
use Attribute;
/**
* 创建节点注解类
*
* @Annotation
* @Target({"METHOD","CLASS"})
* @Attributes({
* @Attribute("time", type = "int")
* })
* action 节点注解类
*/
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_METHOD| Attribute::TARGET_PROPERTY)]
final class NodeAnnotation
{
/** 过滤节点 */
const IGNORE_NODE = 'NODE';
/**
* 节点名称
* @Required()
* @var string
* @param string $title
* @param bool $auth 是否需要权限
* @param string|array $ignore
*/
public string $title;
/**
* 是否开启权限控制
* @Enum({true,false})
* @var bool
*/
public bool $auth = true;
public function __construct(public string $title = '', public bool $auth = true, public string|array $ignore = '')
{
}
}

View File

@@ -2,12 +2,14 @@
namespace app\admin\service\auth;
use Doctrine\Common\Annotations\AnnotationException;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Doctrine\Common\Annotations\DocParser;
use app\admin\service\annotation\ControllerAnnotation;
use app\admin\service\annotation\NodeAnnotation;
use app\admin\service\tool\CommonTool;
use ReflectionException;
/**
* 节点处理类
@@ -20,12 +22,12 @@ class Node
/**
* @var string 当前文件夹
*/
protected $basePath;
protected string $basePath;
/**
* @var string 命名空间前缀
*/
protected $baseNamespace;
protected string $baseNamespace;
/**
* 构造方法
@@ -33,7 +35,7 @@ class Node
* @param string $basePath 读取的文件夹
* @param string $baseNamespace 读取的命名空间前缀
*/
public function __construct($basePath, $baseNamespace)
public function __construct(string $basePath, string $baseNamespace)
{
$this->basePath = $basePath;
$this->baseNamespace = $baseNamespace;
@@ -43,8 +45,8 @@ class Node
/**
* 获取所有节点
* @return array
* @throws \Doctrine\Common\Annotations\AnnotationException
* @throws \ReflectionException
* @throws AnnotationException
* @throws ReflectionException
*/
public function getNodeList(): array
{
@@ -65,15 +67,23 @@ class Node
// 遍历读取所有方法的注释的参数信息
foreach ($methods as $method) {
// 读取NodeAnnotation的注解
$nodeAnnotation = $reader->getMethodAnnotation($method, NodeAnnotation::class);
if (!empty($nodeAnnotation) && !empty($nodeAnnotation->title)) {
$actionTitle = !empty($nodeAnnotation) && !empty($nodeAnnotation->title) ? $nodeAnnotation->title : null;
$actionAuth = !empty($nodeAnnotation) && !empty($nodeAnnotation->auth) ? $nodeAnnotation->auth : false;
// 忽略掉不需要的节点
$property = $reflectionClass->getProperty('ignoreNode');
$propertyAttributes = $property->getAttributes(NodeAnnotation::class);
if (!empty($propertyAttributes[0])) {
$propertyAttribute = $propertyAttributes[0]->newInstance();
if (in_array($method->name, $propertyAttribute->ignore)) continue;
}
$attributes = $reflectionClass->getMethod($method->name)->getAttributes(NodeAnnotation::class);
foreach ($attributes as $attribute) {
$annotation = $attribute->newInstance();
if (!empty($annotation->ignore)) if (strtolower($annotation->ignore) == 'node') continue;
$actionList[] = [
'node' => $controllerFormat . '/' . $method->name,
'title' => $actionTitle,
'is_auth' => $actionAuth,
'title' => $annotation->title ?? null,
'is_auth' => $annotation->auth ?? false,
'type' => 2,
];
}
@@ -81,16 +91,17 @@ class Node
// 方法非空才读取控制器注解
if (!empty($actionList)) {
// 读取Controller的注解
$controllerAnnotation = $reader->getClassAnnotation($reflectionClass, ControllerAnnotation::class);
$controllerTitle = !empty($controllerAnnotation) && !empty($controllerAnnotation->title) ? $controllerAnnotation->title : null;
$controllerAuth = !empty($controllerAnnotation) && !empty($controllerAnnotation->auth) ? $controllerAnnotation->auth : false;
$nodeList[] = [
'node' => $controllerFormat,
'title' => $controllerTitle,
'is_auth' => $controllerAuth,
'type' => 1,
];
$nodeList = array_merge($nodeList, $actionList);
$attributes = $reflectionClass->getAttributes(ControllerAnnotation::class);
foreach ($attributes as $attribute) {
$controllerAnnotation = $attribute->newInstance();
$nodeList[] = [
'node' => $controllerFormat,
'title' => $controllerAnnotation->title ?? null,
'is_auth' => $controllerAnnotation->auth ?? false,
'type' => 1,
];
}
$nodeList = array_merge($nodeList, $actionList);
}
}
@@ -102,7 +113,7 @@ class Node
* 获取所有控制器
* @return array
*/
public function getControllerList()
public function getControllerList(): array
{
return $this->readControllerFiles($this->basePath);
}
@@ -112,10 +123,10 @@ class Node
* @param $path
* @return array
*/
protected function readControllerFiles($path)
protected function readControllerFiles($path): array
{
list($list, $temp_list, $dirExplode) = [[], scandir($path), explode($this->basePath, $path)];
$middleDir = isset($dirExplode[1]) && !empty($dirExplode[1]) ? str_replace('/', '\\', substr($dirExplode[1], 1)) . "\\" : '';
$middleDir = !empty($dirExplode[1]) ? str_replace('/', '\\', substr($dirExplode[1], 1)) . "\\" : '';
foreach ($temp_list as $file) {
// 排除根目录和没有开启注解的模块
@@ -126,7 +137,7 @@ class Node
// 子文件夹,进行递归
$childFiles = $this->readControllerFiles($path . DIRECTORY_SEPARATOR . $file);
$list = array_merge($childFiles, $list);
} else {
}else {
// 判断是不是控制器
$fileExplodeArray = explode('.', $file);
if (count($fileExplodeArray) != 2 || end($fileExplodeArray) != 'php') {

View File

@@ -5,11 +5,11 @@ namespace app\admin\service\console;
class CliEcho
{
private $foreground_colors = [];
private array $foreground_colors = [];
private $background_colors = [];
private array $background_colors = [];
private static $foregroundColors = [
private static array $foregroundColors = [
'black' => '0;30',
'dark_gray' => '1;30',
'blue' => '0;34',
@@ -42,57 +42,57 @@ class CliEcho
public function __construct()
{
// Set up shell colors
$this->foreground_colors['black'] = '0;30';
$this->foreground_colors['dark_gray'] = '1;30';
$this->foreground_colors['blue'] = '0;34';
$this->foreground_colors['light_blue'] = '1;34';
$this->foreground_colors['green'] = '0;32';
$this->foreground_colors['light_green'] = '1;32';
$this->foreground_colors['cyan'] = '0;36';
$this->foreground_colors['light_cyan'] = '1;36';
$this->foreground_colors['red'] = '0;31';
$this->foreground_colors['light_red'] = '1;31';
$this->foreground_colors['purple'] = '0;35';
$this->foreground_colors['black'] = '0;30';
$this->foreground_colors['dark_gray'] = '1;30';
$this->foreground_colors['blue'] = '0;34';
$this->foreground_colors['light_blue'] = '1;34';
$this->foreground_colors['green'] = '0;32';
$this->foreground_colors['light_green'] = '1;32';
$this->foreground_colors['cyan'] = '0;36';
$this->foreground_colors['light_cyan'] = '1;36';
$this->foreground_colors['red'] = '0;31';
$this->foreground_colors['light_red'] = '1;31';
$this->foreground_colors['purple'] = '0;35';
$this->foreground_colors['light_purple'] = '1;35';
$this->foreground_colors['brown'] = '0;33';
$this->foreground_colors['yellow'] = '1;33';
$this->foreground_colors['light_gray'] = '0;37';
$this->foreground_colors['white'] = '1;37';
$this->background_colors['black'] = '40';
$this->background_colors['red'] = '41';
$this->background_colors['green'] = '42';
$this->background_colors['yellow'] = '43';
$this->background_colors['blue'] = '44';
$this->background_colors['magenta'] = '45';
$this->background_colors['cyan'] = '46';
$this->background_colors['light_gray'] = '47';
$this->foreground_colors['brown'] = '0;33';
$this->foreground_colors['yellow'] = '1;33';
$this->foreground_colors['light_gray'] = '0;37';
$this->foreground_colors['white'] = '1;37';
$this->background_colors['black'] = '40';
$this->background_colors['red'] = '41';
$this->background_colors['green'] = '42';
$this->background_colors['yellow'] = '43';
$this->background_colors['blue'] = '44';
$this->background_colors['magenta'] = '45';
$this->background_colors['cyan'] = '46';
$this->background_colors['light_gray'] = '47';
}
// Returns colored string
public function getColoredString($string, $foreground_color = null, $background_color = null, $new_line = false)
public function getColoredString($string, $foreground_color = null, $background_color = null, $new_line = false): string
{
$colored_string = '';
// Check if given foreground color found
if (isset($this->foreground_colors[$foreground_color])) {
$colored_string .= "\033[".$this->foreground_colors[$foreground_color].'m';
$colored_string .= "\033[" . $this->foreground_colors[$foreground_color] . 'm';
}
// Check if given background color found
if (isset($this->background_colors[$background_color])) {
$colored_string .= "\033[".$this->background_colors[$background_color].'m';
$colored_string .= "\033[" . $this->background_colors[$background_color] . 'm';
}
// Add string and end coloring
$colored_string .= $string."\033[0m";
return $new_line ? $colored_string.PHP_EOL : $colored_string;
$colored_string .= $string . "\033[0m";
return $new_line ? $colored_string . PHP_EOL : $colored_string;
}
// Returns all foreground color names
public function getForegroundColors()
public function getForegroundColors(): array
{
return array_keys($this->foreground_colors);
}
// Returns all background color names
public function getBackgroundColors()
public function getBackgroundColors(): array
{
return array_keys($this->background_colors);
}
@@ -100,25 +100,26 @@ class CliEcho
/**
* 获取带颜色的文字.
*
* @param string $string black|dark_gray|blue|light_blue|green|light_green|cyan|light_cyan|red|light_red|purple|brown|yellow|light_gray|white
* @param string $string black|dark_gray|blue|light_blue|green|light_green|cyan|light_cyan|red|light_red|purple|brown|yellow|light_gray|white
* @param string|null $foregroundColor 前景颜色 black|red|green|yellow|blue|magenta|cyan|light_gray
* @param string|null $backgroundColor 背景颜色 同$foregroundColor
*
* @return string
*/
public static function initColoredString(
$string,
$foregroundColor = null,
$backgroundColor = null
) {
string $string,
?string $foregroundColor = null,
?string $backgroundColor = null
): string
{
$coloredString = '';
if (isset(static::$foregroundColors[$foregroundColor])) {
$coloredString .= "\033[".static::$foregroundColors[$foregroundColor].'m';
$coloredString .= "\033[" . static::$foregroundColors[$foregroundColor] . 'm';
}
if (isset(static::$backgroundColors[$backgroundColor])) {
$coloredString .= "\033[".static::$backgroundColors[$backgroundColor].'m';
$coloredString .= "\033[" . static::$backgroundColors[$backgroundColor] . 'm';
}
$coloredString .= $string."\033[0m";
$coloredString .= $string . "\033[0m";
return $coloredString;
}
@@ -127,9 +128,9 @@ class CliEcho
*
* @param $msg
*/
public static function notice($msg)
public static function notice($msg): void
{
fwrite(STDOUT, self::initColoredString($msg, 'light_gray').PHP_EOL);
fwrite(STDOUT, self::initColoredString($msg, 'light_gray') . PHP_EOL);
}
/**
@@ -137,9 +138,9 @@ class CliEcho
*
* @param $msg
*/
public static function error($msg)
public static function error($msg): void
{
fwrite(STDERR, self::initColoredString($msg, 'white','red').PHP_EOL);
fwrite(STDERR, self::initColoredString($msg, 'white', 'red') . PHP_EOL);
}
/**
@@ -147,9 +148,9 @@ class CliEcho
*
* @param $msg
*/
public static function warn($msg)
public static function warn($msg): void
{
fwrite(STDOUT, self::initColoredString($msg, 'red','yellow').PHP_EOL);
fwrite(STDOUT, self::initColoredString($msg, 'red', 'yellow') . PHP_EOL);
}
/**
@@ -157,9 +158,9 @@ class CliEcho
*
* @param $msg
*/
public static function success($msg)
public static function success($msg): void
{
fwrite(STDOUT, self::initColoredString($msg, 'light_cyan').PHP_EOL);
fwrite(STDOUT, self::initColoredString($msg, 'light_cyan') . PHP_EOL);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
<?php
declare(strict_types = 1);
namespace EasyAdmin\curd\exceptions;
namespace app\admin\service\curd\exceptions;
class CurdException extends \Exception
{
}

View File

@@ -1,8 +1,8 @@
<?php
declare(strict_types = 1);
namespace EasyAdmin\curd\exceptions;
namespace app\admin\service\curd\exceptions;
class FileException extends \Exception
{
}

View File

@@ -1,8 +1,8 @@
<?php
declare(strict_types = 1);
namespace app\admin\service\curd\exceptions;
class TableException extends \Exception
{
}

View File

@@ -7,21 +7,22 @@ use app\admin\service\annotation\ControllerAnnotation;
use app\admin\service\annotation\NodeAnnotation;
use think\App;
/**
* @ControllerAnnotation(title="{{controllerAnnotation}}")
*/
#[ControllerAnnotation(title: '{{controllerAnnotation}}')]
class {{controllerName}} extends AdminController
{
use \app\admin\traits\Curd;
private array $notes;
public function __construct(App $app)
{
parent::__construct($app);
$this->model = new {{modelFilename}}();
{{selectList}}
self::$model = new {{modelFilename}}();
$notes = self::$model::$notes;
{{constructRelation}}
$this->notes =$notes;
$this->assign(compact('notes'));
}
{{indexMethod}}
}

View File

@@ -1,25 +1,15 @@
/**
* @NodeAnnotation(title="列表")
*/
public function index()
#[NodeAnnotation(title: '列表', auth: true)]
public function index(\app\Request $request): \think\response\Json|string
{
if ($this->request->isAjax()) {
if ($request->isAjax()) {
if (input('selectFields')) {
return $this->selectList();
}
list($page, $limit, $where) = $this->buildTableParams();
$count = $this->model
{{relationIndexMethod}}
->where($where)
->count();
$list = $this->model
{{relationIndexMethod}}
->where($where)
->page($page, $limit)
->order($this->sort)
->select();
$data = [
$count = self::$model::where($where)->{{relationIndexMethod}}->count();
$list = self::$model::where($where)->{{relationIndexMethod}}->page($page, $limit)->order($this->sort)->select()->toArray();
$data = [
'code' => 0,
'msg' => '',
'count' => $count,

View File

@@ -7,13 +7,17 @@ use app\common\model\TimeModel;
class {{modelName}} extends TimeModel
{
protected $name = "{{table}}";
protected function getOptions(): array
{
return [
'name' => "{{table}}",
'table' => "{{prefix_table}}",
'deleteTime' => {{deleteTime}},
];
}
protected $table = "{{prefix_table}}";
protected $deleteTime = {{deleteTime}};
public static array $notes = {{selectArrays}};
{{relationList}}
{{selectList}}
}

View File

@@ -1,5 +1,5 @@
public function {{relationMethod}}()
{
return $this->belongsTo('{{relationModel}}', '{{foreignKey}}', '{{primaryKey}}');
return $this->belongsTo({{relationModel}}, '{{foreignKey}}', '{{primaryKey}}'){{relationFields}};
}

View File

@@ -9,9 +9,10 @@ define(["jquery", "easy-admin"], function ($, ea) {
delete_url: '{{controllerUrl}}/delete',
export_url: '{{controllerUrl}}/export',
modify_url: '{{controllerUrl}}/modify',
recycle_url: '{{controllerUrl}}/recycle',
};
var Controller = {
return {
index: function () {
ea.table.render({
@@ -29,6 +30,62 @@ define(["jquery", "easy-admin"], function ($, ea) {
edit: function () {
ea.listen();
},
recycle: function () {
init.index_url = init.recycle_url;
ea.table.render({
init: init,
toolbar: ['refresh',
[{
class: 'layui-btn layui-btn-sm',
method: 'get',
field: 'id',
icon: 'fa fa-refresh',
text: '全部恢复',
title: '确定恢复?',
auth: 'recycle',
url: init.recycle_url + '?type=restore',
checkbox: true
}, {
class: 'layui-btn layui-btn-danger layui-btn-sm',
method: 'get',
field: 'id',
icon: 'fa fa-delete',
text: '彻底删除',
title: '确定彻底删除?',
auth: 'recycle',
url: init.recycle_url + '?type=delete',
checkbox: true
}], 'export',
],
cols: [[
{{recycleCols}}
{
width: 250,
title: '操作',
templet: ea.table.tool,
operat: [
[{
title: '确认恢复?',
text: '恢复数据',
filed: 'id',
url: init.recycle_url + '?type=restore',
method: 'get',
auth: 'recycle',
class: 'layui-btn layui-btn-xs layui-btn-success',
}, {
title: '想好了吗?',
text: '彻底删除',
filed: 'id',
method: 'get',
url: init.recycle_url + '?type=delete',
auth: 'recycle',
class: 'layui-btn layui-btn-xs layui-btn-normal layui-bg-red',
}]]
}
]],
});
ea.listen();
},
};
return Controller;
});

View File

@@ -6,6 +6,5 @@
<button type="submit" class="layui-btn layui-btn-normal layui-btn-sm" lay-submit>确认</button>
<button type="reset" class="layui-btn layui-btn-primary layui-btn-sm">重置</button>
</div>
</form>
</div>

View File

@@ -4,7 +4,13 @@
data-auth-add="{:auth('{{controllerUrl}}/add')}"
data-auth-edit="{:auth('{{controllerUrl}}/edit')}"
data-auth-delete="{:auth('{{controllerUrl}}/delete')}"
data-auth-recycle="{:auth('{{controllerUrl}}/recycle')}"
lay-filter="currentTable">
<!-- searchTableShow="false" 隐藏搜索框 -->
</table>
</div>
</div>
</div>
<script>
{{notesScript}}
</script>

View File

@@ -0,0 +1,7 @@
<div class="layui-form-item">
<label class="layui-form-label">{{comment}}</label>
<div class="layui-input-block">
<input type="number" name="{{field}}" class="layui-input" lay-affix="number" {{required}} placeholder="请输入{{comment}}" value="{{value}}">
</div>
</div>

View File

@@ -0,0 +1,13 @@
<div class="layuimini-container">
<div class="layuimini-main">
<table id="currentTable" class="layui-table layui-hide"
data-auth-recycle="{:auth('{{controllerUrl}}/recycle')}"
lay-filter="currentTable">
<!-- searchTableShow="false" 隐藏搜索框 -->
</table>
</div>
</div>
<script>
{{notesScript}}
</script>

View File

@@ -14,7 +14,7 @@ class CommonTool
{
$str = preg_replace_callback('/([-_]+([a-z]{1}))/i', function ($matches) {
return strtoupper($matches[2]);
}, $str);
}, $str);
return $str;
}
@@ -27,7 +27,7 @@ class CommonTool
{
$str = preg_replace_callback('/([A-Z]{1})/', function ($matches) {
return '_' . strtolower($matches[0]);
}, $str);
}, $str);
return $str;
}
@@ -45,11 +45,11 @@ class CommonTool
break;
}
}
} elseif (isset($_SERVER['HTTP_CLIENT_IP']) && preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $_SERVER['HTTP_CLIENT_IP'])) {
}elseif (isset($_SERVER['HTTP_CLIENT_IP']) && preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $_SERVER['HTTP_CLIENT_IP'])) {
$ip = $_SERVER['HTTP_CLIENT_IP'];
} elseif (isset($_SERVER['HTTP_CF_CONNECTING_IP']) && preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $_SERVER['HTTP_CF_CONNECTING_IP'])) {
}elseif (isset($_SERVER['HTTP_CF_CONNECTING_IP']) && preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $_SERVER['HTTP_CF_CONNECTING_IP'])) {
$ip = $_SERVER['HTTP_CF_CONNECTING_IP'];
} elseif (isset($_SERVER['HTTP_X_REAL_IP']) && preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $_SERVER['HTTP_X_REAL_IP'])) {
}elseif (isset($_SERVER['HTTP_X_REAL_IP']) && preg_match('/^([0-9]{1,3}\.){3}[0-9]{1,3}$/', $_SERVER['HTTP_X_REAL_IP'])) {
$ip = $_SERVER['HTTP_X_REAL_IP'];
}
return $ip;
@@ -70,7 +70,7 @@ class CommonTool
if (is_dir($path . DIRECTORY_SEPARATOR . $file)) {
$childFiles = self::readDirAllFiles($path . DIRECTORY_SEPARATOR . $file, $basePath);
$list = array_merge($childFiles, $list);
} else {
}else {
$filePath = $path . DIRECTORY_SEPARATOR . $file;
$fileName = str_replace($basePath . DIRECTORY_SEPARATOR, '', $filePath);
$list[$fileName] = $filePath;
@@ -95,4 +95,14 @@ class CommonTool
return $string;
}
public static function replaceArrayString(?string $arrayString): string
{
$arrayString = str_replace('array (', '[', $arrayString);
$arrayString = str_replace(')', ']', $arrayString);
$arrayString = str_replace('=>
[', '=> [', $arrayString);
return $arrayString;
}
}

View File

@@ -1,122 +0,0 @@
<?php
namespace app\admin\service\upload;
use think\facade\Filesystem;
use think\File;
/**
* 基类
* Class Base
* @package EasyAdmin\upload
*/
class FileBase
{
/**
* 上传配置
* @var array
*/
protected $uploadConfig;
/**
* 上传文件对象
* @var object
*/
protected $file;
/**
* 上传完成的文件路径
* @var string
*/
protected $completeFilePath;
/**
* 上传完成的文件的URL
* @var string
*/
protected $completeFileUrl;
/**
* 保存上传文件的数据表
* @var string
*/
protected $tableName;
/**
* 上传类型
* @var string
*/
protected $uploadType = 'local';
/**
* 设置上传方式
* @param $value
* @return $this
*/
public function setUploadType($value)
{
$this->uploadType = $value;
return $this;
}
/**
* 设置上传配置
* @param $value
* @return $this
*/
public function setUploadConfig($value)
{
$this->uploadConfig = $value;
return $this;
}
/**
* 设置上传配置
* @param $value
* @return $this
*/
public function setFile($value)
{
$this->file = $value;
return $this;
}
/**
* 设置保存文件数据表
* @param $value
* @return $this
*/
public function setTableName($value)
{
$this->tableName = $value;
return $this;
}
/**
* 保存文件
*/
public function save()
{
$this->completeFilePath = Filesystem::disk('public')->putFile('upload', $this->file, 'md5');
$this->completeFileUrl = request()->domain() . '/' . str_replace(DIRECTORY_SEPARATOR, '/', $this->completeFilePath);
}
/**
* 删除保存在本地的文件
* @return bool|string
*/
public function rmLocalSave()
{
try {
$rm = unlink($this->completeFilePath);
} catch (\Exception $e) {
return $e->getMessage();
}
return $rm;
}
}

View File

@@ -1,127 +0,0 @@
<?php
namespace app\admin\service\upload;
use app\admin\service\upload\driver\Alioss;
use app\admin\service\upload\driver\Qnoss;
use app\admin\service\upload\driver\Txcos;
use app\admin\service\upload\driver\Local;
use think\File;
/**
* 上传组件
* Class Uploadfile
* @package EasyAdmin\upload
*/
class Uploadfile
{
/**
* 当前实例对象
* @var object
*/
protected static $instance;
/**
* 上传方式
* @var string
*/
protected $uploadType = 'local';
/**
* 上传配置文件
* @var array
*/
protected $uploadConfig;
/**
* 需要上传的文件对象
* @var File
*/
protected $file;
/**
* 保存上传文件的数据表
* @var string
*/
protected $tableName = 'system_uploadfile';
/**
* 获取对象实例
* @return Uploadfile|object
*/
public static function instance()
{
if (is_null(self::$instance)) {
self::$instance = new static();
}
return self::$instance;
}
/**
* 设置上传对象
* @param $value
* @return $this
*/
public function setFile($value)
{
$this->file = $value;
return $this;
}
/**
* 设置上传文件
* @param $value
* @return $this
*/
public function setUploadConfig($value)
{
$this->uploadConfig = $value;
return $this;
}
/**
* 设置上传方式
* @param $value
* @return $this
*/
public function setUploadType($value)
{
$this->uploadType = $value;
return $this;
}
/**
* 设置保存数据表
* @param $value
* @return $this
*/
public function setTableName($value)
{
$this->tableName = $value;
return $this;
}
/**
* 保存文件
* @return array|void
*/
public function save()
{
$obj = null;
if ($this->uploadType == 'local') {
$obj = new Local();
} elseif ($this->uploadType == 'oss') {
$obj = new Alioss();
} elseif ($this->uploadType == 'cos') {
$obj = new Txcos();
}
$save = $obj->setUploadConfig($this->uploadConfig)
->setUploadType($this->uploadType)
->setTableName($this->tableName)
->setFile($this->file)
->save();
return $save;
}
}

View File

@@ -1,42 +0,0 @@
<?php
namespace app\admin\service\upload\driver;
use app\admin\service\upload\FileBase;
use app\admin\service\upload\trigger\SaveDb;
use app\admin\service\upload\driver\alioss\Oss;
/**
* 阿里云上传
* Class Alioss
* @package EasyAdmin\upload\driver
*/
class Alioss extends FileBase
{
/**
* 重写上传方法
* @return array|void
*/
public function save()
{
parent::save();
$upload = Oss::instance($this->uploadConfig)
->save($this->completeFilePath, $this->completeFilePath);
if ($upload['save'] == true) {
SaveDb::trigger($this->tableName, [
'upload_type' => $this->uploadType,
'original_name' => $this->file->getOriginalName(),
'mime_type' => $this->file->getOriginalMime(),
'file_ext' => strtolower($this->file->getOriginalExtension()),
'url' => $upload['url'],
'create_time' => time(),
]);
}
$this->rmLocalSave();
return $upload;
}
}

View File

@@ -1,45 +0,0 @@
<?php
namespace app\admin\service\upload\driver;
use app\admin\service\upload\FileBase;
use app\admin\service\upload\trigger\SaveDb;
/**
* 本地上传
* Class Local
* @package EasyAdmin\upload\driver
*/
class Local extends FileBase
{
/**
* 重写上传方法
* @return array|void
*/
public function save()
{
if (pathinfo($this->file->getOriginalName(), PATHINFO_EXTENSION) === 'php') {
return [
'save' => false,
'msg' => '上传文件中存在异常文件,请重新选择',
'url' => '',
];
}
parent::save();
SaveDb::trigger($this->tableName, [
'upload_type' => $this->uploadType,
'original_name' => $this->file->getOriginalName(),
'mime_type' => $this->file->getOriginalMime(),
'file_ext' => strtolower($this->file->getOriginalExtension()),
'url' => $this->completeFileUrl,
'create_time' => time(),
]);
return [
'save' => true,
'msg' => '上传成功',
'url' => $this->completeFileUrl,
];
}
}

View File

@@ -1,42 +0,0 @@
<?php
namespace app\admin\service\upload\driver;
use app\admin\service\upload\driver\qnoss\Oss;
use app\admin\service\upload\FileBase;
use app\admin\service\upload\trigger\SaveDb;
/**
* 七牛云上传
* Class Qnoss
* @package EasyAdmin\upload\driver
*/
class Qnoss extends FileBase
{
/**
* 重写上传方法
* @return array|void
*/
public function save()
{
parent::save();
$upload = Oss::instance($this->uploadConfig)
->save($this->completeFilePath, $this->completeFilePath);
if ($upload['save'] == true) {
SaveDb::trigger($this->tableName, [
'upload_type' => $this->uploadType,
'original_name' => $this->file->getOriginalName(),
'mime_type' => $this->file->getOriginalMime(),
'file_ext' => strtolower($this->file->getOriginalExtension()),
'url' => $upload['url'],
'create_time' => time(),
]);
}
$this->rmLocalSave();
return $upload;
}
}

View File

@@ -1,43 +0,0 @@
<?php
namespace app\admin\service\upload\driver;
use app\admin\service\upload\driver\txcos\Cos;
use app\admin\service\upload\FileBase;
use app\admin\service\upload\trigger\SaveDb;
/**
* 腾讯云上传
* Class Txcos
* @package EasyAdmin\upload\driver
*/
class Txcos extends FileBase
{
/**
* 重写上传方法
* @return array|void
*/
public function save()
{
parent::save();
$upload = Cos::instance($this->uploadConfig)
->save($this->completeFilePath, $this->completeFilePath);
if ($upload['save'] == true) {
SaveDb::trigger($this->tableName, [
'upload_type' => $this->uploadType,
'original_name' => $this->file->getOriginalName(),
'mime_type' => $this->file->getOriginalMime(),
'file_ext' => strtolower($this->file->getOriginalExtension()),
'url' => $upload['url'],
'create_time' => time(),
]);
}
$this->rmLocalSave();
return $upload;
}
}

View File

@@ -1,70 +0,0 @@
<?php
namespace app\admin\service\upload\driver\alioss;
use EasyAdmin\upload\interfaces\OssDriver;
use OSS\Core\OssException;
use OSS\OssClient;
class Oss implements OssDriver
{
protected static $instance;
protected $accessKeyId;
protected $accessKeySecret;
protected $endpoint;
protected $bucket;
protected $domain;
protected $ossClient;
protected function __construct($config)
{
$this->accessKeyId = $config['alioss_access_key_id'];
$this->accessKeySecret = $config['alioss_access_key_secret'];
$this->endpoint = $config['alioss_endpoint'];
$this->bucket = $config['alioss_bucket'];
$this->domain = $config['alioss_domain'];
$this->ossClient = new OssClient($this->accessKeyId, $this->accessKeySecret, $this->endpoint);
return $this;
}
public static function instance($config)
{
if (is_null(self::$instance)) {
self::$instance = new static($config);
}
return self::$instance;
}
public function save($objectName,$filePath)
{
try {
$upload = $this->ossClient->uploadFile($this->bucket, $objectName, $filePath);
} catch (OssException $e) {
return [
'save' => false,
'msg' => $e->getMessage(),
];
}
if (!isset($upload['info']['url'])) {
return [
'save' => false,
'msg' => '保存失败',
];
}
return [
'save' => true,
'msg' => '上传成功',
'url' => $upload['info']['url'],
];
}
}

View File

@@ -1,64 +0,0 @@
<?php
namespace app\admin\service\upload\driver\qnoss;
use app\admin\service\upload\interfaces\OssDriver;
use Qiniu\Auth;
use Qiniu\Storage\UploadManager;
class Oss implements OssDriver
{
protected static $instance;
protected $accessKey;
protected $secretKey;
protected $bucket;
protected $domain;
protected $auth;
public function __construct($config)
{
$this->accessKey = $config['qnoss_access_key'];
$this->secretKey = $config['qnoss_secret_key'];
$this->bucket = $config['qnoss_bucket'];
$this->domain = $config['qnoss_domain'];
$this->auth = new Auth($this->accessKey, $this->secretKey);
return $this;
}
public static function instance($config)
{
if (is_null(self::$instance)) {
self::$instance = new static($config);
}
return self::$instance;
}
public function save($objectName, $filePath)
{
$token = $this->auth->uploadToken($this->bucket);
$uploadMgr = new UploadManager();
list($result, $error) = $uploadMgr->putFile($token, $objectName, $filePath);
if ($error !== null) {
return [
'save' => false,
'msg' => '保存失败',
];
} else {
return [
'save' => true,
'msg' => '上传成功',
'url' => $this->domain . '/' . $result['key'],
];
}
}
}

View File

@@ -1,12 +0,0 @@
<?php
namespace app\admin\service\upload\interfaces;
interface OssDriver
{
public function save($objectName,$filePath);
}

View File

@@ -1,31 +0,0 @@
<?php
namespace app\admin\service\upload\trigger;
use think\facade\Db;
/**
* 保存到数据库
* Class SaveDb
* @package EasyAdmin\upload\trigger
*/
class SaveDb
{
/**
* 保存上传文件
* @param $tableName
* @param $data
*/
public static function trigger($tableName, $data)
{
if (isset($data['original_name'])) {
$data['original_name'] = htmlspecialchars($data['original_name'], ENT_QUOTES);
}
Db::name($tableName)->save($data);
}
}

View File

@@ -4,8 +4,10 @@ namespace app\admin\traits;
use app\admin\service\annotation\NodeAnnotation;
use app\admin\service\tool\CommonTool;
use jianyan\excel\Excel;
use app\Request;
use think\db\exception\PDOException;
use think\facade\Db;
use think\response\Json;
/**
* 后台CURD复用
@@ -15,24 +17,16 @@ use think\facade\Db;
trait Curd
{
/**
* @NodeAnnotation(title="列表")
*/
public function index()
#[NodeAnnotation(title: '列表', auth: true)]
public function index(Request $request): Json|string
{
if ($this->request->isAjax()) {
if ($request->isAjax()) {
if (input('selectFields')) {
return $this->selectList();
}
list($page, $limit, $where) = $this->buildTableParams();
$count = $this->model
->where($where)
->count();
$list = $this->model
->where($where)
->page($page, $limit)
->order($this->sort)
->select();
$count = self::$model::where($where)->count();
$list = self::$model::where($where)->page($page, $limit)->order($this->sort)->select()->toArray();
$data = [
'code' => 0,
'msg' => '',
@@ -44,39 +38,39 @@ trait Curd
return $this->fetch();
}
/**
* @NodeAnnotation(title="添加")
*/
public function add()
#[NodeAnnotation(title: '添加', auth: true)]
public function add(Request $request): string
{
if ($this->request->isPost()) {
$post = $this->request->post();
if ($request->isPost()) {
$post = $request->post();
$rule = [];
$this->validate($post, $rule);
try {
$save = $this->model->save($post);
} catch (\Exception $e) {
$this->error('保存失败:' . $e->getMessage());
Db::transaction(function() use ($post, &$save) {
$save = self::$model::create($post);
});
}catch (\Exception $e) {
$this->error('新增失败:' . $e->getMessage());
}
$save ? $this->success('保存成功') : $this->error('保存失败');
$save ? $this->success('新增成功') : $this->error('新增失败');
}
return $this->fetch();
}
/**
* @NodeAnnotation(title="编辑")
*/
public function edit($id)
#[NodeAnnotation(title: '编辑', auth: true)]
public function edit(Request $request, $id = 0): string
{
$row = $this->model->find($id);
$row = self::$model::find($id);
empty($row) && $this->error('数据不存在');
if ($this->request->isPost()) {
$post = $this->request->post();
if ($request->isPost()) {
$post = $request->post();
$rule = [];
$this->validate($post, $rule);
try {
$save = $row->save($post);
} catch (\Exception $e) {
Db::transaction(function() use ($post, $row, &$save) {
$save = $row->save($post);
});
}catch (\Exception $e) {
$this->error('保存失败');
}
$save ? $this->success('保存成功') : $this->error('保存失败');
@@ -85,32 +79,30 @@ trait Curd
return $this->fetch();
}
/**
* @NodeAnnotation(title="删除")
*/
public function delete($id)
#[NodeAnnotation(title: '删除', auth: true)]
public function delete(Request $request): void
{
// 如果不是id作为主键 请在对应的控制器中覆盖重写
$id = $request->param('id', []);
$this->checkPostRequest();
$row = $this->model->whereIn('id', $id)->select();
$row = self::$model::whereIn('id', $id)->select();
$row->isEmpty() && $this->error('数据不存在');
try {
$save = $row->delete();
} catch (\Exception $e) {
}catch (\Exception $e) {
$this->error('删除失败');
}
$save ? $this->success('删除成功') : $this->error('删除失败');
}
/**
* @NodeAnnotation(title="导出")
*/
#[NodeAnnotation(title: '导出', auth: true)]
public function export()
{
if (env('EASYADMIN.IS_DEMO', false)) {
$this->error('演示环境下不允许操作');
}
list($page, $limit, $where) = $this->buildTableParams();
$tableName = $this->model->getName();
$tableName = (new self::$model)->getName();
$tableName = CommonTool::humpToLine(lcfirst($tableName));
$prefix = config('database.connections.mysql.prefix');
$dbList = Db::query("show full columns from {$prefix}{$tableName}");
@@ -121,30 +113,30 @@ trait Curd
$header[] = [$comment, $vo['Field']];
}
}
$list = $this->model
->where($where)
$list = self::$model::where($where)
->limit(100000)
->order('id', 'desc')
->order($this->sort)
->select()
->toArray();
$fileName = time();
return Excel::exportData($list, $header, $fileName, 'xlsx');
try {
exportExcel($header, $list);
}catch (\Throwable $exception) {
$this->error('导出失败: ' . $exception->getMessage() . PHP_EOL . $exception->getFile() . PHP_EOL . $exception->getLine());
}
}
/**
* @NodeAnnotation(title="属性修改")
*/
public function modify()
#[NodeAnnotation(title: '属性修改', auth: true)]
public function modify(Request $request): void
{
$this->checkPostRequest();
$post = $this->request->post();
$post = $request->post();
$rule = [
'id|ID' => 'require',
'field|字段' => 'require',
'value|值' => 'require',
];
$this->validate($post, $rule);
$row = $this->model->find($post['id']);
$row = self::$model::find($post['id']);
if (!$row) {
$this->error('数据不存在');
}
@@ -152,13 +144,60 @@ trait Curd
$this->error('该字段不允许修改:' . $post['field']);
}
try {
$row->save([
$post['field'] => $post['value'],
]);
} catch (\Exception $e) {
Db::transaction(function() use ($post, $row) {
$row->save([
$post['field'] => $post['value'],
]);
});
}catch (\Exception $e) {
$this->error($e->getMessage());
}
$this->success('保存成功');
}
#[NodeAnnotation(title: '回收站', auth: true)]
public function recycle(Request $request): Json|string
{
if (!$request->isAjax()) {
return $this->fetch();
}
$id = $request->param('id', []);
$type = $request->param('type', '');
$deleteTimeField = (new self::$model)->getOption('deleteTime'); // 获取软删除字段
$defaultErrorMsg = 'Model 中未设置软删除 deleteTime 对应字段 或 数据表中不存在该字段';
if (!$deleteTimeField) $this->success($defaultErrorMsg);
switch ($type) {
case 'restore':
self::$model::withTrashed()->whereIn('id', $id)->strict(false)->update([$deleteTimeField => null, 'update_time' => time()]);
$this->success('success');
break;
case 'delete':
self::$model::destroy($id, true);
$this->success('success');
break;
default:
list($page, $limit, $where) = $this->buildTableParams();
try {
$count = self::$model::withTrashed()->where($where)->whereNotNull($deleteTimeField)->count();
$list = self::$model::withTrashed()->where($where)->page($page, $limit)->order($this->sort)->whereNotNull($deleteTimeField)->select()->toArray();
$data = [
'code' => 0,
'msg' => '',
'count' => $count,
'data' => $list,
];
} catch (\Throwable $e) {
$error = $e->getMessage();
if ($e instanceof PDOException) $error .= '<br>' . $defaultErrorMsg;
$data = [
'code' => -1,
'msg' => $error,
'count' => 0,
'data' => [],
];
}
return json($data);
}
}
}

View File

@@ -8,7 +8,7 @@
<div class="layui-input-block layuimini-upload">
<input name="head_img" class="layui-input layui-col-xs6" lay-reqtext="请上传用户头像" placeholder="请上传用户头像" value="{$row.head_img|default=''}">
<div class="layuimini-upload-btn">
<span><a class="layui-btn" data-upload="head_img" data-upload-number="one" data-upload-exts="png|jpg|ico|jpeg"><i class="fa fa-upload"></i> 上传</a></span>
<span><a class="layui-btn" data-upload="head_img" data-upload-number="one" data-upload-exts="png|jpg|ico|jpeg" data-upload-mimetype="image/*"><i class="fa fa-upload"></i> 上传</a></span>
<span><a class="layui-btn layui-btn-normal" id="select_head_img" data-upload-select="head_img" data-upload-number="one"><i class="fa fa-list"></i> 选择</a></span>
</div>
</div>
@@ -30,6 +30,15 @@
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">登录方式</label>
<div class="layui-input-block">
{foreach notes.login_type as $key=>$val}
<input type="radio" name="login_type" lay-skin="primary" title="{$val}" value="{$key}" lay-filter="loginType-filter" {if $key==$row.login_type}checked=""{/if}>
{/foreach}
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">备注信息</label>
<div class="layui-input-block">

View File

@@ -1,5 +1,5 @@
<link rel="stylesheet" href="__STATIC__/plugs/lay-module/layuimini/layuimini.css?v={$version}" media="all">
<link rel="stylesheet" href="__STATIC__/plugs/lay-module/layuimini/themes/default.css?v={$version}" media="all">
<link rel="stylesheet" href="/static/plugs/lay-module/layuimini/layuimini.css?v={$version}" media="all">
<link rel="stylesheet" href="/static/plugs/lay-module/layuimini/themes/default.css?v={$version}" media="all">
<style id="layuimini-bg-color"></style>
<div class="layui-layout-body layuimini-all">
<div class="layui-layout layui-layout-admin">
@@ -26,15 +26,6 @@
</ul>
<ul class="layui-nav layui-layout-right">
<!-- <li class="layui-nav-item" lay-unselect>-->
<!-- <div class="layui-form ws-header-theme" lay-filter="header-theme">-->
<!-- <input type="checkbox" name="theme-mode" id="ID-header-theme-mode" lay-filter="header-theme-mode" lay-skin="switch">-->
<!-- <div lay-checkbox>-->
<!-- <i class="layui-icon layui-icon-moon"></i> |-->
<!-- <i class="layui-icon layui-icon-light"></i>-->
<!-- </div>-->
<!-- </div>-->
<!-- </li>-->
<li class="layui-nav-item" lay-unselect>
<a href="http://easyadmin8.top" target="_blank"><i class="fa fa-home"></i></a>
</li>
@@ -47,6 +38,15 @@
<li class="layui-nav-item mobile layui-hide-xs" lay-unselect>
<a href="javascript:;" data-check-screen="full"><i class="fa fa-arrows-alt"></i></a>
</li>
<li class="layui-nav-item mobile layui-hide-xs" lay-unselect>
<div class="layui-form ws-header-theme" lay-filter="header-theme">
<input type="checkbox" name="theme-mode" lay-filter="header-theme-mode" lay-skin="switch">
<div lay-checkbox>
<i class="layui-icon layui-icon-moon"></i> |
<i class="layui-icon layui-icon-light"></i>
</div>
</div>
</li>
<li class="layui-nav-item layuimini-setting">
<a href="javascript:;">
<img src="{:session('admin.head_img')}" class="layui-nav-img" width="50" height="50">
@@ -91,8 +91,8 @@
<div class="layuimini-site-mobile"><i class="layui-icon"></i></div>
<div class="layui-body">
<div class="layuimini-tab layui-tab-rollTool layui-tab" lay-filter="layuiminiTab" lay-allowclose="true">
<ul class="layui-tab-title">
<div class="layuimini-tab layui-tabs-rollTool layui-tabs" lay-filter="layuiminiTab" id="layuiminiTab">
<ul class="layui-tabs-header">
<li class="layui-this" id="layuiminiHomeTabId" lay-id=""></li>
</ul>
<div class="layui-tab-control">
@@ -111,8 +111,8 @@
</ul>
</li>
</div>
<div class="layui-tab-content">
<div id="layuiminiHomeTabIframe" class="layui-tab-item layui-show"></div>
<div class="layui-tabs-body">
<div id="layuiminiHomeTabIframe" class="layui-tab-item layui-tabs-item layui-show"></div>
</div>
</div>
</div>

View File

@@ -0,0 +1,45 @@
<div class="layuimini-container">
<form id="app-form" class="layui-form layuimini-form" autocomplete="off">
{if $old_secret}
<div class="layui-card">
<div class="layui-card-header">提示</div>
<div class="layui-card-body">
当前账号已经绑定过了 谷歌验证码 ,如果重新保存将替换
</div>
</div>
{/if}
<div class="layui-form-item">
<label class="layui-form-label required">验证秘钥</label>
<div class="layui-input-block">
<input type="text" name="ga_secret" class="layui-input" value="{$secret}" readonly disabled>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">二维码</label>
<div class="layui-input-block">
<img src="{$dataUri}" alt="二维码" style="width: 200px;height: 200px">
<div class="layui-text layui-font-cyan layui-font-12">
使用&nbsp;
<a href="https://2fas.com" target="_blank"><span class="layui-text layui-font-blue">2FAS</span></a>
&nbsp;或者&nbsp;
<a href="https://cn.bing.com/search?q=Google+Authenticator" target="_blank"><span class="layui-text layui-font-blue">Google Authenticator</span></a>
&nbsp;APP 扫描二维码 输入验证码 进行绑定
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">谷歌验证码</label>
<div class="layui-input-block">
<input type="text" name="ga_code" class="layui-input" maxlength="6" lay-verify="required" placeholder="扫描二维码,输入验证码" value="">
</div>
</div>
<div class=" hr-line">
</div>
<div class="layui-form-item text-center">
<button type="submit" class="layui-btn layui-btn-normal layui-btn-sm" lay-submit>确认</button>
<button type="reset" class="layui-btn layui-btn-primary layui-btn-sm">重置</button>
</div>
</form>
</div>

View File

@@ -1,67 +1,59 @@
<link rel="stylesheet" href="__STATIC__/admin/css/welcome.css?v={$version}" media="all">
<div class="layuimini-container">
<div class="layuimini-main">
<div class="layui-row layui-col-space15">
<div class="layui-col-md8">
<div class="layui-row layui-col-space15">
<div class="layui-col-md6">
<link rel="stylesheet" href="/static/admin/css/welcome.css?v={$version}" media="all">
<div class="layui-layout layui-padding-2">
<div class="layui-layout-admin">
<div class="layui-row layui-col-space10">
<div class="layui-col-md8 ">
<div class="layui-row layui-col-space10">
<div class="layui-col-md6 ">
<div class="layui-card">
<div class="layui-card-header"><i class="fa fa-warning icon"></i>数据统计</div>
<div class="layui-card-body">
<div class="welcome-module">
<div class="layui-row layui-col-space10">
<div class="layui-col-xs6">
<div class="panel layui-bg-number">
<div class="panel-body">
<div class="panel-title">
<span class="label pull-right layui-bg-blue">实时</span>
<div class="layui-panel">
<div class="layui-card-body">
<span class="layui-badge layui-bg-cyan pull-right ">实时</span>
<div class="panel-content">
<h5>用户统计</h5>
</div>
<div class="panel-content">
<h1 class="no-margins">1234</h1>
<small>当前分类总记录数</small>
<h2>1234</h2>
<h6>记录数</h6>
</div>
</div>
</div>
</div>
<div class="layui-col-xs6">
<div class="panel layui-bg-number">
<div class="panel-body">
<div class="panel-title">
<span class="label pull-right layui-bg-cyan">实时</span>
<div class="layui-panel">
<div class="layui-card-body">
<span class="layui-badge layui-bg-purple pull-right ">实时</span>
<div class="panel-content">
<h5>商品统计</h5>
</div>
<div class="panel-content">
<h1 class="no-margins">1234</h1>
<small>当前分类总记录数</small>
<h2>1234</h2>
<h6>记录数</h6>
</div>
</div>
</div>
</div>
<div class="layui-col-xs6">
<div class="panel layui-bg-number">
<div class="panel-body">
<div class="panel-title">
<span class="label pull-right layui-bg-orange">实时</span>
<div class="layui-panel">
<div class="layui-card-body ">
<span class="layui-badge layui-bg-orange pull-right ">实时</span>
<div class="panel-content">
<h5>浏览统计</h5>
</div>
<div class="panel-content">
<h1 class="no-margins">1234</h1>
<small>当前分类总记录数</small>
<h2>1234</h2>
<h6>记录数</h6>
</div>
</div>
</div>
</div>
<div class="layui-col-xs6">
<div class="panel layui-bg-number">
<div class="panel-body">
<div class="panel-title">
<span class="label pull-right layui-bg-green">实时</span>
<h5>订单统计</h5>
</div>
<div class="layui-panel">
<div class="layui-card-body ">
<span class="layui-badge layui-bg-red pull-right ">实时</span>
<div class="panel-content">
<h1 class="no-margins">1234</h1>
<small>当前分类总记录数</small>
<h5>订单统计</h5>
<h2>1234</h2>
<h6>记录数</h6>
</div>
</div>
</div>
@@ -71,29 +63,40 @@
</div>
</div>
</div>
<div class="layui-col-md6">
<div class="layui-col-md6 ">
<div class="layui-card">
<div class="layui-card-header"><i class="fa fa-credit-card icon icon-blue"></i>快捷入口</div>
<div class="layui-card-body">
<div class="welcome-module">
<div class="layui-row layui-col-space10 layuimini-qiuck">
<div class="layui-row layui-col-space10">
<div class="swiper mySwiper">
<div class="swiper-wrapper">
{foreach $quicks as $value}
{foreach $quicks as $vo}
<div class="layui-col-xs3 layuimini-qiuck-module">
<a layuimini-content-href="{:url($vo['href'])}" data-title="{$vo['title']}">
<i class="{$vo['icon']|raw}"></i>
<cite>{$vo['title']}</cite>
</a>
<div class="swiper-slide">
{foreach $value as $vo}
<div class="layui-col-xs3 layuimini-qiuck-module">
<a layuimini-content-href="{:url($vo['href'])}" data-title="{$vo['title']}">
<i class="{$vo['icon']|raw}"></i>
<cite>{$vo['title']}</cite>
</a>
</div>
{/foreach}
</div>
{/foreach}
</div>
</div>
{/foreach}
<div class="swiper-pagination"></div>
</div>
</div>
</div>
</div>
</div>
<div class="layui-col-md12">
<div class="layui-col-md12 ">
<div class="layui-card">
<div class="layui-card-header"><i class="fa fa-line-chart icon"></i>报表统计</div>
<div class="layui-card-body">
@@ -104,7 +107,7 @@
</div>
</div>
<div class="layui-col-md4">
<div class="layui-col-md4 ">
<div class="layui-card">
<div class="layui-card-header"><i class="fa fa-fire icon"></i>版本信息</div>
@@ -145,6 +148,21 @@
<button type="button" class="layui-btn layui-btn-xs layui-btn-primary" id="layui-version">-</button>
</td>
</tr>
<tr>
<td>DEBUG模式</td>
<td>
<button type="button" class="layui-btn layui-btn-xs {:env('APP_DEBUG')?'layui-bg-cyan':'layui-bg-gray'}">
{:env('APP_DEBUG')?'开启中':'已关闭'}
</button>
<span class="layui-badge layui-bg-gray">建议线上环境关闭 APP_DEBUG</span>
</td>
</tr>
<tr>
<td>composer信息</td>
<td>
<button type="button" class="layui-btn layui-btn-xs layui-bg-cyan" lay-on="showComposerInfo">点击查看</button>
</td>
</tr>
<tr>
<td>主要特色</td>
<td>
@@ -167,8 +185,8 @@
<tr>
<td>Github</td>
<td>
<a href="https://github.com/wolf-leo/easyAdmin8" target="_blank" style="text-decoration: none;">
<i class="layui-icon layui-icon-github" style="font-size: 25px; color: #333333;"></i>
<a href="https://github.com/wolf-leo/easyAdmin8" target="_blank">
<i class="layui-icon layui-icon-github layui-font-20 layui-font-gray layui-text"></i>
</a>
</td>
</tr>
@@ -179,16 +197,18 @@
<div class="layui-card">
<div class="layui-card-header"><i class="fa fa-paper-plane-o icon"></i>作者心语</div>
<div class="layui-card-body layui-text layadmin-text">
<p>本模板基于layui2.8.x以及font-awesome-4.7.0进行实现。layui开发文档地址<a class="layui-btn layui-btn-xs layui-btn-danger" target="_blank" href="http://layui.dev/docs">layui文档</a></p>
<p class="layui-red">备注:此后台框架永久开源,但请勿进行出售或者上传到任何素材网站,否则将追究相应的责任。</p>
</div>
</div>
<div class="layui-card">
<div class="layui-card-header"><i class="fa fa-qq icon"></i>QQ交流群</div>
<div class="layui-card-body layui-text layadmin-text">
<img src="/static/common/images/EasyAdmin8-ThinkPHP.png">
<div class="layui-card-body layui-text">
<p>
本模板基于layui2.x以及font-awesome-4.7.0进行实现。
<a class="layui-btn layui-btn-xs layui-btn-danger" style="vertical-align: baseline;" target="_blank" href="http://layui.dev/docs">layui文档</a>
</p>
<hr>
<p class="layui-font-red">备注:此后台框架永久开源,但请勿进行出售或者上传到任何素材网站,否则将追究相应的责任。</p>
<hr>
<div class="layui-card-header"><i class="fa fa-qq icon"></i>QQ交流群</div>
<div class="layui-card-body">
<img src="/static/common/images/EasyAdmin8-ThinkPHP.png" width="145">
</div>
</div>
</div>

View File

@@ -2,15 +2,16 @@
<html>
<head>
<meta charset="utf-8">
<title>{:sysconfig('site','site_name')}</title>
<title>{:sysConfig('site','site_name')}</title>
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="icon" href="{:sysConfig('site', 'site_ico')}" type="image/x-icon">
<!--[if lt IE 9]>
<script src="https://cdn.staticfile.org/html5shiv/r29/html5.min.js"></script>
<script src="https://cdn.staticfile.org/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<link rel="stylesheet" href="__STATIC__/admin/css/public.css?v={$version}" media="all">
<link rel="stylesheet" href="/static/admin/css/public.css?v={$version}" media="all">
<link rel="stylesheet" href="" id="layuicss-theme-dark" media="all">
<script>
window.CONFIG = {
@@ -21,14 +22,15 @@
IS_SUPER_ADMIN: "{$isSuperAdmin|default='false'}",
VERSION: "{$version|default='1.0.0'}",
CSRF_TOKEN: "{:token()}",
ADMIN_UPLOAD_URL: "{$adminUploadUrl|DEFAULT=''}",
EDITOR_TYPE: "{$adminEditor|default='ueditor'}",
ADMIN_UPLOAD_URL: "{$adminUploadUrl|default=''}",
IFRAME_OPEN_TOP: "{$iframeOpenTop|default=0}",
EDITOR_TYPE: "{$adminEditor|default='wangEditor'}",
};
</script>
<script src="__STATIC__/plugs/layui-v2.8.x/layui.js?v={$version}" charset="utf-8"></script>
<script src="__STATIC__/plugs/require-2.3.6/require.js?v={$version}" charset="utf-8"></script>
<script src="__STATIC__/config-admin.js?v={$version}" charset="utf-8"></script>
<script src="__STATIC__/common/js/admin.js?v={$version}" charset="utf-8"></script>
<script src="/static/plugs/layui-v2.x/layui.js" charset="utf-8"></script>
<script src="/static/plugs/require-2.3.6/require.js" charset="utf-8"></script>
<script src="/static/config-admin.js?v={$version}" charset="utf-8"></script>
<script src="/static/common/js/admin.js?v={$version}" charset="utf-8"></script>
{include file="layout/editor" /}
</head>
<body>

View File

@@ -1,20 +1,27 @@
{switch $adminEditor}
{case ckeditor}
<script src="__STATIC__/plugs/ckeditor4/ckeditor.js?v={$version}" charset="utf-8"></script>
<script src="/static/plugs/ckeditor4/ckeditor.js?v={$version}" charset="utf-8"></script>
{/case}
{case wangEditor}
<link rel="stylesheet" href="__STATIC__/plugs/wangEditor/dist/style.css?v={$version}">
<script src="__STATIC__/plugs/wangEditor/dist/index.js?v={$version}"></script>
<link rel="stylesheet" href="/static/plugs/wangEditor/dist/style.css?v={$version}">
<script src="/static/plugs/wangEditor/dist/index.js?v={$version}"></script>
{/case}
{case EasyMDE}
<link rel="stylesheet" href="/static/plugs/easymde/easymde.min.css?v={$version}">
<script src="/static/plugs/easymde/easymde.min.js?v={$version}"></script>
{/case}
{default /}
<script src="__STATIC__/plugs/ueditor/ueditor.config.js?v={$version}" charset="utf-8"></script>
<script src="__STATIC__/plugs/ueditor/ueditor.all.js?v={$version}" charset="utf-8"></script>
<script src="__STATIC__/plugs/ueditor/lang/zh-cn/zh-cn.js?v={$version}" charset="utf-8"></script>
<script src="__STATIC__/plugs/ueditor/third-party/codemirror/codemirror.js?v={$version}" charset="utf-8"></script>
<script src="__STATIC__/plugs/ueditor/third-party/zeroclipboard/zeroclipboard.js?v={$version}" charset="utf-8"></script>
<script src="/static/plugs/ueditor/ueditor.config.js?v={$version}" charset="utf-8"></script>
<script src="/static/plugs/ueditor/ueditor.all.js?v={$version}" charset="utf-8"></script>
<script src="/static/plugs/ueditor/lang/zh-cn/zh-cn.js?v={$version}" charset="utf-8"></script>
<script src="/static/plugs/ueditor/third-party/codemirror/codemirror.js?v={$version}" charset="utf-8"></script>
<script src="/static/plugs/ueditor/third-party/zeroclipboard/zeroclipboard.js?v={$version}" charset="utf-8"></script>
{/switch}

View File

@@ -1,14 +1,14 @@
<link rel="stylesheet" href="__STATIC__/admin/css/login.css?v={$version}" media="all">
<link rel="stylesheet" href="/static/admin/css/login.css?v={$version}" media="all">
<div class="container">
<div class="main-body">
<div class="login-main">
<div class="login-top">
<span>{:sysconfig('site','site_name')}</span>
<span>{:sysConfig('site','site_name')}</span>
<span class="bg1"></span>
<span class="bg2"></span>
</div>
<form class="layui-form login-bottom">
<div class="demo{if !$demo} layui-hide{/if}">用户名:admin 密码:123456</div>
<div class="demo {if !$isDemo}layui-hide{/if}">用户名:admin 密码:123456</div>
<div class="center">
<div class="item">
@@ -22,6 +22,11 @@
<span class="bind-password icon icon-4"></span>
</div>
<div class="item layui-hide" id="gaCode">
<span class="icon icon-3"></span>
<input type="text" name="ga_code" placeholder="谷歌验证码" maxlength="6">
</div>
{if $captcha == 1}
<div id="validatePanel" class="item" style="width: 137px;">
<input type="text" name="captcha" placeholder="请输入验证码" maxlength="4">
@@ -36,15 +41,15 @@
<a href="javascript:" class="forget-password">忘记密码?</a>
</div>
<div class="layui-form-item" style="text-align:center; width:100%;height:100%;margin:0px;">
<button class="login-btn" lay-submit>立即登录</button>
<button type="button" class="login-btn" lay-submit>立即登录</button>
</div>
</form>
</div>
</div>
<div class="footer">
{:sysconfig('site','site_copyright')}<span class="padding-5">|</span><a target="_blank" href="http://www.miitbeian.gov.cn">{:sysconfig('site','site_beian')}</a>
{:sysConfig('site','site_copyright')}<span class="padding-5">|</span><a target="_blank" href="http://www.miitbeian.gov.cn">{:sysConfig('site','site_beian')}</a>
</div>
</div>
<script>
let backgroundUrl = "{:sysconfig('site','admin_background')}"
let backgroundUrl = "{:sysConfig('site','admin_background')}"
</script>

View File

@@ -13,8 +13,8 @@
<div class="layui-input-block layuimini-upload">
<input name="image" class="layui-input layui-col-xs6" lay-verify="required" placeholder="请上传分类图片" value="">
<div class="layuimini-upload-btn">
<span><a class="layui-btn" data-upload="image" data-upload-number="one" data-upload-exts="png|jpg|ico|jpeg" data-upload-icon="image"><i class="fa fa-upload"></i> 上传</a></span>
<span><a class="layui-btn layui-btn-normal" id="select_image" data-upload-select="image" data-upload-number="one" data-upload-mimetype="image/*"><i class="fa fa-list"></i> 选择</a></span>
<span><a class="layui-btn" data-upload="image" data-upload-number="one" data-upload-exts="png|jpg|ico|jpeg" data-upload-icon="image" data-upload-mimetype="image/*"><i class="fa fa-upload"></i> 上传</a></span>
<span><a class="layui-btn layui-btn-normal" id="select_image" data-upload-select="image" data-upload-number="one"><i class="fa fa-list"></i> 选择</a></span>
</div>
</div>
</div>
@@ -22,7 +22,7 @@
<div class="layui-form-item">
<label class="layui-form-label">分类排序</label>
<div class="layui-input-block">
<input type="number" name="sort" class="layui-input" placeholder="请输入分类排序" value="0">
<input type="number" name="sort" class="layui-input" lay-affix="number" placeholder="请输入分类排序" value="0">
</div>
</div>

View File

@@ -13,8 +13,8 @@
<div class="layui-input-block layuimini-upload">
<input name="image" class="layui-input layui-col-xs6" lay-verify="required" lay-reqtext="请上传分类图片" placeholder="请上传分类图片" value="{$row.image|default=''}">
<div class="layuimini-upload-btn">
<span><a class="layui-btn" data-upload="image" data-upload-number="one" data-upload-exts="png|jpg|ico|jpeg" data-upload-icon="image"><i class="fa fa-upload"></i> 上传</a></span>
<span><a class="layui-btn layui-btn-normal" id="select_image" data-upload-select="image" data-upload-number="one" data-upload-mimetype="image/*"><i class="fa fa-list"></i> 选择</a></span>
<span><a class="layui-btn" data-upload="image" data-upload-number="one" data-upload-exts="png|jpg|ico|jpeg" data-upload-icon="image" data-upload-mimetype="image/*"><i class="fa fa-upload"></i> 上传</a></span>
<span><a class="layui-btn layui-btn-normal" id="select_image" data-upload-select="image" data-upload-number="one"><i class="fa fa-list"></i> 选择</a></span>
</div>
</div>
</div>
@@ -22,7 +22,7 @@
<div class="layui-form-item">
<label class="layui-form-label">分类排序</label>
<div class="layui-input-block">
<input type="number" name="sort" class="layui-input" placeholder="请输入分类排序" value="{$row.sort|default=''}">
<input type="number" name="sort" class="layui-input" lay-affix="number" placeholder="请输入分类排序" value="{$row.sort|default=''}">
</div>
</div>

View File

@@ -1,84 +1,121 @@
<div class="layuimini-container">
<form id="app-form" class="layui-form layuimini-form">
<div class="layui-form-item">
<label class="layui-form-label">商品分类</label>
<div class="layui-input-block">
<select name="cate_id" lay-verify="required" data-select="{:url('mall.cate/index')}" data-fields="id,title">
</select>
</div>
</div>
<form id="app-form" class="layui-form layuimini-form layui-form-pane">
<div class="layui-form-item">
<label class="layui-form-label">商品标题</label>
<div class="layui-input-block">
<input type="text" name="title" class="layui-input" lay-verify="required" placeholder="请输入商品标题" value="">
</div>
</div>
<div class="layui-row">
<div class="layui-col-xl5 layui-col-lg5 layui-col-md12 layui-col-sm12 layui-col-xs12">
<!-- 可以使用该方式 推荐写法-->
<div class="layui-form-item">
<label class="layui-form-label">商品分类</label>
<div class="layui-input-block">
<select name="cate_id" lay-verify="required" data-select="{:url('mall.cate/index')}" data-fields="id,title">
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">商品LOGO</label>
<div class="layui-input-block layuimini-upload">
<input name="logo" class="layui-input layui-col-xs6" lay-verify="required" placeholder="请上传分类图片" value="">
<div class="layuimini-upload-btn">
<span><a class="layui-btn" data-upload="logo" data-upload-number="one" data-upload-exts="png|jpg|ico|jpeg" data-upload-icon="image"><i class="fa fa-upload"></i> 上传</a></span>
<span><a class="layui-btn layui-btn-normal" id="select_logo" data-upload-select="logo" data-upload-number="one" data-upload-mimetype="image/*"><i class="fa fa-list"></i> 选择</a></span>
<!--也可以使用该方式-->
<!-- <div class="layui-form-item">-->
<!-- <label class="layui-form-label">商品分类</label>-->
<!-- <div class="layui-input-block">-->
<!-- <select name="cate_id" lay-verify="required">-->
<!-- <option value="">请选择</option>-->
<!-- {volist name='cate' id='vo'}-->
<!-- <option value="{$key}">{$vo}</option>-->
<!-- {/volist}-->
<!-- </select>-->
<!-- </div>-->
<!-- </div>-->
<div class="layui-form-item">
<div class="layui-row">
<label class="layui-form-label required">商品标题</label>
<div class="layui-input-block layui-col-space5">
<div class="layui-col-xs10">
<div class="layui-input-wrap">
<input type="text" name="title" class="layui-input" lay-verify="required" placeholder="请输入商品标题" value="">
</div>
</div>
<div class="layui-col-xs2">
<button class="layui-btn layui-bg-purple layui-btn-fluid" type="button" lay-on="AiOptimization">AI优化</button>
</div>
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">商品LOGO</label>
<div class="layui-input-block layuimini-upload">
<input name="logo" class="layui-input layui-col-xs6" lay-verify="required" placeholder="请上传分类图片" value="">
<div class="layuimini-upload-btn">
<span><a class="layui-btn" data-upload="logo" data-upload-number="one" data-upload-exts="png|jpg|ico|jpeg" data-upload-icon="image" data-upload-mimetype="image/*"><i class="fa fa-upload"></i> 上传</a></span>
<span><a class="layui-btn layui-btn-normal" id="select_logo" data-upload-select="logo" data-upload-number="one"><i class="fa fa-list"></i> 选择</a></span>
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">商品图片</label>
<div class="layui-input-block layuimini-upload">
<input name="images" class="layui-input layui-col-xs6" lay-verify="required" placeholder="请上传商品图片" value="">
<div class="layuimini-upload-btn">
<span><a class="layui-btn" data-upload="images" data-upload-number="more" data-upload-exts="png|jpg|ico|jpeg" data-upload-icon="image" data-upload-mimetype="image/*"><i class="fa fa-upload"></i> 上传</a></span>
<span><a class="layui-btn layui-btn-normal" id="select_images" data-upload-select="images" data-upload-number="more"><i class="fa fa-list"></i> 选择</a></span>
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">市场价格</label>
<div class="layui-input-block">
<input type="text" name="market_price" class="layui-input" lay-verify="required" placeholder="请输入市场价格" value="0">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">折扣价格</label>
<div class="layui-input-block">
<input type="text" name="discount_price" class="layui-input" lay-verify="required" placeholder="请输入折扣价格" value="0">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">虚拟销量</label>
<div class="layui-input-block">
<input type="text" name="virtual_sales" class="layui-input" lay-verify="required" placeholder="请输入虚拟销量" value="0">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">分类排序</label>
<div class="layui-input-block">
<input type="number" name="sort" class="layui-input" lay-affix="number" placeholder="请输入分类排序" value="0">
</div>
</div>
<!-- 文档https://xm-select.com/file/xm-select/v1.2.4/#/basic/use -->
<div class="layui-form-item">
<label class="layui-form-label">模拟多选</label>
<div class="layui-input-block">
<div id="demo1" class="xm-select-demo"></div>
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">备注信息</label>
<div class="layui-input-block">
<textarea name="remark" class="layui-textarea" placeholder="请输入备注信息"></textarea>
</div>
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">商品图片</label>
<div class="layui-input-block layuimini-upload">
<input name="images" class="layui-input layui-col-xs6" lay-verify="required" placeholder="请上传商品图片" value="">
<div class="layuimini-upload-btn">
<span><a class="layui-btn" data-upload="images" data-upload-number="more" data-upload-exts="png|jpg|ico|jpeg" data-upload-icon="image"><i class="fa fa-upload"></i> 上传</a></span>
<span><a class="layui-btn layui-btn-normal" id="select_images" data-upload-select="images" data-upload-number="more" data-upload-mimetype="image/*"><i class="fa fa-list"></i> 选择</a></span>
<div class="layui-col-xl7 layui-col-lg7 layui-col-md12 layui-col-sm12 layui-col-xs12">
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">商品描述</label>
<div class="layui-input-block">
{:editor_textarea('','describe')}
</div>
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">市场价格</label>
<div class="layui-input-block">
<input type="text" name="market_price" class="layui-input" lay-verify="required" placeholder="请输入市场价格" value="0">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">折扣价格</label>
<div class="layui-input-block">
<input type="text" name="discount_price" class="layui-input" lay-verify="required" placeholder="请输入折扣价格" value="0">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">虚拟销量</label>
<div class="layui-input-block">
<input type="text" name="virtual_sales" class="layui-input" lay-verify="required" placeholder="请输入虚拟销量" value="0">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">商品描述</label>
<div class="layui-input-block">
{:editor_textarea('','describe')}
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">分类排序</label>
<div class="layui-input-block">
<input type="number" name="sort" class="layui-input" placeholder="请输入分类排序" value="0">
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">备注信息</label>
<div class="layui-input-block">
<textarea name="remark" class="layui-textarea" placeholder="请输入备注信息"></textarea>
</div>
</div>
<div class="hr-line"></div>
<div class="layui-form-item text-center">
<button type="submit" class="layui-btn layui-btn-normal layui-btn-sm" lay-submit>确认</button>

View File

@@ -1,85 +1,122 @@
<div class="layuimini-container">
<form id="app-form" class="layui-form layuimini-form">
<form id="app-form" class="layui-form layuimini-form layui-form-pane">
<div class="layui-form-item">
<label class="layui-form-label">商品分类</label>
<div class="layui-input-block">
<select name="cate_id" lay-verify="required" data-select="{:url('mall.cate/index')}" data-fields="id,title" data-value="{$row.cate_id|default=''}">
</select>
</div>
</div>
<div class="layui-row">
<div class="layui-col-xl5 layui-col-lg5 layui-col-md12 layui-col-sm12 layui-col-xs12">
<!-- 可以使用该方式 推荐写法-->
<div class="layui-form-item">
<label class="layui-form-label">商品分类</label>
<div class="layui-input-block">
<select name="cate_id" lay-verify="required" data-select="{:url('mall.cate/index')}" data-fields="id,title" data-value="{$row.cate_id|default=''}">
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">商品标题</label>
<div class="layui-input-block">
<input type="text" name="title" class="layui-input" lay-verify="required" placeholder="请输入商品标题" value="{$row.title|default=''}">
</div>
</div>
<!--也可以使用该方式-->
<!-- <div class="layui-form-item">-->
<!-- <label class="layui-form-label">商品分类</label>-->
<!-- <div class="layui-input-block">-->
<!-- <select name="cate_id" lay-verify="required">-->
<!-- <option value="">请选择</option>-->
<!-- {volist name='cate' id='vo'}-->
<!-- <option value="{$key}" {if $key==$row.cate_id}selected{/if}>{$vo}</option>-->
<!-- {/volist}-->
<!-- </select>-->
<!-- </div>-->
<!-- </div>-->
<div class="layui-form-item">
<label class="layui-form-label required">商品LOGO</label>
<div class="layui-input-block layuimini-upload">
<input name="logo" class="layui-input layui-col-xs6" lay-verify="required" placeholder="请上传分类图片" value="{$row.logo|default=''}">
<div class="layuimini-upload-btn">
<span><a class="layui-btn" data-upload="logo" data-upload-number="one" data-upload-exts="png|jpg|ico|jpeg" data-upload-icon="image"><i class="fa fa-upload"></i> 上传</a></span>
<span><a class="layui-btn layui-btn-normal" id="select_logo" data-upload-select="logo" data-upload-number="one" data-upload-mimetype="image/*"><i class="fa fa-list"></i> 选择</a></span>
<div class="layui-form-item">
<div class="layui-row">
<label class="layui-form-label required">商品标题</label>
<div class="layui-input-block layui-col-space5">
<div class="layui-col-xs10">
<div class="layui-input-wrap">
<input type="text" name="title" class="layui-input" lay-verify="required" placeholder="请输入商品标题" value="{$row.title|default=''}">
</div>
</div>
<div class="layui-col-xs2">
<button class="layui-btn layui-bg-purple" type="button" lay-on="AiOptimization">AI优化</button>
</div>
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">商品LOGO</label>
<div class="layui-input-block layuimini-upload">
<input name="logo" class="layui-input layui-col-xs6" lay-verify="required" placeholder="请上传分类图片" value="{$row.logo|default=''}">
<div class="layuimini-upload-btn">
<span><a class="layui-btn" data-upload="logo" data-upload-number="one" data-upload-exts="png|jpg|ico|jpeg" data-upload-icon="image" data-upload-mimetype="image/*"><i class="fa fa-upload"></i> 上传</a></span>
<span><a class="layui-btn layui-btn-normal" id="select_logo" data-upload-select="logo" data-upload-number="one"><i class="fa fa-list"></i> 选择</a></span>
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">商品图片</label>
<div class="layui-input-block layuimini-upload">
<input name="images" class="layui-input layui-col-xs6" lay-verify="required" placeholder="请上传商品图片" value="{$row.images|default=''}">
<div class="layuimini-upload-btn">
<span><a class="layui-btn" data-upload="images" data-upload-number="more" data-upload-exts="png|jpg|ico|jpeg" data-upload-icon="image" data-upload-mimetype="image/*"><i class="fa fa-upload"></i> 上传</a></span>
<span><a class="layui-btn layui-btn-normal" id="select_images" data-upload-select="images" data-upload-number="more"><i class="fa fa-list"></i> 选择</a></span>
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">市场价格</label>
<div class="layui-input-block">
<input type="text" name="market_price" class="layui-input" lay-verify="required" placeholder="请输入市场价格" value="{$row.market_price|default=''}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">折扣价格</label>
<div class="layui-input-block">
<input type="text" name="discount_price" class="layui-input" lay-verify="required" placeholder="请输入折扣价格" value="{$row.discount_price|default=''}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">虚拟销量</label>
<div class="layui-input-block">
<input type="text" name="virtual_sales" class="layui-input" lay-verify="required" placeholder="请输入虚拟销量" value="{$row.virtual_sales|default=''}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">分类排序</label>
<div class="layui-input-block">
<input type="number" name="sort" class="layui-input" lay-affix="number" placeholder="请输入分类排序" value="{$row.sort|default=''}">
</div>
</div>
<!-- 文档https://xm-select.com/file/xm-select/v1.2.4/#/basic/use -->
<div class="layui-form-item">
<label class="layui-form-label">模拟多选</label>
<div class="layui-input-block">
<div id="demo1" class="xm-select-demo"></div>
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">备注信息</label>
<div class="layui-input-block">
<textarea name="remark" class="layui-textarea" placeholder="请输入备注信息">{$row.remark|default=''}</textarea>
</div>
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label required">商品图片</label>
<div class="layui-input-block layuimini-upload">
<input name="images" class="layui-input layui-col-xs6" lay-verify="required" placeholder="请上传商品图片" value="{$row.images|default=''}">
<div class="layuimini-upload-btn">
<span><a class="layui-btn" data-upload="images" data-upload-number="more" data-upload-exts="png|jpg|ico|jpeg" data-upload-icon="image"><i class="fa fa-upload"></i> 上传</a></span>
<span><a class="layui-btn layui-btn-normal" id="select_images" data-upload-select="images" data-upload-number="more" data-upload-mimetype="image/*"><i class="fa fa-list"></i> 选择</a></span>
<div class="layui-col-xl7 layui-col-lg7 layui-col-md12 layui-col-sm12 layui-col-xs12">
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">商品描述</label>
<div class="layui-input-block">
{:editor_textarea($row["describe"],'describe')}
</div>
</div>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">市场价格</label>
<div class="layui-input-block">
<input type="text" name="market_price" class="layui-input" lay-verify="required" placeholder="请输入市场价格" value="{$row.market_price|default=''}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">折扣价格</label>
<div class="layui-input-block">
<input type="text" name="discount_price" class="layui-input" lay-verify="required" placeholder="请输入折扣价格" value="{$row.discount_price|default=''}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">虚拟销量</label>
<div class="layui-input-block">
<input type="text" name="virtual_sales" class="layui-input" lay-verify="required" placeholder="请输入虚拟销量" value="{$row.virtual_sales|default=''}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">商品描述</label>
<div class="layui-input-block">
{:editor_textarea($row["describe"],'describe')}
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">分类排序</label>
<div class="layui-input-block">
<input type="number" name="sort" class="layui-input" placeholder="请输入分类排序" value="{$row.sort|default=''}">
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">备注信息</label>
<div class="layui-input-block">
<textarea name="remark" class="layui-textarea" placeholder="请输入备注信息">{$row.remark|default=''}</textarea>
</div>
</div>
<div class="hr-line"></div>
<div class="layui-form-item text-center">
<button type="submit" class="layui-btn layui-btn-normal layui-btn-sm" lay-submit>确认</button>

View File

@@ -5,7 +5,11 @@
data-auth-edit="{:auth('mall.goods/edit')}"
data-auth-delete="{:auth('mall.goods/delete')}"
data-auth-stock="{:auth('mall.goods/stock')}"
data-auth-recycle="{:auth('mall.goods/recycle')}"
lay-filter="currentTable">
</table>
</div>
</div>
</div>
<script>
let cateSelects=JSON.parse('{$cate|json_encode=256|raw}')
</script>

View File

@@ -0,0 +1,11 @@
<div class="layuimini-container">
<div class="layuimini-main">
<table id="currentTable" class="layui-table layui-hide"
data-auth-recycle="{:auth('mall.goods/recycle')}"
lay-filter="currentTable">
</table>
</div>
</div>
<script>
let cateSelects = JSON.parse('{$cate|json_encode=256|raw}')
</script>

View File

@@ -25,7 +25,7 @@
<div class="layui-form-item">
<label class="layui-form-label">入库数量</label>
<div class="layui-input-block">
<input type="number" name="stock" class="layui-input" lay-verify="required" placeholder="请输入入库数量" value="0">
<input type="number" name="stock" class="layui-input" lay-affix="number" lay-verify="required" placeholder="请输入入库数量" value="0">
</div>
</div>

View File

@@ -6,8 +6,8 @@
<div class="layui-input-block layuimini-upload">
<input name="head_img" class="layui-input layui-col-xs6" lay-verify="required" lay-reqtext="请上传用户头像" placeholder="请上传用户头像" value="">
<div class="layuimini-upload-btn">
<span><a class="layui-btn" data-upload="head_img" data-upload-number="one" data-upload-exts="png|jpg|ico|jpeg" data-upload-icon="image"><i class="fa fa-upload"></i> 上传</a></span>
<span><a class="layui-btn layui-btn-normal" id="select_head_img" data-upload-select="head_img" data-upload-number="one" data-upload-mimetype="image/*"><i class="fa fa-list"></i> 选择</a></span>
<span><a class="layui-btn" data-upload="head_img" data-upload-number="one" data-upload-exts="png|jpg|ico|jpeg" data-upload-icon="image" data-upload-mimetype="image/*"><i class="fa fa-upload"></i> 上传</a></span>
<span><a class="layui-btn layui-btn-normal" id="select_head_img" data-upload-select="head_img" data-upload-number="one"><i class="fa fa-list"></i> 选择</a></span>
</div>
</div>
</div>

View File

@@ -6,7 +6,7 @@
<div class="layui-input-block layuimini-upload">
<input name="head_img" class="layui-input layui-col-xs6" lay-verify="required" lay-reqtext="请上传用户头像" placeholder="请上传用户头像" value="{$row.head_img|default=''}">
<div class="layuimini-upload-btn">
<span><a class="layui-btn" data-upload="head_img" data-upload-number="one" data-upload-exts="png|jpg|ico|jpeg"><i class="fa fa-upload"></i> 上传</a></span>
<span><a class="layui-btn" data-upload="head_img" data-upload-number="one" data-upload-exts="png|jpg|ico|jpeg" data-upload-mimetype="image/*"><i class="fa fa-upload"></i> 上传</a></span>
<span><a class="layui-btn layui-btn-normal" id="select_head_img" data-upload-select="head_img" data-upload-number="one"><i class="fa fa-list"></i> 选择</a></span>
</div>
</div>

View File

@@ -1,4 +1,11 @@
<link rel="stylesheet" href="/static/plugs/zTree/fontawesome.css">
<link rel="stylesheet" href="/static/plugs/zTree/zTreeStyle.css?v={$version}">
<script src='/static/plugs/jquery-3.4.1/jquery-3.4.1.min.js'></script>
<script src='/static/plugs/zTree/jquery.ztree.core.js'></script>
<script src='/static/plugs/zTree/jquery.ztree.excheck.js'></script>
<div class="layuimini-container">
<form id="app-form" class="layui-form layuimini-form">
<div class="layui-form-item">
@@ -10,8 +17,8 @@
<div class="layui-form-item">
<label class="layui-form-label required">分配节点</label>
<div class="layui-input-block">
<div id="node_ids" class="demo-tree-more"></div>
<div class="layui-input-block" id="zTree">
<ul id="tree" class="ztree"></ul>
</div>
</div>
@@ -24,4 +31,4 @@
</div>
</form>
</div>
</div>

View File

@@ -1,22 +1,16 @@
<div class="layuimini-container">
<div class="layuimini-main" id="app">
<div class="layui-tab layui-tab-brief" lay-filter="docDemoTabBrief">
<ul class="layui-tab-title">
<div class="layui-tabs layui-tabs-card layui-panel " id="docDemoTabBrief">
<ul class="layui-tabs-header layui-bg-tint">
<li class="layui-this" data-group="site">网站设置</li>
<li data-group="logo">LOGO配置</li>
<li data-group="upload">上传配置</li>
</ul>
<div class="layui-tab-content">
<div class="layui-tab-item layui-show">
{include file="system/config/site" /}
</div>
<div class="layui-tab-item">
{include file="system/config/logo" /}
</div>
<div class="layui-tab-item">
{include file="system/config/upload" /}
</div>
<div class="layui-tabs-body">
<div class="layui-tabs-item layui-show"> {include file="system/config/site" /}</div>
<div class="layui-tabs-item"> {include file="system/config/logo" /}</div>
<div class="layui-tabs-item">{include file="system/config/upload" /}</div>
</div>
</div>

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