mirror of
https://gitee.com/ulthon/ulthon_admin.git
synced 2026-07-01 15:32:48 +08:00
Merge branch 'feat-curd-read'
This commit is contained in:
168
CODERULE.md
Normal file
168
CODERULE.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# Ulthon Admin 项目开发规则
|
||||
|
||||
## 1. 框架技术栈
|
||||
|
||||
- **核心框架**: ThinkPHP 8.0
|
||||
- **开发语言**: PHP 8+
|
||||
- **数据库**: MySQL 8+
|
||||
- **前端框架**: Layui 2.8.6
|
||||
- **模板引擎**: ThinkPHP 内置模板引擎
|
||||
- **代码生成器**: 自定义命令行工具
|
||||
|
||||
## 2. 项目架构设计
|
||||
|
||||
### 2.1 双层架构设计
|
||||
|
||||
Uthon Admin 采用独特的双层架构设计,将系统功能分为核心基础层和业务应用层,便于开发者进行二次开发和功能扩展:
|
||||
|
||||
- **基础核心层 (`extend/base/`)**:
|
||||
- 包含系统内置的所有核心功能代码
|
||||
- 提供标准化的类和方法接口
|
||||
- 不可直接修改,确保系统稳定性
|
||||
- 示例:`extend/base/controller/Base.php`、`extend/base/model/BaseModel.php`
|
||||
|
||||
- **业务应用层 (`application/`)**:
|
||||
- 基于基础核心层扩展开发的具体业务代码
|
||||
- 所有业务类必须继承基础核心层的对应类
|
||||
- 开发者可自由修改和扩展,不会影响核心功能
|
||||
- 示例:`application/admin/controller/User.php` 继承 `base\controller\Base`
|
||||
|
||||
### 2.2 继承重写机制
|
||||
|
||||
**注意**:继承重写机制主要针对 ulthon-admin 自身的内置功能(如管理员管理、菜单、权限等),用于扩展或修改框架内置功能。
|
||||
|
||||
- 对于 ulthon-admin 内置功能,通过继承基础核心类(位于 `extend/base/`),可以获得内置功能并进行扩展
|
||||
- 需要修改内置功能时,在业务类中重写对应方法,保持方法签名一致
|
||||
- 支持调用父类方法:`parent::methodName()`
|
||||
|
||||
**用户自定义代码**:
|
||||
- 用户自己的业务代码不需要遵守该继承规则
|
||||
- 生成的代码直接位于 `application/` 目录下
|
||||
- 可以直接在 `application/` 目录下进行开发和修改
|
||||
|
||||
## 3. 开发流程
|
||||
|
||||
### 3.1 标准开发步骤
|
||||
|
||||
1. **设计数据库表结构**
|
||||
- 按照业务需求设计合理的表结构
|
||||
- 必须为所有字段编写清晰、完整的注释
|
||||
- 遵循命名规范:表名小写,使用下划线分隔
|
||||
|
||||
2. **生成基础代码**
|
||||
- 使用内置命令行工具生成代码
|
||||
- 支持生成到临时目录,避免覆盖已定制代码
|
||||
- 生成内容包括:控制器、模型、视图、验证器、菜单配置
|
||||
|
||||
3. **代码定制**
|
||||
- 基于生成的代码进行业务定制
|
||||
- 仅修改 `application/` 目录下的文件
|
||||
- 使用继承重写机制扩展基础功能
|
||||
|
||||
4. **测试与验证**
|
||||
- 测试所有功能是否正常工作
|
||||
- 确保数据完整性和安全性
|
||||
- 检查代码规范和性能
|
||||
|
||||
### 3.2 代码生成命令
|
||||
|
||||
CURD命令大全
|
||||
|
||||
ulthon_admin框架以内置快速生成CURD的命令, 包括控制器、视图、模型、JS文件。能够使开发者效率得到进一步提升。
|
||||
|
||||
> 备注:在进行CURD命令行之前, 请按照规范设计表结构, 请参考表结构规范模块说明。
|
||||
|
||||
**常用命令**
|
||||
```
|
||||
# 生成ul_test_goods表的CURD
|
||||
php think curd -t test_goods
|
||||
|
||||
# 生成ul_test_goods表的CURD到临时目录
|
||||
php think curd -t test_goods -r
|
||||
|
||||
# 生成ul_test_goods表的CURD, 文件冲突时强制覆盖
|
||||
php think curd -t test_goods -f
|
||||
|
||||
# 删除ul_test_goods表的CURD
|
||||
php think curd -t test_goods -d
|
||||
|
||||
# 生成ul_test_goods表的CURD, 控制器在目录demo下的Goods.php文件
|
||||
# 原路径: `controller\text\Goods.php`,
|
||||
# 此时路径:`controller\demo\Goods.php`
|
||||
# 不建议指定该参数
|
||||
php think curd -t test_goods -c demo/Goods
|
||||
|
||||
# 生成ul_test_goods表的CURD, 模型在目录demo下的Goods.php文件
|
||||
# 原路径: `model\MallCate.php`
|
||||
# 此时路径: `model\demo\Goods.php`
|
||||
# 不建议指定该参数
|
||||
php think curd -t test_goods -m demo/Goods
|
||||
```
|
||||
|
||||
**参数介绍**
|
||||
|
||||
| 短参 | 长参 |说明|
|
||||
| ------------ | ------------ | ------|
|
||||
| -t | `--table=VALUE` | 主表名 |
|
||||
| -c | `--controllerFilename=VALUE` | 控制器文件名 |
|
||||
|-m| `--modelFilename=VALUE` |主表模型文件名|
|
||||
|-f| `--force`|强制覆盖模式 |
|
||||
|-d| `--delete`|删除模式 |
|
||||
|-r |`--runtime`|生成到临时目录|
|
||||
|
||||
**官方命令行文档**: [https://doc.ulthon.com/read/augushong/ulthon_admin/619efc929565f/zh-cn/2.x.html](https://doc.ulthon.com/read/augushong/ulthon_admin/619efc929565f/zh-cn/2.x.html)
|
||||
|
||||
**CURD 文档地址**: [https://doc.ulthon.com/read/augushong/ulthon_admin/619efc816472e/zh-cn/2.x.html](https://doc.ulthon.com/read/augushong/ulthon_admin/619efc816472e/zh-cn/2.x.html)
|
||||
|
||||
### 3.3 表结构设计规范
|
||||
|
||||
**官方表结构规范文档**: [https://doc.ulthon.com/read/augushong/ulthon_admin/619efc9d7af62/zh-cn/2.x.html](https://doc.ulthon.com/read/augushong/ulthon_admin/619efc9d7af62/zh-cn/2.x.html)
|
||||
|
||||
> 以官方在线文档为准,包含详细的表结构设计规范,包括表名规则、特殊字段、数据类型定义等。
|
||||
|
||||
## 4. 命名规范
|
||||
|
||||
**官方命名规范文档**: [https://doc.ulthon.com/read/augushong/ulthon_admin/64fbcf8830640/zh-cn/2.x.html](https://doc.ulthon.com/read/augushong/ulthon_admin/64fbcf8830640/zh-cn/2.x.html)
|
||||
|
||||
> 以官方在线文档为准,包含详细的命名规范说明。
|
||||
|
||||
## 5. 代码规范
|
||||
|
||||
**官方代码规范文档**: [https://doc.ulthon.com/read/augushong/ulthon_admin/64360c249d66a/zh-cn/2.x.html](https://doc.ulthon.com/read/augushong/ulthon_admin/64360c249d66a/zh-cn/2.x.html)
|
||||
|
||||
> 以官方在线文档为准,包含 PHP-CS-Fixer 配置和详细的代码规范说明。
|
||||
|
||||
### 5.1 前端文件组织规范
|
||||
|
||||
**视图与JS文件对应规则**:后台的所有视图,每个HTML文件都会搭配一个对应的JS文件,文件结构示例:
|
||||
|
||||
```
|
||||
goods
|
||||
├─ add.html
|
||||
├─ add.js
|
||||
├─ edit.html
|
||||
├─ edit.js
|
||||
├─ index.html
|
||||
├─ index.js
|
||||
├─ read.html
|
||||
├─ read.js
|
||||
└─ _common.js
|
||||
```
|
||||
|
||||
- 每个功能页面的HTML和JS文件名保持一致
|
||||
- `_common.js` 用于存放该模块通用的JS代码
|
||||
- JS文件中应包含对应页面的业务逻辑、事件绑定等
|
||||
- 这种结构便于维护和扩展,确保前端代码的组织性
|
||||
|
||||
## 7. 二次开发注意事项
|
||||
|
||||
- 不要修改 `extend/base/` 目录下的任何文件
|
||||
- 所有业务逻辑都应放在 `application/` 目录下
|
||||
- 扩展功能时优先考虑继承重写机制
|
||||
- 保持代码结构清晰,便于维护
|
||||
- 定期备份代码和数据库
|
||||
- 遵循版本控制规范,提交信息清晰
|
||||
|
||||
---
|
||||
|
||||
以上规则是 Ulthon Admin 项目开发的基本规范,所有开发者必须严格遵守。遵循这些规则可以提高代码质量,减少开发成本,便于系统维护和扩展。
|
||||
@@ -760,9 +760,9 @@ class BuildCurdServiceBase
|
||||
|
||||
$values = '[';
|
||||
foreach ($array as $k => $v) {
|
||||
$values .= "'{$k}'=>'{$v}',";
|
||||
$values .= "'{$k}' => '{$v}', ";
|
||||
}
|
||||
$values .= ']';
|
||||
$values = rtrim($values, ', ') . ']';
|
||||
$selectCode = $this->replaceTemplate(
|
||||
$this->getTemplate("model{$this->DS}select"),
|
||||
[
|
||||
@@ -774,6 +774,31 @@ class BuildCurdServiceBase
|
||||
return $selectCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建获取器模型(用于多图片、多文件字段).
|
||||
* @param $field
|
||||
* @param $val
|
||||
* @return mixed
|
||||
*/
|
||||
protected function buildAccessorModel($field, $val)
|
||||
{
|
||||
// 获取器名称,如 images -> ImagesListAttr
|
||||
$accessorName = Str::studly($field) . 'List';
|
||||
$separator = isset($val['define']) ? $val['define'] : '|';
|
||||
|
||||
$accessorCode = $this->replaceTemplate(
|
||||
$this->getTemplate("model{$this->DS}accessor"),
|
||||
[
|
||||
'comment' => $val['comment'],
|
||||
'accessorName' => $accessorName,
|
||||
'field' => $field,
|
||||
'separator' => $separator,
|
||||
]
|
||||
);
|
||||
|
||||
return $accessorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建下拉框视图.
|
||||
* @param $field
|
||||
@@ -1179,6 +1204,7 @@ class BuildCurdServiceBase
|
||||
}
|
||||
|
||||
$selectList = '';
|
||||
$accessorList = '';
|
||||
|
||||
$doc_content = '';
|
||||
|
||||
@@ -1186,6 +1212,20 @@ class BuildCurdServiceBase
|
||||
if (isset($val['formType']) && in_array($val['formType'], ['select', 'switch', 'radio', 'checkbox']) && isset($val['define'])) {
|
||||
$selectList .= $this->buildSelectModel($field, $val['define']);
|
||||
}
|
||||
|
||||
// 为多图片、多文件、多选字段生成获取器
|
||||
if (in_array($val['formType'], ['images', 'files', 'checkbox'])) {
|
||||
// checkbox 使用逗号分隔,images/files 使用自定义分隔符
|
||||
if ($val['formType'] == 'checkbox') {
|
||||
$val['define'] = ','; // 多选框固定使用逗号分隔
|
||||
} elseif ($val['formType'] == 'images' || $val['formType'] == 'files') {
|
||||
if (!isset($val['define'])) {
|
||||
$val['define'] = '|'; // 默认分隔符
|
||||
}
|
||||
}
|
||||
$accessorList .= $this->buildAccessorModel($field, $val);
|
||||
}
|
||||
|
||||
$doc_content .= " * @property {$val['property_type']} \${$val['property_name']} {$val['comment']} {$val['data_list']}\n";
|
||||
}
|
||||
|
||||
@@ -1207,6 +1247,7 @@ class BuildCurdServiceBase
|
||||
'deleteTime' => $this->delete ? '"delete_time"' : 'false',
|
||||
'relationList' => $relationList,
|
||||
'selectList' => $selectList,
|
||||
'accessorList' => $accessorList,
|
||||
'doc_content' => $doc_content,
|
||||
]
|
||||
);
|
||||
@@ -1454,6 +1495,38 @@ class BuildCurdServiceBase
|
||||
);
|
||||
$this->fileList[$viewEditFile] = $viewEditValue;
|
||||
|
||||
// 详情页面
|
||||
$viewReadFile = "{$this->rootDir}app{$this->DS}admin{$this->DS}view{$this->DS}{$this->viewFilename}{$this->DS}read.html";
|
||||
$mainFields = '';
|
||||
$basicFields = '';
|
||||
|
||||
// 定义基础字段(右侧显示)
|
||||
$basicFieldList = ['id', 'create_time', 'update_time', 'status', 'sort', 'delete_time'];
|
||||
|
||||
foreach ($this->tableColumns as $field => $val) {
|
||||
if (in_array($field, ['id'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldContent = $this->buildReadFieldContent($field, $val);
|
||||
|
||||
// 根据字段类型分配到不同区域
|
||||
if (in_array($field, $basicFieldList)) {
|
||||
$basicFields .= $fieldContent;
|
||||
} else {
|
||||
$mainFields .= $fieldContent;
|
||||
}
|
||||
}
|
||||
|
||||
$viewReadValue = $this->replaceTemplate(
|
||||
$this->getTemplate("view{$this->DS}read"),
|
||||
[
|
||||
'mainFields' => $mainFields,
|
||||
'basicFields' => $basicFields,
|
||||
]
|
||||
);
|
||||
$this->fileList[$viewReadFile] = $viewReadValue;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -1538,7 +1611,23 @@ class BuildCurdServiceBase
|
||||
}
|
||||
}
|
||||
|
||||
$indexCols .= $this->formatColsRow("{width: 250, title: '操作', templet: ua.table.tool , fixed:'right'},\r");
|
||||
$indexCols .= $this->formatColsRow("{
|
||||
width: 250, title: '操作', templet: ua.table.tool, fixed: 'right', operat: [
|
||||
[{
|
||||
class: 'layui-btn layui-btn-primary layui-btn-xs',
|
||||
method: 'tab',
|
||||
field: 'id',
|
||||
text: '详情',
|
||||
title: '查看详情',
|
||||
auth: 'read',
|
||||
url: init.readUrl,
|
||||
icon: ''
|
||||
}],
|
||||
'edit',
|
||||
'delete'
|
||||
]
|
||||
},
|
||||
");
|
||||
|
||||
$js_index = $this->replaceTemplate(
|
||||
$this->getTemplate("js{$this->DS}index"),
|
||||
@@ -1560,12 +1649,16 @@ class BuildCurdServiceBase
|
||||
|
||||
$js_add_file = "{$this->rootDir}app{$this->DS}admin{$this->DS}view{$this->DS}{$this->viewFilename}{$this->DS}add.js";
|
||||
$js_edit_file = "{$this->rootDir}app{$this->DS}admin{$this->DS}view{$this->DS}{$this->viewFilename}{$this->DS}edit.js";
|
||||
$js_read_file = "{$this->rootDir}app{$this->DS}admin{$this->DS}view{$this->DS}{$this->viewFilename}{$this->DS}read.js";
|
||||
|
||||
$js_add = $this->replaceTemplate($this->getTemplate("js{$this->DS}add"));
|
||||
$this->fileList[$js_add_file] = $js_add;
|
||||
|
||||
$js_edit = $this->replaceTemplate($this->getTemplate("js{$this->DS}edit"));
|
||||
$this->fileList[$js_edit_file] = $js_edit;
|
||||
|
||||
$js_read = $this->replaceTemplate($this->getTemplate("js{$this->DS}read"));
|
||||
$this->fileList[$js_read_file] = $js_read;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -1756,4 +1849,80 @@ class BuildCurdServiceBase
|
||||
|
||||
return 'mixed';
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建详情页字段内容
|
||||
* @param $field
|
||||
* @param $val
|
||||
* @return string
|
||||
*/
|
||||
protected function buildReadFieldContent($field, $val)
|
||||
{
|
||||
$templateFile = "view{$this->DS}module{$this->DS}readText";
|
||||
$define = '';
|
||||
|
||||
// 根据formType去获取具体的详情页模板
|
||||
if ($val['formType'] == 'image') {
|
||||
$templateFile = "view{$this->DS}module{$this->DS}readImage";
|
||||
} elseif ($val['formType'] == 'images') {
|
||||
$templateFile = "view{$this->DS}module{$this->DS}readImages";
|
||||
$define = isset($val['define']) ? $val['define'] : '|';
|
||||
} elseif ($val['formType'] == 'file') {
|
||||
$templateFile = "view{$this->DS}module{$this->DS}readFile";
|
||||
} elseif ($val['formType'] == 'files') {
|
||||
$templateFile = "view{$this->DS}module{$this->DS}readFiles";
|
||||
$define = isset($val['define']) ? $val['define'] : '|';
|
||||
} elseif ($val['formType'] == 'editor') {
|
||||
$templateFile = "view{$this->DS}module{$this->DS}readEditor";
|
||||
} elseif (in_array($field, ['remark']) || $val['formType'] == 'textarea') {
|
||||
$templateFile = "view{$this->DS}module{$this->DS}readTextarea";
|
||||
} elseif ($val['formType'] == 'date') {
|
||||
$templateFile = "view{$this->DS}module{$this->DS}readDate";
|
||||
} elseif ($val['formType'] == 'radio' || $val['formType'] == 'switch') {
|
||||
$templateFile = "view{$this->DS}module{$this->DS}readSwitch";
|
||||
if (isset($val['define']) && !empty($val['define'])) {
|
||||
// 使用 kv 格式直接获取值
|
||||
$var_name = $this->getFieldVarName($field);
|
||||
$define = '<span class="layui-badge">{$' . $var_name . '[$row.' . $field . ']|default=\'\'}</span>';
|
||||
}
|
||||
} elseif ($val['formType'] == 'checkbox') {
|
||||
$templateFile = "view{$this->DS}module{$this->DS}readSelect";
|
||||
if (isset($val['define']) && !empty($val['define'])) {
|
||||
$var_name = $this->getFieldVarName($field);
|
||||
// 使用获取器列表,如 shop_type_list
|
||||
$define = '{volist name="row.' . $field . '_list" id="item"}<span class="layui-badge layui-badge-rim" style="margin-right: 5px;">{$' . $var_name . '[$item]|default=\'\'}</span>{/volist}';
|
||||
}
|
||||
} elseif ($val['formType'] == 'select') {
|
||||
$templateFile = "view{$this->DS}module{$this->DS}readSelect";
|
||||
if (isset($val['bindRelation'])) {
|
||||
$define = '{$row.' . $field . '}';
|
||||
} elseif (isset($val['define']) && !empty($val['define'])) {
|
||||
$var_name = $this->getFieldVarName($field);
|
||||
$define = '{$' . $var_name . '[$row.' . $field . ']|default=\'\'}';
|
||||
}
|
||||
} elseif ($val['formType'] == 'relation') {
|
||||
// 关联表字段暂时使用文本显示
|
||||
$templateFile = "view{$this->DS}module{$this->DS}readText";
|
||||
} elseif ($val['formType'] == 'table') {
|
||||
// 表格选择器暂时使用文本显示
|
||||
$templateFile = "view{$this->DS}module{$this->DS}readText";
|
||||
} elseif ($val['formType'] == 'city') {
|
||||
// 城市选择器暂时使用文本显示
|
||||
$templateFile = "view{$this->DS}module{$this->DS}readText";
|
||||
} elseif ($val['formType'] == 'tag') {
|
||||
// 标签暂时使用文本显示
|
||||
$templateFile = "view{$this->DS}module{$this->DS}readText";
|
||||
}
|
||||
|
||||
$fieldContent = $this->replaceTemplate(
|
||||
$this->getTemplate($templateFile),
|
||||
[
|
||||
'comment' => $val['comment'],
|
||||
'field' => $field,
|
||||
'define' => $define,
|
||||
]
|
||||
);
|
||||
|
||||
return $fieldContent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ var init = {
|
||||
indexUrl: '{{controllerUrl}}/index',
|
||||
addUrl: '{{controllerUrl}}/add' + location.search,
|
||||
editUrl: '{{controllerUrl}}/edit',
|
||||
readUrl: '{{controllerUrl}}/read',
|
||||
deleteUrl: '{{controllerUrl}}/delete',
|
||||
exportUrl: '{{controllerUrl}}/export',
|
||||
modifyUrl: '{{controllerUrl}}/modify',
|
||||
};
|
||||
};
|
||||
|
||||
22
extend/base/admin/service/curd/templates/js/read.code
Normal file
22
extend/base/admin/service/curd/templates/js/read.code
Normal file
@@ -0,0 +1,22 @@
|
||||
$(function(){
|
||||
// 删除数据
|
||||
window.deleteData = function(id) {
|
||||
layer.confirm('确定要删除这条数据吗?', {
|
||||
icon: 3,
|
||||
title: '提示'
|
||||
}, function(index) {
|
||||
$.post('{{:url("delete")}}', {id: id}, function(res) {
|
||||
if (res.code == 0) {
|
||||
layer.msg('删除成功', {icon: 1}, function() {
|
||||
location.href = '{{:url("index")}}';
|
||||
});
|
||||
} else {
|
||||
layer.msg(res.msg, {icon: 2});
|
||||
}
|
||||
}, 'json');
|
||||
layer.close(index);
|
||||
});
|
||||
};
|
||||
|
||||
ua.listen();
|
||||
})
|
||||
12
extend/base/admin/service/curd/templates/model/accessor.code
Normal file
12
extend/base/admin/service/curd/templates/model/accessor.code
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* {{comment}}获取器
|
||||
* @param $value
|
||||
* @return array
|
||||
*/
|
||||
public function get{{accessorName}}Attr($value, $data)
|
||||
{
|
||||
if (empty($data['{{field}}'])) {
|
||||
return [];
|
||||
}
|
||||
return explode('{{separator}}', $data['{{field}}']);
|
||||
}
|
||||
@@ -15,7 +15,7 @@ class {{modelName}} extends TimeModel
|
||||
protected $deleteTime = {{deleteTime}};
|
||||
|
||||
{{selectList}}
|
||||
|
||||
{{accessorList}}
|
||||
{{relationList}}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">{{comment}}</div>
|
||||
<div class="detail-field-value">
|
||||
{notempty name="row.{{field}}"}
|
||||
{$row.{{field}}|date="Y-m-d H:i:s"}
|
||||
{else/}
|
||||
<span class="layui-text-em">暂无数据</span>
|
||||
{/notempty}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="detail-field-item detail-field-full">
|
||||
<div class="detail-field-label">{{comment}}</div>
|
||||
<div class="detail-field-value detail-editor-content">
|
||||
{notempty name="row.{{field}}"}
|
||||
{$row.{{field}}|raw}
|
||||
{else/}
|
||||
<span class="layui-text-em">暂无内容</span>
|
||||
{/notempty}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,13 @@
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">{{comment}}</div>
|
||||
<div class="detail-field-value">
|
||||
{notempty name="row.{{field}}"}
|
||||
<a href="{$row.{{field}}}" target="_blank" class="layui-btn layui-btn-xs layui-btn-normal">
|
||||
<i class="layui-icon layui-icon-download-circle"></i> 下载文件
|
||||
</a>
|
||||
<div style="margin-top: 5px; color: #999; font-size: 12px;">{$row.{{field}}}</div>
|
||||
{else/}
|
||||
<span class="layui-text-em">暂无文件</span>
|
||||
{/notempty}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,17 @@
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">{{comment}}</div>
|
||||
<div class="detail-field-value">
|
||||
{notempty name="row.{{field}}_list"}
|
||||
{volist name="row.{{field}}_list" id="file"}
|
||||
<div style="margin-bottom: 8px;">
|
||||
<a href="{$file}" target="_blank" class="layui-btn layui-btn-xs layui-btn-normal">
|
||||
<i class="layui-icon layui-icon-download-circle"></i> 文件 {$key + 1}
|
||||
</a>
|
||||
<span style="margin-left: 10px; color: #999; font-size: 12px;">{$file}</span>
|
||||
</div>
|
||||
{/volist}
|
||||
{else/}
|
||||
<span class="layui-text-em">暂无文件</span>
|
||||
{/notempty}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">{{comment}}</div>
|
||||
<div class="detail-field-value">
|
||||
{notempty name="row.{{field}}"}
|
||||
<img src="{$row.{{field}}}" class="detail-image" style="max-width: 300px; max-height: 300px; border-radius: 4px; cursor: pointer;" onclick="layer.photos({photos: {data: [{src: this.src}]}, anim: 5})">
|
||||
{else/}
|
||||
<span class="layui-text-em">暂无图片</span>
|
||||
{/notempty}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,12 @@
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">{{comment}}</div>
|
||||
<div class="detail-field-value">
|
||||
{notempty name="row.{{field}}_list"}
|
||||
{volist name="row.{{field}}_list" id="img"}
|
||||
<img src="{$img}" class="detail-image" style="max-width: 150px; max-height: 150px; margin-right: 10px; margin-bottom: 10px; border-radius: 4px; cursor: pointer;" onclick="layer.photos({photos: {data: {volist name='row.{{field}}_list' id='imgItem'}[{src: '{$imgItem}'}{notlast},{/notlast}]{/volist}}, start: {$key}}, anim: 5})">
|
||||
{/volist}
|
||||
{else/}
|
||||
<span class="layui-text-em">暂无图片</span>
|
||||
{/notempty}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,6 @@
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">{{comment}}</div>
|
||||
<div class="detail-field-value">
|
||||
{{define}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,6 @@
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">{{comment}}</div>
|
||||
<div class="detail-field-value">
|
||||
{{define}}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">{{comment}}</div>
|
||||
<div class="detail-field-value">
|
||||
{notempty name="row.{{field}}"}
|
||||
{$row.{{field}}}
|
||||
{else/}
|
||||
<span class="layui-text-em">暂无数据</span>
|
||||
{/notempty}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">{{comment}}</div>
|
||||
<div class="detail-field-value" style="white-space: pre-wrap;">
|
||||
{notempty name="row.{{field}}"}
|
||||
{$row.{{field}}|raw}
|
||||
{else/}
|
||||
<span class="layui-text-em">暂无内容</span>
|
||||
{/notempty}
|
||||
</div>
|
||||
</div>
|
||||
39
extend/base/admin/service/curd/templates/view/read.code
Normal file
39
extend/base/admin/service/curd/templates/view/read.code
Normal file
@@ -0,0 +1,39 @@
|
||||
<div class="layuimini-container detail-container">
|
||||
<div class="layuimini-main">
|
||||
<div class="layui-card detail-card">
|
||||
<div class="layui-card-header detail-header">
|
||||
<div class="layui-row">
|
||||
<div class="layui-col-md9">
|
||||
<h2 class="detail-title">#{$row.id} {$title}</h2>
|
||||
<div class="detail-id">ID: {$row.id}</div>
|
||||
</div>
|
||||
<div class="layui-col-md3 text-right detail-actions">
|
||||
<button class="layui-btn layui-btn-primary" layuimini-content-href="{$Request.param.backTagId}" data-back="1">返回</button>
|
||||
<button class="layui-btn" onclick="location.href='{:url("edit", ["id" => $row.id])}'">编辑</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-card-body detail-content">
|
||||
<div class="layui-row layui-col-space12">
|
||||
<!-- 左侧主体内容 -->
|
||||
<div class="layui-col-md8 detail-main">
|
||||
<div class="detail-field-group">
|
||||
{{mainFields}}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧基础信息 -->
|
||||
<div class="layui-col-md4 detail-side">
|
||||
<h3 class="detail-side-title">基础信息</h3>
|
||||
<div class="detail-field-group">
|
||||
<div class="detail-field-item">
|
||||
<div class="detail-field-label">ID</div>
|
||||
<div class="detail-field-value">{$row.id}</div>
|
||||
</div>
|
||||
{{basicFields}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -130,6 +130,23 @@ trait CurdTraitBase
|
||||
return download($content, $export_file_name . date('YmdHis') . '.xlsx', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @\app\admin\service\annotation\NodeAnotation(title="详情")
|
||||
*/
|
||||
public function read($id)
|
||||
{
|
||||
$row = $this->model->find($id);
|
||||
empty($row) && $this->error('数据不存在');
|
||||
|
||||
// 获取模型的标题(表注释)
|
||||
$title = $row->title;
|
||||
|
||||
$this->assign('row', $row);
|
||||
$this->assign('title', $title);
|
||||
|
||||
return $this->fetch();
|
||||
}
|
||||
|
||||
/**
|
||||
* @\app\admin\service\annotation\NodeAnotation(title="属性修改")
|
||||
*/
|
||||
|
||||
@@ -532,6 +532,128 @@ table样式
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/**
|
||||
详情页面样式
|
||||
*/
|
||||
/* 详情页面容器 */
|
||||
.detail-container {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
/* 详情页面卡片 */
|
||||
.detail-card {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 详情页面头部 */
|
||||
.detail-header {
|
||||
background-color: #f8f9fa;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
/* 详情标题 */
|
||||
.detail-title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 详情ID */
|
||||
.detail-id {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 详情内容区域 */
|
||||
.detail-content {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
/* 左侧主体内容 */
|
||||
.detail-main {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
/* 右侧基础信息 */
|
||||
.detail-side {
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 右侧面板标题 */
|
||||
.detail-side-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
padding: 5px;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
/* 字段组样式 */
|
||||
.detail-field-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 字段项样式 */
|
||||
.detail-field-item {
|
||||
padding: 5px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.detail-field-item:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
/* 字段标签样式 */
|
||||
.detail-field-label {
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
margin-bottom: 5px;
|
||||
font-size: 14px;
|
||||
float: left;
|
||||
width: 100px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* 字段值样式 */
|
||||
.detail-field-value {
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
margin-left: 110px;
|
||||
min-height: 30px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* 详情图片样式 */
|
||||
.detail-image {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
/* 详情操作按钮 */
|
||||
.detail-actions {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.detail-field-label {
|
||||
float: none;
|
||||
width: 100%;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.detail-field-value {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
.search-hide-item {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -538,8 +538,129 @@ table样式
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
详情页面样式
|
||||
*/
|
||||
/* 详情页面容器 */
|
||||
.detail-container {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
/* 详情页面卡片 */
|
||||
.detail-card {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e8e8e8;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 详情页面头部 */
|
||||
.detail-header {
|
||||
background-color: #f8f9fa;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
/* 详情标题 */
|
||||
.detail-title {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 详情ID */
|
||||
.detail-id {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 详情内容区域 */
|
||||
.detail-content {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
/* 左侧主体内容 */
|
||||
.detail-main {
|
||||
background-color: #ffffff;
|
||||
}
|
||||
|
||||
/* 右侧基础信息 */
|
||||
.detail-side {
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 右侧面板标题 */
|
||||
.detail-side-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
padding: 5px;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
/* 字段组样式 */
|
||||
.detail-field-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 字段项样式 */
|
||||
.detail-field-item {
|
||||
padding: 5px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.detail-field-item:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
/* 字段标签样式 */
|
||||
.detail-field-label {
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
margin-bottom: 5px;
|
||||
font-size: 14px;
|
||||
float: left;
|
||||
width: 100px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* 字段值样式 */
|
||||
.detail-field-value {
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
font-size: 14px;
|
||||
margin-left: 110px;
|
||||
min-height: 30px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* 详情图片样式 */
|
||||
.detail-image {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
/* 详情操作按钮 */
|
||||
.detail-actions {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.detail-field-label {
|
||||
float: none;
|
||||
width: 100%;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.detail-field-value {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.search-hide-item {
|
||||
display: none;
|
||||
|
||||
@@ -89,8 +89,7 @@
|
||||
.elem-style-neomorphic .elem-style-sicfi .layui-side.layui-bg-black > .layuimini-menu-left > ul,
|
||||
.elem-style-sicfi .elem-style-neomorphic .layuimini-menu-left-zoom > ul,
|
||||
.elem-style-neomorphic .elem-style-sicfi .layuimini-menu-left-zoom > ul, .elem-style-sicfi .elem-style-neomorphic .layuimini-qiuck-module, .elem-style-neomorphic .elem-style-sicfi .layuimini-qiuck-module, .elem-style-sicfi .elem-style-neomorphic .panel, .elem-style-neomorphic .elem-style-sicfi .panel, .elem-style-sicfi .elem-style-neomorphic .layui-card, .elem-style-neomorphic .elem-style-sicfi .layui-card, .elem-style-sicfi .elem-style-neomorphic .layui-layer, .elem-style-neomorphic .elem-style-sicfi .layui-layer, .elem-style-sicfi .elem-style-neomorphic .layuimini-main, .elem-style-neomorphic .elem-style-sicfi .layuimini-main, .elem-style-sicfi .elem-style-neomorphic .layui-table-tool .layui-inline[lay-event], .elem-style-neomorphic .layui-table-tool .elem-style-sicfi .layui-inline[lay-event], .elem-style-sicfi .elem-style-win7 .layui-layer-easy .layui-layer-btn a, .elem-style-win7 .layui-layer-easy .layui-layer-btn .elem-style-sicfi a, .elem-style-sicfi .elem-style-nes .layui-layer-easy .layui-layer-btn a, .elem-style-nes .layui-layer-easy .layui-layer-btn .elem-style-sicfi a, .elem-style-sicfi .elem-style-nes .layuimini-container .layui-table-tool .layui-inline[lay-event], .elem-style-nes .layuimini-container .layui-table-tool .elem-style-sicfi .layui-inline[lay-event], .elem-style-sicfi .elem-style-gtk .layuimini-header-menu > li.layui-nav-item, .elem-style-gtk .elem-style-sicfi .layuimini-header-menu > li.layui-nav-item, .elem-style-sicfi .elem-style-gtk .layui-tab-brief > .layui-tab-title li, .elem-style-gtk .layui-tab-brief > .layui-tab-title .elem-style-sicfi li, .elem-style-sicfi .elem-style-gtk .layui-laydate-footer span, .elem-style-gtk .layui-laydate-footer .elem-style-sicfi span, .elem-style-sicfi .elem-style-gtk .laydate-footer-btns span, .elem-style-gtk .laydate-footer-btns .elem-style-sicfi span, .elem-style-sicfi .elem-style-gtk .layuimini-container .layui-table-tool .layui-inline[lay-event], .elem-style-gtk .layuimini-container .layui-table-tool .elem-style-sicfi .layui-inline[lay-event], .elem-style-sicfi .layui-layer-easy .layui-layer-btn a {
|
||||
-webkit-clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 0);
|
||||
clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 0);
|
||||
clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 0);
|
||||
padding-right: 10px;
|
||||
position: relative;
|
||||
background-color: transparent;
|
||||
@@ -119,8 +118,7 @@
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
-webkit-clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 0);
|
||||
clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 0);
|
||||
clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 0);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: -0.5px;
|
||||
@@ -138,8 +136,7 @@
|
||||
display: block;
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
-webkit-clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 0);
|
||||
clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 0);
|
||||
clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 0);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: -0.5px;
|
||||
@@ -275,8 +272,7 @@
|
||||
border-color: rgb(126, 252, 246);
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
-webkit-clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 0);
|
||||
clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 0);
|
||||
clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 0);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
@@ -298,8 +294,7 @@
|
||||
background-color: rgb(2, 17, 20) !important;
|
||||
}
|
||||
.elem-style-sicfi .layuimini-color .elem-content li {
|
||||
-webkit-clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 0);
|
||||
clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 0);
|
||||
clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 0);
|
||||
}
|
||||
.elem-style-sicfi .layuimini-color .elem-content li.layui-this {
|
||||
background-color: rgb(126, 252, 246);
|
||||
@@ -671,8 +666,7 @@
|
||||
background-color: rgb(126, 252, 246);
|
||||
border-color: rgb(6, 216, 215);
|
||||
color: rgb(126, 252, 246) !important;
|
||||
-webkit-clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 0);
|
||||
clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 0);
|
||||
clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 0);
|
||||
}
|
||||
.elem-style-sicfi .layui-layer-shade {
|
||||
background-color: #fff !important;
|
||||
@@ -682,8 +676,7 @@
|
||||
width: calc(100% - 10px);
|
||||
}
|
||||
.elem-style-sicfi .layuimini-menu-left .layui-nav .layui-nav-item {
|
||||
-webkit-clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 0);
|
||||
clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 0);
|
||||
clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%, 0 0);
|
||||
color: rgb(126, 252, 246);
|
||||
background-color: rgba(62, 251, 251, 0.5);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user