Browse Source

修改字段关键词

master
zhanglei 10 hours ago
parent
commit
c88680ad09
  1. 4
      frontend_vue.py
  2. 45
      generator.py
  3. 2
      templates/java/21.controller.java.j2
  4. 66
      templates/java/CommonController.java.j2
  5. 21
      templates/java/CommonVueEnumsEntity.java.j2
  6. 33
      templates/java/controller.java.j2
  7. 11
      templates/vue/index.vue.j2
  8. 18
      utils.py
  9. 69
      vue-vben-admin/apps/web-antd/src/api/fun.ts
  10. 31
      vue-vben-admin/apps/web-antd/src/router/routes/modules/fun.ts
  11. 253
      vue-vben-admin/apps/web-antd/src/views/fun/data.ts
  12. 276
      vue-vben-admin/apps/web-antd/src/views/fun/form.ts
  13. 675
      vue-vben-admin/apps/web-antd/src/views/fun/index.vue

4
frontend_vue.py

@ -24,7 +24,7 @@ def build_fields(table):
for c in cols:
field = {}
field["name"] = to_camel(c["column_name"])
field["comment"] = c["column_comment"]
field["comment"] = tab_chane_comment(c["column_comment"])
field["type"] = c["data_type"]
# ⭐ 在这里调用组件解析
field["component"] = parse_component(c)
@ -45,7 +45,7 @@ def generate(table):
EDIT_FIELDS = cfg["frontend"]["editFields"]
table_info = get_table(table)
entity = table.replace("health_","")
entity = table # table.replace("health_","")
ctx = {
"table":table,
"table_comment":table_info["table_comment"],

45
generator.py

@ -19,7 +19,7 @@ def build_fields(table_name):
"java_name": to_camel(c["column_name"]),
"tab_name": c["column_name"],
"tab_type": c["data_type"],
"java_get_name": to_m_camel(c["column_name"]),
"java_get_name": to_class(c["column_name"]),
"java_type": mysql_to_java(c["data_type"]),
"comment": c["column_comment"]
})
@ -80,28 +80,6 @@ def render(template_name, out_path, context, java_version, overwrite=False):
def generate(table_names: list[str], model_names: list[str], conf_name: str, over_write: bool, java_version: str):
# context = {
# "mainModule": MAIN_MODULE,
# "moduleName": MODULE_NAME,
# "groupId": GROUP_ID,
# "author": AUTHOR,
# "wechat": WECHAT,
# "date": datetime.now().strftime("%Y-%m-%d"),
# "entityLombokModel": True,
# "package": {
# "Base": BASE_PACKAGE,
# "Common": f"{BASE_PACKAGE}.{DEFAULT_PREFIX_URL}.common",
# "Entity": f"{BASE_PACKAGE}.{DEFAULT_PREFIX_URL}.entity",
# "Service": f"{BASE_PACKAGE}.{DEFAULT_PREFIX_URL}.service",
# "Controller": f"{BASE_PACKAGE}.{DEFAULT_PREFIX_URL}.controller",
# "ServiceImpl": f"{BASE_PACKAGE}.{DEFAULT_PREFIX_URL}.service.impl",
# "Mapper": f"{BASE_PACKAGE}.{DEFAULT_PREFIX_URL}.mapper"
# },
# "db": DB,
# "application": APPLICATION,
# "restControllerStyle": REST_CONTROLLER_STYLE
# }
with open(conf_name, "r", encoding="utf-8") as f:
cfg = yaml.safe_load(f)
cfg = resolve_config(cfg)
@ -140,6 +118,7 @@ def generate(table_names: list[str], model_names: list[str], conf_name: str, ove
context.update({
"fields": build_fields(table_name),
"table": {
"tabToPath": tab_to_path(table_name),
"entity": entity,
"lowerEntity": lower_first(entity),
"name": table_name,
@ -354,6 +333,26 @@ def generate(table_names: list[str], model_names: list[str], conf_name: str, ove
context,
java_version
)
case "vue":
# 如果生成vue,则一定要有table_names
if table_names is None or len(table_names) == 0:
print("生成vue相关公共,需要生成相对应的表")
else :
render(
"CommonController.java.j2",
f"{BASE_DIR}{MAIN_OUTPUT_DIR}/controller/CommonController.java",
context,
java_version,
over_write
)
render(
"CommonVueEnumsEntity.java.j2",
f"{BASE_DIR}{MAIN_OUTPUT_DIR}/entity/CommonVueEnumsEntity.java",
context,
java_version,
over_write
)
if __name__ == "__main__":
args = parse_args()

2
templates/java/21.controller.java.j2

@ -28,7 +28,7 @@ import java.util.List;
{% else %}
@Controller
{% endif %}
@RequestMapping("{{ table.name }}")
@RequestMapping("{{ table.tabToPath }}")
public class {{ table.entity }}Controller {
@Resource

66
templates/java/CommonController.java.j2

@ -0,0 +1,66 @@
package {{ package.Controller }};
//VUE 固定引入 CommonEnums
import {{ package.Entity }}.CommonEnums;
import {{ package.Service }}.{{ table.entity }}Service;
import {{ package.Common }}.vo.Result;
//--- import 固定引入 ---//
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
//--- import 固定引入 ---//
/**
* <p>
* 后台枚举 前端控制器
* </p>
*
* @author tiger
* @since 2026-03-17
*/
@Api(tags = "公共方法对外接口")
@RestController
@RequestMapping("/model/common")
public class CommonController {
@Resource
private {{ table.entity }}Service {{table.lowerEntity}}Service;
@ApiOperation(value = "枚举列表查询", response = {{ table.entity }}.class)
@PostMapping(value = "/optionList")
public Result<?> optionList(@Valid @RequestBody {{ table.entity }} param,
@RequestHeader("token") String token,
@RequestHeader(value = "version", defaultValue = "1.0") String version) {
List<{{ table.entity }}> list = {{table.lowerEntity}}Service.list(param);
if (list == null) {
return Result.error("无数据");
}
List<CommonVueEnumsEntity> option = new ArrayList<>();
list.forEach(item -> {
AtomicReference<Boolean> isExt = new AtomicReference<>(false);
option.forEach(item2 -> {
if (item.getFieldName().equals(item2.getFieldName())
&& item.getFieldLabel().equals(item2.getFieldLabel())) {
item2.getOptions().add(item);
isExt.set(true);
}
});
if(!isExt.get()){
CommonVueEnumsEntity optionItem = new CommonVueEnumsEntity();
optionItem.setFieldName(item.getFieldName());
optionItem.setFieldLabel(item.getFieldLabel());
optionItem.setOptions(new ArrayList<>());
optionItem.getOptions().add(item);
option.add(optionItem);
}
});
return Result.OK( option );
}
}

21
templates/java/CommonVueEnumsEntity.java.j2

@ -0,0 +1,21 @@
package {{ package.Entity }};
import lombok.Data;
import java.util.List;
/**
* <p>
* 枚举自增
* </p>
*
* @author zmm
* @since 2026-03-12
*/
@Data
public class CommonVueEnumsEntity {
private static final long serialVersionUID = 1L;
private String fieldName;
private String fieldLabel;
private List<AdminEnums> options;
}

33
templates/java/controller.java.j2

@ -28,7 +28,7 @@ import java.util.List;
{% else %}
@Controller
{% endif %}
@RequestMapping("{{ table.name }}")
@RequestMapping("{{ table.tabToPath }}")
public class {{ table.entity }}Controller {
@Resource
@ -104,4 +104,35 @@ public class {{ table.entity }}Controller {
}
@ApiOperation(value = "枚举自增分页列表查询", response = HealthEnums.class)
@PostMapping(value = "/optionList")
public Result<?> allList(@Valid @RequestBody HealthEnums param,
@RequestHeader("token") String token,
@RequestHeader(value = "version", defaultValue = "1.0") String version) {
List<HealthEnums> list = healthEnumsService.list(param);
if (list == null) {
return Result.error("无数据");
}
List<HealthEnumsOption> option = new ArrayList<>();
list.forEach(item -> {
AtomicReference<Boolean> isExt = new AtomicReference<>(false);
option.forEach(item2 -> {
if (item.getFieldName().equals(item2.getFieldName())
&& item.getFieldLabel().equals(item2.getFieldLabel())) {
item2.getOptions().add(item);
isExt.set(true);
}
});
if(!isExt.get()){
HealthEnumsOption optionItem = new HealthEnumsOption();
optionItem.setFieldName(item.getFieldName());
optionItem.setFieldLabel(item.getFieldLabel());
optionItem.setOptions(new ArrayList<>());
optionItem.getOptions().add(item);
option.add(optionItem);
}
});
return Result.OK( option );
}
}

11
templates/vue/index.vue.j2

@ -231,7 +231,7 @@ const [Modal, modalApi] = useVbenModal({
closable: true,
maskClosable: false,
draggable: true,
width: 800,
class: 'w-[60vw]',
onCancel() {
modalApi.close();
},
@ -258,7 +258,6 @@ const [Modal, modalApi] = useVbenModal({
await {{entity}}Api.save({...finalSubmitValues, id: currentRow.value.id});
Object.assign(currentRow.value, finalSubmitValues);
modalApi.close();
gridApi.reloadRow(currentRow.value);
loadingMessage();
isEdit.value = false;
message.success('保存成功!');
@ -438,13 +437,7 @@ const getEditFormSchema = () => {
const [Form, formApi] = useVbenForm({
schema: editFormSchema,
showDefaultActions: false,
wrapperClass: 'grid-cols-1 md:grid-cols-2',
commonConfig: {
componentProps: {
class: 'w-full',
autocomplete: 'off',
},
},
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
})
// ========== 上传文件 ==========

18
utils.py

@ -2,29 +2,39 @@ import argparse
import re
from copy import deepcopy
# 下划线转-划线
def to_kebab(name):
parts = name.split("_")
return "-".join(parts)
# 下划线转驼峰,首单词的首字母小写
def to_camel(name):
parts = name.split("_")
return parts[0] + "".join(p.capitalize() for p in parts[1:])
# 下划线转驼峰,首单词的首字母大写
def to_m_camel(name):
return "".join(p.capitalize() for p in name.split("_"))
def to_class(name):
return "".join(p.capitalize() for p in name.split("_"))
# 根据逗号分隔符,将字段名转换成驼峰
def to_class_join(name):
parts = name.split(",")
return "'"+"','".join(to_camel(p) for p in parts)+"'"
# 表名转路径
def tab_to_path(name):
return "/"+"/".join(p for p in name.split("_"))
def lower_first(s: str) -> str:
if not s:
return s
return s[0].lower() + s[1:]
# 路径转换
def to_path(name):
parts = name.split(".")
if len(parts) < 2: # 如果没有".",parts长度就是1
@ -43,6 +53,9 @@ def get_first_part(name, default=""):
return parts[0].capitalize()
def tab_chane_comment(name, default="", delimiter=":"):
return name.split(delimiter, 1)[0] if name else default
def parse_args():
parser = argparse.ArgumentParser(description="Java Code Generator")
@ -129,7 +142,7 @@ def resolve_config(config: dict, max_rounds=5) -> dict:
return result
# mysql类型转java类型
def mysql_to_java(mysql_type):
mapping = {
"bigint": "Long",
@ -143,8 +156,8 @@ def mysql_to_java(mysql_type):
}
return mapping.get(mysql_type, "String")
# mysql类型转element-ui组件
def parse_component(field):
t = field["data_type"]
if "datetime" in t:
return "DatePicker"
@ -152,5 +165,4 @@ def parse_component(field):
return "InputTextArea"
if "int" in t:
return "InputNumber"
return "Input"

69
vue-vben-admin/apps/web-antd/src/api/fun.ts

@ -1,69 +0,0 @@
/**
* API
*
*/
import { requestClient } from '#/api/request';
import { useAppConfig } from '@vben/hooks';
import { useAccessStore } from '@vben/stores';
export namespace funApi {
const applicationConfig = useAppConfig(import.meta.env, import.meta.env.PROD);
console.log('=== 接口域名 ===', applicationConfig.javaURL)
/**
*
*/
export function page(params: any) {
return requestClient.post(applicationConfig.javaURL+'/health-fun/page', params,
{ headers: {'Content-Type': 'application/json', Token: useAccessStore().accessToken, version: '1.0.1'}});
}
/**
*
*/
export function get(id: number) {
return requestClient.get(applicationConfig.javaURL+'/health-fun/' + id);
}
/**
*
*/
export function add(data: any) {
return requestClient.post(applicationConfig.javaURL+'/health-fun/add', data,
{ headers: {'Content-Type': 'application/json', Token: useAccessStore().accessToken, version: '1.0.1'}});
}
/**
*
*/
export function save(data: any) {
return requestClient.post(applicationConfig.javaURL+'/health-fun/modify', data,
{ headers: {'Content-Type': 'application/json', Token: useAccessStore().accessToken, version: '1.0.1'}});
}
/**
*
*/
export function remove(id: number) {
return requestClient.delete(applicationConfig.javaURL+'/health-fun/' + id);
}
/**
*
*/
export function enumList(params: any) {
return requestClient.post(applicationConfig.javaURL+'/health-enums/optionList', params,
{ headers: {'Content-Type': 'application/json', Token: useAccessStore().accessToken, version: '1.0.1'}});
}
/**
*
*/
export function upload(params: any) {
return requestClient.post(applicationConfig.javaURL+'/file/up', params,
{ headers: {'Content-Type': 'multipart/form-data', Token: useAccessStore().accessToken, version: '1.0.1'}});
}
}

31
vue-vben-admin/apps/web-antd/src/router/routes/modules/fun.ts

@ -1,31 +0,0 @@
/**
*
*/
import type { RouteRecordRaw } from 'vue-router';
const routes: RouteRecordRaw[] = [
{
path: '/fun',
name: '功能配置表模块',
meta: {
icon: 'ic:baseline-view-in-ar',
order: 1000,
keepAlive: true,
title: '',
},
children: [
{
meta: {
title: "功能配置表列表",
},
name: 'funList',
path: '/fun',
component: () => import('#/views/fun/index.vue'),
},
],
}
];
export default routes;

253
vue-vben-admin/apps/web-antd/src/views/fun/data.ts

@ -1,253 +0,0 @@
/**
*
*/
import type { VxeGridProps } from '#/adapter/vxe-table';
export const columns: VxeGridProps['columns'] = [
{
// 列标题
title: '主键ID',
// 对应字段
field: 'id',
// 宽度
width: 150
},
{
// 列标题
title: '父类ID',
// 对应字段
field: 'funParentId',
// 宽度
width: 150
},
{
// 列标题
title: '功能名称',
// 对应字段
field: 'funName',
// 宽度
width: 150
},
{
// 列标题
title: '功能码对外做映射用',
// 对应字段
field: 'funCode',
// 宽度
width: 150
},
{
// 列标题
title: '功能数值',
// 对应字段
field: 'funValue',
// 宽度
width: 150
},
{
// 列标题
title: '告警级别: 0默认,1级,2级,3级',
// 对应字段
field: 'funWarnLevel',
// 宽度
width: 150
},
{
// 列标题
title: '用位运算组合',
// 对应字段
field: 'funWarnType',
// 宽度
width: 150
},
{
// 列标题
title: '是否在首页展示: 0展示,1不展示',
// 对应字段
field: 'funIndex',
// 宽度
width: 150
},
{
// 列标题
title: '创建时间',
// 对应字段
field: 'createdAt',
// 宽度
width: 150
},
{
// 列标题
title: '创建人',
// 对应字段
field: 'createdBy',
// 宽度
width: 150
},
{
// 列标题
title: '更新时间',
// 对应字段
field: 'updatedAt',
// 宽度
width: 150
},
{
// 列标题
title: '更新人',
// 对应字段
field: 'updatedBy',
// 宽度
width: 150
},
{
// 列标题
title: '删除 0 默认 1删除',
// 对应字段
field: 'deletedFlag',
// 宽度
width: 150
},
{
// 列标题
title: '用户ID',
// 对应字段
field: 'userId',
// 宽度
width: 150
},
{
// 列标题
title: '设备唯一标识',
// 对应字段
field: 'deviceCode',
// 宽度
width: 150
},
{
// 列标题
title: '功能图',
// 对应字段
field: 'funImg',
// 宽度
width: 150
},
{
// 列标题
title: '设备类型',
// 对应字段
field: 'deviceType',
// 宽度
width: 150
},
{
// 列标题
title: '功能状态: 默认 0开通,1关闭,2隐藏',
// 对应字段
field: 'funStatus',
// 宽度
width: 150
},
{
// 列标题
title: '完成10,30,60逐级提升告警持续',
// 对应字段
field: 'graduallyIntervalTime',
// 宽度
width: 150
},
{
// 列标题
title: '消息展示语',
// 对应字段
field: 'funMsgTitle',
// 宽度
width: 150
},
{
// 列标题
title: '持续时间默认值',
// 对应字段
field: 'funStatisticsTimes',
// 宽度
width: 150
},
{
// 列标题
title: '短消息提示语言',
// 对应字段
field: 'funShortMsg',
// 宽度
width: 150
},
];

276
vue-vben-admin/apps/web-antd/src/views/fun/form.ts

@ -1,276 +0,0 @@
/**
* schema
* component parse_component()
*/
import type { VbenFormSchema } from '#/adapter/form';
export const formSchema: VbenFormSchema[] = [
{
// 字段名
fieldName: 'id',
// label
label: '主键ID',
// 自动组件
component: 'InputNumber'
},
{
// 字段名
fieldName: 'funParentId',
// label
label: '父类ID',
// 自动组件
component: 'InputNumber'
},
{
// 字段名
fieldName: 'funName',
// label
label: '功能名称',
// 自动组件
component: 'Input'
},
{
// 字段名
fieldName: 'funCode',
// label
label: '功能码对外做映射用',
// 自动组件
component: 'Input'
},
{
// 字段名
fieldName: 'funValue',
// label
label: '功能数值',
// 自动组件
component: 'Input'
},
{
// 字段名
fieldName: 'funWarnLevel',
// label
label: '告警级别: 0默认,1级,2级,3级',
// 自动组件
component: 'InputNumber'
},
{
// 字段名
fieldName: 'funWarnType',
// label
label: '用位运算组合',
// 自动组件
component: 'InputNumber'
},
{
// 字段名
fieldName: 'funIndex',
// label
label: '是否在首页展示: 0展示,1不展示',
// 自动组件
component: 'InputNumber'
},
{
// 字段名
fieldName: 'createdAt',
// label
label: '创建时间',
// 自动组件
component: 'DatePicker'
},
{
// 字段名
fieldName: 'createdBy',
// label
label: '创建人',
// 自动组件
component: 'InputNumber'
},
{
// 字段名
fieldName: 'updatedAt',
// label
label: '更新时间',
// 自动组件
component: 'DatePicker'
},
{
// 字段名
fieldName: 'updatedBy',
// label
label: '更新人',
// 自动组件
component: 'InputNumber'
},
{
// 字段名
fieldName: 'deletedFlag',
// label
label: '删除 0 默认 1删除',
// 自动组件
component: 'InputNumber'
},
{
// 字段名
fieldName: 'userId',
// label
label: '用户ID',
// 自动组件
component: 'InputNumber'
},
{
// 字段名
fieldName: 'deviceCode',
// label
label: '设备唯一标识',
// 自动组件
component: 'Input'
},
{
// 字段名
fieldName: 'funImg',
// label
label: '功能图',
// 自动组件
component: 'Input'
},
{
// 字段名
fieldName: 'deviceType',
// label
label: '设备类型',
// 自动组件
component: 'InputNumber'
},
{
// 字段名
fieldName: 'funStatus',
// label
label: '功能状态: 默认 0开通,1关闭,2隐藏',
// 自动组件
component: 'InputNumber'
},
{
// 字段名
fieldName: 'graduallyIntervalTime',
// label
label: '完成10,30,60逐级提升告警持续',
// 自动组件
component: 'Input'
},
{
// 字段名
fieldName: 'funMsgTitle',
// label
label: '消息展示语',
// 自动组件
component: 'Input'
},
{
// 字段名
fieldName: 'funStatisticsTimes',
// label
label: '持续时间默认值',
// 自动组件
component: 'InputNumber'
},
{
// 字段名
fieldName: 'funShortMsg',
// label
label: '短消息提示语言',
// 自动组件
component: 'Input'
},
];

675
vue-vben-admin/apps/web-antd/src/views/fun/index.vue

@ -1,675 +0,0 @@
<script setup lang="ts">
/**
* fun管理页面
*/
import {ref, reactive, onMounted, watch, h} from 'vue';
import {useVbenVxeGrid} from '#/adapter/vxe-table';
import {useVbenModal} from '@vben/common-ui';
import {useVbenForm} from '#/adapter/form';
import {Upload, message, Image} from 'ant-design-vue';
import dayjs from 'dayjs';
import {columns} from './data';
import {formSchema} from './form';
import { funApi } from '#/api/fun';
// ========== API URL ==========
const API_BASE_URL = import.meta.env.VITE_GLOB_API_URL || '';
// ========== ==========
const currentRow = ref(null);
const isEdit = ref(false);
const modalTitle = ref('新增');
const uploadFieldName = ref('');
const uploadImageUrl = ref('');
const uploadedUrl = ref(''); //
const formUploadUrls = ref<Record<string, string>>({}); // URL
// ========== Schema ==========
const querySchema = ref([]);
// ========== ==========
const enumData = reactive({
loading: true,
list: [], //
});
//todo
const hiddenColumns = ref(['fun_code', 'graduallyIntervalTime', 'funMsgTitle', 'funImg', 'createdAt', 'updatedBy', 'updatedAt', 'userPassword', 'userId', 'userSys', 'deletedFlag', "userFace"])
const editFields = ['userId','createdAt','createdBy','updatedAt','updatedBy','deletedFlag'];
// ========== ==========
function isImageField(field: string) {
return /img|face|picture/i.test(field);
}
// ========== ==========
async function initEnumData() {
try {
enumData.loading = true;
//
const res = await funApi.enumList({});
const enums = res.result || res;
//
enumData.list = enums;
// queryFields schema`
querySchema.value = formSchema.filter(formItem => {
// 2.
if (hiddenColumns.value.includes(formItem.fieldName)) {
console.log('跳过隐藏字段:', formItem.fieldName)
return false;
}
return true;
}).map(formSchemaTmp => {
//
const matchedEnums = enums.filter(item => item.fieldName === formSchemaTmp.fieldName);
// fieldName options
const allOptions = matchedEnums.reduce((acc, item) => {
if (item.options && Array.isArray(item.options)) {
return [...acc, ...item.options];
}
return acc;
}, []);
//
if (allOptions.length > 0) {
return {
component: 'Select',
fieldName: formSchemaTmp.fieldName,
label: formSchemaTmp.label,
componentProps: {
placeholder: `请选择`,
allowClear: true,
options: allOptions.map(opt => ({
label: opt.label,
value: String(opt.value), // value
})),
},
};
} else {
// 使 Input
return {
component: 'Input',
fieldName: formSchemaTmp.fieldName,
label: formSchemaTmp.label,
componentProps: {
placeholder: `请输入${formSchemaTmp.label}`,
allowClear: true,
},
};
}
});
} catch (error) {
} finally {
enumData.loading = false;
}
}
//
onMounted(() => {
initEnumData();
});
// ========== ( schema) ==========
const [QueryForm, queryFormApi] = useVbenForm({
schema: [], //
layout: 'inline',
showDefaultActions: false,
wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-5',
})
// ========== querySchema ==========
watch(
querySchema,
(newSchema) => {
if (newSchema && newSchema.length > 0) {
console.log('🔄 更新查询表单 schema:', newSchema);
// 使 setState schema
queryFormApi.setState({
schema: newSchema,
});
}
},
{immediate: false}
);
// ========== ==========
function getFullUrl(url: string) {
if (!url) return '';
if (url.startsWith('http')) return url;
return `${API_BASE_URL}${url}`;
}
const gridOptions = {
columns: [
...columns.map(col => {
// ( img, face, picture)使 +
if (isImageField(col.field)) {
return {
...col,
width: 200,
align: 'center',
slots: {
default: ({row}: any) => {
const imgUrl = row[col.field];
const handleClick = (e: Event) => {
e.stopPropagation();
currentRow.value = row;
isEdit.value = true;
openUploadDialog(col.field, imgUrl);
};
if (!imgUrl) {
return h('div', { class: 'flex items-center justify-center gap-2 h-[50px]' }, [
h('span', { class: 'text-gray-400 text-xs' }, '无图片'),
h('button', { onClick: handleClick, class: 'text-blue-500 text-xs' }, '上传'),
]);
}
return h('div', { class: 'flex items-center gap-2 justify-center' }, [
h(Image, {
src: getFullUrl(imgUrl),
width: 80,
height: 60,
preview: true,
}),
h('button', { onClick: handleClick, class: 'text-blue-500 text-xs' }, '更换'),
]);
}
}
};
}
return col;
}),
{
field: 'action',
title: '操作',
width: 120,
fixed: 'right',
slots: {default: 'action'},
},
],
proxyConfig: {
ajax: {
query: async ({page}) => {
try {
//
const queryValues = await queryFormApi.getValues();
const res = await funApi.page({
pageNum: page.currentPage || 1,
pageSize: page?.pageSize || 10,
...queryValues, //
})
const data = res.error?.result || res.result || res
return {
items: data.records || [],
total: data.total || 0
}
} catch (error) {
console.error('查询列表失败:', error)
throw error
}
}
},
response: {
result: 'items',
total: 'total'
}
},
pagerConfig: {
enabled: true,
pageSize: 10,
},
showOverflow: true,
minHeight: '100%',
maxHeight: 'auto',
showHeaderOverflow: true,
}
const [Grid, gridApi] = useVbenVxeGrid({gridOptions})
// ========== Schema ==========
const editFormSchema = formSchema.filter(item => !editFields.includes(item.fieldName));
// ========== ==========
const [Modal, modalApi] = useVbenModal({
centered: true,
closable: true,
maskClosable: false,
draggable: true,
width: 800,
onCancel() {
modalApi.close();
},
onConfirm: async () => {
let loadingMessage: any = null;
try {
const values = await formApi.validateAndSubmitForm();
if (!values) return;
// URL
const submitValues = !isEdit.value
? {...values, ...formUploadUrls.value}
: values;
// Day.js
const finalSubmitValues = {
...submitValues,
userBirthday: submitValues.userBirthday ? dayjs(submitValues.userBirthday).format('YYYY-MM-DD') : null,
};
// loading
loadingMessage = message.loading(isEdit.value ? '保存中...' : '新增中...', 0);
if (isEdit.value && currentRow.value?.id) {
await funApi.save({...finalSubmitValues, id: currentRow.value.id});
Object.assign(currentRow.value, finalSubmitValues);
modalApi.close();
gridApi.reloadRow(currentRow.value);
loadingMessage();
isEdit.value = false;
message.success('保存成功!');
} else {
await funApi.add(finalSubmitValues);
// reload
modalApi.close();
gridApi.reload();
formUploadUrls.value = {};
loadingMessage();
message.success('新增成功!');
}
} catch (error) {
console.error('保存失败:', error);
if (loadingMessage) {
loadingMessage();
}
}
},
onOpenChange(isOpen: boolean) {
if (!isOpen && !isEdit.value) {
formApi.resetForm();
formUploadUrls.value = {};
}
},
})
// ========== ==========
const uploadFileList = ref<any[]>([]);
const [UploadModal, uploadModalApi] = useVbenModal({
centered: true,
closable: true,
maskClosable: false,
draggable: true,
width: 600,
title: '上传图片',
onCancel() {
uploadModalApi.close();
uploadFileList.value = [];
uploadedUrl.value = '';
},
onConfirm: async () => {
let loadingMessage: any = null;
console.log('=== 点击确认保存 ===', {
uploadedUrl: uploadedUrl.value,
isEdit: isEdit.value,
currentRowId: currentRow.value?.id,
uploadFieldName: uploadFieldName.value,
currentRowData: currentRow.value,
});
// URL
if (uploadedUrl.value) {
try {
// URL
if (currentRow.value) {
currentRow.value[uploadFieldName.value] = uploadedUrl.value;
console.log('✅ 已更新当前行图片字段:', {
fieldName: uploadFieldName.value,
newUrl: uploadedUrl.value,
updatedRow: currentRow.value,
});
}
// save
if (isEdit.value && currentRow.value?.id) {
const submitData = {
...currentRow.value,
id: currentRow.value.id
};
console.log('📤 提交保存数据:', submitData);
// loading
loadingMessage = message.loading('保存图片中...', 0);
await funApi.save(submitData);
message.success('保存成功!');
//
uploadModalApi.close();
// gridApi.reload();
loadingMessage()
//
uploadFileList.value = [];
uploadedUrl.value = '';
} else {
console.warn('⚠️ 不满足保存条件:', {
isEdit: isEdit.value,
hasId: !!currentRow.value?.id,
});
message.warning('非编辑模式或无 ID,仅更新预览');
//
uploadModalApi.close();
// gridApi.reload();
}
} catch (error: any) {
console.error('❌ 保存失败:', error);
if (loadingMessage) {
loadingMessage();
}
message.error(error?.message || '保存失败');
}
} else {
message.warning('请先选择并上传图片');
}
},
})
// ========== Schema ==========
// id
// id
const getEditFormSchema = () => {
return formSchema.filter(item => {
// editFields
if (!editFields.includes(item.fieldName)) {
// id
if (!isEdit.value && item.fieldName === 'id') {
return false;
}
return true;
}
return false;
}).map(item => {
//
if (!isEdit.value && isImageField(item.fieldName)) {
// 使 formUploadUrls URL使
const currentImageUrl = formUploadUrls.value[item.fieldName] || '';
return {
...item,
component: 'Upload',
componentProps: {
class: 'w-full',
accept: 'image/*',
maxCount: 1,
listType: 'picture-card',
fileList: currentImageUrl && typeof currentImageUrl === 'string' ? [{
uid: '-1',
name: 'image.png',
status: 'done',
url: currentImageUrl,
}] : (Array.isArray(currentImageUrl) ? currentImageUrl : []),
beforeUpload: async (file: any) => {
console.log('📦 新增表单内上传文件:', file);
try {
const resultUrl = await uploadFile(file);
console.log(item.fieldName + ' 新增表单内上传成功,URL:', resultUrl);
message.success('上传成功!');
// URL
formUploadUrls.value[item.fieldName] = resultUrl;
// schema fileList
setTimeout(() => {
formApi.setState({
schema: getEditFormSchema(),
});
}, 0);
// Deleted:formApi.resetForm();
// false
return false;
} catch (error: any) {
console.error('❌ 新增表单内上传失败:', error);
message.error(error?.message || '上传失败');
return false;
}
},
},
};
}
return item;
});
};
// ========== ==========
const [Form, formApi] = useVbenForm({
schema: editFormSchema,
showDefaultActions: false,
wrapperClass: 'grid-cols-1 md:grid-cols-2',
commonConfig: {
componentProps: {
class: 'w-full',
autocomplete: 'off',
},
},
})
// ========== ==========
async function uploadFile(file: File) {
let loadingMessage: any = null;
// loading
loadingMessage = message.loading('图片上传中...', 0);
const formData = new FormData();
formData.append('file', file);
const res = await funApi.upload(formData);
loadingMessage();
return res.result || res.message || res.url || res;
}
// ========== ==========
function openUploadDialog(fieldName: string, currentUrl: string) {
console.log('=== 打开上传对话框 ===', {
fieldName,
currentUrl,
isEdit: isEdit.value,
currentRowId: currentRow.value?.id,
currentRowFull: currentRow.value,
});
uploadFieldName.value = fieldName;
uploadImageUrl.value = currentUrl || '';
uploadedUrl.value = ''; // URL
uploadFileList.value = []; //
uploadModalApi.open();
}
// ========== - 使 custom-request ==========
async function handleCustomRequest(options: any) {
const {file, onSuccess, onError} = options;
try {
console.log('📦 开始上传文件:', file);
console.log('📦 文件详情:', {
name: file.name,
type: file.type,
size: file.size,
lastModified: file.lastModified,
});
const resultUrl = await uploadFile(file);
console.log('✅ 上传成功,URL:', resultUrl);
message.success('上传成功!');
// URL
uploadedUrl.value = resultUrl;
console.log('=== uploadedUrl 赋值后 ===', uploadedUrl.value);
//
uploadFileList.value = [{
uid: file.uid,
name: file.name,
status: 'done',
url: resultUrl,
}];
//
onSuccess({url: resultUrl});
} catch (error: any) {
console.error('❌ 上传失败:', error);
message.error(error?.message || '上传失败');
onError(error);
}
}
// ========== ==========
function handleAdd() {
isEdit.value = false;
modalTitle.value = '功能配置表新增';
// URL
formUploadUrls.value = {};
// schema ( id)
formApi.setState({
schema: getEditFormSchema(),
});
formApi.resetForm();
modalApi.open();
}
// ========== ==========
function handleEdit(row: any) {
isEdit.value = true;
currentRow.value = row;
modalTitle.value = '功能配置表编辑';
// schema ( id)
formApi.setState({
schema: getEditFormSchema(),
});
// editFields
const formValues: any = {};
editFormSchema.forEach(item => {
const field = item.fieldName;
if (row[field] !== undefined && row[field] !== null) {
formValues[field] = row[field];
}
});
formApi.setValues(formValues);
modalApi.open();
}
// ========== ==========
function handleDelete(row: any) {
if (!row.id) return;
window.confirm(`确定要删除 "${row.userName}" 吗?`) &&
funApi.remove(row.id).then(() => {
gridApi.reload();
});
}
// ========== ==========
function handleSearch() {
console.log('=== 点击查询按钮 ===')
//
gridApi.query();
}
// ========== ==========
function handleReset() {
queryFormApi.resetForm();
gridApi.reload();
}
</script>
<template>
<div
style="height: 100vh; padding: 16px; box-sizing: border-box; display: flex; flex-direction: column;">
<!-- 查询表单 -->
<div class="bg-card mb-4 p-4 rounded shadow flex-shrink-0">
<h3 class="text-lg font-semibold mb-3">查询条件</h3>
<div v-if="enumData.loading" class="text-center py-4 text-gray-500">
正在加载查询条件...
</div>
<QueryForm v-else/>
<div class="mt-3 flex gap-2">
<button @click="handleSearch"
class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
🔍 查询
</button>
<button @click="handleReset"
class="bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-600">
🔄 重置
</button>
</div>
</div>
<!-- 数据表格 (可滚动区域) -->
<div class="flex-1 overflow-hidden bg-card rounded shadow">
<Grid>
<template #toolbar-tools>
<button @click="handleAdd" class="mr-2"> 新增</button>
<button @click="() => gridApi.reload()">🔄 刷新</button>
</template>
<template #action="{ row }">
<div class="flex gap-2">
<button @click="() => handleEdit(row)" class="text-blue-500 hover:text-blue-700"> 编辑
</button>
<button @click="() => handleDelete(row)" class="text-red-500 hover:text-red-700">🗑 删除
</button>
</div>
</template>
</Grid>
</div>
<Modal :title="modalTitle">
<Form/>
</Modal>
<!-- 上传对话框 -->
<UploadModal>
<div style="padding: 20px;margin-top: 16px; text-align: center;">
<Upload
name="file"
:file-list="uploadFileList"
:custom-request="handleCustomRequest"
:show-upload-list="true"
accept="image/*" style="width: 100%;"
>
<div
style="width: 100%; height: 150px; border: 2px dashed #d9d9d9; border-radius: 8px; background: #fafafa; display: flex; flex-direction: column; justify-content: center; align-items: center; cursor: pointer;">
<!-- 使用 SVG 图标代替 -->
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#1890ff"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
style="margin-bottom: 8px;">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="17 8 12 3 7 8"></polyline>
<line x1="12" y1="3" x2="12" y2="15"></line>
</svg>
<span style="color: #666;">点击或拖拽图片到此上传</span>
<span style="color: #999; font-size: 12px; margin-top: 4px;">支持 JPGPNG 格式</span>
</div>
</Upload>
<!-- 展示上传成功后的图片 -->
<div v-if="uploadedUrl" style="margin-top: 16px; text-align: center;">
<p style="color: #67c23a; font-size: 14px; margin-bottom: 8px;"> 上传成功</p>
<p style="color: #999; font-size: 12px; margin-bottom: 8px;">新图片预览:</p>
<Image
:src="uploadedUrl"
style="max-width: 200px; border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);"
:preview="true"
/>
<p style="color: #999; font-size: 12px; margin-top: 8px; word-break: break-all;"></p>
</div>
<!-- 展示原图如果有 -->
<div v-if="uploadImageUrl && !uploadedUrl" style="margin-top: 16px; text-align: center;">
<p style="color: #999; font-size: 12px; margin-bottom: 8px;">当前图片:</p>
<Image
:src="getFullUrl(uploadImageUrl)"
style="max-width: 200px; border-radius: 4px;"
:preview="true"
/>
</div>
</div>
</UploadModal>
</div>
</template>
<style scoped>
:deep(.vxe-pager--wrapper) {
margin-bottom: 0.5rem;
}
</style>
Loading…
Cancel
Save