This commit is contained in:
zhang zhuo 2025-11-19 17:47:24 +08:00
parent 001db78c40
commit 5cd9759340
9 changed files with 334 additions and 45 deletions

View File

@ -18,6 +18,7 @@
"dayjs": "^1.11.18",
"echarts": "^6.0.0",
"element-plus": "2.11.3",
"highlight.js": "^11.11.1",
"image-conversion": "^2.1.1",
"nprogress": "^0.2.0",
"pinia": "^3.0.3",

View File

@ -2,7 +2,6 @@ import auth from "@/api/model/auth"
import system from "@/api/model/system"
import tools from "@/api/model/tools"
export default {
auth,
system,

View File

@ -9,7 +9,7 @@ export default {
return await http.post("gen_table/add", data);
},
edit: async function (data = {}) {
return await http.put("gen_table/edit", data);
return await http.post("gen_table/edit", data);
},
del: async function (data = {}) {
return await http.delete("gen_table/del", data);
@ -20,5 +20,14 @@ export default {
build: async function (data = {}) {
return await http.post("gen_table/build", data);
},
info: async function (data = {}) {
return await http.get("gen_table/info", data);
},
sync: async function (data = {}) {
return await http.post("gen_table/sync", data);
},
show: async function (data = {}) {
return await http.get("gen_table/show", data);
},
}
}

View File

@ -0,0 +1,42 @@
<template>
<section>
<el-icon v-if="copy" class="copy" size="24px" v-copy="copyVal">
<component :is="'el-icon-copy-document'"/>
</el-icon>
<pre><code ref="codeRef" :class="`language-${lang}`"><slot></slot></code></pre>
</section>
</template>
<script setup>
import {nextTick, ref, useSlots, watch} from "vue"
import hljs from "highlight.js"
import "highlight.js/styles/github.css"
const slots = useSlots()
const codeRef = ref(null)
const props = defineProps({
lang: {type: String, required: true, default: ""},
copy: {type: Boolean, default: false},
})
let copyVal = ref("")
watch(() => slots.default?.(), () => {
highlightCode();
}
)
const highlightCode = () => {
nextTick(() => {
if (codeRef.value) {
copyVal.value = codeRef.value?.innerText || ""
codeRef.value.removeAttribute('data-highlighted');
hljs.highlightElement(codeRef.value);
}
})
}
</script>
<style scoped>
.copy {
float: right;
}
</style>

View File

@ -13,7 +13,8 @@
</div>
<div class="right-panel">
<slot name="search"></slot>
<el-button v-if="hasExtendSearchSlot" type="primary" icon="el-icon-filter" @click="show = !show" :plain="show"/>
<el-button v-if="hasExtendSearchSlot" type="primary" icon="el-icon-filter" @click="show = !show"
:plain="show"/>
</div>
</el-header>
<el-main class="nopadding">
@ -92,8 +93,9 @@
<script setup>
import columnSetting from './columnSetting'
import config from "@/config/table"
import {ref, watch, computed, onMounted, onActivated, onDeactivated, getCurrentInstance, useSlots} from "vue"
import tools from "@/utils/tools.js";
import {ref, watch, computed, onMounted, onActivated, onDeactivated, getCurrentInstance, useSlots, nextTick} from "vue"
import tools from "@/utils/tools";
import sortablejs from 'sortablejs'
defineOptions({
name: 'piTable'
@ -106,7 +108,7 @@ defineExpose({
})
const {proxy} = getCurrentInstance()
const emit = defineEmits(['dataChange'])
const emit = defineEmits(['dataChange', 'onEnd'])
const slots = useSlots()
const hasSearchSlot = computed(() => !!slots.search)
@ -145,6 +147,7 @@ const props = defineProps({
hideRefresh: {type: Boolean, default: false},
hideSetting: {type: Boolean, default: false},
paginationLayout: {type: String, default: config.paginationLayout},
sortable: {type: Boolean, default: false}
})
const piTableRef = ref(null)
@ -160,7 +163,6 @@ let currentPage = ref(1)
let prop = ref(null)
let order = ref(null)
let loading = ref(false)
let tableHeight = ref('100%')
let tableParams = ref(props.params)
let userColumn = ref([])
let customColumnShow = ref(false)
@ -170,7 +172,6 @@ let _config = ref({
border: props.border,
stripe: props.stripe
})
let nowWork = ref(null)
let show = ref(false)
watch(() => props.data, () => {
@ -205,6 +206,10 @@ onMounted(() => {
tableData.value = data.value
total.value = tableData.value.length
}
//
if (props.sortable) {
initSortable()
}
})
onActivated(() => {
@ -217,6 +222,19 @@ onDeactivated(() => {
isActive.value = false
})
async function initSortable() {
await nextTick()
const tbody = document.querySelector('.el-table__body-wrapper tbody');
sortablejs.create(tbody, {
animation: 150, // 使
ghostClass: 'sortable-ghost', //
onEnd: ({newIndex, oldIndex}) => { //
if (newIndex === oldIndex) return;
emit('onEnd', newIndex, oldIndex)
}
});
}
async function getCustomColumn() {
userColumn.value = await config.columnSettingGet(props.tableName, props.column)
}

View File

@ -1,4 +1,4 @@
import { nextTick } from 'vue'
import {nextTick} from 'vue'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import router from '@/router'
@ -18,7 +18,8 @@ export default {
keepAlive.pushKeepLive(route.name)
keepAlive.setRouteShow(true)
NProgress.done()
}).then(()=>{})
}).then(() => {
})
},
//关闭标签
close(tag) {
@ -26,15 +27,24 @@ export default {
const keepAlive = keepAliveStore()
const iframe = iframeStore()
const route = tag || router.currentRoute.value
const tagList = viewTags.viewTags
// 查询标签位置
const index = tagList.findIndex(item => item.fullPath === route.fullPath)
// 移除标签
viewTags.removeViewTags(route)
iframe.removeIframeList(route)
keepAlive.removeKeepLive(route.name)
const tagList = viewTags.viewTags
const latestView = tagList.slice(-1)[0]
if (latestView) {
router.push(latestView)
if (index <= 0 && tagList.length >= index) {
const latestView = tagList.slice(-1)[0]
if (latestView) {
router.push(latestView)
} else {
router.push('/')
}
} else {
router.push('/')
// 关闭标签向前一位
const latestView = tagList[index-1]
router.push(latestView)
}
},
//关闭标签后处理
@ -46,7 +56,7 @@ export default {
viewTags.removeViewTags(route)
iframe.removeIframeList(route)
keepAlive.removeKeepLive(route.name)
if(next){
if (next) {
const tagList = viewTags.viewTags
next(tagList)
}
@ -57,15 +67,15 @@ export default {
const route = router.currentRoute.value
const tagList = [...viewTags.viewTags]
tagList.forEach(tag => {
if(tag.meta&&tag.meta.affix || route.fullPath==tag.fullPath){
if (tag.meta && tag.meta.affix || route.fullPath == tag.fullPath) {
return true
}else{
} else {
this.close(tag)
}
})
},
//设置标题
setTitle(title){
setTitle(title) {
const viewTags = viewTagsStore()
viewTags.updateViewTagsTitle(title)
}

View File

@ -2,8 +2,118 @@
<el-container class="pi-panel">
<el-main>
<el-tabs v-model="type" style="width: 100%;">
<el-tab-pane name="base" label="基本信息"></el-tab-pane>
<el-tab-pane name="field" label="字段信息"></el-tab-pane>
<el-tab-pane name="base" label="基本信息">
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px" label-position="right">
<el-row :gutter="20">
<el-col :md="12" :sm="24">
<el-form-item label="表名称" prop="table_name">
<el-input v-model="form.table_name" clearable></el-input>
</el-form-item>
</el-col>
<el-col :md="12" :sm="24">
<el-form-item label="表描述" prop="table_comment">
<el-input v-model="form.table_comment" clearable></el-input>
</el-form-item>
</el-col>
<el-col :md="12" :sm="24">
<el-form-item label="模块名称" prop="controller_name">
<el-input v-model="form.controller_name" clearable></el-input>
</el-form-item>
</el-col>
<el-col :md="12" :sm="24">
<el-form-item label="控制器名称" prop="module_name">
<el-input v-model="form.module_name" clearable></el-input>
</el-form-item>
</el-col>
<el-col :sm="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" :rows="3" type="textarea" clearable></el-input>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-tab-pane>
<el-tab-pane name="field" label="字段信息">
<pi-table :data="form.gen_table_columns" row-key="column_id" hide-pagination hide-do :sortable="true" @on-end="onEnded">
<el-table-column type="index" width="50" label="序号"></el-table-column>
<el-table-column label="字段名称" width="120" prop="column_name"></el-table-column>
<el-table-column label="字段描述" width="150" prop="column_comment">
<template #default="scope">
<el-input v-model="scope.row.column_comment"></el-input>
</template>
</el-table-column>
<el-table-column label="SQL类型" width="120" prop="column_type"></el-table-column>
<el-table-column label="数据类型" width="120" prop="php_type">
<template #default="scope">
<el-select v-model="scope.row.php_type">
<el-option label="string" value="string"></el-option>
<el-option label="int" value="int"></el-option>
<el-option label="float" value="float"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column label="变量名称" width="150" prop="php_field">
<template #default="scope">
<el-input v-model="scope.row.php_field"></el-input>
</template>
</el-table-column>
<el-table-column label="插入" width="120" prop="is_insert">
<template #default="scope">
<el-checkbox v-model="scope.row.is_insert" :true-value="1" :false-value="0"/>
</template>
</el-table-column>
<el-table-column label="编辑" width="120" prop="is_edit">
<template #default="scope">
<el-checkbox v-model="scope.row.is_edit" :true-value="1" :false-value="0"/>
</template>
</el-table-column>
<el-table-column label="列表" width="120" prop="is_list">
<template #default="scope">
<el-checkbox v-model="scope.row.is_list" :true-value="1" :false-value="0"/>
</template>
</el-table-column>
<el-table-column label="查询" width="120" prop="is_query">
<template #default="scope">
<el-checkbox v-model="scope.row.is_query" :true-value="1" :false-value="0"/>
</template>
</el-table-column>
<el-table-column label="查询方式" width="120" prop="query_type">
<template #default="scope">
<el-select v-model="scope.row.query_type">
<el-option label="=" value="eq"></el-option>
<el-option label="!=" value="neq"></el-option>
<el-option label=">" value="gt"></el-option>
<el-option label="<" value="lt"></el-option>
<el-option label=">=" value="gte"></el-option>
<el-option label="<=" value="lte"></el-option>
<el-option label="LIKE" value="like"></el-option>
<el-option label="BETWEEN" value="between"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column label="必填" width="120" prop="is_required">
<template #default="scope">
<el-checkbox v-model="scope.row.is_required" :true-value="1" :false-value="0"/>
</template>
</el-table-column>
<el-table-column label="显示类型" width="120" prop="html_type">
<template #default="scope">
<el-select v-model="scope.row.html_type">
<el-option label="文本框" value="text"></el-option>
<el-option label="文本域" value="textarea"></el-option>
<el-option label="下拉框" value="select"></el-option>
<el-option label="单选框" value="radio"></el-option>
<el-option label="复选框" value="checkbox"></el-option>
<el-option label="日期控件" value="date"></el-option>
<el-option label="时间控件" value="datetime"></el-option>
<el-option label="图片上传" value="image"></el-option>
<el-option label="文件上传" value="file"></el-option>
<el-option label="富文本" value="editor"></el-option>
</el-select>
</template>
</el-table-column>
</pi-table>
</el-tab-pane>
</el-tabs>
</el-main>
<el-footer class="pi-center">
@ -15,24 +125,72 @@
<script setup>
import api from "@/api/index";
import {getCurrentInstance, nextTick, ref} from "vue";
import useTabs from '@/utils/useTabs'
import {getCurrentInstance, onMounted, ref,} from "vue";
import {useRoute, useRouter} from "vue-router";
defineOptions({
name: "toolsGenEdit"
})
const type = ref("base")
const route = useRoute()
const router = useRouter()
const {proxy} = getCurrentInstance()
const formRef = ref(null)
const type = ref("field")
let isSaving = ref(false)
let form = ref({})
let rules = ref({
table_name: [
{required: true, message: '请输入表名称'}
],
controller_name: [
{required: true, message: '请输入控制器名称'}
],
module_name: [
{required: true, message: '请输入模块名称'}
]
})
onMounted(() => {
initData()
})
async function initData() {
const res = await api.tools.gen_table.info({id: route.query.id})
form.value = res.data
}
function onEnded(newIndex, oldIndex) {
//
if (oldIndex < 0 || oldIndex >= form.value.gen_table_columns.length || newIndex < 0 || newIndex >= form.value.gen_table_columns.length) {
return form.value.gen_table_columns.slice(); //
}
//
const newArray = form.value.gen_table_columns.slice();
// splice
const [movedElement] = newArray.splice(oldIndex, 1);
//
newArray.splice(newIndex, 0, movedElement);
form.value.gen_table_columns = newArray
}
function back() {
useTabs.close()
router.back()
}
function submit() {
async function submit() {
//
const validate = await formRef.value.validate().catch(() => {
});
if (!validate) {
return false
}
// isSaving.value = true;
const res = await api.tools.gen_table.edit(form.value);
isSaving.value = false;
proxy.$message.success(res.msg)
}
</script>
<style scoped>

View File

@ -4,7 +4,8 @@
<el-button v-auth="'gen_table:add'" type="primary" icon="el-icon-plus" @click="add"></el-button>
<el-button v-auth="'gen_table:edit'" type="success" icon="el-icon-edit" @click="edit()"
:disabled="selection.length!==1"></el-button>
<el-button v-auth="'gen_table:del'" type="danger" plain icon="el-icon-delete" :disabled="selection.length===0"
<el-button v-auth="'gen_table:del'" type="danger" plain icon="el-icon-delete"
:disabled="selection.length===0"
@click="batch_del"></el-button>
</template>
<template #search>
@ -35,7 +36,8 @@
<el-button v-auth="'gen_table:del'" text type="danger" size="small">删除</el-button>
</template>
</el-popconfirm>
<el-popconfirm title="确定删除吗?" @confirm="del(scope.row, scope.$index)">
<el-popconfirm title="确定要强制同步吗?同步将会覆盖原来的数据!!!"
@confirm="sync(scope.row, scope.$index)">
<template #reference>
<el-button v-auth="'gen_table:sync'" text type="warning" size="small">同步</el-button>
</template>
@ -44,12 +46,13 @@
</template>
</el-table-column>
</pi-table>
<save-dialog v-if="dialogShow" ref="dialogRef" @success="tableRef.refresh()"
@closed="dialogShow=false"></save-dialog>
<save-dialog v-if="dialogSave" ref="saveRef" @closed="dialogSave=false"></save-dialog>
<show-dialog v-if="dialogShow" ref="showRef" @closed="dialogShow=false"></show-dialog>
</template>
<script setup>
import saveDialog from './save'
import showDialog from './show'
import api from "@/api/index";
import {getCurrentInstance, nextTick, ref} from "vue";
import router from "@/router/index"
@ -60,8 +63,10 @@ defineOptions({
const {proxy} = getCurrentInstance()
const tableRef = ref(null)
const dialogRef = ref(null)
const saveRef = ref(null)
const showRef = ref(null)
let dialogSave = ref(false)
let dialogShow = ref(false)
let selection = ref([])
let search = ref({
@ -71,9 +76,9 @@ let search = ref({
//
function add() {
dialogShow.value = true
dialogSave.value = true
nextTick(() => {
dialogRef.value.open()
saveRef.value.open()
})
}
@ -91,16 +96,7 @@ async function edit(row) {
async function show(row) {
dialogShow.value = true
nextTick(() => {
dialogRef.value.open('show', row)
})
}
function show_log(row) {
router.push({
path: "/monitor/crontab_log",
query: {
crontab_id: row?.crontab_id
}
showRef.value.open(row.table_id)
})
}
@ -113,6 +109,14 @@ async function del(row) {
proxy.$message.success(res.msg)
}
async function sync(row) {
const loading = proxy.$loading();
const res = await api.tools.gen_table.sync({id: row.table_id});
tableRef.value.refresh()
loading.close();
proxy.$message.success(res.msg)
}
//
async function batch_del() {
proxy.$confirm(`确定删除选中的 ${selection.value.length} 项吗?如果删除项中含有子集将会被一并删除`, '提示', {

View File

@ -0,0 +1,48 @@
<template>
<el-dialog title="代码预览" v-model="visible" :width="1200" destroy-on-close @closed="$emit('closed')">
<el-tabs v-model="type" style="width: 100%;">
<el-tab-pane name="model" label="model.php">
<pi-pre-code lang="php" copy>{{ data['model.php'] }}</pi-pre-code>
</el-tab-pane>
<el-tab-pane name="request" label="request.php">
<pi-pre-code lang="php" copy>{{ data['request.php'] }}</pi-pre-code>
</el-tab-pane>
<el-tab-pane name="controller" label="controller.php">
<pi-pre-code lang="php" copy>{{ data['controller.php'] }}</pi-pre-code>
</el-tab-pane>
<el-tab-pane name="api.ts" label="api.ts">
<pi-pre-code lang="typescript" copy>{{ data['api.ts'] }}</pi-pre-code>
</el-tab-pane>
<el-tab-pane name="index" label="index.vue">
<pi-pre-code lang="typescript" copy>{{ data['index.vue'] }}</pi-pre-code>
</el-tab-pane>
<el-tab-pane name="save" label="save.vue">
<pi-pre-code lang="typescript" copy>{{ data['save.vue'] }}</pi-pre-code>
</el-tab-pane>
</el-tabs>
</el-dialog>
</template>
<script setup>
import {ref} from 'vue'
import api from "@/api/index.js"
import piPreCode from "@/components/piPreCode"
defineExpose({
open
})
let visible = ref(false)
let type = ref("model")
let data = ref({})
function open(id) {
initData(id)
visible.value = true
}
async function initData(id) {
const res = await api.tools.gen_table.show({id: id})
data.value = res.data
}
</script>