48 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
76 changed files with 1157 additions and 419 deletions

View File

@@ -14,7 +14,7 @@ DB_PORT=3306
DB_CHARSET=utf8mb4
DB_PREFIX=ea8_
# 限流器开关
# 限流器开关 若启动需要配置 Redis 服务
RATE_LIMITING_STATUS=false
# Redis配置

2
.gitignore vendored
View File

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

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

@@ -15,7 +15,7 @@ class Cate extends AdminController
public function __construct(App $app)
{
parent::__construct($app);
$this->model = new MallCate();
self::$model = MallCate::class;
}
}

View File

@@ -11,6 +11,8 @@ use app\admin\service\annotation\NodeAnnotation;
use app\Request;
use think\App;
use think\response\Json;
use Wolfcode\Ai\Enum\AiType;
use Wolfcode\Ai\Service\AiChatService;
#[ControllerAnnotation(title: '商城商品管理')]
class Goods extends AdminController
@@ -22,8 +24,8 @@ class Goods extends AdminController
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: '列表', auth: true)]
@@ -32,8 +34,8 @@ class Goods extends AdminController
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' => '',
@@ -48,7 +50,7 @@ class Goods extends AdminController
#[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();
@@ -72,4 +74,62 @@ class Goods extends AdminController
{
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

@@ -24,8 +24,8 @@ 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: '列表', auth: true)]
@@ -36,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)
@@ -68,9 +65,9 @@ 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('保存失败');
}
@@ -80,7 +77,7 @@ class Admin extends AdminController
#[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();
@@ -88,18 +85,14 @@ 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();
}
@@ -107,7 +100,7 @@ class Admin extends AdminController
#[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();
@@ -128,7 +121,6 @@ class Admin extends AdminController
}
$save ? $this->success('保存成功') : $this->error('保存失败');
}
$row->auth_ids = explode(',', $row->auth_ids ?: '');
$this->assign('row', $row);
return $this->fetch();
}
@@ -138,7 +130,7 @@ class Admin extends AdminController
{
$this->checkPostRequest();
$id = $request->param('id');
$row = $this->model->whereIn('id', $id)->select();
$row = self::$model::whereIn('id', $id)->select();
$row->isEmpty() && $this->error('数据不存在');
$id == AdminConstant::SUPER_ADMIN_ID && $this->error('超级管理员不允许修改');
if (is_array($id)) {
@@ -171,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

@@ -23,16 +23,16 @@ class Auth extends AdminController
public function __construct(App $app)
{
parent::__construct($app);
$this->model = new SystemAuth();
self::$model = SystemAuth::class;
}
#[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);
@@ -46,7 +46,7 @@ class Auth extends AdminController
$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

@@ -9,6 +9,7 @@ use app\admin\service\annotation\ControllerAnnotation;
use app\admin\service\annotation\NodeAnnotation;
use app\Request;
use think\App;
use think\facade\Cache;
use think\response\Json;
#[ControllerAnnotation(title: '系统配置管理')]
@@ -18,7 +19,7 @@ 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'));
}
@@ -41,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

@@ -22,7 +22,7 @@ class Log extends AdminController
public function __construct(App $app)
{
parent::__construct($app);
$this->model = new SystemLog();
self::$model = SystemLog::class;
}
#[NodeAnnotation(title: '列表', auth: true)]
@@ -34,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();
@@ -54,13 +54,14 @@ class Log extends AdminController
}
#[NodeAnnotation(title: '导出', auth: true)]
public function export(): bool
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}");
@@ -71,20 +72,21 @@ 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');
}

View File

@@ -25,7 +25,7 @@ class Menu extends AdminController
public function __construct(App $app)
{
parent::__construct($app);
$this->model = new SystemMenu();
self::$model = SystemMenu::class;
}
#[NodeAnnotation(title: '列表', auth: true)]
@@ -35,8 +35,8 @@ class Menu extends AdminController
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' => '',
@@ -52,7 +52,7 @@ class Menu extends AdminController
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('首页不能添加子菜单');
}
@@ -65,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('保存失败');
}
@@ -76,7 +76,7 @@ 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();
@@ -85,7 +85,7 @@ class Menu extends AdminController
#[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();
@@ -108,7 +108,7 @@ class Menu extends AdminController
$this->error('保存失败');
}
}
$pidMenuList = $this->model->getPidMenuList();
$pidMenuList = self::$model::getPidMenuList();
$this->assign([
'id' => $id,
'pidMenuList' => $pidMenuList,
@@ -122,7 +122,7 @@ class Menu extends AdminController
{
$this->checkPostRequest();
$id = $request->param('id');
$row = $this->model->whereIn('id', $id)->select();
$row = self::$model::whereIn('id', $id)->select();
empty($row) && $this->error('数据不存在');
try {
$save = $row->delete();
@@ -148,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('首页状态不允许关闭');

View File

@@ -10,6 +10,9 @@ 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: '系统节点管理')]
@@ -19,7 +22,7 @@ class Node extends AdminController
public function __construct(App $app)
{
parent::__construct($app);
$this->model = new SystemNode();
self::$model = SystemNode::class;
}
#[NodeAnnotation(title: '列表', auth: true)]
@@ -29,10 +32,8 @@ class Node extends AdminController
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' => '',
@@ -51,15 +52,14 @@ class Node extends AdminController
$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'],
@@ -67,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) {
@@ -76,8 +76,10 @@ class Node extends AdminController
}
}
}
$model->saveAll($nodeList);
TriggerService::updateNode();
if (!empty($nodeList)) {
(new self::$model)->saveAll($nodeList);
TriggerService::updateNode();
}
}catch (\Exception $e) {
$this->error('节点更新失败');
}
@@ -89,12 +91,11 @@ class Node extends AdminController
{
$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

@@ -21,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

@@ -15,7 +15,7 @@ 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'));
}

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

@@ -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,7 +8,12 @@ use app\common\model\TimeModel;
class SystemAdmin extends TimeModel
{
protected $deleteTime = 'delete_time';
protected function getOptions(): array
{
return [
'deleteTime' => 'delete_time',
];
}
public array $notes = [
'login_type' => [
@@ -17,12 +22,15 @@ class SystemAdmin extends TimeModel
],
];
public function getAuthList()
public static function getAuthIdsAttr($value): array
{
$list = (new SystemAuth())
->where('status', 1)
->column('title', 'id');
return $list;
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

@@ -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,6 +390,7 @@ class BuildCurd
'bindSelectField' => $bindSelectField,
'delete' => $delete,
'tableColumns' => $formatColumns,
'onlyFields' => $onlyFields,
];
if (!empty($bindSelectField)) {
$relationArray = explode('\\', $modelFilename);
@@ -1038,7 +1041,7 @@ class BuildCurd
$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'] . '");';
}
@@ -1092,16 +1095,17 @@ class BuildCurd
$relationList = '';
if (!empty($this->relationArray)) {
foreach ($this->relationArray as $key => $val) {
$relation = CommonTool::lineToHump($key);
// $relationCode = CommonTool::replaceTemplate(
// $this->getTemplate("model{$this->DS}relation"),
// [
// 'relationMethod' => $relation,
// 'relationModel' => "\app\admin\model\\{$val['modelFilename']}",
// 'foreignKey' => $val['foreignKey'],
// 'primaryKey' => $val['primaryKey'],
// ]);
// $relationList .= $relationCode;
$relation = CommonTool::lineToHump($key);
$relationCode = CommonTool::replaceTemplate(
$this->getTemplate("model{$this->DS}relation"),
[
'relationMethod' => $relation,
'relationModel' => "{$val['modelFilename']}::class",
'foreignKey' => $val['foreignKey'],
'primaryKey' => $val['primaryKey'],
'relationFields' => empty($val['onlyFields']) ? "" : "->field('{$val['primaryKey']}," . implode(',', $val['onlyFields']) . "')",
]);
$relationList .= $relationCode;
}
}
@@ -1343,6 +1347,15 @@ class BuildCurd
);
$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;
}
@@ -1363,7 +1376,7 @@ class BuildCurd
$templateValue = "{field: '{$field}', title: '{$val['comment']}', templet: ea.table.image}";
} elseif ($val['formType'] == 'datetime') {
$templateValue = "{field: '{$field}', search: 'range', title: '{$val['comment']}'}";
} elseif ($val['formType'] == 'images') {
} elseif ($val['formType'] == 'images') {
continue;
} elseif ($val['formType'] == 'file') {
$templateValue = "{field: '{$field}', title: '{$val['comment']}', templet: ea.table.url}";
@@ -1390,13 +1403,12 @@ class BuildCurd
} 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}";
@@ -1417,20 +1429,22 @@ class BuildCurd
} elseif (in_array($field, $this->sortFields)) {
$templateValue = "{field: '{$table}.{$field}', title: '{$val['comment']}', edit: 'text'}";
} else {
$templateValue = "";
$templateValue = "{field: '{$table}.{$field}', title: '{$val['comment']}'}";
}
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;

View File

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

@@ -0,0 +1,21 @@
#[NodeAnnotation(title: '列表', auth: true)]
public function index(\app\Request $request): \think\response\Json|string
{
if ($request->isAjax()) {
if (input('selectFields')) {
return $this->selectList();
}
list($page, $limit, $where) = $this->buildTableParams();
$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,
'data' => $list,
];
return json($data);
}
return $this->fetch();
}

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

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;
@@ -25,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' => '',
@@ -46,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());
@@ -60,14 +60,14 @@ trait Curd
#[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,7 +85,7 @@ trait Curd
// 如果不是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();
@@ -102,7 +102,7 @@ trait Curd
$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}");
@@ -113,14 +113,16 @@ 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: '属性修改', auth: true)]
@@ -134,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('数据不存在');
}
@@ -142,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'],
]);
@@ -153,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>

View File

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

@@ -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>
@@ -151,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>
@@ -193,7 +199,7 @@
<div class="layui-card-header"><i class="fa fa-paper-plane-o icon"></i>作者心语</div>
<div class="layui-card-body layui-text">
<p>
本模板基于layui2.9.x以及font-awesome-4.7.0进行实现。
本模板基于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

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

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>

View File

@@ -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>
@@ -82,6 +91,14 @@
</div>
</div>
<!-- 文档https://xm-select.com/file/xm-select/v1.2.4/#/basic/use -->
<div class="layui-form-item">
<label class="layui-form-label">模拟多选</label>
<div class="layui-input-block">
<div id="demo1" class="xm-select-demo"></div>
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">备注信息</label>
<div class="layui-input-block">

View File

@@ -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>
@@ -82,6 +92,14 @@
</div>
</div>
<!-- 文档https://xm-select.com/file/xm-select/v1.2.4/#/basic/use -->
<div class="layui-form-item">
<label class="layui-form-label">模拟多选</label>
<div class="layui-input-block">
<div id="demo1" class="xm-select-demo"></div>
</div>
</div>
<div class="layui-form-item layui-form-text">
<label class="layui-form-label">备注信息</label>
<div class="layui-input-block">

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

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

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

@@ -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;
@@ -100,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

@@ -19,9 +19,9 @@ class AdminController extends BaseController
/**
* 当前模型
* @Model
* @var object
* @var mixed
*/
protected object $model;
protected static mixed $model;
/**
* 字段排序
@@ -172,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;
@@ -218,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);
}

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

@@ -27,16 +27,16 @@
"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": "^2.0.0",
"phpoffice/phpspreadsheet": "^1.28",
"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": "*",
"ext-mysqli": "*",
"ext-pdo": "*"

View File

@@ -571,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';

2
extend/.gitignore vendored
View File

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

2
log.md
View File

@@ -1,3 +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

@@ -4,6 +4,10 @@
@import url("../css/themes/index.css");
@import url("../css/iconfont.css");
:root {
--ea8-theme-main-color: #16b777;
}
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";
@@ -75,8 +79,8 @@ body {
/**重写layui表格自适应*/
.layuimini-container .layui-table-cell {
height: 100%;
max-width: 100%;
height: 50px;
line-height: 42px;
}
/**数据表格-搜索表单样式*/
@@ -304,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 {
@@ -517,4 +529,12 @@ 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

@@ -140,6 +140,39 @@ define(["jquery", "easy-admin", "echarts", "echarts-theme", "miniAdmin", "miniTh
echartsRecords.resize();
});
})
let util = layui.util;
util.on({
showComposerInfo: function () {
// <div style="padding: 25px;">12313</div>
let html = ``
ea.request.get({
url: ea.url('ajax/composerInfo'),
}, function (success) {
let data = success.data
data.forEach(function (item) {
html += `${item.name} ${item.version}\r\n`
})
html = `<pre class="layui-code code-demo">${html}</pre>`
layer.open({
type: 1,
title: 'composer 信息',
area: ['50%', '90%'],
shade: 0.8,
shadeClose: true,
scrollbar: false,
content: html,
success: function () {
layui.code({elem: '.code-demo', theme: 'dark', lang: 'php'});
}
})
}, function (error) {
console.error(error)
return false;
})
}
})
},
editAdmin: function () {
let form = layui.form

View File

@@ -10,6 +10,7 @@ define(["jquery", "easy-admin"], function ($, ea) {
export_url: 'mall.goods/export',
modify_url: 'mall.goods/modify',
stock_url: 'mall.goods/stock',
recycle_url: 'mall.goods/recycle',
};
return {
@@ -27,7 +28,7 @@ define(["jquery", "easy-admin"], function ($, ea) {
icon: 'fa fa-plus ',
extend: 'data-width="90%" data-height="95%"',
}],
'delete', 'export'],
'delete', 'export', 'recycle'],
cols: [[
{type: "checkbox"},
{field: 'id', width: 80, title: 'ID', searchOp: '='},
@@ -85,13 +86,151 @@ define(["jquery", "easy-admin"], function ($, ea) {
ea.listen();
},
add: function () {
layui.util.on({
AiOptimization: function (data) {
let layOn = $(data).attr('lay-on')
$(data).attr('lay-on', layOn + 'Loading')
aiOptimization(data)
},
})
var demo1 = xmSelect.render({
el: '#demo1',
name: 'xxx', // form表单提交的name
theme: {color: getComputedStyle(document.documentElement).getPropertyValue('--ea8-theme-main-color') || '#16b777'},
data: [
{name: 'Make', value: 1},
{name: 'PHP', value: 2},
{name: 'Great Again', value: 3},
]
})
ea.listen();
},
edit: function () {
layui.util.on({
AiOptimization: function (data) {
let layOn = $(data).attr('lay-on')
$(data).attr('lay-on', layOn + 'Loading')
aiOptimization(data)
},
})
var demo1 = xmSelect.render({
el: '#demo1',
name: 'xxx', // form表单提交的name
theme: {color: getComputedStyle(document.documentElement).getPropertyValue('--ea8-theme-main-color') || '#16b777'},
data: [
{name: 'Make', value: 1},
{name: 'PHP', value: 2, selected: true,},
{name: 'Great Again', value: 3, selected: true,},
]
})
ea.listen();
},
stock: 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: [[
{type: "checkbox"},
{field: 'id', width: 80, title: 'ID', searchOp: '='},
{field: 'sort', width: 80, title: '排序', edit: 'text'},
{field: 'cate_id', minWidth: 80, title: '商品分类', search: 'select', selectList: cateSelects, laySearch: true},
{field: 'title', minWidth: 80, title: '商品名称'},
{field: 'logo', minWidth: 80, title: '分类图片', search: false, templet: ea.table.image},
{field: 'status', title: '状态', width: 85, selectList: {0: '禁用', 1: '启用'}},
// 演示多选,实际数据库并无 status2 字段,搜索后会报错
{
field: 'status2', title: '演示多选', width: 105, search: 'xmSelect', selectList: {1: '模拟选项1', 2: '模拟选项2', 3: '模拟选项3', 4: '模拟选项4', 5: '模拟选项5'},
searchOp: 'in', templet: function (res) {
// 根据自己实际项目进行输出
return res?.status2 || '模拟数据'
}
},
{field: 'create_time', minWidth: 80, title: '创建时间', search: 'range'},
{field: 'delete_time', minWidth: 80, title: '删除时间', search: 'range'},
{
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();
},
};
function aiOptimization(data) {
let layOn = $(data).attr('lay-on')
let title = $('input[name="title"]').val()
// 告诉AI 你需要做什么
let message = `优化这个标题 ${title}`
if ($.trim(title) === '') {
ea.msg.error('标题不能为空', function () {
$(data).attr('lay-on', layOn.split('Loading')[0])
})
return false
}
let url = ea.url('mall.goods/aiOptimization')
ea.request.post({url: url, data: {message: message}}, function (res) {
let content = res.data?.choices[0]?.message?.content
// stream 为true 时AI 内容会逐字输出
let stream = true
ea.ai.chat(content, {stream: stream}, function () {
$(data).attr('lay-on', layOn.split('Loading')[0])
})
}, function (error) {
ea.msg.error(error.msg, function () {
$(data).attr('lay-on', layOn.split('Loading')[0])
})
})
}
});

View File

@@ -1,15 +1,15 @@
define(["jquery", "easy-admin", "vue"], function ($, ea, Vue) {
define(["jquery", "easy-admin"], function ($, ea) {
var form = layui.form;
return {
index: function () {
var _group = 'site'
var element = layui.element;
element.on('tab(docDemoTabBrief)', function (data) {
let tabs = layui.tabs
var TABS_ID = 'docDemoTabBrief';
tabs.on(`afterChange(${TABS_ID})`, function (data) {
_group = $(this).data('group')
});
})
let _upload_type = upload_type || 'local'
$('.upload_type').addClass('layui-hide')
$('.' + _upload_type).removeClass('layui-hide')
@@ -20,12 +20,15 @@ define(["jquery", "easy-admin", "vue"], function ($, ea, Vue) {
$('.' + _upload_type).removeClass('layui-hide')
});
form.on("submit", function (data) {
data.field['group'] = _group
});
ea.listen();
ea.listen('', function (res) {
ea.msg.success(res.msg);
}, function (err) {
ea.msg.error(err.msg);
});
}
};
});

View File

@@ -18,7 +18,7 @@ define(["jquery", "easy-admin", "miniTab"], function ($, ea, miniTab) {
<fieldset class="layui-elem-field">
<legend>提示</legend>
<div class="layui-field-box">
<p><a class="layui-font-blue" target="_blank" rel="nofollow" href="https://edocs.easyadmin8.top/curd/command.html">命令可查询文档</a></p>
<p><a class="layui-font-blue" target="_blank" rel="nofollow" href="https://edocs.easyadmin8.top/#/md/curd/command">命令可查询文档</a></p>
</div>
</fieldset>
<form class="layui-form layui-form-pane" action="">

View File

@@ -51,7 +51,7 @@ define(["jquery", "easy-admin"], function ($, ea) {
field: 'content', minWidth: 200, title: '请求数据', align: "left", templet: function (res) {
let html = '<div class="layui-colla-item">' +
'<div class="layui-colla-title">点击预览</div>' +
'<div class="layui-colla-content">' + prettyFormat(res.content) + '</div>' +
'<div class="layui-colla-content">' + prettyFormat(JSON.stringify(res.content)) + '</div>' +
'</div>'
return '<div class="layui-collapse" lay-accordion>' + html + '</div>'
}
@@ -60,7 +60,7 @@ define(["jquery", "easy-admin"], function ($, ea) {
field: 'response', minWidth: 200, title: '回调数据', align: "left", templet: function (res) {
let html = '<div class="layui-colla-item">' +
'<div class="layui-colla-title">点击预览</div>' +
'<div class="layui-colla-content">' + prettyFormat(res.response) + '</div>' +
'<div class="layui-colla-content">' + prettyFormat(JSON.stringify(res.response)) + '</div>' +
'</div>'
return '<div class="layui-collapse" lay-accordion>' + html + '</div>'
}

View File

@@ -121,6 +121,22 @@ define(["jquery", "easy-admin", "treetable", "iconPickerFa", "autocomplete"], fu
return false;
});
$('body').on('click', '[data-treetable-arrow]', function () {
const $icon = $(this).find('i');
const $textNode = $icon[0].nextSibling;
if ($icon.hasClass('fa-arrow-up')) {
treetable.foldAll(init.table_elem);
$icon.removeClass('fa-arrow-up').addClass('fa-arrow-down');
$textNode.textContent = ' 一键展开';
$(this).attr('data-arrow', 'down');
} else {
treetable.expandAll(init.table_elem);
$icon.removeClass('fa-arrow-down').addClass('fa-arrow-up');
$textNode.textContent = ' 一键折叠';
$(this).attr('data-arrow', 'up');
}
})
ea.table.listenSwitch({filter: 'status', url: init.modify_url});
ea.table.listenEdit(init, 'currentTable', init.table_render_id, true);

View File

@@ -49,7 +49,11 @@ define(["jquery", "easy-admin"], function ($, ea) {
}
]],
cols: [[
{field: 'node', minWidth: 200, align: 'left', title: '系统节点'},
{
field: 'node', minWidth: 200, align: 'left', title: '系统节点', templet: function (d) {
return `<span>${d.node}</span>`;
}
},
{field: 'title', minWidth: 80, title: '节点名称 <i class="table-edit-tips color-red">*</i>', edit: 'text'},
{field: 'update_time', minWidth: 80, title: '更新时间', search: 'range'},
{field: 'is_auth', title: '节点控制', width: 85, search: 'select', selectList: {0: '禁用', 1: '启用'}, templet: ea.table.switch},

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@@ -154,4 +154,46 @@ function prettyFormat(str) {
return ''
}
return "<pre>" + result + "</pre>"
}
}
if (self === top) {
console.group('温馨提示');
console.log(`%c
▄▄ ▄▄
▀███▀▀▀███ ██ ▀███ ██ ▄█▄▀▄██▄
██ ▀█ ▄██▄ ██ ██ ██
██ █ ▄█▀██▄ ▄██▀█████▀ ▀██▀ ▄█▀██▄ ▄█▀▀███ ▀████████▄█████▄ ▀███ ▀████████▄ ▀██▄ ▄▄█
██████ ██ ██ ██ ▀▀ ██ ▄█ ▄█ ▀██ ▄██ ██ ██ ██ ██ ██ ██ ██ ▄█████▄
██ █ ▄▄█████ ▀█████▄ ██ ▄█ ████████ ███ ██ ██ ██ ██ ██ ██ ██ ██ ▀███
██ ▄██ ██ █▄ ██ ███ █▀ ██ ▀██ ██ ██ ██ ██ ██ ██ ██ ██ ▀██
▄██████████████▀██▄██████▀ ▄█ ▄███▄ ▄████▄ ▀████▀███▄████ ████ ████▄████▄████ ████▄███████
▄█
██▀
%c
官方网站https://easyadmin8.top
官方文档https://edocs.easyadmin8.top
问答社区https://meta.easyadmin8.top
%c重要事情说3遍
%c
常见问题https://easyadmin8.top/guide/question.html
常见问题https://easyadmin8.top/guide/question.html
常见问题https://easyadmin8.top/guide/question.html
%c遇到问题先把 DEBUG 模式打开然后把错误信息找出来当不能解决的时候再去社区提问或者QQ群交流
`,
"color:#4290f7;font-weight:bold;font-size:10px;",
"color:#5672cd;",
"color:#ff5722;font-weight:bold;font-size:1rem;",
"color:#5672cd;",
"color:#ff5722;font-weight:bold;font-size:1rem;background:#f9de97;",
);
console.groupEnd();
}

View File

@@ -22,6 +22,7 @@ require.config({
"vue": ["plugs/vue-2.6.10/vue.min"],
"swiper": ["plugs/swiper/swiper-bundle.min"],
"colorMode": ["plugs/colorMode/colorMode"],
"lazyload": ["plugs/lazyload/lazyload.min"],
}
});

View File

@@ -1,4 +1,4 @@
define(["jquery", "tableSelect", "miniTheme", "xmSelect"], function ($, tableSelect, miniTheme, xmSelect) {
define(["jquery", "tableSelect", "miniTheme", "xmSelect", "lazyload"], function ($, tableSelect, miniTheme, xmSelect, lazyload) {
//切换日夜模式
window.onInitElemStyle = function () {
@@ -8,6 +8,7 @@ define(["jquery", "tableSelect", "miniTheme", "xmSelect"], function ($, tableSel
iframe.contentWindow.onInitElemStyle();
}
});
miniTheme.changeThemeMainColor();
};
window.onInitElemStyle();
@@ -253,7 +254,7 @@ define(["jquery", "tableSelect", "miniTheme", "xmSelect"], function ($, tableSel
}
// 初始化表格左上方工具栏
options.toolbar = options.toolbar || ['refresh', 'add', 'delete', 'export'];
options.toolbar = options.toolbar || ['refresh', 'add', 'delete', 'export', 'recycle'];
options.toolbar = admin.table.renderToolbar(options.toolbar, options.elem, options.id, options.init);
// 判断是否有操作列表权限
@@ -312,6 +313,14 @@ define(["jquery", "tableSelect", "miniTheme", "xmSelect"], function ($, tableSel
if (admin.checkAuth('export', elem)) {
toolbarHtml += '<button class="layui-btn layui-btn-sm layui-btn-success easyadmin-export-btn" data-url="' + init.export_url + '" data-table-export="' + tableId + '"><i class="fa fa-file-excel-o"></i> 导出</button>\n';
}
} else if (v === 'recycle') {
if (init.recycle_url === undefined) {
console.warn('未定义回收站地址 init.recycle_url')
return false
}
if (admin.checkAuth('recycle', elem)) {
toolbarHtml += '<button class="layui-btn layui-btn-sm layui-bg-orange" data-open="' + init.recycle_url + '" data-title="回收站"><i class="fa fa-recycle"></i> 回收站</button>\n';
}
} else if (typeof v === "object") {
$.each(v, function (ii, vv) {
vv.class = vv.class || '';
@@ -364,7 +373,7 @@ define(["jquery", "tableSelect", "miniTheme", "xmSelect"], function ($, tableSel
var selectHtml = '';
$.each(d.selectList, function (sI, sV) {
var selected = '';
if (sI === d.searchValue) {
if (sI == d.searchValue) {
selected = 'selected=""';
}
selectHtml += '<option value="' + sI + '" ' + selected + '>' + sV + '</option>/n';
@@ -387,7 +396,7 @@ define(["jquery", "tableSelect", "miniTheme", "xmSelect"], function ($, tableSel
formHtml += '\t<div class="layui-form-item layui-inline">\n' +
'<label class="layui-form-label">' + d.title + '</label>\n' +
'<div class="layui-input-inline">\n' +
'<div id="c-' + d.fieldAlias + '" class="tableSearch-xmSelect xmSelect-' + d.fieldAlias + '" name="' + d.fieldAlias + '" data-search-op="' + d.searchOp + '"></div>\n' +
'<div id="c-' + d.fieldAlias + '" class="tableSearch-xmSelect xmSelect-' + d.fieldAlias + '" name="' + d.fieldAlias + '" data-search-op="' + d.searchOp + '" data-search-value="' + d.searchValue + '"></div>\n' +
'</div>\n' +
'</div>';
init.xmSelectList[d.fieldAlias] = d.selectList
@@ -748,8 +757,11 @@ define(["jquery", "tableSelect", "miniTheme", "xmSelect"], function ($, tableSel
var values = value.split(option.imageSplit),
valuesHtml = [];
values.forEach((value, index) => {
valuesHtml.push('<img style="max-width: ' + option.imageWidth + 'px; max-height: ' + option.imageHeight + 'px;" src="' + value + '" data-image="' + title + '">');
valuesHtml.push('<img style="max-width: ' + option.imageWidth + 'px; max-height: ' + option.imageHeight + 'px;" class="lazyload" src="/static/common/images/loading.gif" data-src="' + value + '" data-image="' + title + '">');
});
$(function () {
$("img.lazyload").lazyload({threshold: 1});
})
return valuesHtml.join(option.imageJoin);
}
},
@@ -862,12 +874,15 @@ define(["jquery", "tableSelect", "miniTheme", "xmSelect"], function ($, tableSel
listenTableSearch: function (tableId) {
if (Object.keys(init.xmSelectList).length > 0) {
$.each(init.xmSelectList, function (index, value) {
let xmSearchValue = $('#c-' + index).data('search-value') || [];
if (!Array.isArray(xmSearchValue)) xmSearchValue = (xmSearchValue.toString()).split(',')
const keysArray = Object.keys(value).map((key) => {
return {name: value[key], value: key}
return {name: value[key], value: key, selected: xmSearchValue.indexOf(key) !== -1}
})
init.xmSelectModel[index] = xmSelect.render({
el: '.xmSelect-' + index, language: 'zn', data: keysArray, name: index,
filterable: true, paging: true, pageSize: 10, theme: {color: '#16b777'}, toolbar: {show: true},
filterable: true, paging: true, pageSize: 10, toolbar: {show: true},
theme: {color: getComputedStyle(document.documentElement).getPropertyValue('--ea8-theme-main-color') || '#16b777'}
})
})
}
@@ -983,7 +998,21 @@ define(["jquery", "tableSelect", "miniTheme", "xmSelect"], function ($, tableSel
},
listenSort: function (options) {
table.on('sort(' + options.layFilter + ')', function (obj) {
let defaultWhere = options.where || {}
let defaultWhere = {}
$.each(options.cols, function (_, colsV) {
let formatFilter = {}
let formatOp = {}
$.each(colsV, function (i, v) {
if (v.field) {
if ($('#c-' + v.field).val()) {
formatFilter[v.field] = $('#c-' + v.field).val()
formatOp[v.field] = v.searchOp || '='
defaultWhere['filter'] = JSON.stringify(formatFilter);
defaultWhere['op'] = JSON.stringify(formatOp);
}
}
})
})
let sortWhere = {tableOrder: obj.field + ' ' + obj.type}
table.reload(options.id, {
where: {...defaultWhere, ...sortWhere}
@@ -1020,8 +1049,9 @@ define(["jquery", "tableSelect", "miniTheme", "xmSelect"], function ($, tableSel
maxmin: true,
anim: 0,
moveOut: true,
move: false,
shade: 0.3,
shadeClose: shadeClose,
scrollbar: false,
before: function () {
},
success: function (layero, index) {
@@ -1593,7 +1623,7 @@ define(["jquery", "tableSelect", "miniTheme", "xmSelect"], function ($, tableSel
cols: [[
{type: selectCheck},
{field: 'id', title: 'ID'},
{field: 'url', minWidth: 80, search: false, title: '图片信息', imageHeight: 40, align: "center", templet: admin.table.image},
{field: 'url', minWidth: 80, search: false, title: '图片信息', imageHeight: 30, align: "center", templet: admin.table.image},
{field: 'original_name', width: 150, title: '文件原名', align: "center"},
{field: 'mime_type', width: 120, title: 'mime类型', align: "center"},
{field: 'create_time', width: 200, title: '创建时间', align: "center", search: 'range'},
@@ -1761,6 +1791,53 @@ define(["jquery", "tableSelect", "miniTheme", "xmSelect"], function ($, tableSel
}
},
},
ai: {
chat: function (content, options, cancel) {
let id = 'chat_' + (new Date()).getTime()
layer.open({
'title': options?.title || 'AI建议',
type: 1,
area: options?.area || (admin.checkMobile() ? ['95%', '60%'] : ['50%', '60%']),
shade: options?.shade || 0,
shadeClose: options?.shadeClose || false,
scrollbar: options?.scrollbar || false,
maxmin: options?.maxmin || true,
anim: options?.anim || 0,
content: `<div style="padding: 20px;white-space: pre-wrap;" id="${id}"></div>`,
success: function (layero, index) {
let elem = document.getElementById(id)
if (options?.stream) {
let index = 0;
let lastTime = performance.now();
const interval = options.interval || 100;
function typeCharacter(currentTime) {
if (index < content.length) {
if (currentTime - lastTime >= interval) {
elem.innerHTML += content.charAt(index);
index++;
lastTime = currentTime;
elem.scrollIntoView({behavior: "smooth", block: "end"});
}
requestAnimationFrame(typeCharacter);
}
}
requestAnimationFrame(typeCharacter);
} else {
content = content.replace(/\r\n/g, '<br>').replace(/\n/g, '<br>')
setTimeout(() => {
elem.innerHTML = content
}, 100)
}
},
cancel: function (index, layero) {
cancel()
}
})
},
},
};
return admin;
});

View File

@@ -123,43 +123,47 @@
/**tab选项卡 */
.layuimini-tab {
margin: 0px;
margin: 0;
overflow: hidden;
height: 100% !important;
}
.layuimini-tab .layui-tab-content {
.layuimini-tab .layui-tabs-scroll {
position: marker;
}
.layuimini-tab .layui-tabs-body {
height: calc(100% - 37px) !important;
}
.layuimini-tab .layui-tab-content .layui-tab-item {
.layuimini-tab .layui-tabs-body .layui-tabs-item {
height: 100%;
}
.layuimini-tab .layui-tab-content {
padding: 0px;
.layuimini-tab .layui-tabs-body {
padding: 0;
}
.layuimini-tab .layui-tab-title {
.layuimini-tab .layui-tabs-header {
border: none;
border: 1px solid whitesmoke;
background-color: white;
}
.layuimini-tab .layui-tab-title li {
.layuimini-tab .layui-tabs-header li {
border-right: 1px solid whitesmoke;
color: dimgray;
}
.layuimini-tab .layui-tab-title .layui-tab-bar {
.layuimini-tab .layui-tabs-header .layui-tab-bar {
display: none;
}
.layuimini-tab .layui-tab-title .layui-this:after {
.layuimini-tab .layui-tabs-header .layui-this:after {
display: none;
}
.layuimini-tab .layui-tab-title .layuimini-tab-active {
.layuimini-tab .layui-tabs-header .layuimini-tab-active {
display: inline-block;
background-color: lightgray;
width: 9px;
@@ -168,49 +172,52 @@
margin-right: 5px;
}
.layuimini-tab .layui-tab-title .layui-this .layuimini-tab-active {
background-color: #1aa094;
.layuimini-tab .layui-tabs-header .layui-this .layuimini-tab-active {
background-color: var(--ea8-theme-main-color);
}
.layuimini-tab > .layui-tab-title, .layuimini-tab > .close-box {
.layuimini-tab > .layui-tabs-header, .layuimini-tab > .close-box {
height: 35px !important;
}
.layuimini-tab > .layui-tab-title li, .layuimini-tab > .close-box li {
.layuimini-tab > .layui-tabs-header li, .layuimini-tab > .close-box li {
line-height: 35px !important;
}
.layuimini-tab .layui-tab-title span {
.layuimini-tab .layui-tabs-header span {
color: #acafb1;
}
.layuimini-tab .layui-tab-title .layui-this span {
.layuimini-tab .layui-tabs-header .layui-this span {
color: dimgray;
}
.layuimini-tab .layui-tab-title .layui-tab-close {
.layuimini-tab .layui-tabs-header .layui-tab-close {
font-size: 12px !important;
width: 14px !important;
height: 14px !important;
line-height: 16px !important;
margin-left: 5px;
padding: 2px;
}
.layuimini-tab .layui-tab-title .layui-tab-close:hover {
.layuimini-tab .layui-tabs-header .layui-tab-close:hover {
border-radius: 4em;
background: #ff5722;
color: #fff;
}
.layuimini-tab .layui-tab-title .disable-close + .layui-tab-close {
.layuimini-tab .layui-tabs-header .disable-close + .layui-tab-close {
display: none;
}
.layuimini-tab .layui-tab-title .able-close + .layui-tab-close {
.layuimini-tab .layui-tabs-header .able-close + .layui-tab-close {
display: inline-block;
}
.layuimini-tab .layui-tab-control > li {
position: absolute;
top: 0px;
top: 0;
height: 35px;
line-height: 35px;
width: 35px;
@@ -221,7 +228,7 @@
}
.layuimini-tab .layuimini-tab-roll-left {
left: 0px;
left: 0;
border-right: whitesmoke 1px solid;
border-left: whitesmoke 1px solid;
}
@@ -232,7 +239,7 @@
}
.layuimini-tab .layui-tab-tool {
right: 0px;
right: 0;
border-left: 1px solid whitesmoke;
}
@@ -248,11 +255,11 @@
}
.layuimini-tab.layui-tab-roll .layui-tab-control .layuimini-tab-roll-right {
right: 0px;
right: 0;
border-right: 1px solid whitesmoke;
}
.layuimini-tab.layui-tab-roll .layui-tab-title {
.layuimini-tab.layui-tab-roll .layui-tabs-header {
padding-left: 35px;
padding-right: 35px;
}
@@ -262,13 +269,13 @@
display: block;
}
.layuimini-tab.layui-tab-tool .layui-tab-title {
padding-left: 0px;
.layuimini-tab.layui-tab-tool .layui-tabs-header {
padding-left: 0;
padding-right: 35px;
}
.layuimini-tab.layui-tab-rollTool .layui-tab-title {
.layuimini-tab.layui-tab-rollTool .layui-tabs-header {
padding-left: 35px;
padding-right: 80px;
}
@@ -451,7 +458,7 @@
/**其它 */
.layui-tab-item {
.layui-tabs-item {
width: 100% !important;
height: 100% !important;
}
@@ -466,7 +473,7 @@
}
.layui-tab {
margin: 0 0 0 0;
margin: 0;
z-index: 99999;
}
@@ -630,9 +637,10 @@
.layuimini-tab-mousedown {
display: none;
width: 80px;
position: absolute;
top: 0px !important;
left: 0px !important;
position: fixed;
top: 55px !important;
left: 0 !important;
z-index: 99999;
}
.layuimini-tab-mousedown dd a {
@@ -640,26 +648,28 @@
color: #484545;
}
.layuimini-tab-make{
.layuimini-tab-make {
position: absolute;
top: 36px;
bottom: 0px;
bottom: 0;
width: 100%;
background: rgb(255, 255, 255,0);
padding: 0px;
background: rgb(255, 255, 255, 0);
padding: 0;
overflow: hidden;
}
/**
菜单缩放
*/
.popup-tips .layui-layer-TipsG{
.popup-tips .layui-layer-TipsG {
display: none;
}
.popup-tips.layui-layer-tips .layui-layer-content{
.popup-tips.layui-layer-tips .layui-layer-content {
padding: 0;
}
.popup-tips .layui-nav-tree{
.popup-tips .layui-nav-tree {
width: 150px;
border-radius: 10px;
}
@@ -670,13 +680,13 @@
}
/**头部菜单字体间距*/
.layui-layout-admin .layui-header .layuimini-header-menu.layuimini-pc-show,.layui-layout-admin .layui-header .layuimini-header-menu.layuimini-mobile-show {
.layui-layout-admin .layui-header .layuimini-header-menu.layuimini-pc-show, .layui-layout-admin .layui-header .layuimini-header-menu.layuimini-mobile-show {
letter-spacing: 1px;
}
/**左侧菜单更多下拉样式*/
.layuimini-menu-left .layui-nav-more,.layuimini-menu-left-zoom .layui-nav-more {
.layuimini-menu-left .layui-nav-more, .layuimini-menu-left-zoom .layui-nav-more {
font-family: layui-icon !important;
font-size: 12px;
font-style: normal;
@@ -695,10 +705,11 @@
margin-top: -6px !important;
}
.layuimini-menu-left .layui-nav-more:before,.layuimini-menu-left-zoom .layui-nav-more:before {
.layuimini-menu-left .layui-nav-more:before, .layuimini-menu-left-zoom .layui-nav-more:before {
content: "\e61a";
}
.layuimini-menu-left .layui-nav-itemed > a > .layui-nav-more,.layuimini-menu-left-zoom .layui-nav-itemed > a > .layui-nav-more {
.layuimini-menu-left .layui-nav-itemed > a > .layui-nav-more, .layuimini-menu-left-zoom .layui-nav-itemed > a > .layui-nav-more {
transform: rotate(180deg);
-ms-transform: rotate(180deg);
-moz-transform: rotate(180deg);
@@ -706,10 +717,10 @@
-o-transform: rotate(180deg);
width: 12px;
text-align: center;
border-style:none;
border-style: none;
}
.layuimini-menu-left .layui-nav-itemed > a > .layui-nav-more:before,.layuimini-menu-left-zoom .layui-nav-itemed > a > .layui-nav-more:before {
.layuimini-menu-left .layui-nav-itemed > a > .layui-nav-more:before, .layuimini-menu-left-zoom .layui-nav-itemed > a > .layui-nav-more:before {
content: '\e61a';
background-color: transparent;
display: inline-block;
@@ -717,7 +728,7 @@
}
/**修复左侧菜单字体不对齐的问题*/
.layuimini-menu-left .layui-nav-item a .fa,.layuimini-menu-left .layui-nav-item a .layui-icon{
.layuimini-menu-left .layui-nav-item a .fa, .layuimini-menu-left .layui-nav-item a .layui-icon {
width: 20px;
}
@@ -773,16 +784,18 @@
left: 95px !important;
}
.layuimini-pc-show{
.layuimini-pc-show {
display: block;
}
.layuimini-mobile-show{
.layuimini-mobile-show {
display: none;
}
/**菜单缩放*/
.layuimini-mini .layuimini-menu-left .layui-nav-more,.layuimini-mini .layuimini-menu-left .layui-nav-child{
display: none;!important;
.layuimini-mini .layuimini-menu-left .layui-nav-more, .layuimini-mini .layuimini-menu-left .layui-nav-child {
display: none;
!important;
}
}
@@ -794,29 +807,31 @@
/**
todo 修复低版本IOS不能滑动问题, 但还是有问题, 低版本IOS部分情况下子页面无法自适应
*/
.layuimini-tab .layui-tab-content .layui-tab-item{
.layuimini-tab .layui-tabs-body .layui-tabs-item {
-webkit-overflow-scrolling: touch;
overflow: scroll;
width:100%;
height:100%;
width: 100%;
height: 100%;
}
.layuimini-tab .layui-tab-content .layui-tab-item iframe {
.layuimini-tab .layui-tabs-body .layui-tabs-item iframe {
height: 100%;
width: 100%;
}
.layuimini-pc-show{
.layuimini-pc-show {
display: none;
}
.layuimini-mobile-show{
.layuimini-mobile-show {
display: block;
}
.layuimini-header-content {
left: 0;
}
.layui-layout-admin .layui-body .layui-tab-item.layui-show {
.layui-layout-admin .layui-body .layui-tabs-item.layui-show {
border-top: 1px solid #e2e2e2;
}
@@ -854,7 +869,7 @@
}
.layuimini-mini .layui-layout-admin .layui-body {
left: 0!important;
left: 0 !important;
transition: left .2s;
top: 0;
z-index: 998;
@@ -905,7 +920,7 @@
}
}
@media screen and (max-width: 550px){
@media screen and (max-width: 550px) {
/**头部右侧数据*/
.layuimini-multi-module.layuimini-mini .layuimini-header-content .layui-layout-right {

View File

@@ -360,7 +360,7 @@ define(["jquery", "miniMenu", "miniTheme", "miniTab", "colorMode"], function ($,
* 刷新
*/
$('body').on('click', '[data-refresh]', function () {
$(".layui-tab-item.layui-show").find("iframe")[0].contentWindow.location.reload();
$(".layui-tabs-item.layui-show").find("iframe")[0].contentWindow.location.reload();
miniAdmin.success('刷新成功');
});

View File

@@ -6,6 +6,7 @@
*/
define(["jquery"], function ($) {
var element = layui.element,
tabs = layui.tabs,
$ = layui.$;
@@ -44,15 +45,14 @@ define(["jquery"], function ($) {
options.title = options.title || null;
options.isIframe = options.isIframe || false;
options.maxTabNum = options.maxTabNum || 20;
if ($(".layuimini-tab .layui-tab-title li").length >= options.maxTabNum) {
if ($(".layuimini-tab .layui-tabs-header li").length >= options.maxTabNum) {
layer.msg('Tab窗口已达到限定数量请先关闭部分Tab');
return false;
}
var ele = element;
if (options.isIframe) ele = parent.layui.element;
ele.tabAdd('layuiminiTab', {
title: '<span class="layuimini-tab-active"></span><span>' + options.title + '</span><i class="layui-icon layui-unselect layui-tab-close"></i>' //用于演示
, content: '<iframe width="100%" height="100%" frameborder="no" border="0" marginwidth="0" marginheight="0" src="' + options.href + '"></iframe>'
if (options.isIframe) tabs = parent.layui.tabs;
tabs.add('layuiminiTab', {
title: `<span class="layuimini-tab-active"></span><span>${options.title}</span><i class="layui-icon layui-unselect layui-tab-close">ဆ</i>`
, content: `<iframe width="100%" height="100%" frameborder="no" border="0" src="${options.href}" style="width: 100%; height:100%;"></iframe>`
, id: options.tabId
});
$('.layuimini-menu-left').attr('layuimini-tab-tag', 'add');
@@ -65,7 +65,7 @@ define(["jquery"], function ($) {
* @param tabId
*/
change: function (tabId) {
element.tabChange('layuiminiTab', tabId);
tabs.change('layuiminiTab', tabId);
},
/**
@@ -74,13 +74,10 @@ define(["jquery"], function ($) {
* @param isParent
*/
delete: function (tabId, isParent) {
// todo 未知BUG不知道是不是layui问题必须先删除元素
$(".layuimini-tab .layui-tab-title .layui-unselect.layui-tab-bar").remove();
if (isParent === true) {
parent.layui.element.tabDelete('layuiminiTab', tabId);
parent.layui.tabs.close('layuiminiTab', tabId);
} else {
element.tabDelete('layuiminiTab', tabId);
tabs.close('layuiminiTab', tabId);
}
},
@@ -101,7 +98,7 @@ define(["jquery"], function ($) {
isIframe: true,
});
}
parent.layui.element.tabChange('layuiminiTab', options.href);
parent.layui.tabs.change('layuiminiTab', options.href);
parent.layer.close(loading);
},
@@ -109,7 +106,7 @@ define(["jquery"], function ($) {
* 在iframe层关闭当前tab方法
*/
deleteCurrentByIframe: function () {
var ele = $(".layuimini-tab .layui-tab-title li.layui-this", parent.document);
var ele = $(".layuimini-tab .layui-tabs-header li.layui-this", parent.document);
if (ele.length > 0) {
var layId = $(ele[0]).attr('lay-id');
miniTab.delete(layId, true);
@@ -123,14 +120,14 @@ define(["jquery"], function ($) {
// 判断选项卡上是否有
var checkTab = false;
if (isIframe === undefined || isIframe === false) {
$(".layui-tab-title li").each(function () {
$(".layui-tabs-header li").each(function () {
var checkTabId = $(this).attr('lay-id');
if (checkTabId != null && checkTabId === tabId) {
checkTab = true;
}
});
} else {
parent.layui.$(".layui-tab-title li").each(function () {
parent.layui.$(".layui-tabs-header li").each(function () {
var checkTabId = $(this).attr('lay-id');
if (checkTabId != null && checkTabId === tabId) {
checkTab = true;
@@ -155,7 +152,7 @@ define(["jquery"], function ($) {
'</dl>\n' +
'</div>';
var makeHtml = '<div class="layuimini-tab-make"></div>';
$('.layuimini-tab .layui-tab-title').after(menuHtml);
$('.layuimini-tab .layui-tabs-header').after(menuHtml);
$('.layuimini-tab .layui-tab-content').after(makeHtml);
},
@@ -233,7 +230,7 @@ define(["jquery"], function ($) {
maxTabNum: options.maxTabNum,
});
}
element.tabChange('layuiminiTab', tabId);
tabs.change('layuiminiTab', tabId);
layer.close(loading);
});
@@ -262,14 +259,14 @@ define(["jquery"], function ($) {
maxTabNum: options.maxTabNum,
});
}
parent.layui.element.tabChange('layuiminiTab', tabId);
parent.layui.tabs.change('layuiminiTab', tabId);
parent.layer.close(loading);
});
/**
* 关闭选项卡
**/
$('body').on('click', '.layuimini-tab .layui-tab-title .layui-tab-close', function () {
$('body').on('click', '.layuimini-tab .layui-tabs-header .layui-tab-close', function () {
var loading = layer.load(0, {shade: false, time: 2 * 1000});
var $parent = $(this).parent();
var tabId = $parent.attr('lay-id');
@@ -285,7 +282,7 @@ define(["jquery"], function ($) {
$('body').on('click', '[layuimini-tab-close]', function () {
var loading = layer.load(0, {shade: false, time: 2 * 1000});
var closeType = $(this).attr('layuimini-tab-close');
$(".layuimini-tab .layui-tab-title li").each(function () {
$(".layuimini-tab .layui-tabs-header li").each(function () {
var tabId = $(this).attr('lay-id');
var id = $(this).attr('id');
var isCurrent = $(this).hasClass('layui-this');
@@ -307,7 +304,7 @@ define(["jquery"], function ($) {
/**
* 禁用网页右键
*/
$(".layuimini-tab .layui-tab-title").unbind("mousedown").bind("contextmenu", function (e) {
$(".layuimini-tab .layui-tabs-header").unbind("mousedown").bind("contextmenu", function (e) {
e.preventDefault();
return false;
});
@@ -315,10 +312,11 @@ define(["jquery"], function ($) {
/**
* 注册鼠标右键
*/
$('body').on('mousedown', '.layuimini-tab .layui-tab-title li', function (e) {
var left = $(this).offset().left - $('.layuimini-tab ').offset().left + ($(this).width() / 2),
$('body').on('mousedown', '.layuimini-tab .layui-tabs-header li', function (e) {
var left = e.pageX ,
tabId = $(this).attr('lay-id');
if (e.which === 3) {
e.preventDefault();
miniTab.openTabRignMenu(tabId, left);
}
});
@@ -337,7 +335,7 @@ define(["jquery"], function ($) {
var loading = layer.load(0, {shade: false, time: 2 * 1000});
var closeType = $(this).attr('layuimini-tab-menu-close'),
currentTabId = $('.layuimini-tab-mousedown').attr('data-tab-id');
$(".layuimini-tab .layui-tab-title li").each(function () {
$(".layuimini-tab .layui-tabs-header li").each(function () {
var tabId = $(this).attr('lay-id');
var id = $(this).attr('id');
if (id !== 'layuiminiHomeTabId') {
@@ -368,7 +366,7 @@ define(["jquery"], function ($) {
options.listenSwichCallback = options.listenSwichCallback || function () {
};
element.on('tab(' + options.filter + ')', function (data) {
tabs.on('afterChange(layuiminiTab)', function (data) {
var tabId = $(this).attr('lay-id');
if (options.urlHashLocation) {
location.hash = tabId;
@@ -403,10 +401,10 @@ define(["jquery"], function ($) {
options.menuList = options.menuList || [];
if (!options.urlHashLocation) return false;
var tabId = location.hash.replace(/^#/, '');
if (tabId === null || tabId === undefined || tabId ==='') return false;
if (tabId === null || tabId === undefined || tabId === '') return false;
// 判断是否为首页
if(tabId ===options.homeInfo.href) return false;
if (tabId === options.homeInfo.href) return false;
// 判断是否为右侧菜单
var menu = miniTab.searchMenu(tabId, options.menuList);
@@ -419,7 +417,7 @@ define(["jquery"], function ($) {
maxTabNum: options.maxTabNum,
});
$('.layuimini-menu-left').attr('layuimini-tab-tag', 'no');
element.tabChange('layuiminiTab', tabId);
tabs.change('layuiminiTab', tabId);
return false;
}
@@ -436,7 +434,7 @@ define(["jquery"], function ($) {
maxTabNum: options.maxTabNum,
});
$('.layuimini-menu-left').attr('layuimini-tab-tag', 'no');
element.tabChange('layuiminiTab', tabId);
tabs.change('layuiminiTab', tabId);
isSearchMenu = true;
return false;
}
@@ -452,7 +450,7 @@ define(["jquery"], function ($) {
isIframe: false,
maxTabNum: options.maxTabNum,
});
element.tabChange('layuiminiTab', tabId);
tabs.change('layuiminiTab', tabId);
return false;
},
@@ -543,7 +541,7 @@ define(["jquery"], function ($) {
* 自动定位
*/
rollPosition: function () {
var $tabTitle = $('.layuimini-tab .layui-tab-title');
var $tabTitle = $('.layuimini-tab .layui-tabs-header');
var autoLeft = 0;
$tabTitle.children("li").each(function () {
if ($(this).hasClass('layui-this')) {
@@ -562,7 +560,7 @@ define(["jquery"], function ($) {
* @param direction
*/
rollClick: function (direction) {
var $tabTitle = $('.layuimini-tab .layui-tab-title');
var $tabTitle = $('.layuimini-tab .layui-tabs-header');
var left = $tabTitle.scrollLeft();
if ('left' === direction) {
$tabTitle.animate({

View File

@@ -41,7 +41,7 @@ define(["jquery"], function ($) {
headerRightBg: '#23262e', //头部右侧背景色
headerRightBgThis: '#0c0c0c', //头部右侧选中背景色,
headerRightColor: 'rgba(255,255,255,.7)', //头部右侧字体颜色,
headerRightChildColor: 'rgba(255,255,255,.7)', //头部右侧下拉字体颜色,
headerRightChildColor: '#676767', //头部右侧下拉字体颜色,
headerRightColorThis: 'rgba(255,255,255,.7)', //头部右侧鼠标选中,
headerRightNavMore: 'rgba(255,255,255,.7)', //头部右侧更多下拉颜色,
headerRightNavMoreBg: '#1aa094', //头部右侧更多下拉列表选中背景色,
@@ -295,6 +295,24 @@ define(["jquery"], function ($) {
}
miniTheme.buildBodyElemStyle(elemStyleName);
},
changeThemeMainColor() {
let bgcolorId = localStorage.getItem('layuiminiBgColorId');
if (bgcolorId === null || bgcolorId === undefined || bgcolorId === '') return false;
let bgcolorData = miniTheme.config(bgcolorId);
let mainColor = bgcolorData.headerRightBg
if (bgcolorId == 0) mainColor = '#16b777';
const bgColor = window.getComputedStyle(document.documentElement).getPropertyValue('--ea8-theme-main-color');
document.documentElement.style.setProperty('--ea8-theme-main-color', mainColor);
const iframes = document.getElementsByTagName('iframe');
if (iframes.length === 0) return false;
$.each(iframes, (i, iframe) => {
if (iframe === '' || iframe === undefined) return false;
const iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
iframeDocument.documentElement.style.setProperty('--ea8-theme-main-color', mainColor);
})
},
/**
* 构建主题样式
* @param bgcolorId
@@ -555,6 +573,7 @@ define(["jquery"], function ($) {
bgColorDefault: bgcolorId,
listen: false,
});
miniTheme.changeThemeMainColor()
});
$('body').on('click', '[data-select-style]', function () {
var elemStyleName = $(this).attr('data-select-style');

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
/*! Lazy Load 2.0.0-rc.2 - MIT license - Copyright 2007-2019 Mika Tuupola */
!function(t,e){"object"==typeof exports?module.exports=e(t):"function"==typeof define&&define.amd?define([],e):t.LazyLoad=e(t)}("undefined"!=typeof global?global:this.window||this.global,function(t){"use strict";function e(t,e){this.settings=s(r,e||{}),this.images=t||document.querySelectorAll(this.settings.selector),this.observer=null,this.init()}"function"==typeof define&&define.amd&&(t=window);const r={src:"data-src",srcset:"data-srcset",selector:".lazyload",root:null,rootMargin:"0px",threshold:0},s=function(){let t={},e=!1,r=0,o=arguments.length;"[object Boolean]"===Object.prototype.toString.call(arguments[0])&&(e=arguments[0],r++);for(;r<o;r++)!function(r){for(let o in r)Object.prototype.hasOwnProperty.call(r,o)&&(e&&"[object Object]"===Object.prototype.toString.call(r[o])?t[o]=s(!0,t[o],r[o]):t[o]=r[o])}(arguments[r]);return t};if(e.prototype={init:function(){if(!t.IntersectionObserver)return void this.loadImages();let e=this,r={root:this.settings.root,rootMargin:this.settings.rootMargin,threshold:[this.settings.threshold]};this.observer=new IntersectionObserver(function(t){Array.prototype.forEach.call(t,function(t){if(t.isIntersecting){e.observer.unobserve(t.target);let r=t.target.getAttribute(e.settings.src),s=t.target.getAttribute(e.settings.srcset);"img"===t.target.tagName.toLowerCase()?(r&&(t.target.src=r),s&&(t.target.srcset=s)):t.target.style.backgroundImage="url("+r+")"}})},r),Array.prototype.forEach.call(this.images,function(t){e.observer.observe(t)})},loadAndDestroy:function(){this.settings&&(this.loadImages(),this.destroy())},loadImages:function(){if(!this.settings)return;let t=this;Array.prototype.forEach.call(this.images,function(e){let r=e.getAttribute(t.settings.src),s=e.getAttribute(t.settings.srcset);"img"===e.tagName.toLowerCase()?(r&&(e.src=r),s&&(e.srcset=s)):e.style.backgroundImage="url('"+r+"')"})},destroy:function(){this.settings&&(this.observer.disconnect(),this.settings=null)}},t.lazyload=function(t,r){return new e(t,r)},t.jQuery){const r=t.jQuery;r.fn.lazyload=function(t){return t=t||{},t.attribute=t.attribute||"data-src",new e(r.makeArray(this),t),this}}return e});

View File

@@ -10,10 +10,4 @@
// +----------------------------------------------------------------------
use think\facade\Route;
Route::get('think', function () {
return 'hello,ThinkPHP6!';
});
Route::get('hello/:name', 'index/hello');
Route::any('install', '\app\index\controller\Install@index');

2
runtime/.gitignore vendored
View File

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