142 Commits

Author SHA1 Message Date
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
175 changed files with 66556 additions and 32967 deletions

View File

@@ -1,5 +1,8 @@
APP_DEBUG=true
# 后台系统日志开关
APP_ADMIN_SYSTEM_LOG=true
DEFAULT_TIMEZONE=Asia/Shanghai
DB_TYPE=mysql
@@ -11,6 +14,16 @@ DB_PORT=3306
DB_CHARSET=utf8mb4
DB_PREFIX=ea8_
# 限流器开关 若启动需要配置 Redis 服务
RATE_LIMITING_STATUS=false
# Redis配置
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_PREFIX=
REDIS_DATABASE=0
# 后台配置项组
[EASYADMIN]

2
.gitignore vendored
View File

@@ -9,4 +9,4 @@ Thumbs.db
/vendor
/.settings
/.buildpath
/.project
/.project

View File

@@ -1,27 +1,31 @@
<div align="center" dir="auto">
<img alt="log" src="/public/static/common/images/logo-8.png" />
<img alt="log" src="public/static/common/images/logo-8.png" />
<span><img src="https://img.shields.io/badge/php-%3E=8.0.0-brightgreen.svg?style=for-the-badge&logo=php&colorB=ff69b4" alt="php"></span>
<span><img src="https://img.shields.io/badge/mysql-%3E=5.7-brightgreen.svg?style=for-the-badge&logo=mysql&colorB=blue" alt="MySQL"></span>
<span><img src="https://img.shields.io/badge/thinkphp-%3E=8.0.0-brightgreen.svg?style=for-the-badge&logo=thinkphp" alt="ThinkPHP"></span>
<span><img src="https://img.shields.io/badge/layui-%3E=2.9.0-brightgreen.svg?style=for-the-badge&logo=layui&colorB=orange" alt="layui"></span>
<span><img src="https://img.shields.io/badge/license-MIT-green?style=for-the-badge&logo=license&colorB=purple" alt="License"></span>
<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)
>
@@ -39,18 +43,24 @@
> 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
@@ -69,7 +79,7 @@
## CURD命令大全
> 参考 [CURD命令大全](CURD.md)
> 参考 [CURD命令大全](https://edocs.easyadmin8.top/curd/command.html)
## 常见问题
@@ -91,7 +101,7 @@
## 相关文档
* [ThinkPHP 8.0](https://doc.thinkphp.cn/v8_0)
* [ThinkPHP 8.1](https://doc.thinkphp.cn)
* [EasyAdmin](http://easyadmin.99php.cn/docs)

View File

@@ -4,6 +4,7 @@ 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;
// 你可以在这里继续写你需要的路由
@@ -16,6 +17,8 @@ use app\admin\middleware\SystemLog;
return [
'middleware' => [
// 限流中间件
RateLimiting::class,
// 判断是否已经安装后台系统
CheckInstall::class,
// 检测是否登录

View File

@@ -109,24 +109,21 @@ class Ajax extends AdminController
*/
public function getUploadFiles(Request $request): Json
{
$get = $request->get();
$page = !empty($get['page']) ? $get['page'] : 1;
$limit = !empty($get['limit']) ? $get['limit'] : 10;
$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()->toArray();
$data = [
$data = [
'code' => 0,
'msg' => '',
'count' => $count,
@@ -149,7 +146,7 @@ class Ajax extends AdminController
$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 = [
@@ -213,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

@@ -36,11 +36,9 @@ 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();
}
@@ -55,7 +53,7 @@ class Index extends AdminController
*/
public function editAdmin(Request $request): string
{
$id = $this->adminUid;
$id = $this->adminUid;
$row = (new SystemAdmin())
->withoutField('password')
->find($id);
@@ -66,15 +64,20 @@ class Index extends AdminController
$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();
}
@@ -82,13 +85,10 @@ class Index extends AdminController
* 修改密码
* @param Request $request
* @return string
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function editPassword(Request $request): string
{
$id = $this->adminUid;
$id = $this->adminUid;
$row = (new SystemAdmin())
->withoutField('password')
->find($id);
@@ -124,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,17 +4,19 @@ 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\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 extends AdminController
{
protected bool $ignoreAuth = true;
protected bool $ignoreLogin = true;
public function initialize(): void
{
@@ -34,6 +36,7 @@ class Login extends AdminController
* @throws DbException
* @throws ModelNotFoundException
*/
#[RateLimitingMiddleware(key: [Helper::class, 'getIp'], seconds: 1, limit: 1, message: '请求过于频繁')]
public function index(Request $request): string
{
$captcha = env('EASYADMIN.CAPTCHA', 1);
@@ -56,6 +59,11 @@ class Login extends AdminController
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();

View File

@@ -8,18 +8,14 @@ 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
{
public function __construct(App $app)
{
parent::__construct($app);
$this->model = new MallCate();
self::$model = MallCate::class;
}
}

View File

@@ -4,40 +4,38 @@ namespace app\admin\controller\mall;
use app\admin\model\MallCate;
use app\admin\model\MallGoods;
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\db\exception\DbException;
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
{
#[NodeAnnotation(ignore: ['export'])] // 过滤不需要生成的权限节点 默认 CURD 中会自动生成部分节点 可以在此处过滤
protected array $ignoreNode;
public function __construct(App $app)
{
parent::__construct($app);
$this->model = new MallGoods();
$this->assign('cate', (new MallCate())->column('title', 'id'));
self::$model = new MallGoods();
$this->assign('cate', MallCate::column('title', 'id'));
}
/**
* @NodeAnnotation(title="列表")
* @throws DbException
*/
#[NodeAnnotation(title: '列表', auth: true)]
public function index(Request $request): Json|string
{
if ($request->isAjax()) {
if (input('selectFields')) return $this->selectList();
list($page, $limit, $where) = $this->buildTableParams();
$count = $this->model->where($where)->count();
$list = $this->model->with(['cate'])->where($where)->page($page, $limit)->order($this->sort)->select()->toArray();
$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' => '',
@@ -49,12 +47,10 @@ class Goods extends AdminController
return $this->fetch();
}
/**
* @NodeAnnotation(title="入库")
*/
#[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 ($request->isPost()) {
$post = $request->post();
@@ -73,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

@@ -10,14 +10,9 @@ use app\admin\service\annotation\ControllerAnnotation;
use app\admin\service\annotation\NodeAnnotation;
use app\Request;
use think\App;
use think\db\exception\DbException;
use think\response\Json;
/**
* Class Admin
* @package app\admin\controller\system
* @ControllerAnnotation(title="管理员管理")
*/
#[ControllerAnnotation(title: '管理员管理')]
class Admin extends AdminController
{
@@ -29,14 +24,11 @@ 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="列表")
* @throws DbException
*/
#[NodeAnnotation(title: '列表', auth: true)]
public function index(Request $request): Json|string
{
if ($request->isAjax()) {
@@ -44,11 +36,8 @@ class Admin extends AdminController
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)
@@ -64,9 +53,7 @@ class Admin extends AdminController
return $this->fetch();
}
/**
* @NodeAnnotation(title="添加")
*/
#[NodeAnnotation(title: '添加', auth: true)]
public function add(Request $request): string
{
if ($request->isPost()) {
@@ -78,21 +65,19 @@ class Admin extends AdminController
if (empty($post['password'])) $post['password'] = '123456';
$post['password'] = password($post['password']);
try {
$save = $this->model->save($post);
$save = self::$model::create($post);
}catch (\Exception $e) {
$this->error('保存失败');
$this->error('保存失败' . $e->getMessage());
}
$save ? $this->success('保存成功') : $this->error('保存失败');
}
return $this->fetch();
}
/**
* @NodeAnnotation(title="编辑")
*/
#[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 ($request->isPost()) {
$post = $request->post();
@@ -100,28 +85,22 @@ class Admin extends AdminController
$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('保存失败');
$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="编辑")
*/
#[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 ($request->isAjax()) {
$post = $request->post();
@@ -142,18 +121,16 @@ class Admin extends AdminController
}
$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): void
#[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)) {
@@ -169,9 +146,7 @@ class Admin extends AdminController
$save ? $this->success('删除成功') : $this->error('删除失败');
}
/**
* @NodeAnnotation(title="属性修改")
*/
#[NodeAnnotation(title: '属性修改', auth: true)]
public function modify(Request $request): void
{
$this->checkPostRequest();
@@ -188,7 +163,7 @@ 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([

View File

@@ -11,11 +11,7 @@ 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
{
@@ -27,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="授权")
*/
#[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 ($request->isAjax()) {
$list = $this->model->getAuthorizeNodeListByAdminId($id);
$list = self::$model::getAuthorizeNodeListByAdminId($id);
$this->success('获取成功', $list);
}
$this->assign('row', $row);
return $this->fetch();
}
/**
* @NodeAnnotation(title="授权保存")
*/
#[NodeAnnotation(title: '授权保存', auth: true)]
public function saveAuthorize(Request $request): void
{
$this->checkPostRequest();
$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();

View File

@@ -12,33 +12,25 @@ 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="列表")
*/
#[NodeAnnotation(title: '列表', auth: true)]
public function index(Request $request): Json|string
{
return $this->fetch();
}
/**
* @NodeAnnotation(title="保存")
*/
#[NodeAnnotation(title: '保存', auth: true)]
public function save(Request $request): void
{
$this->checkPostRequest();
@@ -50,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,]);
if (self::$model::where('name', $key)->count()) {
self::$model::where('name', $key)->update(['value' => $val,]);
}else {
$this->model->create(
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('保存失败');
$this->error('保存失败' . $e->getMessage());
}
$this->success('保存成功');
}

View File

@@ -3,7 +3,6 @@
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;
@@ -15,24 +14,16 @@ 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="列表")
*/
#[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();
@@ -146,6 +137,7 @@ class CurdGenerate extends AdminController
$command = $request->post('command', '');
if (empty($command)) $this->error('请输入命令');
$commandExp = explode(' ', $command);
$commandExp = array_values(array_filter($commandExp));
try {
$output = Console::call('curd', [...$commandExp]);

View File

@@ -3,6 +3,7 @@
namespace app\admin\controller\system;
use app\admin\model\SystemLog;
use app\admin\service\annotation\MiddlewareAnnotation;
use app\admin\service\tool\CommonTool;
use app\common\controller\AdminController;
use app\admin\service\annotation\ControllerAnnotation;
@@ -15,24 +16,16 @@ 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
{
protected array $ignoreLog = ['record'];
public function __construct(App $app)
{
parent::__construct($app);
$this->model = new SystemLog();
self::$model = SystemLog::class;
}
/**
* @NodeAnnotation(title="列表")
*/
#[NodeAnnotation(title: '列表', auth: true)]
public function index(Request $request): Json|string
{
if ($request->isAjax()) {
@@ -41,7 +34,7 @@ class Log extends AdminController
}
[$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();
@@ -60,16 +53,15 @@ class Log extends AdminController
return $this->fetch();
}
/**
* @NodeAnnotation(title="导出")
*/
public function export(): bool
#[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}");
@@ -80,25 +72,64 @@ 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="框架日志")
*/
#[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

@@ -11,14 +11,9 @@ use app\admin\service\annotation\NodeAnnotation;
use app\common\controller\AdminController;
use app\Request;
use think\App;
use think\db\exception\DbException;
use think\response\Json;
/**
* Class Menu
* @package app\admin\controller\system
* @ControllerAnnotation(title="菜单管理",auth=true)
*/
#[ControllerAnnotation(title: '菜单管理')]
class Menu extends AdminController
{
@@ -30,21 +25,18 @@ class Menu extends AdminController
public function __construct(App $app)
{
parent::__construct($app);
$this->model = new SystemMenu();
self::$model = SystemMenu::class;
}
/**
* @NodeAnnotation(title="列表")
* @throws DbException
*/
#[NodeAnnotation(title: '列表', auth: true)]
public function index(Request $request): Json|string
{
if ($request->isAjax()) {
if (input('selectFields')) {
return $this->selectList();
}
$count = $this->model->count();
$list = $this->model->order($this->sort)->select()->toArray();
$count = self::$model::count();
$list = self::$model::order($this->sort)->select()->toArray();
$data = [
'code' => 0,
'msg' => '',
@@ -56,13 +48,11 @@ class Menu extends AdminController
return $this->fetch();
}
/**
* @NodeAnnotation(title="添加")
*/
#[NodeAnnotation(title: '添加', auth: true)]
public function add(Request $request): string
{
$id = $request->param('id');
$homeId = $this->model->where(['pid' => MenuConstant::HOME_PID,])->value('id');
$homeId = self::$model::where(['pid' => MenuConstant::HOME_PID,])->value('id');
if ($id == $homeId) {
$this->error('首页不能添加子菜单');
}
@@ -75,7 +65,7 @@ class Menu extends AdminController
];
$this->validate($post, $rule);
try {
$save = $this->model->save($post);
$save = self::$model::create($post);
}catch (\Exception $e) {
$this->error('保存失败');
}
@@ -86,18 +76,16 @@ class Menu extends AdminController
$this->error('保存失败');
}
}
$pidMenuList = $this->model->getPidMenuList();
$pidMenuList = self::$model::getPidMenuList();
$this->assign('id', $id);
$this->assign('pidMenuList', $pidMenuList);
return $this->fetch();
}
/**
* @NodeAnnotation(title="编辑")
*/
#[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 ($request->isPost()) {
$post = $request->post();
@@ -120,7 +108,7 @@ class Menu extends AdminController
$this->error('保存失败');
}
}
$pidMenuList = $this->model->getPidMenuList();
$pidMenuList = self::$model::getPidMenuList();
$this->assign([
'id' => $id,
'pidMenuList' => $pidMenuList,
@@ -129,13 +117,12 @@ class Menu extends AdminController
return $this->fetch();
}
/**
* @NodeAnnotation(title="删除")
*/
public function delete($id): void
#[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();
@@ -150,9 +137,7 @@ class Menu extends AdminController
}
}
/**
* @NodeAnnotation(title="属性修改")
*/
#[NodeAnnotation(title: '属性修改', auth: true)]
public function modify(Request $request): void
{
$this->checkPostRequest();
@@ -163,17 +148,16 @@ class Menu extends AdminController
'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('首页状态不允许关闭');
@@ -189,9 +173,7 @@ class Menu extends AdminController
$this->success('保存成功');
}
/**
* @NodeAnnotation(title="添加菜单提示")
*/
#[NodeAnnotation(title: '添加菜单提示', auth: true)]
public function getMenuTips(): Json
{
$node = input('get.keywords');

View File

@@ -10,37 +10,30 @@ 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
{
public function __construct(App $app)
{
parent::__construct($app);
$this->model = new SystemNode();
self::$model = SystemNode::class;
}
/**
* @NodeAnnotation(title="列表")
* @throws DbException
*/
#[NodeAnnotation(title: '列表', auth: true)]
public function index(Request $request): Json|string
{
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' => '',
@@ -52,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'],
@@ -77,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) {
@@ -86,27 +76,26 @@ class Node extends AdminController
}
}
}
$model->saveAll($nodeList);
TriggerService::updateNode();
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) {

View File

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

0
app/admin/entity/.keep Normal file
View File

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

@@ -35,7 +35,7 @@ class CheckAuth
!$check && $this->error('无权限访问');
// 判断是否为演示环境
if (env('EASYADMIN.IS_DEMO', false) && $request->isPost()) {
if (!in_array($currentNode, ['system.log/record', ''])) $this->error('演示环境下不允许修改');
if (!in_array($currentNode, ['system.log/record', 'mall.goods/aiOptimization'])) $this->error('演示环境下不允许修改');
}
}
return $next($request);

View File

@@ -7,6 +7,7 @@ use app\Request;
use Closure;
use ReflectionClass;
use ReflectionException;
use app\admin\service\annotation\MiddlewareAnnotation;
class CheckLogin
{
@@ -24,13 +25,25 @@ class CheckLogin
$controllerClass = 'app\\admin\\controller\\' . $controller;
$classObj = new ReflectionClass($controllerClass);
$properties = $classObj->getDefaultProperties();
$ignoreAuth = $properties['ignoreAuth'] ?? false;
$adminUserInfo = session('admin');
if (!$ignoreAuth) {
// 整个控制器是否忽略登录
$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'));
}

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,14 +3,12 @@
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 ReflectionClass;
use Closure;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\DocParser;
use ReflectionException;
class SystemLog
@@ -34,7 +32,8 @@ class SystemLog
public function handle(Request $request, Closure $next)
{
$response = $next($request);
$params = $request->param();
if (!env('APP_ADMIN_SYSTEM_LOG', true)) return $response;
$params = $request->param();
if (isset($params['s'])) unset($params['s']);
foreach ($params as $key => $val) {
in_array($key, $this->sensitiveParams) && $params[$key] = "***********";
@@ -48,15 +47,6 @@ class SystemLog
if ($request->isAjax()) {
if (in_array($method, ['post', 'put', 'delete'])) {
$controller = $request->controller();
if (str_contains($controller, '.')) $controller = str_replace('.', '\\', $controller);
$action = $request->action();
$controllerClass = 'app\\admin\\controller\\' . $controller;
$classObj = new ReflectionClass($controllerClass);
$properties = $classObj->getDefaultProperties();
$ignoreLog = $properties['ignoreLog'] ?? [];
if (in_array($action, $ignoreLog)) return $response;
$title = '';
try {
$pathInfo = $request->pathinfo();
@@ -65,16 +55,27 @@ 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) {
}

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

@@ -8,10 +8,12 @@ use think\model\relation\HasOne;
class MallGoods extends TimeModel
{
protected $table = "";
protected $deleteTime = 'delete_time';
protected function getOptions(): array
{
return [
'deleteTime' => 'delete_time',
];
}
// * +++++++++++++++++++++++++++
// | 以下两种写法适用于 with 关联

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');
cache('site_version', $version);
Cache::set('version', $version, 3600);
}
return $version;
}

View File

@@ -44,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();
@@ -85,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;
}

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

@@ -67,15 +67,23 @@ class Node
// 遍历读取所有方法的注释的参数信息
foreach ($methods as $method) {
// 读取NodeAnnotation的注解
$nodeAnnotation = $reader->getMethodAnnotation($method, NodeAnnotation::class);
if (!empty($nodeAnnotation)) {
$actionTitle = !empty($nodeAnnotation->title) ? $nodeAnnotation->title : null;
$actionAuth = !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,
];
}
@@ -83,16 +91,17 @@ class Node
// 方法非空才读取控制器注解
if (!empty($actionList)) {
// 读取Controller的注解
$controllerAnnotation = $reader->getClassAnnotation($reflectionClass, ControllerAnnotation::class);
$controllerTitle = !empty($controllerAnnotation->title) ? $controllerAnnotation->title : null;
$controllerAuth = !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);
}
}

View File

@@ -300,7 +300,7 @@ class BuildCurd
}
}
$this->tableComment = $this->table;
}catch (Exception $e) {
} catch (Exception $e) {
throw new TableException($e->getMessage());
}
@@ -308,11 +308,11 @@ class BuildCurd
$nodeArray = explode('_', $this->table);
if (count($nodeArray) == 1) {
$this->controllerFilename = ucfirst($nodeArray[0]);
}else {
} else {
foreach ($nodeArray as $k => $v) {
if ($k == 0) {
$this->controllerFilename = "{$v}{$this->DS}";
}else {
} else {
$this->controllerFilename .= ucfirst($v);
}
}
@@ -355,6 +355,7 @@ class BuildCurd
if (!empty($bindSelectField) && !in_array($bindSelectField, array_column($columns, 'Field'))) {
throw new TableException("关联表{$relationTable}不存在该字段: {$bindSelectField}");
}
$onlyFields = [];
foreach ($columns as $vo) {
if (empty($primaryKey) && $vo['Key'] == 'PRI') {
$primaryKey = $vo['Field'];
@@ -362,6 +363,7 @@ class BuildCurd
if (!empty($onlyShowFields) && !in_array($vo['Field'], $onlyShowFields)) {
continue;
}
if (!empty($onlyShowFields)) $onlyFields[] = $vo['Field'];
$colum = [
'type' => $vo['Type'],
'comment' => $vo['Comment'],
@@ -388,15 +390,16 @@ class BuildCurd
'bindSelectField' => $bindSelectField,
'delete' => $delete,
'tableColumns' => $formatColumns,
'onlyFields' => $onlyFields,
];
if (!empty($bindSelectField)) {
$relationArray = explode('\\', $modelFilename);
$this->tableColumns[$foreignKey]['bindSelectField'] = $bindSelectField;
$this->tableColumns[$foreignKey]['bindRelation'] = end($relationArray);
$this->tableColumns[$foreignKey]['bindRelation'] = lcfirst(end($relationArray)) . ucfirst($bindSelectField);
}
$this->relationArray[$relationTable] = $relation;
$this->selectFields[] = $foreignKey;
}catch (Exception $e) {
} catch (Exception $e) {
throw new TableException($e->getMessage());
}
return $this;
@@ -652,7 +655,6 @@ class BuildCurd
if (in_array($key, ['describe', 'content', 'details'])) {
$this->editorFields[] = $key;
}
}
return $this;
}
@@ -706,7 +708,7 @@ class BuildCurd
}
}
!empty($formatDefine) && $colum['define'] = $formatDefine;
}else {
} else {
$colum['define'] = $define;
}
}
@@ -730,7 +732,8 @@ class BuildCurd
$this->getTemplate("controller{$this->DS}select"),
[
'name' => $name,
]);
]
);
return $selectCode;
}
@@ -754,7 +757,8 @@ class BuildCurd
[
'name' => $name,
'values' => $values,
]);
]
);
return $selectCode;
}
@@ -775,7 +779,8 @@ class BuildCurd
'name' => "notes['$field']",
'relation' => $relation,
'values' => $field,
]);
]
);
return $selectCode;
}
@@ -794,7 +799,8 @@ class BuildCurd
[
'name' => "notes['$field']",
'select' => $select,
]);
]
);
}
/**
@@ -813,7 +819,8 @@ class BuildCurd
'field' => $field,
'name' => "notes['$field']",
'select' => $select,
]);
]
);
}
/**
@@ -832,7 +839,8 @@ class BuildCurd
'field' => $field,
'name' => "notes['$field']",
'select' => $select,
]);
]
);
}
/**
@@ -945,7 +953,6 @@ class BuildCurd
$this->tableColumns[$field]['formType'] = 'select';
continue;
}
}
// 关联表
@@ -1018,7 +1025,6 @@ class BuildCurd
}
return $this;
}
/**
@@ -1027,20 +1033,25 @@ class BuildCurd
*/
protected function renderController(): static
{
$controllerFile = "{$this->rootDir}app{$this->DS}admin{$this->DS}controller{$this->DS}{$this->controllerFilename}.php";
$controllerFile = "{$this->rootDir}app{$this->DS}admin{$this->DS}controller{$this->DS}{$this->controllerFilename}.php";
$constructRelation = '';
if (empty($this->relationArray)) {
$controllerIndexMethod = '';
}else {
} else {
$relationCode = '';
foreach ($this->relationArray as $key => $val) {
$relation = CommonTool::lineToHump($key);
$relationCode = "->withJoin('{$relation}', 'LEFT')\r";
$relationCode = "withJoin('{$relation}', 'LEFT')";
if (!empty($val['bindSelectField']) && !empty($val['primaryKey'])) {
$constructRelation = '$notes["' . lcfirst($val['foreignKey']) . '"] = \app\admin\model\\' . $val['modelFilename'] . '::column("' . $val['bindSelectField'] . '", "' . $val['primaryKey'] . '");';
}
}
$controllerIndexMethod = CommonTool::replaceTemplate(
$this->getTemplate("controller{$this->DS}indexMethod"),
[
'relationIndexMethod' => $relationCode,
]);
]
);
}
$selectList = '';
// foreach ($this->relationArray as $relation) {
@@ -1066,7 +1077,9 @@ class BuildCurd
'modelFilename' => "\app\admin\model\\{$modelFilenameExtend}",
'indexMethod' => $controllerIndexMethod,
'selectList' => $selectList,
]);
'constructRelation' => $constructRelation,
]
);
$this->fileList[$controllerFile] = $controllerValue;
return $this;
}
@@ -1087,9 +1100,10 @@ class BuildCurd
$this->getTemplate("model{$this->DS}relation"),
[
'relationMethod' => $relation,
'relationModel' => "\app\admin\model\\{$val['modelFilename']}",
'relationModel' => "{$val['modelFilename']}::class",
'foreignKey' => $val['foreignKey'],
'primaryKey' => $val['primaryKey'],
'relationFields' => empty($val['onlyFields']) ? "" : "->field('{$val['primaryKey']}," . implode(',', $val['onlyFields']) . "')",
]);
$relationList .= $relationCode;
}
@@ -1124,7 +1138,9 @@ class BuildCurd
'relationList' => $relationList,
// 'selectList' => $selectList,
'selectArrays' => CommonTool::replaceArrayString(var_export($selectArrays, true)),
]);
]
);
$this->fileList[$modelFile] = $modelValue;
@@ -1153,11 +1169,14 @@ class BuildCurd
[
'modelName' => $val['modelName'],
'modelNamespace' => "app\admin\model{$extendNamespace}",
'prefix_table' => $this->tablePrefix == config('database.connections.mysql.prefix') ? "" : $this->tablePrefix . $this->table,
'table' => $key,
'deleteTime' => $val['delete'] ? '"delete_time"' : 'false',
'relationList' => '',
'selectList' => '',
]);
'selectArrays' => "[]",
]
);
$this->fileList[$relationModelFile] = $relationModelValue;
}
return $this;
@@ -1176,7 +1195,8 @@ class BuildCurd
[
'controllerUrl' => $this->controllerUrl,
'notesScript' => $this->formatNotesScript(),
]);
]
);
$this->fileList[$viewIndexFile] = $viewIndexValue;
// 添加页面
@@ -1194,42 +1214,44 @@ class BuildCurd
// 根据formType去获取具体模板
if ($val['formType'] == 'image') {
$templateFile = "view{$this->DS}module{$this->DS}image";
}elseif ($val['formType'] == 'images') {
} elseif ($val['formType'] == 'images') {
$templateFile = "view{$this->DS}module{$this->DS}images";
$define = $val['define'] ?? '|';
}elseif ($val['formType'] == 'file') {
} elseif ($val['formType'] == 'file') {
$templateFile = "view{$this->DS}module{$this->DS}file";
}elseif ($val['formType'] == 'files') {
} elseif ($val['formType'] == 'files') {
$templateFile = "view{$this->DS}module{$this->DS}files";
$define = $val['define'] ?? '|';
}elseif ($val['formType'] == 'editor') {
} elseif ($val['formType'] == 'editor') {
$templateFile = "view{$this->DS}module{$this->DS}editor";
$val['default'] = '""';
}elseif ($val['formType'] == 'date') {
} elseif ($val['formType'] == 'date') {
$templateFile = "view{$this->DS}module{$this->DS}date";
$define = 'date';
}elseif ($val['formType'] == 'datetime') {
} elseif ($val['formType'] == 'datetime') {
$templateFile = "view{$this->DS}module{$this->DS}date";
$define = 'datetime';
}elseif ($val['formType'] == 'radio') {
} elseif ($val['formType'] == 'radio') {
$templateFile = "view{$this->DS}module{$this->DS}radio";
if (!empty($val['define'])) {
$define = $this->buildRadioView($field, '{in name="k" value="' . $val['default'] . '"}checked=""{/in}');
}
}elseif ($val['formType'] == 'checkbox') {
} elseif ($val['formType'] == 'checkbox') {
$templateFile = "view{$this->DS}module{$this->DS}checkbox";
if (!empty($val['define'])) {
$define = $this->buildCheckboxView($field, '{in name="k" value="' . $val['default'] . '"}checked=""{/in}');
}
}elseif ($val['formType'] == 'select') {
} elseif ($val['formType'] == 'select') {
$templateFile = "view{$this->DS}module{$this->DS}select";
if (isset($val['bindRelation'])) {
$define = $this->buildOptionView($val['bindRelation']);
}elseif (!empty($val['define'])) {
$define = $this->buildOptionView($field);
} elseif (!empty($val['define'])) {
$define = $this->buildOptionView($field);
}
}elseif ($field == 'remark' || $val['formType'] == 'textarea') {
} elseif ($field == 'remark' || $val['formType'] == 'textarea') {
$templateFile = "view{$this->DS}module{$this->DS}textarea";
} elseif ($field == 'sort') {
$templateFile = "view{$this->DS}module{$this->DS}sort";
}
$addFormList .= CommonTool::replaceTemplate(
$this->getTemplate($templateFile),
@@ -1239,13 +1261,15 @@ class BuildCurd
'required' => $this->buildRequiredHtml($val['required']),
'value' => $val['default'],
'define' => $define,
]);
]
);
}
$viewAddValue = CommonTool::replaceTemplate(
$this->getTemplate("view{$this->DS}form"),
[
'formList' => $addFormList,
]);
]
);
$this->fileList[$viewAddFile] = $viewAddValue;
@@ -1266,43 +1290,44 @@ class BuildCurd
// 根据formType去获取具体模板
if ($val['formType'] == 'image') {
$templateFile = "view{$this->DS}module{$this->DS}image";
}elseif ($val['formType'] == 'images') {
} elseif ($val['formType'] == 'images') {
$templateFile = "view{$this->DS}module{$this->DS}images";
}elseif ($val['formType'] == 'file') {
} elseif ($val['formType'] == 'file') {
$templateFile = "view{$this->DS}module{$this->DS}file";
}elseif ($val['formType'] == 'files') {
} elseif ($val['formType'] == 'files') {
$templateFile = "view{$this->DS}module{$this->DS}files";
}elseif ($val['formType'] == 'editor') {
} elseif ($val['formType'] == 'editor') {
$templateFile = "view{$this->DS}module{$this->DS}editor";
$value = '$row["' . $field . '"]';
}elseif ($val['formType'] == 'date') {
} elseif ($val['formType'] == 'date') {
$templateFile = "view{$this->DS}module{$this->DS}date";
$define = 'date';
}elseif ($val['formType'] == 'datetime') {
} elseif ($val['formType'] == 'datetime') {
$templateFile = "view{$this->DS}module{$this->DS}date";
$define = 'datetime';
}elseif ($val['formType'] == 'radio') {
} elseif ($val['formType'] == 'radio') {
$templateFile = "view{$this->DS}module{$this->DS}radio";
if (!empty($val['define'])) {
$define = $this->buildRadioView($field, '{in name="k" value="$row.' . $field . '"}checked=""{/in}');
}
}elseif ($val['formType'] == 'checkbox') {
} elseif ($val['formType'] == 'checkbox') {
$templateFile = "view{$this->DS}module{$this->DS}checkbox";
if (!empty($val['define'])) {
$define = $this->buildCheckboxView($field, '{in name="k" value="$row.' . $field . '"}checked=""{/in}');
}
}elseif ($val['formType'] == 'select') {
} elseif ($val['formType'] == 'select') {
$templateFile = "view{$this->DS}module{$this->DS}select";
if (isset($val['bindRelation'])) {
$define = $this->buildOptionView($val['bindRelation'], '{in name="k" value="$row.' . $field . '"}selected=""{/in}');
}elseif (!empty($val['define'])) {
$define = $this->buildOptionView($field, '{in name="k" value="$row.' . $field . '"}selected=""{/in}');
} elseif (!empty($val['define'])) {
$define = $this->buildOptionView($field, '{in name="k" value="$row.' . $field . '"}selected=""{/in}');
}
}elseif ($field == 'remark' || $val['formType'] == 'textarea') {
} elseif ($field == 'remark' || $val['formType'] == 'textarea') {
$templateFile = "view{$this->DS}module{$this->DS}textarea";
$value = '{$row.' . $field . '|raw|default=\'\'}';
} elseif ($field == 'sort') {
$templateFile = "view{$this->DS}module{$this->DS}sort";
}
$editFormList .= CommonTool::replaceTemplate(
$this->getTemplate($templateFile),
[
@@ -1311,15 +1336,26 @@ class BuildCurd
'required' => $this->buildRequiredHtml($val['required']),
'value' => $value,
'define' => $define,
]);
]
);
}
$viewEditValue = CommonTool::replaceTemplate(
$this->getTemplate("view{$this->DS}form"),
[
'formList' => $editFormList,
]);
]
);
$this->fileList[$viewEditFile] = $viewEditValue;
$viewRecycleFile = "{$this->rootDir}app{$this->DS}admin{$this->DS}view{$this->DS}{$this->viewFilename}{$this->DS}recycle.html";
$viewRecycleValue = CommonTool::replaceTemplate(
$this->getTemplate("view{$this->DS}recycle"),
[
'controllerUrl' => $this->controllerUrl,
'notesScript' => $this->formatNotesScript(),
]
);
$this->fileList[$viewRecycleFile] = $viewRecycleValue;
return $this;
}
@@ -1338,75 +1374,79 @@ class BuildCurd
if ($val['formType'] == 'image') {
$templateValue = "{field: '{$field}', title: '{$val['comment']}', templet: ea.table.image}";
}elseif ($val['formType'] == 'images') {
} elseif ($val['formType'] == 'datetime') {
$templateValue = "{field: '{$field}', search: 'range', title: '{$val['comment']}'}";
} elseif ($val['formType'] == 'images') {
continue;
}elseif ($val['formType'] == 'file') {
} elseif ($val['formType'] == 'file') {
$templateValue = "{field: '{$field}', title: '{$val['comment']}', templet: ea.table.url}";
}elseif ($val['formType'] == 'files') {
} elseif ($val['formType'] == 'files') {
continue;
}elseif ($val['formType'] == 'editor') {
} elseif ($val['formType'] == 'editor') {
continue;
}elseif (in_array($field, $this->switchFields)) {
} elseif (in_array($field, $this->switchFields)) {
if (!empty($val['define'])) {
$templateValue = "{field: '{$field}', search: 'select', selectList: notes?.{$field} || {}, title: '{$val['comment']}', templet: ea.table.switch}";
}else {
} else {
$templateValue = "{field: '{$field}', title: '{$val['comment']}', templet: ea.table.switch}";
}
}elseif (in_array($val['formType'], ['select', 'checkbox', 'radio', 'switch'])) {
} elseif (in_array($val['formType'], ['select', 'checkbox', 'radio', 'switch'])) {
if (!empty($val['define'])) {
$templateValue = "{field: '{$field}', search: 'select', selectList: notes?.{$field} || {}, title: '{$val['comment']}'}";
}else {
} else {
$templateValue = "{field: '{$field}', title: '{$val['comment']}'}";
}
}elseif ($field == 'remark') {
} elseif ($field == 'remark') {
$templateValue = "{field: '{$field}', title: '{$val['comment']}', templet: ea.table.text}";
}elseif (in_array($field, $this->sortFields)) {
} elseif (in_array($field, $this->sortFields)) {
$templateValue = "{field: '{$field}', title: '{$val['comment']}', edit: 'text'}";
}else {
} else {
$templateValue = "{field: '{$field}', title: '{$val['comment']}'}";
}
$indexCols .= $this->formatColsRow("{$templateValue},\r");
}
// 关联表
foreach ($this->relationArray as $table => $tableVal) {
$table = CommonTool::lineToHump($table);
$table = CommonTool::humpToLine($table);
foreach ($tableVal['tableColumns'] as $field => $val) {
if ($val['formType'] == 'image') {
$templateValue = "{field: '{$table}.{$field}', title: '{$val['comment']}', templet: ea.table.image}";
}elseif ($val['formType'] == 'images') {
} elseif ($val['formType'] == 'images') {
continue;
}elseif ($val['formType'] == 'file') {
} elseif ($val['formType'] == 'file') {
$templateValue = "{field: '{$table}.{$field}', title: '{$val['comment']}', templet: ea.table.url}";
}elseif ($val['formType'] == 'files') {
} elseif ($val['formType'] == 'files') {
continue;
}elseif ($val['formType'] == 'editor') {
} elseif ($val['formType'] == 'editor') {
continue;
}elseif ($val['formType'] == 'select') {
} elseif ($val['formType'] == 'select') {
$templateValue = "{field: '{$table}.{$field}', title: '{$val['comment']}'}";
}elseif ($field == 'remark') {
} elseif ($field == 'remark') {
$templateValue = "{field: '{$table}.{$field}', title: '{$val['comment']}', templet: ea.table.text}";
}elseif (in_array($field, $this->switchFields)) {
} elseif (in_array($field, $this->switchFields)) {
$templateValue = "{field: '{$table}.{$field}', title: '{$val['comment']}', templet: ea.table.switch}";
}elseif (in_array($field, $this->sortFields)) {
} elseif (in_array($field, $this->sortFields)) {
$templateValue = "{field: '{$table}.{$field}', title: '{$val['comment']}', edit: 'text'}";
}else {
} else {
$templateValue = "{field: '{$table}.{$field}', title: '{$val['comment']}'}";
}
$indexCols .= $this->formatColsRow("{$templateValue},\r");
if ($templateValue) $indexCols .= $this->formatColsRow("{$templateValue},\r");
}
}
$indexCols .= $this->formatColsRow("{width: 250, title: '操作', templet: ea.table.tool},\r");
$recycleCols = $indexCols;
$indexCols .= $this->formatColsRow("{width: 250, title: '操作', templet: ea.table.tool},\r");
$jsValue = CommonTool::replaceTemplate(
$this->getTemplate("static{$this->DS}js"),
[
'controllerUrl' => $this->controllerUrl,
'indexCols' => $indexCols,
]);
'recycleCols' => $recycleCols,
]
);
$this->fileList[$jsFile] = $jsValue;
return $this;
}
@@ -1531,4 +1571,4 @@ class BuildCurd
{
return ' let notes = JSON.parse(\'{$notes|json_encode=256|raw}\');';
}
}
}

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,9 +7,7 @@ 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
{
@@ -18,8 +16,10 @@ class {{controllerName}} extends AdminController
public function __construct(App $app)
{
parent::__construct($app);
$this->model = new {{modelFilename}}();
$this->notes = $notes = $this->model->notes;
self::$model = new {{modelFilename}}();
$notes = self::$model::$notes;
{{constructRelation}}
$this->notes =$notes;
$this->assign(compact('notes'));
}

View File

@@ -1,17 +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()->toArray();
$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,12 +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}}";
public static array $notes = {{selectArrays}};
protected $deleteTime = {{deleteTime}};
public array $notes = {{selectArrays}};
{{relationList}}
}

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,6 +9,7 @@ define(["jquery", "easy-admin"], function ($, ea) {
delete_url: '{{controllerUrl}}/delete',
export_url: '{{controllerUrl}}/export',
modify_url: '{{controllerUrl}}/modify',
recycle_url: '{{controllerUrl}}/recycle',
};
return {
@@ -29,5 +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();
},
};
});

View File

@@ -4,7 +4,9 @@
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>

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

@@ -100,6 +100,9 @@ class CommonTool
{
$arrayString = str_replace('array (', '[', $arrayString);
$arrayString = str_replace(')', ']', $arrayString);
$arrayString = str_replace('=>
[', '=> [', $arrayString);
return $arrayString;
}
}

View File

@@ -5,7 +5,7 @@ namespace app\admin\traits;
use app\admin\service\annotation\NodeAnnotation;
use app\admin\service\tool\CommonTool;
use app\Request;
use jianyan\excel\Excel;
use think\db\exception\PDOException;
use think\facade\Db;
use think\response\Json;
@@ -17,9 +17,7 @@ use think\response\Json;
trait Curd
{
/**
* @NodeAnnotation(title="列表")
*/
#[NodeAnnotation(title: '列表', auth: true)]
public function index(Request $request): Json|string
{
if ($request->isAjax()) {
@@ -27,8 +25,8 @@ trait Curd
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()->toArray();
$count = self::$model::where($where)->count();
$list = self::$model::where($where)->page($page, $limit)->order($this->sort)->select()->toArray();
$data = [
'code' => 0,
'msg' => '',
@@ -40,9 +38,7 @@ trait Curd
return $this->fetch();
}
/**
* @NodeAnnotation(title="添加")
*/
#[NodeAnnotation(title: '添加', auth: true)]
public function add(Request $request): string
{
if ($request->isPost()) {
@@ -50,8 +46,8 @@ trait Curd
$rule = [];
$this->validate($post, $rule);
try {
Db::transaction(function () use ($post, &$save) {
$save = $this->model->save($post);
Db::transaction(function() use ($post, &$save) {
$save = self::$model::create($post);
});
}catch (\Exception $e) {
$this->error('新增失败:' . $e->getMessage());
@@ -61,19 +57,17 @@ trait Curd
return $this->fetch();
}
/**
* @NodeAnnotation(title="编辑")
*/
#[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 ($request->isPost()) {
$post = $request->post();
$rule = [];
$this->validate($post, $rule);
try {
Db::transaction(function () use ($post, $row, &$save) {
Db::transaction(function() use ($post, $row, &$save) {
$save = $row->save($post);
});
}catch (\Exception $e) {
@@ -85,13 +79,13 @@ trait Curd
return $this->fetch();
}
/**
* @NodeAnnotation(title="删除")
*/
public function delete($id): void
#[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();
@@ -101,16 +95,14 @@ trait Curd
$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,19 +113,19 @@ 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="属性修改")
*/
#[NodeAnnotation(title: '属性修改', auth: true)]
public function modify(Request $request): void
{
$this->checkPostRequest();
@@ -144,7 +136,7 @@ trait Curd
'value|值' => 'require',
];
$this->validate($post, $rule);
$row = $this->model->find($post['id']);
$row = self::$model::find($post['id']);
if (!$row) {
$this->error('数据不存在');
}
@@ -152,7 +144,7 @@ trait Curd
$this->error('该字段不允许修改:' . $post['field']);
}
try {
Db::transaction(function () use ($post, $row) {
Db::transaction(function() use ($post, $row) {
$row->save([
$post['field'] => $post['value'],
]);
@@ -163,4 +155,49 @@ trait Curd
$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

@@ -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" 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

@@ -16,8 +16,8 @@
<span class="layui-badge layui-bg-cyan pull-right ">实时</span>
<div class="panel-content">
<h5>用户统计</h5>
<h1>1234</h1>
<h6>当前分类总记录数</h6>
<h2>1234</h2>
<h6>记录数</h6>
</div>
</div>
</div>
@@ -28,8 +28,8 @@
<span class="layui-badge layui-bg-purple pull-right ">实时</span>
<div class="panel-content">
<h5>商品统计</h5>
<h1>1234</h1>
<h6>当前分类总记录数</h6>
<h2>1234</h2>
<h6>记录数</h6>
</div>
</div>
</div>
@@ -40,8 +40,8 @@
<span class="layui-badge layui-bg-orange pull-right ">实时</span>
<div class="panel-content">
<h5>浏览统计</h5>
<h1>1234</h1>
<h6>当前分类总记录数</h6>
<h2>1234</h2>
<h6>记录数</h6>
</div>
</div>
</div>
@@ -52,8 +52,8 @@
<span class="layui-badge layui-bg-red pull-right ">实时</span>
<div class="panel-content">
<h5>订单统计</h5>
<h1>1234</h1>
<h6>当前分类总记录数</h6>
<h2>1234</h2>
<h6>记录数</h6>
</div>
</div>
</div>
@@ -69,16 +69,27 @@
<div class="layui-card-body">
<div class="welcome-module">
<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>
@@ -140,12 +151,18 @@
<tr>
<td>DEBUG模式</td>
<td>
<button type="button" class="layui-btn layui-btn-xs {:env('APP_DEBUG')?'layui-btn-warm':'layui-bg-gray'}">
<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>
@@ -169,7 +186,7 @@
<td>Github</td>
<td>
<a href="https://github.com/wolf-leo/easyAdmin8" target="_blank">
<i class="layui-icon layui-icon-github layui-font-20 layui-font-cyan layui-text"></i>
<i class="layui-icon layui-icon-github layui-font-20 layui-font-gray layui-text"></i>
</a>
</td>
</tr>
@@ -181,8 +198,8 @@
<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">
<p class="layui-font-cyan">
本模板基于layui2.9.x以及font-awesome-4.7.0进行实现。
<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>

View File

@@ -6,6 +6,7 @@
<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>
@@ -21,8 +22,9 @@
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.x/layui.js" charset="utf-8"></script>

View File

@@ -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,7 +41,7 @@
<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>

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

@@ -2,7 +2,7 @@
<form id="app-form" class="layui-form layuimini-form layui-form-pane">
<div class="layui-row">
<div class="layui-col-xl5 layui-col-lg5 layui-col-md5 layui-col-sm5 layui-col-xs5">
<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>
@@ -26,9 +26,18 @@
<!-- </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="">
<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>
@@ -37,8 +46,8 @@
<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>
<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>
@@ -48,8 +57,8 @@
<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>
<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>
@@ -78,7 +87,15 @@
<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>
<!-- 文档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>
@@ -89,7 +106,7 @@
</div>
</div>
</div>
<div class="layui-col-xl7 layui-col-lg7 layui-col-md7 layui-col-sm7 layui-col-xs7">
<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">

View File

@@ -2,7 +2,7 @@
<form id="app-form" class="layui-form layuimini-form layui-form-pane">
<div class="layui-row">
<div class="layui-col-xl5 layui-col-lg5 layui-col-md5 layui-col-sm5 layui-col-xs5">
<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>
@@ -26,19 +26,29 @@
<!-- </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 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"><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>
<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>
@@ -48,8 +58,8 @@
<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>
<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>
@@ -78,7 +88,15 @@
<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>
<!-- 文档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>
@@ -89,7 +107,7 @@
</div>
</div>
</div>
<div class="layui-col-xl7 layui-col-lg7 layui-col-md7 layui-col-sm7 layui-col-xs7">
<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">

View File

@@ -5,6 +5,7 @@
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>

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>

View File

@@ -13,7 +13,7 @@
<div class="layui-input-block layuimini-upload">
<input name="logo_image" class="layui-input layui-col-xs6" lay-verify="required" placeholder="请上传LOGO图标" value="{:sysConfig('site','logo_image')}">
<div class="layuimini-upload-btn">
<span><a class="layui-btn" data-upload="logo_image" data-upload-number="one" data-upload-exts="ico|png|jpg|jpeg"><i class="fa fa-upload"></i> 上传</a></span>
<span><a class="layui-btn" data-upload="logo_image" data-upload-number="one" data-upload-exts="ico|png|jpg|jpeg" data-upload-mimetype="image/*"><i class="fa fa-upload"></i> 上传</a></span>
<span><a class="layui-btn layui-btn-normal" id="select_logo_image" data-upload-select="logo_image" data-upload-number="one"><i class="fa fa-list"></i> 选择</a></span>
</div>
</div>

View File

@@ -13,7 +13,7 @@
<div class="layui-input-block layuimini-upload">
<input name="site_ico" class="layui-input layui-col-xs6" lay-verify="required" placeholder="请上传浏览器图标,ico类型" value="{:sysConfig('site','site_ico')}">
<div class="layuimini-upload-btn">
<span><a class="layui-btn" data-upload="site_ico" data-upload-number="one" data-upload-exts="ico"><i class="fa fa-upload"></i> 上传</a></span>
<span><a class="layui-btn" data-upload="site_ico" data-upload-number="one" data-upload-exts="ico" data-upload-mimetype="image/*"><i class="fa fa-upload"></i> 上传</a></span>
<span><a class="layui-btn layui-btn-normal" id="select_site_ico" data-upload-select="site_ico" data-upload-number="one"><i class="fa fa-list"></i> 选择</a></span>
</div>
</div>
@@ -24,7 +24,7 @@
<div class="layui-input-block layuimini-upload">
<input name="admin_background" class="layui-input layui-col-xs6" placeholder="不填默认#333333" value="{:sysConfig('site','admin_background')}">
<div class="layuimini-upload-btn">
<span><a class="layui-btn" data-upload="admin_background" data-upload-number="one" data-upload-exts="png|jpg|jpeg"><i class="fa fa-upload"></i> 上传</a></span>
<span><a class="layui-btn" data-upload="admin_background" data-upload-number="one" data-upload-exts="png|jpg|jpeg" data-upload-mimetype="image/*"><i class="fa fa-upload"></i> 上传</a></span>
<span><a class="layui-btn layui-btn-normal" id="select_admin_background" data-upload-select="admin_background" data-upload-number="one"><i class="fa fa-list"></i> 选择</a></span>
</div>
</div>
@@ -54,6 +54,15 @@
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">新标签页窗口</label>
<div class="layui-input-block">
<input type="radio" name="iframe_open_top" value="0" title="不允许" {if 1!=sysConfig('site','iframe_open_top')}checked{/if}>
<input type="radio" name="iframe_open_top" value="1" title="允许" {if 1==sysConfig('site','iframe_open_top')}checked{/if}>
<br>
<tip>是否允许弹框在新标签页打开。</tip>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">默认编辑器</label>

View File

@@ -0,0 +1,23 @@
<div class="layuimini-container">
<form id="app-form" class="layui-form layuimini-form">
<div class="layui-form-item">
<div class="layui-input-group">
<div class="layui-input-prefix layui-input-split">删除</div>
<label>
<input type="number" name="month" lay-affix="number" placeholder="" min="1" class="layui-input" value="3">
</label>
<div class="layui-input-suffix layui-input-split">个月前的日志</div>
</div>
</div>
<div class="hr-line"></div>
<div class="layui-form-item text-center">
<button type="button" class="layui-btn" lay-submit lay-filter="submit">提交</button>
</div>
</form>
</div>

View File

@@ -2,6 +2,7 @@
<div class="layuimini-main">
<table id="currentTable" class="layui-table layui-hide"
data-auth-record="{:auth('system.log/record')}"
data-auth-deleteMonthLog="{:auth('system.log/deleteMonthLog')}"
lay-filter="currentTable">
</table>
</div>

View File

@@ -53,7 +53,7 @@
<div class="layui-form-item">
<label class="layui-form-label">菜单排序</label>
<div class="layui-input-block">
<input type="number" name="sort" lay-reqtext="菜单排序不能为空" placeholder="请输入菜单排序" value="0" class="layui-input">
<input type="number" name="sort" lay-reqtext="菜单排序不能为空" placeholder="请输入菜单排序" value="0" class="layui-input" lay-affix="number">
</div>
</div>

View File

@@ -53,7 +53,7 @@
<div class="layui-form-item">
<label class="layui-form-label">菜单排序</label>
<div class="layui-input-block">
<input type="number" name="sort" lay-reqtext="菜单排序不能为空" placeholder="请输入菜单排序" value="{$row.sort|default=''}" class="layui-input">
<input type="number" name="sort" lay-reqtext="菜单排序不能为空" placeholder="请输入菜单排序" value="{$row.sort|default=''}" class="layui-input" lay-affix="number">
</div>
</div>

View File

@@ -20,4 +20,5 @@
<button class="layui-btn layui-btn-sm layuimini-btn-primary" data-treetable-refresh><i class="fa fa-refresh"></i></button>
<button class="layui-btn layui-btn-normal layui-btn-sm {if !auth('system.menu/add')}layui-hide{/if}" data-open="system.menu/add" data-title="添加"><i class="fa fa-plus"></i> 添加</button>
<button class="layui-btn layui-btn-sm layui-btn-danger {if !auth('system.menu/delete')}layui-hide{/if}" data-url="system.menu/delete" data-treetable-delete="currentTableRenderId"><i class="fa fa-trash-o"></i> 删除</button>
<button class="layui-btn layui-btn-sm" type="button" data-treetable-arrow data-arrow="up"><i class="fa fa-arrow-up"></i> 一键折叠</button>
</script>

View File

@@ -31,7 +31,7 @@
<div class="layui-form-item">
<label class="layui-form-label">菜单排序</label>
<div class="layui-input-block">
<input type="number" name="sort" lay-reqtext="菜单排序不能为空" placeholder="请输入菜单排序" value="0" class="layui-input">
<input type="number" name="sort" lay-reqtext="菜单排序不能为空" placeholder="请输入菜单排序" value="0" class="layui-input" lay-affix="number">
</div>
</div>

View File

@@ -31,7 +31,7 @@
<div class="layui-form-item">
<label class="layui-form-label">菜单排序</label>
<div class="layui-input-block">
<input type="number" name="sort" lay-reqtext="菜单排序不能为空" placeholder="请输入菜单排序" value="{$row.sort|default=''}" class="layui-input">
<input type="number" name="sort" lay-reqtext="菜单排序不能为空" placeholder="请输入菜单排序" value="{$row.sort|default=''}" class="layui-input" lay-affix="number">
</div>
</div>

View File

@@ -2,6 +2,8 @@
// 应用公共文件
use app\common\service\AuthService;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
@@ -19,6 +21,7 @@ if (!function_exists('__url')) {
*/
function __url(string $url = '', array $vars = [], bool $suffix = true, bool $domain = false): string
{
if (filter_var($url, FILTER_VALIDATE_URL)) return $url;
return url($url, $vars, $suffix, $domain)->build();
}
}
@@ -99,21 +102,63 @@ if (!function_exists('auth')) {
$authService = new AuthService(session('admin.id'));
return $authService->checkNode($node);
}
}
/**
* @param string|null $detail
* @param string $name
* @param string $placeholder
* @return string
*/
function editor_textarea(?string $detail, string $name = 'desc', string $placeholder = '请输入'): string
{
$editor_type = sysConfig('site', 'editor_type');
return match ($editor_type) {
'ckeditor' => "<textarea name='{$name}' rows='20' class='layui-textarea editor' placeholder='{$placeholder}'>{$detail}</textarea>",
'ueditor' => "<script type='text/plain' id='{$name}' name='{$name}' class='editor' data-content='{$detail}'></script>",
'EasyMDE' => "<textarea id='{$name}' class='editor' name='{$name}'>{$detail}</textarea>",
default => "<div class='wangEditor_div'><textarea name='{$name}' rows='20' class='layui-textarea editor layui-hide'>{$detail}</textarea><div id='editor_toolbar_{$name}'></div><div id='editor_{$name}' style='height: 300px'></div></div>",
};
/**
* @param string|null $detail
* @param string $name
* @param string $placeholder
* @return string
*/
function editor_textarea(?string $detail, string $name = 'desc', string $placeholder = '请输入'): string
{
$editor_type = sysConfig('site', 'editor_type');
return match ($editor_type) {
'ckeditor' => "<textarea name='{$name}' rows='20' class='layui-textarea editor' placeholder='{$placeholder}'>{$detail}</textarea>",
'ueditor' => "<script type='text/plain' id='{$name}' name='{$name}' class='editor' data-content='{$detail}'></script>",
'EasyMDE' => "<textarea id='{$name}' class='editor' name='{$name}'>{$detail}</textarea>",
default => "<div class='wangEditor_div'><textarea name='{$name}' rows='20' class='layui-textarea editor layui-hide'>{$detail}</textarea><div id='editor_toolbar_{$name}'></div><div id='editor_{$name}' style='height: 500px'></div></div>",
};
}
/**
* @desc 导出excel
* @tip 追求性能请使用 xlsWriter https://xlswriter-docs.viest.me/zh-cn
* @param array $header
* @param array $list
* @param string $fileName
* @return void
* @throws Exception
*/
function exportExcel(array $header = [], array $list = [], string $fileName = ''): void
{
if (empty($fileName)) $fileName = time();
if (empty($header) || empty($list)) throw new \Exception('导出数据不能为空');
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$headers = array_column($header, 0) ?? array_keys($list[0]);
$sheet->fromArray([$headers], null, 'A1');
$rowIndex = 2;
foreach ($list as $row) {
$rowData = [];
foreach ($header as $item) {
$value = $row[$item[1]] ?? '';
if ($value === null) {
$rowData[] = '';
continue;
}
$rowData[] = $value;
}
$sheet->fromArray([$rowData], null, "A{$rowIndex}");
$rowIndex++;
}
foreach (range('A', $sheet->getHighestColumn()) as $col) {
$sheet->getColumnDimension($col)->setAutoSize(true);
}
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename="' . $fileName . '.xlsx"');
header('Cache-Control: max-age=0');
$writer = new Xlsx($spreadsheet);
$writer->save('php://output');
die();
}

View File

@@ -168,6 +168,12 @@ class Curd extends Command
}
}
}
if (PHP_SAPI == 'cli') {
$output->info(">>>>>>>>>>>>>>>");
$output->info('执行成功');
}else {
$output->writeln('执行成功');
}
}catch (\Exception $e) {
if (PHP_SAPI == 'cli')
CliEcho::error($e->getMessage());

View File

@@ -1,47 +0,0 @@
<?php
namespace app\common\command;
use EasyAdmin\console\CliEcho;
use EasyAdmin\tool\CommonTool;
use EasyAdmin\upload\driver\alioss\Oss;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
class OssStatic extends Command
{
protected function configure()
{
$this->setName('OssStatic')
->setDescription('将静态资源上传到oss上');
}
protected function execute(Input $input, Output $output)
{
$output->writeln("========正在上传静态资源到OSS上========" . date('Y-m-d H:i:s'));
$dir = root_path() . 'public' . DIRECTORY_SEPARATOR . 'static';
$list = CommonTool::readDirAllFiles($dir);
$uploadConfig = sysConfig('upload');
$uploadPrefix = config('app.oss_static_prefix', 'oss_static_prefix');
foreach ($list as $key => $val) {
list($objectName, $filePath) = [$uploadPrefix . DIRECTORY_SEPARATOR . $key, $val];
try {
$upload = Oss::instance($uploadConfig)
->save($objectName, $filePath);
} catch (\Exception $e) {
CliEcho::error('文件上传失败:' . $filePath . '。错误信息:' . $e->getMessage());
continue;
}
if ($upload['save'] == true) {
CliEcho::success('文件上传成功:' . $filePath . '。上传地址:' . $upload['url']);
} else {
CliEcho::error('文件上传失败:' . $filePath . '。错误信息:' . $upload['msg']);
}
}
$output->writeln("========已完成静态资源上传到OSS上========" . date('Y-m-d H:i:s'));
}
}

View File

@@ -19,9 +19,9 @@ class AdminController extends BaseController
/**
* 当前模型
* @Model
* @var object
* @var mixed
*/
protected object $model;
protected static mixed $model;
/**
* 字段排序
@@ -44,6 +44,12 @@ class AdminController extends BaseController
'title',
];
/**
* 过滤节点更新
* @var array
*/
protected array $ignoreNode = [];
/**
* 不导出的字段信息
* @var array
@@ -166,7 +172,7 @@ class AdminController extends BaseController
$where = [];
$excludes = [];
// 判断是否关联查询
$tableName = Str::snake(lcfirst($this->model->getName()));
$tableName = Str::snake(lcfirst((new self::$model)->getName()));
foreach ($filters as $key => $val) {
if (in_array($key, $excludeFields)) {
$excludes[$key] = $val;
@@ -190,6 +196,9 @@ class AdminController extends BaseController
case '%*':
$where[] = [$key, 'LIKE', "%{$val}"];
break;
case 'in':
$where[] = [$key, 'IN', $val];
break;
case 'range':
[$beginTime, $endTime] = explode(' - ', $val);
$where[] = [$key, '>=', strtotime($beginTime)];
@@ -209,7 +218,7 @@ class AdminController extends BaseController
public function selectList(): Json
{
$fields = input('selectFields');
$data = $this->model->where($this->selectWhere)->field($fields)->select()->toArray();
$data = self::$model::where($this->selectWhere)->field($fields)->select()->toArray();
$this->success(null, $data);
}
@@ -240,6 +249,7 @@ class AdminController extends BaseController
'version' => env('APP_DEBUG') ? time() : ConfigService::getVersion(),
'adminUploadUrl' => url('ajax/upload', [], false),
'adminEditor' => sysConfig('site', 'editor_type') ?: 'wangEditor',
'iframeOpenTop' => sysConfig('site', 'iframe_open_top') ?: 0,
];
View::assign($data);
}

View File

@@ -0,0 +1,22 @@
<?php
namespace app\common\entity;
use think\Entity;
use think\model\type\DateTime;
class BaseEntity extends Entity
{
protected function getOptions(): array
{
return [
'type' => [
'create_time' => DateTime::class,
'update_time' => DateTime::class,
'delete_time' => DateTime::class,
],
];
}
}

View File

@@ -13,28 +13,20 @@ use think\model\concern\SoftDelete;
class TimeModel extends Model
{
/**
* 自动时间戳类型
* @var string
*/
protected $autoWriteTimestamp = true;
/**
* 添加时间
* @var string
*/
protected $createTime = 'create_time';
/**
* 更新时间
* @var string
*/
protected $updateTime = 'update_time';
/**
* 软删除
*/
use SoftDelete;
protected $deleteTime = false;
protected function getOptions(): array
{
return [
'autoWriteTimestamp' => true,
'createTime' => 'create_time',
'updateTime' => 'update_time',
'deleteTime' => false,
];
}
}

View File

@@ -2,8 +2,8 @@
namespace app\common\service;
use app\admin\service\annotation\NodeAnnotation;
use app\common\constants\AdminConstant;
use EasyAdmin\tool\CommonTool;
use think\facade\Db;
/**
@@ -53,7 +53,7 @@ class AuthService
/***
* 构造方法
* AuthService constructor.
* @param null $adminId
* @param null $adminId
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
@@ -69,7 +69,7 @@ class AuthService
/**
* 检测检测权限
* @param null $node
* @param null $node
* @return bool
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
@@ -88,7 +88,7 @@ class AuthService
// 判断是否需要获取当前节点
if (empty($node)) {
$node = $this->getCurrentNode();
} else {
}else {
$node = $this->parseNodeStr($node);
}
// 判断是否加入节点控制,优先获取缓存信息
@@ -107,9 +107,30 @@ class AuthService
if (in_array($node, $this->adminNode)) {
return true;
}
if ($this->checkNodeAnnotationAttrAuth($node)) return true;
return false;
}
protected function checkNodeAnnotationAttrAuth(string $node): bool
{
$bool = false;
$controller = request()->controller();
try {
$controllerExplode = explode('.', $controller);
[$_name, $_controller] = $controllerExplode;
$nodeExplode = explode('/', $node);
$action = end($nodeExplode);
$reflectionClass = new \ReflectionClass("app\admin\controller\\{$_name}\\{$_controller}");
$attributes = $reflectionClass->getMethod($action)->getAttributes(NodeAnnotation::class);
foreach ($attributes as $attribute) {
$annotation = $attribute->newInstance();
$bool = $annotation->auth === false;
}
}catch (\Throwable) {
}
return $bool;
}
/**
* 获取当前节点
* @return string
@@ -131,25 +152,25 @@ class AuthService
{
$nodeList = [];
$adminInfo = Db::name($this->config['system_admin'])
->where([
'id' => $this->adminId,
'status' => 1,
])->find();
->where([
'id' => $this->adminId,
'status' => 1,
])->find();
if (!empty($adminInfo) && !empty($adminInfo['auth_ids'])) {
$buildAuthSql = Db::name($this->config['system_auth'])
->distinct(true)
->whereIn('id', $adminInfo['auth_ids'])
->field('id')
->buildSql(true);
->distinct(true)
->whereIn('id', $adminInfo['auth_ids'])
->field('id')
->buildSql(true);
$buildAuthNodeSql = Db::name($this->config['system_auth_node'])
->distinct(true)
->where("auth_id IN {$buildAuthSql}")
->field('node_id')
->buildSql(true);
->distinct(true)
->where("auth_id IN {$buildAuthSql}")
->field('node_id')
->buildSql(true);
$nodeList = Db::name($this->config['system_node'])
->distinct(true)
->where("id IN {$buildAuthNodeSql}")
->column('node');
->distinct(true)
->where("id IN {$buildAuthNodeSql}")
->column('node');
}
return $nodeList;
}
@@ -163,7 +184,7 @@ class AuthService
public function getNodeList()
{
return Db::name($this->config['system_node'])
->column('id,node,title,type,is_auth', 'node');
->column('id,node,title,type,is_auth', 'node');
}
/**
@@ -178,13 +199,13 @@ class AuthService
public function getAdminInfo()
{
return Db::name($this->config['system_admin'])
->where('id', $this->adminId)
->find();
->where('id', $this->adminId)
->find();
}
/**
* 驼峰转下划线规则
* @param string $node
* @param string $node
* @return string
*/
public function parseNodeStr($node)

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types = 1);
namespace app\common\utils;
class Helper
{
/**
* 获取当前IP地址
* @return string
*/
public static function getIp(): string
{
return request()->ip();
}
/**
* 获取当前登录用户ID
* @return int|string
*/
public static function getAdminUid(): int|string
{
return session('admin.id') ?: 0;
}
}

View File

@@ -14,6 +14,6 @@ class Index extends BaseController
public function index(): Redirect
{
// 这是项目首页 系统默认跳转后台页面
return redirect('/' . Env::get('EASYADMIN.ADMIN', false));
return redirect('/' . Env::get('EASYADMIN.ADMIN', 'admin'));
}
}

View File

@@ -22,15 +22,26 @@ class Install extends BaseController
// $this->redirect('/');
$isInstall = true;
$errorInfo = '已安装系统,如需重新安装请删除文件:/config/install/lock/install.lock或者删除 /install 路由';
}elseif (version_compare(phpversion(), '8.0.0', '<')) {
$errorInfo = 'PHP版本不能小于8.0.0';
}elseif (version_compare(phpversion(), '8.1.0', '<')) {
$errorInfo = 'PHP版本不能小于8.1.0';
}elseif (!extension_loaded("PDO")) {
$errorInfo = '当前未开启PDO无法进行安装';
}
if (!is_file(root_path() . '.env')) {
$errorInfo = '.env 文件不存在,请先配置 .env 文件';
}
if (!$request->isAjax()) {
$envInfo = [
'DB_HOST' => $isInstall ? '' : env('DB_HOST', '127.0.0.1'),
'DB_NAME' => $isInstall ? '' : env('DB_NAME', 'easyadmin8'),
'DB_USER' => $isInstall ? '' : env('DB_USER', 'root'),
'DB_PASS' => $isInstall ? '' : env('DB_PASS', 'root'),
'DB_PORT' => $isInstall ? '' : env('DB_PORT', 3306),
'DB_PREFIX' => $isInstall ? '' : env('DB_PREFIX', 'ea8_'),
];
$currentHost = '://';
$result = compact('errorInfo', 'currentHost', 'isInstall');
return view('index/install/index', $result);
$result = compact('errorInfo', 'currentHost', 'isInstall', 'envInfo');
return view('index@install/index', $result);
}
if ($errorInfo) $this->error($errorInfo);
$charset = 'utf8mb4';
@@ -88,12 +99,11 @@ class Install extends BaseController
$installPath = config_path() . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR;
$sqlPath = file_get_contents($installPath . 'sql' . DIRECTORY_SEPARATOR . 'install.sql');
$sqlArray = $this->parseSql($sqlPath, $config['prefix'], 'ea_');
$conn = mysqli_connect($config['host'], $config['username'], $config['password'], null, $config['port']);
$dsn = $this->pdoDsn($config, true);
try {
mysqli_set_charset($conn, $config['charset']);
mysqli_select_db($conn, $config['database']);
$pdo = new \PDO($dsn, $config['username'] ?? 'root', $config['password'] ?? '');
foreach ($sqlArray as $sql) {
mysqli_query($conn, $sql);
$pdo->query($sql);
}
$_password = password($password);
$tableName = 'system_admin';
@@ -105,9 +115,8 @@ class Install extends BaseController
'update_time' => time()
];
foreach ($update as $_k => $_up) {
mysqli_query($conn, "UPDATE {$config['prefix']}{$tableName} SET {$_k} = '{$_up}' WHERE id = 1");
$pdo->query("UPDATE {$config['prefix']}{$tableName} SET {$_k} = '{$_up}' WHERE id = 1");
}
mysqli_close($conn);
// 处理安装文件
!is_dir($installPath) && @mkdir($installPath);
!is_dir($installPath . 'lock' . DIRECTORY_SEPARATOR) && @mkdir($installPath . 'lock' . DIRECTORY_SEPARATOR);
@@ -158,11 +167,11 @@ class Install extends BaseController
protected function createDatabase($database, $config): bool
{
$dsn = $this->pdoDsn($config);
try {
$con = mysqli_connect($config['host'] ?? '127.0.0.1', $config['username'] ?? 'root', $config['password'] ?? '', null, $config['port'] ?? '');
mysqli_query($con, "CREATE DATABASE IF NOT EXISTS `{$database}` DEFAULT CHARACTER SET {$config['charset']} COLLATE=utf8mb4_general_ci");
mysqli_close($con);
}catch (\Throwable $e) {
$pdo = new \PDO($dsn, $config['username'] ?? 'root', $config['password'] ?? '');
$pdo->query("CREATE DATABASE IF NOT EXISTS `{$database}` DEFAULT CHARACTER SET {$config['charset']} COLLATE=utf8mb4_general_ci");
}catch (\PDOException $e) {
return false;
}
return true;
@@ -184,19 +193,32 @@ class Install extends BaseController
protected function checkConnect(array $config): ?bool
{
$dsn = $this->pdoDsn($config);
try {
$con = mysqli_connect($config['host'] ?? '127.0.0.1', $config['username'] ?? 'root', $config['password'] ?? '', null, $config['port'] ?? '');
$res = mysqli_query($con, 'select VERSION()');
$mysqlVersion = mysqli_fetch_row($res);
mysqli_close($con);
$_version = $mysqlVersion[0] ?? 0;
$pdo = new \PDO($dsn, $config['username'] ?? 'root', $config['password'] ?? '');
$res = $pdo->query('select VERSION()');
$_version = $res->fetch()[0] ?? 0;
if (version_compare($_version, '5.7.0', '<')) {
$this->error('mysql版本最低要求 5.7.x');
}
}catch (\mysqli_sql_exception $e) {
}catch (\PDOException $e) {
$this->error($e->getMessage());
}
return true;
}
/**
* @param array $config
* @param bool $needDatabase
* @return string
*/
protected function pdoDsn(array $config, bool $needDatabase = false): string
{
$host = $config['host'] ?? '127.0.0.1';
$database = $config['database'] ?? '';
$port = $config['port'] ?? '3306';
$charset = $config['charset'] ?? 'utf8mb4';
if ($needDatabase) return "mysql:host=$host;port=$port;dbname=$database;charset=$charset";
return "mysql:host=$host;port=$port;charset=$charset";
}
}

View File

@@ -20,24 +20,26 @@
}
],
"require": {
"php": ">=8.0.0",
"php": ">=8.1.0",
"topthink/framework": "^8.0",
"topthink/think-orm": "^3.0",
"topthink/think-multi-app": "^1.0",
"topthink/think-orm": "^4.0",
"topthink/think-multi-app": "^1.1.0",
"topthink/think-view": "^2.0",
"topthink/think-captcha": "^3.0",
"topthink/think-filesystem": "^2.0",
"aliyuncs/oss-sdk-php": "^2.6",
"aliyuncs/oss-sdk-php": "^2.7.2",
"qcloud/cos-sdk-v5": "^2.6",
"jianyan74/php-excel": "^1.0.2",
"doctrine/annotations": "^1.13",
"phpoffice/phpspreadsheet": "^1.28",
"doctrine/annotations": "^2.0.0",
"phpoffice/phpspreadsheet": "^4.1.0",
"myclabs/php-enum": "^1.8",
"qiniu/php-sdk": "^7.11.0",
"wolf-leo/phplogviewer": "^0.11.3",
"wolfcode/authenticator": "^0.0.6",
"wolfcode/rate-limiting": "^0.1.0",
"wolfcode/php-ai": "^0.1.2",
"ext-json": "*",
"qiniu/php-sdk": "v7.11.0",
"ext-mysqli": "*",
"ext-pdo": "*",
"wolf-leo/phplogviewer": "^0.10.0"
"ext-pdo": "*"
},
"require-dev": {
"symfony/var-dumper": ">=4.2",

View File

@@ -97,6 +97,8 @@ CREATE TABLE `ea_system_admin`
`create_time` int(11) DEFAULT NULL COMMENT '创建时间',
`update_time` int(11) DEFAULT NULL COMMENT '更新时间',
`delete_time` int(11) DEFAULT NULL COMMENT '删除时间',
`login_type` tinyint unsigned NOT NULL DEFAULT '1' COMMENT '登录方式',
`ga_secret` varchar(32) NOT NULL DEFAULT '' COMMENT '谷歌验证码秘钥',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`) USING BTREE,
KEY `phone` (`phone`)
@@ -106,7 +108,7 @@ CREATE TABLE `ea_system_admin`
-- Records of ea_system_admin
-- ----------------------------
INSERT INTO `ea_system_admin`
VALUES ('1', null, '/static/admin/images/head.jpg', 'admin', 'a33b679d5581a8692988ec9f92ad2d6a2259eaa7', 'admin', 'admin', '0', '0', '1', '1589454169', '1589476815', null);
VALUES ('1', null, '/static/admin/images/head.jpg', 'admin', 'a33b679d5581a8692988ec9f92ad2d6a2259eaa7', 'admin', 'admin', '0', '0', '1', '1589454169', '1589476815', null,1,'');
-- ----------------------------
-- Table structure for ea_system_auth
@@ -233,11 +235,11 @@ VALUES ('64', 'oss_domain', 'upload', '填你的', '阿里云oss访问域名', '
INSERT INTO `ea_system_config`
VALUES ('65', 'logo_title', 'site', 'EasyAdmin', 'LOGO标题', '0', null, null);
INSERT INTO `ea_system_config`
VALUES ('66', 'logo_image', 'site', '/favicon.ico', 'logo图片', '0', null, null);
VALUES ('66', 'logo_image', 'site', '/static/common/images/logo-1.png', 'logo图片', '0', null, null);
INSERT INTO `ea_system_config`
VALUES ('68', 'site_name', 'site', 'EasyAdmin后台系统', '站点名称', '0', null, null);
INSERT INTO `ea_system_config`
VALUES ('69', 'site_ico', 'site', '填你的', '浏览器图标', '0', null, null);
VALUES ('69', 'site_ico', 'site', '/favicon.ico', '浏览器图标', '0', null, null);
INSERT INTO `ea_system_config`
VALUES ('70', 'site_copyright', 'site', '填你的', '版权信息', '0', null, null);
INSERT INTO `ea_system_config`
@@ -569,3 +571,22 @@ INSERT INTO `ea_system_uploadfile`
VALUES ('290', 'oss', 'image/jpeg', 'https://lxn-99php.oss-cn-shenzhen.aliyuncs.com/upload/20191111/2c412adf1b30c8be3a913e603c7b6e4a.jpg', '', '', '', '0', 'image/jpeg', '0', 'jpg', '', 1573612437, null, null);
INSERT INTO `ea_system_uploadfile`
VALUES ('296', 'cos', 'image/jpeg', 'https://easyadmin-1251997243.cos.ap-guangzhou.myqcloud.com/upload/20191114/2381eaf81208ac188fa994b6f2579953.jpg', '', '', '', '0', 'image/jpeg', '0', 'jpg', '', 1573612437, null, null);
-- ----------------------------
-- Table structure for ea_system_log
-- ----------------------------
DROP TABLE IF EXISTS `ea_system_log`;
CREATE TABLE `ea_system_log`
(
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
`admin_id` int unsigned DEFAULT '0' COMMENT '管理员ID',
`url` varchar(1500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '操作页面',
`method` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '请求方法',
`title` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '日志标题',
`content` json NOT NULL COMMENT '请求数据',
`response` json DEFAULT NULL COMMENT '回调数据',
`ip` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'IP',
`useragent` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT 'User-Agent',
`create_time` int DEFAULT NULL COMMENT '操作时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='后台操作日志表 - 202412';

View File

@@ -42,4 +42,6 @@ return [
'default_jsonp_handler' => 'jsonpReturn',
// 默认JSONP处理方法
'var_jsonp_handler' => 'callback',
// 操作方法的参数绑定方式 route get param
'action_bind_param' => 'param',
];

2
extend/.gitignore vendored
View File

@@ -1,2 +1,2 @@
*
!.gitignore
!.gitignore

3
log.md
View File

@@ -1,2 +1,5 @@
> 2025年03月27日 重构了 `model` 的调用方式 原因查看 [https://github.com/top-think/think-orm/issues/704](https://github.com/top-think/think-orm/issues/704)
>
> 2025年01月01日 `PHP` 要求升级到 `8.1+`
>
> 2024年05月 更新 `EasyAdmin8` 重置版,多处语法、写法进行变更

View File

@@ -0,0 +1,59 @@
.green {
color: #11c26d !important;
}
.red {
color: #ff4c52 !important;
}
.cyan {
color: #0bb2d4 !important;
}
.purple {
color: #9463f7 !important;
}
.blue {
color: #3e8ef7 !important;
}
.orange {
color: #eb6709 !important;
}
.indigo {
color: #6610f2 !important;
}
.pink {
color: #e83e8c !important;
}
.teal {
color: #20c997 !important;
}
.white {
color: #fff !important;
}
.gray {
color: #6c757d !important;
}
.gray-dark {
color: #343a40 !important;
}
.secondary {
color: #6c757d !important;
}
.yellow {
color: rgb(255, 214, 102) !important;
}
.magenta {
color: #eb2f96 !important;
}

View File

@@ -1,3 +1,6 @@
body {
background: #333333;
}
.demo {
padding-top: 20px;
@@ -170,7 +173,6 @@
}
.container {
background: #333333;
position: static;
font-size: 12px;
height: 100%;

View File

@@ -1,21 +1,27 @@
@import url("../../plugs/layui-v2.x/css/layui.css");
@import url("../../plugs/font-awesome-4.7.0/css/font-awesome.min.css");
@import url("../css/color.css");
@import url("../css/themes/index.css");
@import url("../css/iconfont.css");
html, body {
height: 100%;
background: #f2f2f2;
:root {
--ea8-theme-main-color: #16b777;
}
html.dark, body {
height: 100%;
background: var(--lay-color-bg-1);
html,
body {
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}
.ws-header-theme .layui-form-switch {
vertical-align: baseline;
}
.ws-header-theme .layui-form-onswitch {
background: #333333;
border: 1px solid rgba(255, 255, 255, .7);
}
.layuimini-container {
min-height: 250px;
padding: 15px;
@@ -58,10 +64,8 @@ html.dark, body {
}
.hr-line {
color: #fff;
height: 1px;
margin: 30px 0;
background-color: #fff;
border-top: 1px dashed #e7eaec;
}
@@ -75,8 +79,8 @@ html.dark, body {
/**重写layui表格自适应*/
.layuimini-container .layui-table-cell {
height: 100%;
max-width: 100%;
height: 50px;
line-height: 42px;
}
/**数据表格-搜索表单样式*/
@@ -84,7 +88,6 @@ html.dark, body {
margin: 0;
border: 1px solid #e6e6e6;
padding: 10px 20px 5px 20px;
color: #6b6b6b;
}
/**数据表格-搜索表单样式*/
@@ -134,6 +137,19 @@ html.dark, body {
color: #a29c9c;
}
.tableSearch-xmSelect xm-select {
min-height: 30px !important;
line-height: 30px !important;
margin: 0 !important;
background-color: var(--lay-color-fill-2);
}
.elem-style-dark .tableSearch-xmSelect xm-select {
background-color: var(--lay-color-fill-2);
color: var(--lay-color-text-2);
border-color: var(--lay-color-border-1) !important;
}
/** 按钮背景色 */
.layuimini-container .layuimini-btn-primary {
color: #fff;
@@ -253,39 +269,12 @@ table样式
.layuimini-container .layui-table-tool {
background-color: #ffffff;
border-bottom: none !important;
padding: 10px;
padding-bottom: 15px !important;
}
.dark .layuimini-container .layui-table-tool {
background-color: var(--lay-color-bg-1);
border-bottom: none !important;
padding-bottom: 15px !important;
}
.layuimini-container .layui-table-view {
border: none !important;
}
.layuimini-container .layui-table-box {
border-width: 1px;
border-style: solid;
border-color: #e6e6e6;
}
.dark .layuimini-container .layui-table-box {
border-width: 1px;
border-style: solid;
border-color: #363636;
}
.layuimini-container .layui-table-page, .layui-table-total {
border-width: 0px 0 0;
}
.layuimini-container .layui-table-box .layui-table-header th {
font-weight: bold !important;
color: #565656 !important;
}
/**
@@ -319,9 +308,17 @@ table样式
}
.layui-form-select dl {
border: 1px #16b777 solid;
border: 1px var(--ea8-theme-main-color) solid;
border-top: none;
z-index: 99999;
padding: 0;
border-radius: 0;
}
.layui-form-select dl dd.layui-this {
background-color: var(--ea8-theme-main-color);
border-top: none;
color: #ffffff;
}
.form-search .layui-form-select dl {
@@ -329,10 +326,6 @@ table样式
padding: 0;
}
/**
按钮
*/
.layuimini-container .layui-btn-success {
color: #fff;
background-color: #4bb368;
@@ -360,19 +353,11 @@ table样式
font-size: 16px;
}
.layuimini-container .layui-table-tool-self {
right: 0px;
}
.layuimini-container .layui-table-tool {
padding: 10px 0px;
}
/**
弹出层样式
*/
.layui-layer-easy .layui-layer-title {
background: #2c3e50 !important;
background: var(--lay-color-bg-5);
color: #fff !important;
border-bottom: none;
}
@@ -395,7 +380,6 @@ table样式
.layui-layer-easy .layui-layer-btn {
text-align: center !important;
padding: 10px !important;
background: #ecf0f1;
overflow: hidden;
}
@@ -513,6 +497,18 @@ table样式
.form-search .layui-input-inline input, .form-search .layui-input-inline select {
width: 100% !important;
}
.tableSelect {
margin: 5px !important;
min-width: auto !important;
left: 1px !important;
right: 1px !important;
}
.tableSelect img {
object-fit: cover;
height: 100%;
}
}
/**
@@ -532,4 +528,13 @@ table样式
.wangEditor_div {
z-index: 99999;
border: 1px solid var(--w-e-textarea-slight-border-color);
}
.layui-input:focus, .layui-textarea:focus {
border-color: var(--ea8-theme-main-color) !important;
}
.layui-tabs-item {
height: 100%;
}

View File

@@ -0,0 +1,802 @@
// 通常用于背景色
$black-color :rgb(2, 17, 20);
// 通常用于字体颜色
$main-color :rgb(126, 252, 246);
// 通常用于边框
$less-main-color:rgb(6, 216, 215);
$plus-main-color:rgb(0, 125, 124);
// 通常用于激活状态,通常跟背景色搭配
$cover-color :rgba(62, 251, 251, 0.05);
// 更强的激活状态,适合做选项操作时使用
$active-color :rgba(62, 251, 251, 0.5);
$red-color :rgb(255, 0, 0);
$yellow-color:rgb(255, 255, 153);
$green-color :rgb(0, 255, 0);
& {
background-color: $black-color;
color: $main-color;
}
.layuimini-main {
background-color: $black-color;
}
.layuimini-container {
background-color: $black-color;
}
.layuimini-container .layui-table-tool {
background-color: $black-color;
}
$box-shape: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 0);
.layui-btn {
&.layui-btn-lg {
padding-right: 25px;
}
clip-path : $box-shape;
padding-right : 10px;
position : relative;
background-color: transparent;
border : 1px solid;
color : rgb(126, 252, 246);
text-shadow : rgb(126, 252, 246) 0px 0px 1px;
background-color: rgb(126, 252, 246);
&::before {
content: '';
display: block;
position: absolute;
z-index: -1;
clip-path: $box-shape;
width: 100%;
height: 100%;
left: -0.5px;
top: -0.5px;
background-color: $black-color;
}
&:hover::after {
content: '';
display: block;
position: absolute;
z-index: -1;
clip-path: $box-shape;
width: 100%;
height: 100%;
left: -0.5px;
top: -0.5px;
background-color: rgba(126, 252, 246, 0.2);
}
&.layui-btn-disabled {
&::after {
// todo:实现按钮禁用样式
}
}
&.layui-btn-success {
text-shadow: rgb(0, 255, 0) 0px 0px 1px;
color: rgb(0, 255, 0);
background-color: rgba(0, 255, 0);
border: unset;
&:hover::after {
background-color: rgba(0, 255, 0, 0.2);
}
}
&.layui-btn-danger {
text-shadow: rgb(255, 0, 0) 0px 0px 1px;
color: rgb(255, 0, 0);
background-color: rgb(255, 0, 0);
border: unset;
&:hover::after {
background-color: rgba(255, 0, 0, 0.2);
}
}
&.layuimini-btn-primary {
text-shadow: rgb(255, 255, 153) 0px 0px 1px;
color: rgb(255, 255, 153);
background-color: rgb(255, 255, 153);
border: unset;
&:hover::after {
background-color: rgba(255, 255, 153, 0.2);
}
}
&.layui-btn-primary {
border: unset;
}
&.layui-btn-normal {
color: $black-color;
background-color: $main-color;
border: unset;
&::before {
background-color: $main-color;
}
&:hover::after {
background-color: $main-color;
}
}
}
.layui-table {
background: $black-color;
color: $main-color;
tr {
background-color: $cover-color;
}
td {
border-color: $less-main-color;
}
}
.layui-table-header {
background-color: $black-color;
}
.layuimini-container .layui-table-box {
border-color: $less-main-color;
}
.layuimini-container .layui-table-box .layui-table-header th {
color: $main-color !important;
}
.layui-table-tool .layui-inline[lay-event] {
color: $main-color;
border: none;
position: relative;
&::after {
border-color: $main-color;
border-width: 1px;
border-style: solid;
clip-path: $box-shape;
position: absolute;
left: 0;
top: 0;
width: calc(100% - 1px);
height: calc(100% - 1px);
content: '';
display: block;
}
}
.layui-table tbody tr:hover {
background-color: rgba(126, 252, 246, 0.1);
}
.layuimini-main {
border-color: $less-main-color;
}
.layui-table-header {
border-color: $less-main-color;
}
.color-content>ul>li>a>div>span:nth-child(2) {
background-color: $black-color !important;
}
.layuimini-color .elem-content li {
clip-path: $box-shape;
}
.layuimini-color .elem-content li.layui-this {
background-color: $main-color;
color: $black-color !important;
border-color: $less-main-color;
}
.layuimini-color .more-menu-item {
color: $main-color;
&:hover {
background-color: rgba(62, 251, 251, 0.05);
}
}
.layui-layout-admin .layui-header {
background-color: $cover-color !important;
}
.layuimini-tab .layui-tab-title {
background-color: $cover-color !important;
border-color: $less-main-color;
}
.layuimini-tab .layui-tab-title span {
color: $active-color;
}
.layui-header .layuimini-menu-header-pc.layui-nav .layui-nav-item a:hover,
.layui-header .layuimini-header-menu.layuimini-pc-show.layui-nav .layui-this a {
color: $black-color !important;
}
.layui-layout-admin .layui-header .layui-nav .layui-nav-item a {
color: $main-color !important;
}
.layui-layout-admin .layui-header .layui-nav .layui-nav-item {
background-color: $active-color;
}
.layui-layout-admin .layui-header .layuimini-header-content>ul>.layui-nav-item.layui-this,
.layuimini-tool i:hover {
background-color: $main-color !important;
}
.layuimini-tab .layui-tab-control>li {
background-color: $black-color;
border-color: $less-main-color;
}
.layuimini-tab .layui-tab-title li {
border-color: $less-main-color;
}
.layui-flow-more a *,
.layui-laypage input,
.layui-table-view select[lay-ignore] {
border-color: $less-main-color;
background-color: $black-color;
color: $main-color;
}
.layui-laypage button,
.layui-laypage input {
border-color: $less-main-color;
background-color: $black-color;
color: $main-color;
}
.layuimini-container .layui-form-switch {
border-color: $less-main-color !important;
background-color: $black-color !important;
}
.layuimini-container .layui-form-onswitch {
background-color: $main-color !important;
}
.layuimini-container .layui-form-switch.layui-form-onswitch i {
background-color: $black-color !important;
}
.layuimini-container .layui-laypage .layui-laypage-curr .layui-laypage-em {
background-color: $main-color !important;
}
.layui-laypage .layui-laypage-curr em {
color: $black-color !important;
}
.layuimini-tab .layui-tab-title .layui-this .layuimini-tab-active {
background-color: $less-main-color;
}
.layui-table-view .layui-form-checkbox.layui-form-checked[lay-skin="primary"] i {
border-color: $main-color;
background-color: $main-color;
color: $black-color;
}
.layui-table-view .layui-form-checkbox[lay-skin="primary"] i {
background-color: $cover-color;
}
.layui-table-init {
background-color: $black-color;
}
.box-border-line {
position: relative;
border: 1px solid $less-main-color;
border-width: 1px;
$border-offset: 3px;
&::before {
content: '';
position: absolute;
width: 50px;
height: 20px;
transition: all 0.2s;
border: 1px solid $less-main-color;
top: -$border-offset;
left: -$border-offset;
border-right: none;
border-bottom: none;
pointer-events: none;
}
&::after {
content: "";
position: absolute;
width: 50px;
height: 20px;
transition: all 0.2s;
border: 1px solid $less-main-color;
bottom: -$border-offset;
right: -$border-offset;
border-left: none;
border-top: none;
pointer-events: none;
}
&:hover {
&::after,
&::before {
width: calc(100% + $border-offset);
height: calc(100% + $border-offset);
}
}
}
.table-search-fieldset {
.layui-input-inline {
margin-right: 0;
}
.layui-form-item:not(:last-child) {
margin-right: 10px;
@extend .box-border-line;
}
.layui-form-label {
border: unset;
}
}
.layui-form-pane .layui-form-label {
background-color: $cover-color;
color: $main-color;
}
.layuimini-container .table-search-fieldset {
color: $main-color;
border-color: $less-main-color;
}
.layui-form-select,
.layui-form-autocomplete {
dl {
color: $main-color;
background-color: $black-color;
border-color: $less-main-color;
dd {
&.layui-this {
background-color: $main-color !important;
color: $black-color;
}
&:hover {
background-color: $less-main-color !important;
color: $black-color;
}
}
}
}
.layui-iconpicker-item,
.layui-iconpicker-item:hover {
border-color: $less-main-color !important;
color: $black-color;
.layui-iconpicker-icon:hover {
border-color: $less-main-color !important;
}
}
.layui-nav-child {
background-color: $black-color;
color: $main-color;
}
.layui-layout-admin .layui-header .layui-nav .layui-nav-item .layui-nav-child a {
color: $main-color !important;
}
.layui-badge,
.layui-badge-dot,
.layui-badge-rim {
background-color: $main-color;
}
.layui-nav .layui-nav-child a:hover {
background-color: $active-color;
}
.layui-table-tool-panel {
background-color: $black-color;
}
.layui-form-checkbox[lay-skin="primary"] span {
color: $main-color;
}
.layui-table-tool-panel li:hover {
background-color: $active-color;
}
.layui-side.layui-bg-black,
.layui-side.layui-bg-black>.layuimini-menu-left>ul,
.layuimini-menu-left-zoom>ul {
background-color: $black-color !important;
}
.layui-side {
border-right: 1px solid $main-color;
}
.layui-bg-blue {
background-color: #0000ff !important;
}
.layui-bg-orange {
background-color: $yellow-color !important;
color: $black-color !important;
}
.layui-bg-green {
background-color: $green-color !important;
color: $black-color !important;
}
.layui-table-hover {
background-color: $cover-color;
}
.layui-table-grid-down {
background-color: $black-color;
color: $main-color;
border-color: unset;
}
.layui-table-tips-main {
background-color: $main-color;
color: $black-color;
}
.layui-layout-admin .layui-header .layuimini-tool i {
color: $main-color;
}
.color-red {
color: $red-color !important;
}
.layuimini-tab .layui-tab-title .layui-this span {
color: $main-color;
}
.layui-card {
background-color: $cover-color;
border-radius: 0;
@extend .box-border-line;
.layui-card-header {
color: $main-color;
}
}
.layui-text {
color: $main-color;
}
.layuimini-qiuck-module {
cursor: pointer;
a i {
background-color: $cover-color;
color: $active-color;
}
a cite {
color: $active-color;
}
&:hover {
a i {
background-color: $active-color;
color: $main-color;
}
a cite {
color: $main-color;
}
}
}
.layui-bg-number {
background-color: $cover-color;
}
.layui-input,
.layui-select,
.layui-textarea,
.city-picker-span,
.main-input {
background-color: $cover-color;
color: $main-color;
}
.city-picker-span>.title>span {
color: $main-color;
&:hover {
background-color: $main-color;
color: $black-color;
}
}
.layui-form-radio:hover *,
.layui-form-radioed,
.layui-form-radioed>i {
color: $main-color;
}
.layui-form-checked[lay-skin="primary"] i {
border-color: $main-color !important;
background-color: $main-color;
color: $black-color;
}
.layui-input:focus,
.layui-textarea:focus {
border-color: $main-color !important;
}
.layui-input:hover,
.layui-textarea:hover {
border-color: $main-color !important;
}
.layui-badge-rim,
.layui-border,
.layui-colla-content,
.layui-colla-item,
.layui-collapse,
.layui-elem-field,
.layui-form-pane .layui-form-item[pane],
.layui-form-pane .layui-form-label,
.layui-input,
.layui-layedit,
.layui-layedit-tool,
.layui-panel,
.layui-quote-nm,
.layui-select,
.layui-tab-bar,
.layui-tab-card,
.layui-tab-title,
.layui-tab-title .layui-this::after,
.layui-textarea {
border-color: $main-color !important;
}
.form-search .layui-input-inline input,
.form-search .layui-input-inline select {
border-width: 0 0 0 1px;
}
.layuimini-tab .layui-tab-tool .layui-nav-child {
border-color: $main-color;
}
.layui-nav .layui-nav-child a {
color: $main-color;
}
.layui-form-item {
margin-bottom: 12px;
}
.layuimini-upload .layuimini-upload-btn {
background-color: $black-color;
}
.layui-layer {
border: 1px solid $main-color;
background-color: $black-color;
}
.layui-layer-easy .layui-layer-title {
background-color: $plus-main-color !important;
color: $main-color !important;
}
.layui-table td,
.layui-table th,
.layui-table-col-set,
.layui-table-fixed-r,
.layui-table-grid-down,
.layui-table-header,
.layui-table-page,
.layui-table-tips-main,
.layui-table-tool,
.layui-table-total,
.layui-table-view,
.layui-table[lay-skin="line"],
.layui-table[lay-skin="row"] {
border-color: $less-main-color;
}
.layui-table tbody tr:hover,
.layui-table thead tr,
.layui-table-click,
.layui-table-header,
.layui-table-hover,
.layui-table-mend,
.layui-table-patch,
.layui-table-tool,
.layui-table-total,
.layui-table-total tr {
background-color: $black-color;
}
.hr-line {
color: $main-color;
border-color: $less-main-color;
background-color: $main-color;
}
.layui-layer-easy .layui-layer-btn {
background-color: $black-color;
border-top: 1px solid $less-main-color;
}
.layui-layer-easy .layui-layer-btn .layui-layer-btn0 {
background-color: $main-color;
border-color: $less-main-color;
color: $main-color !important;
clip-path: $box-shape;
}
.layui-layer-easy .layui-layer-btn a {
@extend .layui-btn
}
.layui-layer-shade {
background-color: #fff !important;
}
.layuimini-menu-left {
.layui-nav-tree .layui-nav-item {
margin: 5px;
width: calc(100% - 10px);
}
.layui-nav .layui-nav-item {
clip-path: $box-shape;
color: $main-color;
background-color: $active-color;
&.layui-this a {
background-color: $main-color !important;
color: $black-color !important;
}
&.layui-this {
background-color: $main-color !important;
color: $black-color !important;
span {
background-color: $main-color !important;
color: $black-color !important;
}
}
}
.layui-nav .layui-nav-item a:hover {
background-color: $active-color !important;
}
}
.layuimini-menu-left .layui-nav .layui-nav-item a,
.layuimini-menu-left-zoom.layui-nav .layui-nav-item a {
color: $main-color !important;
}
.layuimini-menu-left .layui-nav-item a span {
color: $main-color !important;
}
.layuimini-menu-left .layui-nav-item:hover a span {
color: $black-color !important;
}
.layui-layout-admin .layuimini-logo {
background-color: $black-color !important;
}
.layuimini-menu-left .layui-nav-itemed>.layui-nav-child {
background-color: $black-color !important;
}
.layuimini-menu-left .layui-nav-tree .layui-this,
.layuimini-menu-left .layui-nav-tree .layui-this>a,
.layuimini-menu-left .layui-nav-tree .layui-nav-child dd.layui-this,
.layuimini-menu-left .layui-nav-tree .layui-nav-child dd.layui-this a,
.layuimini-menu-left-zoom.layui-nav-tree .layui-this,
.layuimini-menu-left-zoom.layui-nav-tree .layui-this>a,
.layuimini-menu-left-zoom.layui-nav-tree .layui-nav-child dd.layui-this,
.layuimini-menu-left-zoom.layui-nav-tree .layui-nav-child dd.layui-this a {
background-color: $main-color !important;
color: $black-color !important;
.layui-left-nav {
color: $black-color !important;
}
}
.layui-iconpicker-icon {
border-color: $less-main-color !important;
background-color: $less-main-color !important;
}
.layui-iconpicker .layui-anim {
background-color: $black-color;
}
.layui-iconpicker .layui-iconpicker-list {
background-color: $black-color;
}
.layui-header .layui-nav .layui-nav-child dd.layui-this a,
.layui-header .layui-nav-child dd.layui-this,
.layui-layout-admin .layui-header .layui-nav .layui-nav-item .layui-nav-child .layui-this a {
background-color: $active-color !important;
}

View File

@@ -0,0 +1,81 @@
.layui-btn {
color : #808185 !important;
background-color: #cde7ff !important;
border : 1px solid #d3dde6 !important;
}
.layui-layer-easy .layui-layer-title {
background-color: rgb(215, 215, 215) !important;
color : #6C6E71 !important;
}
.layui-layer-easy .layui-layer-setwin>a::after {
color: #6C6E71 !important;
}
.layui-layer-easy .layui-layer-btn .layui-layer-btn0 {
color : #808185 !important;
background-color: #cde7ff !important;
border : 1px solid #d3dde6 !important;
}
.layuimini-tab .layui-tab-title .layuimini-tab-active {
display: none !important;
}
.layuimini-tab .layui-tab-title li {
padding : 0 5px !important;
font-size : 12px;
border-width: 1px 1px 1px 0 !important;
border-style: solid !important;
}
.layuimini-tab .layui-tab-title li.layui-this {
color : #fff !important;
border-color: rgba(0, 0, 255, 1) !important;
background : rgba(90, 173, 255, 1) !important;
}
.layuimini-tab .layui-tab-title .layui-this span {
color: #fff !important;
}
.layuimini-tab>.layui-tab-title li,
.layuimini-tab>.close-box li {
line-height: 27px !important;
}
.layuimini-tab>.layui-tab-title,
.layuimini-tab>.close-box {
height: 28px !important;
}
.layuimini-tab .layui-tab-control>li {
height : 28px !important;
line-height: 28px !important;
}
.layuimini-container .layui-laypage .layui-laypage-curr .layui-laypage-em {
background-color: #6C6E71 !important;
}
.layuimini-container .layui-form-onswitch {
background-color: #cde7ff !important;
border-color : #cde7ff !important;
}
.layuimini-container .layui-form-switch {
background-color: #6C6E71;
border-color : #6C6E71;
}
.layui-table-tool .layui-inline[lay-event] {
border-color: transparent;
}
.layui-form-checked[lay-skin="primary"] i {
border-color : #cde7ff !important;
background-color: #cde7ff !important;
}

View File

@@ -0,0 +1,708 @@
& {
background-color: #f6f5f4;
color : #2e3436;
}
.layuimini-container {}
.layuimini-main {
padding: 0;
}
.layuimini-container .layui-table-box .layui-table-header th {
color : #979a9b !important;
font-weight : 700 !important;
font-weight : bold;
text-shadow : none;
border-color : #d7d2ce;
border-bottom: 1px solid;
&:not(:last-child) {
border-right: 1px solid #d7d2ce;
}
}
.layui-table-view .layui-table td {
border-bottom: unset;
&:not(:last-child) {
border-right: 1px solid #d7d2ce;
}
}
.layui-table {
color : #2e3436;
background: #f6f5f4;
}
.layui-table-view .layui-table[lay-size="sm"] .layui-table-cell {
line-height: 30px;
}
.layui-table tbody tr:hover,
.layui-table thead tr,
.layui-table-click,
.layui-table-header,
.layui-table-hover,
.layui-table-mend,
.layui-table-patch,
.layui-table-tool,
.layui-table-total,
.layui-table-total tr {
background-color: unset;
}
.layui-table-cell {
padding: 0 4px;
}
.layui-table img {
height: 25px !important;
width : auto !important;
}
.layui-btn {
position : relative;
outline : none;
overflow : visible;
line-height : 28px;
border-radius : 5px;
transition : all .15s cubic-bezier(.25, .46, .45, .94);
padding : 4px 9px;
user-select : none;
color : #2e3436;
outline-color : rgba(53, 132, 228, .5);
border : 1px solid #cdc7c2;
border-bottom-color: rgb(205, 199, 194);
border-bottom-color: #bfb8b1;
background-color : #f6f5f4;
box-shadow : inset 0 1px #fff, 0 1px 2px rgba(0, 0, 0, .07);
&::before {
content : ' ';
position : absolute;
pointer-events: none;
width : 100%;
height : 100%;
top : 0px;
left : 0px;
background : linear-gradient(to top, rgba(0, 0, 0, 0.03), transparent);
transition : all 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
&::after {
content : '';
position : absolute;
pointer-events: none;
z-index : 2;
display : inline-block;
border : 2px solid transparent;
border-radius : inherit;
transition : all 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
top : -4px;
left : -4px;
width : calc(100% - -4px);
height : calc(100% - -4px);
}
&.layui-btn-normal {
color : #fff;
border-color : #1b6acb #1b6acb #15539e;
background-color: #3c88e5;
box-shadow : inset 0 1px #3181e3, 0 1px 2px rgba(0, 0, 0, .07);
}
&.layui-btn-danger {
color : #fff;
border-color : #b2161d #b2161d #851015;
background-color: #e41f28;
box-shadow : inset 0 1px #db1a23, 0 1px 2px rgba(0, 0, 0, .07);
}
&.layui-btn-sm {
line-height: 20px;
}
&.layui-btn-xs {
line-height: 12px
}
}
.layuimini-container .layui-table-tool .layui-inline[lay-event] {
@extend .layui-btn;
line-height : 32px;
min-width : 32px;
min-height : 32px;
padding : 0;
border-radius: 9999px;
}
.layui-laydate .layui-this {
background-color: #3584e4 !important;
}
.layui-laydate-content td,
.layui-laydate-content th {
width : 36px;
height : 36px;
border-radius: 100%;
}
.laydate-footer-btns span {
@extend .layui-btn;
line-height: 16px;
&:not(:last-child) {
border-right-style : none;
border-top-right-radius : 0;
border-bottom-right-radius: 0;
}
&:not(:first-child) {
border-top-left-radius : 0;
border-bottom-left-radius: 0;
}
}
.layui-laydate-footer span {
@extend .layui-btn;
line-height: 16px;
}
.layui-laydate-footer span.layui-laydate-preview {
line-height: 22px;
}
.layui-form-checked[lay-skin="primary"] i {
border-color : #185fb4 !important;
background-color: #5094e8;
background-image: linear-gradient(0deg, #3987e5, #5094e8);
}
.layui-form-checkbox[lay-skin="primary"]:hover i {
border-color : #185fb4;
background-image: linear-gradient(0deg, #5094e8, #6ba5eb);
}
.layui-table-view .layui-form-checkbox[lay-skin="primary"] i {
border-radius: 3px;
}
.layui-table-view .layui-form-checkbox i {
font-weight: bold;
}
.layui-layer-title {
padding : 0 6px;
min-height : 46px;
border : 1px solid #bfb8b1;
border-top-left-radius : 8px;
border-top-right-radius: 8px;
background : #f1f0ee linear-gradient(0deg, #dfdcd8, #f4f2f1) !important;
color : #2e3436 !important;
padding-left : 12px;
padding-right : 12px;
font-weight : bold;
}
.layui-layer {
background-color: #f6f5f4;
}
.layui-layer-easy .layui-layer-title~.layui-layer-setwin>a {
position : relative;
outline : none;
overflow : visible;
border-radius: 5px;
transition : all .15s cubic-bezier(.25, .46, .45, .94);
user-select : none;
outline-color : rgba(53, 132, 228, .5);
border : 1px solid #cdc7c2;
border-bottom-color: rgb(205, 199, 194);
border-bottom-color: #bfb8b1;
background-color : #f6f5f4;
box-shadow : inset 0 1px #fff, 0 1px 2px rgba(0, 0, 0, .07);
height : 20px;
line-height : 20px;
width : 20px;
text-align : center;
display : inline-flex;
align-items : center;
justify-content : center;
&::after {
color: #2e3436 !important;
}
}
.layui-layer-setwin {
display : flex;
align-items: center;
}
.layuimini-container .layui-form-switch.layui-form-onswitch {
color : #ffffff;
border-color : #185fb4;
background-color: #3584e4;
text-shadow : 0 1px rgba(53, 132, 228, 0.5), 0 0 2px rgba(255, 255, 255, 0.6);
}
.layuimini-container .layui-form-switch {
border : 1px solid #cdc7c2;
border-radius : 20px;
color : rgba(46, 52, 54, 0.2);
background-color: #e1dedb;
text-shadow : 0 1px rgba(0, 0, 0, 0.1);
transition : all 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.layui-form-switch {
height: 20px;
width : 40px;
}
.layui-form-switch {
padding : 0;
line-height: 20px;
}
.layui-form-switch.layui-form-onswitch i {
top : 0;
height : 20px;
width : 20px;
margin-left: -20px;
}
.layui-form-switch i {
top : 0;
height : 20px;
width : 20px;
margin-left : -5px;
border-radius : 50%;
transition : all 150ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
color : #2e3436;
outline-color : rgba(53, 132, 228, 0.5);
border-color : #cdc7c2;
border-bottom-color: #bfb8b1;
background-color : #f6f5f4;
box-shadow : inset 0 1px white, 0 1px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.07);
}
.layuimini-main {
background-color: unset;
}
.layui-tab-title li {
transition : none;
padding : 3px 12px;
min-height : 30px;
min-width : 65px;
display : flex;
justify-content : center;
align-items : center;
white-space : nowrap;
color : #2e3436;
font-weight : normal;
user-select : none;
border : 0;
border-top-style : none;
border-right-style : none;
border-bottom-style: none;
border-left-style : none;
&:hover {
box-shadow : inset 0 -3px #c8c2bc;
background-color: #edebe9;
}
&.layui-this {
box-shadow : inset 0 -3px #3584e4;
background-color: #f1f0ee;
}
}
.layui-tab-title {
display : flex;
align-items : center;
flex-direction: row;
padding-left : 4px;
padding-right : 4px;
}
.layuimini-tab>.layui-tab-title li,
.layuimini-tab>.close-box li {
line-height: 20px !important;
}
.layuimini-tab .layui-tab-title .layui-this .layuimini-tab-active {
display: none;
}
.layuimini-tab .layui-tab-title .layui-this span {
color: #2e3436 !important
}
.layuimini-tab .layui-tab-title span {
color: #2e3436 !important
}
.layuimini-tab .layui-tab-title .layuimini-tab-active {
display: none;
}
.layuimini-tab .layui-tab-title {
background-color: #e1dedb;
border-color : #cdc7c2;
}
.layui-layer-easy .layui-layer-btn {
background-color: #f2f2f2;
}
.layui-layer-dialog .layui-layer-content {
background-color: #f2f2f2;
}
.layui-layer-easy .layui-layer-btn a {
position : relative;
outline : none;
overflow : visible;
line-height : 22px;
height : 22px;
border-radius : 5px;
transition : all .15s cubic-bezier(.25, .46, .45, .94);
padding : 4px 9px;
user-select : none;
color : #2e3436 !important;
outline-color : rgba(53, 132, 228, .5);
border : 1px solid #cdc7c2;
border-bottom-color: rgb(205, 199, 194);
border-bottom-color: #bfb8b1;
background-color : #f6f5f4;
box-shadow : inset 0 1px #fff, 0 1px 2px rgba(0, 0, 0, .07);
&::before {
content : ' ';
position : absolute;
pointer-events: none;
width : 100%;
height : 100%;
top : 0px;
left : 0px;
background : linear-gradient(to top, rgba(0, 0, 0, 0.03), transparent);
transition : all 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
&::after {
content : '';
position : absolute;
pointer-events: none;
z-index : 2;
display : inline-block;
border : 2px solid transparent;
border-radius : inherit;
transition : all 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
top : -4px;
left : -4px;
width : calc(100% - -4px);
height : calc(100% - -4px);
}
&.layui-layer-btn0 {
color : #fff !important;
border-color : #1b6acb #1b6acb #15539e;
background-color: #3c88e5;
box-shadow : inset 0 1px #3181e3, 0 1px 2px rgba(0, 0, 0, .07);
}
}
.layui-tab-brief {
&>.layui-tab-title {
li {
@extend .layui-btn;
&:not(:last-child) {
border-right-style : none;
border-top-right-radius : 0;
border-bottom-right-radius: 0;
}
&:not(:first-child) {
border-top-left-radius : 0;
border-bottom-left-radius: 0;
}
&.layui-this {
color : unset;
color : #2e3436;
border-color : #cdc7c2;
background-color: #d6d1cd;
box-shadow : inset 0 1px rgba(255, 255, 255, 0);
&::after {
display: none;
}
}
&:hover {
box-shadow : unset;
background-color: #edebe9;
}
}
}
}
.layui-form-radio:hover *,
.layui-form-radioed,
.layui-form-radioed>i {
color: #5094e8;
}
.layui-card {
.layui-card-header {
padding : 0 6px;
min-height : 46px;
border-width : 1px;
border-style : solid;
border-color : #bfb8b1;
border-top-left-radius : 8px;
border-top-right-radius: 8px;
background : #f1f0ee linear-gradient(to top, #dfdcd8, #f4f2f1);
box-shadow : inset 0 1px rgba(255, 255, 255, 0.8), inset 0 -2px 2px rgba(0, 0, 0, 0.02);
.icon {
color: #979a9b !important;
}
}
.layui-card-body {
background-color: #f6f5f4;
border : 1px solid #cdc7c2;
}
}
.layui-btn,
.layui-input,
.layui-select,
.layui-textarea,
.layui-upload-button,
.city-picker-span {
border-radius: 5px;
border : 1px solid #cdc7c2;
}
.layui-side.layui-bg-black,
.layui-side.layui-bg-black>.layuimini-menu-left>ul,
.layuimini-menu-left-zoom>ul {
background-color: #f6f5f4 !important;
}
.layuimini-menu-left {
background-color: #fbfafa;
border-right : 1px solid #cdc7c2;
.layui-nav {
li {
&:not(:first-child) {
border-top: 1px solid #cdc7c2;
}
&.layui-this {
a {
background-color: #347cd3 !important;
color : #fff !important;
}
}
}
}
}
.layuimini-menu-left .layui-nav .layui-this a,
.layuimini-menu-left-zoom.layui-nav .layui-this a {
color: #2e3436 !important;
}
.layuimini-menu-left .layui-nav .layui-nav-item.layui-this a:hover,
.layuimini-menu-left-zoom.layui-nav .layui-nav-item.layui-this a:hover {
color: #fff !important;
}
.layuimini-menu-left .layui-nav .layui-nav-item a,
.layuimini-menu-left-zoom.layui-nav .layui-nav-item a {
color: #2e3436 !important;
&:hover {
color: #2e3436 !important;
}
}
.layuimini-menu-left .layui-nav .layui-nav-item a {
height : 30px;
line-height: 30px;
}
.layuimini-menu-left .layui-nav-itemed>.layui-nav-child {
background-color: rgba(46, 52, 54, 0.05) !important;
border-top : 1px solid #d7d2ce;
dd {
border-bottom: 1px solid #d7d2ce;
}
}
.layuimini-menu-left .layui-nav-tree .layui-this,
.layuimini-menu-left .layui-nav-tree .layui-this>a,
.layuimini-menu-left .layui-nav-tree .layui-nav-child dd.layui-this,
.layuimini-menu-left .layui-nav-tree .layui-nav-child dd.layui-this a,
.layuimini-menu-left-zoom.layui-nav-tree .layui-this,
.layuimini-menu-left-zoom.layui-nav-tree .layui-this>a,
.layuimini-menu-left-zoom.layui-nav-tree .layui-nav-child dd.layui-this,
.layuimini-menu-left-zoom.layui-nav-tree .layui-nav-child dd.layui-this a {
background-color: #347cd3 !important;
color : #fff !important;
}
.layui-layout-admin .layuimini-logo {
background-color: unset !important;
}
.layuimini-header-menu>li.layui-nav-item {
@extend .layui-btn;
margin-top : 10px;
height : 30px !important;
line-height: 30px !important;
&::after {
background-color: transparent;
}
&:not(:last-child) {
border-right-style : none;
border-top-right-radius : 0;
border-bottom-right-radius: 0;
}
&:not(:first-child) {
border-top-left-radius : 0;
border-bottom-left-radius: 0;
}
&:nth-last-child(2) {
border-right-style : solid;
border-radius : 5px;
border-top-left-radius : 0;
border-bottom-left-radius: 0;
}
}
.layui-layout-admin .layui-header {
background-color: #f6f5f4 !important;
}
.layui-layer-dialog {
border-radius: 20px;
}
.layui-layer-iframe {
border-radius: 20px;
}
.layuimini-tab .layui-tab-control>li {
background-color: #e1dedb;
border-style : solid;
border-color : #cdc7c2;
}
.form-search .layui-btn {
height : 32px;
line-height: 22px;
}
.form-search .layui-form-select dl {
transition : all 80ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
background-color : #ffffff;
background-clip : padding-box;
border : 1px solid rgba(0, 0, 0, 0.23);
border-top-left-radius : 0;
border-top-right-radius: 0;
border-radius : 9px;
border-top-left-radius : 0px;
border-top-right-radius: 0px;
box-shadow : 0 1px 3px 2px rgba(0, 0, 0, 0.08);
top : 28px;
}
.layui-form-select dl dd,
.layui-form-select dl dt {
height : 30px;
line-height: 30px;
padding : 0 10px;
}
.layuimini-container .layui-form-select dl dd.layui-this {
background-color: #3c88e5 !important;
}
.layui-table-header .layui-table {
margin-bottom: 0;
}
.layui-header .layui-nav .layui-nav-child dd.layui-this a,
.layui-header .layui-nav-child dd.layui-this,
.layui-layout-admin .layui-header .layui-nav .layui-nav-item .layui-nav-child .layui-this a {
background-color: #3c88e5 !important;
}
.layuimini-mobile-show {
.layui-nav-more {
top: 4px;
}
}
.layui-layer-easy .layui-layer-setwin>span {
color: #2e3436;
}
.layui-layer-easy .layui-layer-setwin>span:after {
color: #2e3436;
}
.layui-layer-easy .layui-layer-setwin .layui-layer-max::after,
.layui-layer-easy .layui-layer-setwin .layui-layer-max::before {
border-color: #2e3436;
}
.layui-layer-easy .layui-layer-setwin .layui-layer-min::before {
background-color: #2e3436;
}

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