This commit is contained in:
zhang zhuo 2025-11-19 17:46:10 +08:00
parent e1abaa7ff2
commit 92d959937f
12 changed files with 574 additions and 1 deletions

View File

@ -7,6 +7,7 @@ namespace App\Controller;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Contract\ResponseInterface;
use Hyperf\View\RenderInterface;
use Psr\Container\ContainerInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
@ -23,4 +24,7 @@ abstract class AbstractController
#[Inject]
protected EventDispatcherInterface $eventDispatcher;
#[Inject]
protected RenderInterface $render;
}

View File

@ -578,4 +578,56 @@ class System extends Base
}
return $this->toAjax(gtModel::syncTable($table_id));
}
#[GetMapping(path: "gen_table/show")]
#[Auth(auth: "gen_table:show")]
public function genTableShow()
{
$table_id = $this->request->input("id");
if (!$table_id) {
return $this->error("生成失败");
}
$table = gtModel::info($table_id);
if (empty($table)) {
return $this->error("生成失败");
}
// 表前缀
$prefix = config("databases.default.prefix");
// 表名
$table_name = str_replace($prefix, "", $table['table_name']);
$module_name = $table['module_name'];
$controller_name = $table['controller_name'];
// 搜搜字段
$query_fields = [];
$insert_fields = [];
$list_fields = [];
$required_fields = [];
$fields = $table['gen_table_columns'];
foreach ($fields as $column) {
if ($column['is_query']) {
$query_fields[] = $column;
}
if ($column['is_insert'] && !in_array($column['column_name'], ['create_time', 'update_time', 'deleted_at'])) {
$insert_fields[] = $column;
}
if ($column['is_edit'] && !in_array($column['column_name'], ['create_time', 'update_time', 'deleted_at'])) {
$edit_fields[] = $column;
}
if ($column['is_list']) {
$list_fields[] = $column;
}
if ($column['is_required'] && !in_array($column['column_name'], ['create_time', 'update_time', 'deleted_at'])) {
$required_fields[] = $column;
}
}
$data = [
'model.php' => $this->render->getContents('templates/model.php.twig', compact("table_name", "fields", "controller_name", "query_fields", "list_fields")),
'request.php' => $this->render->getContents('templates/request.php.twig', compact("controller_name", "insert_fields", "fields", "required_fields", "edit_fields")),
'controller.php' => $this->render->getContents('templates/controller.php.twig', compact("controller_name", "module_name", "query_fields", "insert_fields", "edit_fields", "table_name")),
'api.ts' => $this->render->getContents('templates/api.ts.twig', compact('table_name')),
'index.vue' => $this->render->getContents('templates/index.vue.twig', compact('table_name', 'list_fields', 'query_fields')),
'save.vue' => $this->render->getContents('templates/save.vue.twig', compact('table_name', 'insert_fields', 'required_fields'))
];
return $this->success("模板信息", $data);
}
}

View File

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace App\Model;
use App\Exception\ServiceException;
use App\Utils\Str;
use Hyperf\DbConnection\Db;
@ -66,6 +65,7 @@ class GenTable extends Model
Db::rollBack();
return false;
}
// 字段管理
$columns = Db::select("SHOW FULL COLUMNS FROM `{$row->Name}`");
$i = 1;

View File

@ -32,8 +32,10 @@
"hyperf/rate-limit": "^3.1",
"hyperf/redis": "~3.1.0",
"hyperf/validation": "^3.1",
"hyperf/view": "^3.1",
"hyperf/websocket-server": "^3.1",
"phpoffice/phpspreadsheet": "^4.5",
"twig/twig": "^3.22",
"zoujingli/ip2region": "^3.0"
},
"require-dev": {

14
config/autoload/view.php Normal file
View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
use Hyperf\View\Mode;
return [
'engine' => Hyperf\View\Engine\TwigEngine::class,
'mode' => Mode::SYNC,
'config' => [
'view_path' => BASE_PATH . '/static/view/',
'cache_path' => BASE_PATH . '/runtime/view/'
]
];

View File

@ -0,0 +1,24 @@
import http from "@/utils/request"
export default {
{{ table_name }}: {
list: async function (data = {}) {
return await http.get("{{ table_name }}/list", data);
},
info: async function (data = {}) {
return await http.get("{{ table_name }}/list", data);
},
add: async function (data = {}) {
return await http.post("{{ table_name }}/add", data);
},
edit: async function (data = {}) {
return await http.put("{{ table_name }}/edit", data);
},
del: async function (data = {}) {
return await http.delete("{{ table_name }}/del", data);
},
option: async function (data = {}) {
return await http.get("{{ table_name }}/option", data);
},
},
}

View File

@ -0,0 +1,71 @@
<?php
/**
* Author: cfn <cfn@leapy.cn>
*/
namespace {{ module_name }};
use App\Annotation\Auth;
use App\Model\{{ controller_name }} as {{ table_name | slice(0, 1) }}Model;
use App\Request\{{ controller_name }} as {{ table_name | slice(0, 1) }}Request;
use App\Utils\Param;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\DeleteMapping;
use Hyperf\HttpServer\Annotation\GetMapping;
use Hyperf\HttpServer\Annotation\PostMapping;
use Hyperf\HttpServer\Annotation\PutMapping;
#[Controller(prefix: "admin")]
class {{ controller_name }} extends Base
{
#[GetMapping(path: "{{ table_name }}/list")]
#[Auth(auth: "{{ table_name }}:list")]
public function {{ table_name }}List()
{
$param = Param::only([{% for field in query_fields %}"{{ field.column_name }}"{% if not loop.last %}, {% endif %}{% endfor %}]);
return $this->success({{ table_name | slice(0, 1) }}Model::list($param));
}
#[GetMapping(path: "{{ table_name }}/option")]
#[Auth(needAuth: false)]
public function {{ table_name }}Option()
{
return $this->success({{ table_name | slice(0, 1) }}Model::options());
}
#[GetMapping(path: "{{ table_name }}/info")]
#[Auth(auth: "{{ table_name }}:info")]
public function {{ table_name }}Info()
{
$id = $this->request->input("id");
return $this->success({{ table_name | slice(0, 1) }}Model::getById());
}
#[PostMapping(path: "{{ table_name }}/add")]
#[Auth(auth: "{{ table_name }}:add")]
public function {{ table_name }}Add()
{
$request = $this->container->get({{ table_name | slice(0, 1)}}Request::class);
$request->scene('add')->validateResolved();
$data = Param::only([{% for field in insert_fields %}"{{ field.column_name }}"{% if not loop.last %}, {% endif %}{% endfor %}]);
return $this->toAjax({{ table_name | slice(0, 1) }}Model::add($data));
}
#[PutMapping(path: "{{ table_name }}/edit")]
#[Auth(auth: "{{ table_name }}:edit")]
public function {{ table_name }}Edit()
{
$request = $this->container->get({{ table_name | slice(0, 1)}}Request::class);
$request->scene('edit')->validateResolved();
$data = Param::only([{% for field in edit_fields %}"{{ field.column_name }}"{% if not loop.last %}, {% endif %}{% endfor %}]);
return $this->toAjax({{ table_name | slice(0, 1) }}Model::edit($data));
}
#[DeleteMapping(path: "{{ table_name }}/del")]
#[Auth(auth: "{{ table_name }}:del")]
public function {{ table_name }}Del()
{
$ids = $this->request->input("ids");
return $this->toAjax({{ table_name | slice(0, 1) }}Model::del($ids));
}
}

View File

@ -0,0 +1,170 @@
<template>
<pi-table ref="tableRef" :apiObj="api.{{ table_name }}.list" @selection-change="selectionChange">
{% if query_fields|length > 2 %}
<template #extend>
{% for field in query_fields %}
{% if loop.index > 2 %}
{% if field.html_type == 'select' or field.html_type == 'radio' or field.html_type == 'checkbox' %}
<el-select v-model="search.{{ field.php_field }}" style="width: 200px;" placeholder="{{ field.column_comment }}" clearable>
<el-option label="" value=""></el-option>
</el-select>
{% elseif field.html_type == 'date' %}
<el-date-picker v-model="search.{{ field.php_field }}" type="daterange" range-separator="-" start-placeholder="{{ field.column_comment }}开始时间"
value-format="YYYY-MM-DD" format="YYYY-MM-DD" end-placeholder="{{ field.column_comment }}结束时间" style="width: 360px;"/>
{% elseif field.html_type == 'datetime' %}
<el-date-picker v-model="search.{{ field.php_field }}" type="datetimerange" range-separator="-" start-placeholder="{{ field.column_comment }}开始时间"
value-format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss" end-placeholder="{{ field.column_comment }}结束时间" style="width: 360px;"/>
{% else %}
<el-input v-model="search.{{ field.php_field }}" placeholder="{{ field.column_comment }}" clearable style="width: 200px;"></el-input>
{% endif %}
{% endif %}
{% endfor %}
</template>
{% endif %}
<template #do>
<el-button v-auth="'{{ table_name }}:add'" type="primary" icon="el-icon-plus" @click="add"></el-button>
<el-button v-auth="'{{ table_name }}:edit'" type="success" icon="el-icon-edit" @click="edit"
:disabled="selection.length!==1"></el-button>
<el-button v-auth="'{{ table_name }}:del'" type="danger" plain icon="el-icon-delete"
:disabled="selection.length===0" @click="batch_del"></el-button>
</template>
<template #search>
{% if query_fields|length > 2 %}
{% for field in query_fields %}
{% if loop.index <= 2 %}
{% if field.html_type == 'select' or field.html_type == 'radio' or field.html_type == 'checkbox' %}
<el-select v-model="search.{{ field.php_field }}" style="width: 200px;" placeholder="{{ field.column_comment }}" clearable>
<el-option label="" value=""></el-option>
</el-select>
{% elseif field.html_type == 'date' %}
<el-date-picker v-model="search.{{ field.php_field }}" type="daterange" range-separator="-" start-placeholder="{{ field.column_comment }}开始时间"
value-format="YYYY-MM-DD" format="YYYY-MM-DD" end-placeholder="{{ field.column_comment }}结束时间" style="width: 360px;"/>
{% elseif field.html_type == 'datetime' %}
<el-date-picker v-model="search.{{ field.php_field }}" type="datetimerange" range-separator="-" start-placeholder="{{ field.column_comment }}开始时间"
value-format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss" end-placeholder="{{ field.column_comment }}结束时间" style="width: 360px;"/>
{% else %}
<el-input v-model="search.{{ field.php_field }}" placeholder="{{ field.column_comment }}" clearable style="width: 200px;"></el-input>
{% endif %}
{% endif %}
{% endfor %}
{% else %}
{% for field in query_fields %}
{% if field.html_type == 'select' or field.html_type == 'radio' or field.html_type == 'checkbox' %}
<el-select v-model="search.{{ field.php_field }}" style="width: 200px;" placeholder="{{ field.column_comment }}" clearable>
<el-option label="" value=""></el-option>
</el-select>
{% elseif field.html_type == 'date' %}
<el-date-picker v-model="search.{{ field.php_field }}" type="daterange" range-separator="-" start-placeholder="{{ field.column_comment }}开始时间"
value-format="YYYY-MM-DD" format="YYYY-MM-DD" end-placeholder="{{ field.column_comment }}结束时间" style="width: 360px;"/>
{% elseif field.html_type == 'datetime' %}
<el-date-picker v-model="search.{{ field.php_field }}" type="datetimerange" range-separator="-" start-placeholder="{{ field.column_comment }}开始时间"
value-format="YYYY-MM-DD HH:mm:ss" format="YYYY-MM-DD HH:mm:ss"
end-placeholder="{{ field.column_comment }}结束时间" style="width: 360px;"/>
{% else %}
<el-input v-model="search.{{ field.php_field }}" placeholder="{{ field.column_comment }}" clearable style="width: 200px;"></el-input>
{% endif %}
{% endfor %}
{% endif %}
<el-button type="primary" icon="el-icon-search" @click="upsearch"></el-button>
</template>
<el-table-column type="selection" width="50"></el-table-column>
{% for field in list_fields %}
<el-table-column label="{{ field.column_comment }}" prop="{{ field.php_field }}"></el-table-column>
{% endfor %}
<el-table-column label="操作" fixed="right" align="right" width="170">
<template #default="scope">
<el-button-group>
<el-button text type="primary" size="small" @click="table_show(scope.row, scope.$index)">查看
</el-button>
<el-button v-auth="'{{ table_name }}:edit'" text type="success" size="small"
@click="table_edit(scope.row, scope.$index)">编辑
</el-button>
<el-popconfirm title="确定删除吗?" @confirm="table_del(scope.row, scope.$index)">
<template #reference>
<el-button v-auth="'{{ table_name }}:del'" text type="danger" size="small">删除</el-button>
</template>
</el-popconfirm>
</el-button-group>
</template>
</el-table-column>
</pi-table>
<save-dialog v-if="dialogShow" ref="dialogRef" @success="tableRef.refresh()" @closed="dialogShow=false"></save-dialog>
</template>
<script setup>
import saveDialog from './save'
import api from "@/api/index";
import {getCurrentInstance, nextTick, ref} from "vue";
defineOptions({
name: ""
})
const {proxy} = getCurrentInstance()
const tableRef = ref(null)
const dialogRef = ref(null)
let dialogShow = ref(false)
let selection = ref([])
let search = ref({
{% for field in query_fields %}
{{ field.php_field }}: null,
{% endfor %}
})
//添加
function add() {
dialogShow.value = true
nextTick(() => {
dialogRef.value.open()
})
}
//编辑
async function table_edit(row) {
dialogShow.value = true
nextTick(() => {
dialogRef.value.open('edit', row)
})
}
//查看
async function table_show(row) {
dialogShow.value = true
nextTick(() => {
dialogRef.value.open('show', row)
})
}
//删除
async function table_del(row) {
const loading = proxy.$loading();
const res = await api.{{ table_name }}.del({ids: [row.{{ table_name }}_id]});
tableRef.value.refresh()
loading.close();
proxy.$message.success(res.msg)
}
//批量删除
async function batch_del() {
proxy.$confirm(`确定删除选中的 ${selection.value.length} 项吗?如果删除项中含有子集将会被一并删除`, '提示', {
type: 'warning'
}).then(async () => {
const loading = proxy.$loading();
const res = await api.{{ table_name }}.del({ids: selection.value.map(item => item.{{ table_name }}_id)});
tableRef.value.refresh()
loading.close();
proxy.$message.success(res.msg)
})
}
//表格选择后回调事件
function selectionChange(e) {
selection.value = e;
}
//搜索
function upsearch() {
tableRef.value.upData(search.value)
}
</script>

View File

@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace App\Model;
/**
{% for field in fields %}
* @property {{ field.php_type }} ${{ field.column_name }}
{% endfor %}
*/
class {{ controller_name }} extends Model
{
/**
* The table associated with the model.
*/
protected ?string $table = '{{ table_name }}';
protected string $primaryKey = '{{ table_name }}_id';
/**
* The attributes that are mass assignable.
*/
protected array $fillable = [];
/**
* The attributes that should be cast to native types.
*/
protected array $casts = [{% set first = true %}{% for field in fields %}{% if field.php_type == 'int' %}{% if not first %}, {% endif %}'{{ field.column_name }}' => 'integer'{% set first = false %}{% endif %}{% endfor %}];
public static function list(array $param)
{
$model = self::query();
{% for field in query_fields %}
{% if field.query_type == 'eq' %}
if (isset($param['{{ field.column_name }}']) && $param['{{ field.column_name }}'] != '') {
$model = $model->where('{{field.column_name}}', $param['{{ field.column_name }}']);
}
{% elseif field.query_type == 'neq' %}
if (isset($param['{{ field.column_name }}']) && $param['{{ field.column_name }}'] != '') {
$model = $model->where('{{field.column_name}}', "<>", $param['{{ field.column_name }}']);
}
{% elseif field.query_type == 'gt' %}
if (isset($param['{{ field.column_name }}']) && $param['{{ field.column_name }}'] != '') {
$model = $model->where('{{field.column_name}}', ">", $param['{{ field.column_name }}']);
}
{% elseif field.query_type == 'lt' %}
if (isset($param['{{ field.column_name }}']) && $param['{{ field.column_name }}'] != '') {
$model = $model->where('{{field.column_name}}', "<", $param['{{ field.column_name }}']);
}
{% elseif field.query_type == 'gte' %}
if (isset($param['{{ field.column_name }}']) && $param['{{ field.column_name }}'] != '') {
$model = $model->where('{{field.column_name}}', ">=", $param['{{ field.column_name }}']);
}
{% elseif field.query_type == 'lte' %}
if (isset($param['{{ field.column_name }}']) && $param['{{ field.column_name }}'] != '') {
$model = $model->where('{{field.column_name}}', "<=", $param['{{ field.column_name }}']);
}
{% elseif field.query_type == 'like' %}
if (isset($param['{{ field.column_name }}']) && $param['{{ field.column_name }}'] != '') {
$model = $model->where('{{field.column_name}}', "like", "%{$param['{{ field.column_name }}']}%");
}
{% elseif field.query_type == 'between' %}
if (isset($param['{{ field.column_name }}']) && $param['{{ field.column_name }}'] != '') {
$model = $model->where('{{field.column_name}}', "between", $param['{{ field.column_name }}']);
}
{% endif %}
{% endfor %}
return $model->orderByDesc("{{ table_name }}_id")
->select([{% for field in list_fields %}"{{ field.column_name }}"{% if not loop.last %}, {% endif %}{% endfor %}])
->paginate((int)$param['limit']);
}
public static function options()
{
return self::select(["{{ table_name }}_id", "{{ table_name }}_name"])->get()->toArray();
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Request;
use Hyperf\Validation\Request\FormRequest;
class {{ controller_name }} extends FormRequest
{
protected array $scenes = [
'add' => [{% for field in insert_fields %}"{{ field.column_name }}"{% if not loop.last %}, {% endif %}{% endfor %}],
'edit' => [{% for field in edit_fields %}"{{ field.column_name }}"{% if not loop.last %}, {% endif %}{% endfor %}],
];
/**
* Determine if the user is authorized to make this request.
*/
public function authorize(): bool
{
return true;
}
/**
* Get the validation rules that apply to the request.
*/
public function rules(): array
{
return [
{% for field in fields %}
'{{ field.column_name }}' => 'required',
{% endfor %}
];
}
public function messages(): array
{
return [
{% for field in edit_fields %}
'{{ field.column_name }}.required' => '{{ field.column_comment }}必传!',
{% endfor %}
];
}
}

View File

@ -0,0 +1,100 @@
<template>
<el-dialog :title="titleMap[mode]" v-model="visible" :width="500" destroy-on-close @closed="$emit('closed')">
<el-form :model="form" :rules="rules" :disabled="mode==='show'" ref="formRef" label-width="100px">
{% for field in insert_fields %}
{% if field.html_type == 'select' %}
<el-form-item label="{{ field.column_comment }}" prop="{{ field.php_field }}">
<el-select v-model="form.{{ field.php_field }}" style="width: 100%" clearable placeholder="请选择{{ field.column_comment }}">
<el-option value="" label=""></el-option>
</el-select>
</el-form-item>
{% elseif field.html_type == 'radio' %}
<el-form-item label="{{ field.column_comment }}" prop="{{ field.php_field }}">
<el-radio-group v-model="form.{{ field.php_field }}">
<el-radio value=""></el-radio>
</el-radio-group>
</el-form-item>
{% elseif field.html_type == 'checkbox' %}
<el-form-item label="{{ field.column_comment }}" prop="{{ field.php_field }}">
<el-checkbox-group v-model="form.{{ field.php_field }}">
<el-checkbox label="" value=""/>
</el-checkbox-group>
</el-form-item>
{% elseif field.html_type == 'date' %}
<el-form-item label="{{ field.column_comment }}" prop="{{ field.php_field }}">
<el-date-picker v-model="form.{{ field.php_field }}" type="date" placeholder="请选择{{ field.column_comment }}" value-format="YYYY-MM-DD"
@change="loadTime" style="width: 100%;"/>
</el-form-item>
{% elseif field.html_type == 'datetime' %}
<el-form-item label="{{ field.column_comment }}" prop="{{ field.php_field }}">
<el-date-picker v-model="form.{{ field.php_field }}" type="datetime" placeholder="请选择{{ field.column_comment }}"
value-format="YYYY-MM-DD HH:mm:ss" @change="loadTime" style="width: 100%;" />
</el-form-item>
{% elseif field.html_type == 'textarea' %}
<el-form-item label="{{ field.column_comment }}" prop="{{ field.php_field }}">
<el-input type="textarea" :rows="3" v-model="form.{{ field.php_field }}" placeholder="请输入{{ field.column_comment }}" clearable></el-input>
</el-form-item>
{% else %}
<el-form-item label="{{ field.column_comment }}" prop="{{ field.php_field }}">
<el-input type="text" v-model="form.{{ field.php_field }}" placeholder="请输入{{ field.column_comment }}" clearable></el-input>
</el-form-item>
{% endif %}
{% endfor %}
</el-form>
<template #footer>
<el-button @click="visible=false" >取 消</el-button>
<el-button v-if="mode!=='show'" type="primary" :loading="isSaveing" @click="submit()">保 存</el-button>
</template>
</el-dialog>
</template>
<script setup>
import {getCurrentInstance, ref} from 'vue'
import api from "@/api/index.js";
defineExpose({
open
})
const emit = defineEmits(['success', 'closed'])
const formRef = ref(null)
const {proxy} = getCurrentInstance()
let mode = ref('add')
let titleMap = ref({
add: '新增',
edit: '编辑',
show: '查看'
})
let visible = ref(false)
let isSaveing = ref(false)
let form = ref({
{% for field in insert_fields %}
{{ field.php_field }}: null
{% endfor %}
})
const rules = ref({
{% for field in required_fields %}
{{ field.php_field }}: [
{required: true, message: '请填写{{ field.column_comment }}'}
],
{% endfor %}
})
function open(m = 'add', data = null) {
mode.value = m
visible.value = true
Object.assign(form.value, data)
}
async function submit(){
// 校验登录
const validate = await formRef.value.validate().catch(() => {});
if(!validate){ return false }
isSaveing.value = true;
const res = form.value.{{table_name}}_id? await api.{{table_name}}.edit(form.value) : await api.{{table_name}}.add(form.value);
isSaveing.value = false;
emit('success')
visible.value = false;
proxy.$message.success(res.msg)
}}
</script>

16
watcher.php Normal file
View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
use Hyperf\Watcher\Driver\ScanFileDriver;
return [
'driver' => ScanFileDriver::class,
'bin' => PHP_BINARY,
'watch' => [
'dir' => ['app', 'config'],
'file' => ['.env'],
'scan_interval' => 2000,
],
'ext' => ['.php', '.env'],
];