13 changed files with 161 additions and 1343 deletions
@ -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 ); |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
@ -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'}}); |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -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; |
|
||||
@ -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 |
|
||||
}, |
|
||||
|
|
||||
|
|
||||
]; |
|
||||
@ -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' |
|
||||
|
|
||||
}, |
|
||||
|
|
||||
|
|
||||
]; |
|
||||
@ -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;">支持 JPG、PNG 格式</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…
Reference in new issue