Compare commits

...

2 Commits

Author SHA1 Message Date
eb3307594a add 原因管理界面 2025-06-11 19:17:37 +08:00
ff112d066d feat 液袋增加图标显示 2025-06-11 15:24:12 +08:00
6 changed files with 675 additions and 165 deletions

View File

@ -8429,3 +8429,121 @@ export interface ResultOutputBagDtoGetOutput {
message?: string | null
data?: BagDto
}
/** 撤销原因分页查询输入 */
export interface PageInputRevokeReasonGetPageInput {
dynamicFilter?: DynamicFilterInfo
/** 排序列表 */
sortList?: SortInput[] | null
/**
*
* @format int32
*/
currentPage?: number
/**
*
* @format int32
*/
pageSize?: number
/** 分页请求 */
filter?: RevokeReasonGetPageInput
}
/** 撤销原因分页查询过滤条件 */
export interface RevokeReasonGetPageInput {
/** 原因描述 */
revokeReason?: string | null
/** 类型 */
revokeType?: string | null
/** 是否下发 */
isDown?: boolean
}
/** 撤销原因分页查询输出 */
export interface RevokeReasonGetPageOutput {
/**
*
* @format int64
*/
id?: number
/** 原因描述 */
revokeReason?: string | null
/** 类型 */
revokeType?: string | null
/** 是否下发 */
isDown?: boolean
/** 创建者 */
createdUserName?: string | null
/**
*
* @format date-time
*/
createdTime?: string | null
/**
*
* @format date-time
*/
modifiedTime?: string | null
}
export interface ResultOutputPageOutputRevokeReasonGetPageOutput {
success?: boolean
code?: string | null
message?: string | null
data?: PageOutputBagDtoGetPageOutput
}
export interface PageOutputRevokeReasonGetPageOutput {
list?: RevokeReasonGetPageOutput[] | null
total?: number
}
/** 撤销原因详情输出 */
export interface RevokeReasonGetOutput {
/** 原因描述 */
revokeReason?: string | null
/** 类型 */
revokeType?: string | null
/** 是否下发 */
isDown?: boolean
/**
* Id
* @format int64
*/
id: number
}
/** 撤销原因新增/更新输入 */
export interface RevokeReasonDto {
/** 原因描述 */
keyWord?: string | null
revokeReason?: string | null
/** 类型 */
revokeType?: string | null
/** 是否下发 */
isDown?: boolean
/**
* Id
* @format int64
*/
id?: number
}
/** 撤回类型枚举项 */
export interface RevokeTypeEnumItem {
/** 枚举值 */
value: number
/** 枚举名称 */
name: string
/** 显示名称/描述 */
label: string
}
/** 撤回类型枚举列表输出 */
export interface RevokeTypeEnumListOutput {
success?: boolean
code?: string | null
message?: string | null
data?: RevokeTypeEnumItem[]
}

View File

@ -0,0 +1,84 @@
/* eslint-disable */
/* tslint:disable */
/*
* ---------------------------------------------------------------
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
* ## ##
* ## AUTHOR: adademo / https://github.com/adademo/swagger-typescript-api ##
* ## SOURCE: https://github.com/adademo/swagger-typescript-api ##
* ---------------------------------------------------------------
*/
import { PageInputRevokeReasonGetPageInput, RevokeReasonDto, RevokeReasonGetOutput,ResultOutputPageOutputRevokeReasonGetPageOutput, RevokeTypeEnumListOutput } from './data-contracts'
import { RequestParams } from './http-client'
import { ContentType, HttpClient } from './http-client'
export class RevokeReasonApi extends HttpClient {
/**
*
*/
getPage = (data: PageInputRevokeReasonGetPageInput, params: RequestParams = {}) =>
this.request<ResultOutputPageOutputRevokeReasonGetPageOutput>({
path: `/api/admin/revoke-reason/get-page`,
method: 'POST',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
get = (params: { id: number }, requestParams: RequestParams = {}) =>
this.request<RevokeReasonGetOutput>({
path: `/api/admin/revoke-reason/get`,
method: 'GET',
query: params,
...requestParams,
})
/**
*
*/
add = (data: RevokeReasonDto, params: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/revoke-reason/add`,
method: 'POST',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
update = (data: RevokeReasonDto, params: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/revoke-reason/update`,
method: 'PUT',
body: data,
type: ContentType.Json,
...params,
})
/**
*
*/
softDelete = (params: { id: number }, requestParams: RequestParams = {}) =>
this.request<any>({
path: `/api/admin/revoke-reason/soft-delete`,
method: 'DELETE',
query: params,
...requestParams,
})
/**
*
*/
getRevokeTypeEnumList = (params: RequestParams = {}) =>
this.request<RevokeTypeEnumListOutput>({
path: `/api/admin/revoke-reason/get-revoke-type-enum-list`,
method: 'GET',
...params,
})
}

View File

@ -1,20 +1,7 @@
<template>
<el-dialog
v-model="state.dialogVisible"
:title="props.title"
width="900px"
:close-on-click-modal="false"
:close-on-press-escape="false"
draggable
destroy-on-close
>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-width="120px"
@submit.prevent
>
<el-dialog v-model="state.dialogVisible" :title="props.title" width="900px" :close-on-click-modal="false"
:close-on-press-escape="false" draggable destroy-on-close>
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px" @submit.prevent>
<div class="form-section">
<div class="section-title">基本信息</div>
<el-row :gutter="25">
@ -26,12 +13,7 @@
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="型号" prop="model">
<el-select v-model="form.model" placeholder="请选择型号" style="width: 100%;">
<el-option
v-for="item in state.modelList"
:key="item.value"
:label="item.label"
:value="item.value"
/>
<el-option v-for="item in state.modelList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
@ -54,7 +36,21 @@
<el-form-item label="残余量" prop="residualVolume">
<el-input-number v-model="form.residualVolume" :min="0" style="width: 100%;" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="图标" prop="materialIcon">
<el-upload class="h100 personal-user-left-upload" :action="iconAction" :headers="iconHeaders"
:data="{ autoUpdate: true }" :show-file-list="false" :before-upload="() => {
state.token = storesUserInfo.getToken()
state.iconLoading = true
}
" :on-success="iconSuccess" :on-error="iconError">
<img :src="iconUrl" />
</el-upload>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="状态" prop="status">
<el-switch v-model="form.status" />
@ -73,11 +69,16 @@
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue'
import { ref, computed, reactive, onMounted, getCurrentInstance } from 'vue'
import { BagDto, BagModelEnum } from '/@/api/admin/data-contracts'
import { ItemDefBagApi } from '/@/api/admin/item-def-bag'
import { DictApi } from '/@/api/admin/Dict'
import eventBus from '/@/utils/mitt'
import { useUserInfo } from '/@/stores/userInfo'
import pinia from '/@/stores/index'
import { storeToRefs } from 'pinia'
import { AxiosResponse } from 'axios'
const props = defineProps({
title: {
@ -86,12 +87,17 @@ const props = defineProps({
}
})
const { proxy } = getCurrentInstance() as any
const formRef = ref()
const storesUserInfo = useUserInfo(pinia)
const { userInfos } = storeToRefs(storesUserInfo)
const state = reactive({
dialogVisible: false,
sureLoading: false,
modelList: [] as BagModelEnum[],
iconLoading: false,
token: storesUserInfo.getToken(),
})
const form = reactive<Partial<BagDto>>({
@ -103,6 +109,7 @@ const form = reactive<Partial<BagDto>>({
lowerLimitVolume: 0,
residualVolume: 0,
status: true,
materialIcon: '',
})
const rules = {
@ -166,6 +173,34 @@ const onSubmit = async () => {
state.sureLoading = false
}
const iconUrl = computed(() => {
return form.materialIcon || 'https://img2.baidu.com/it/u=1978192862,2048448374&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=500'
})
const iconAction = computed(() => {
return import.meta.env.VITE_API_URL + '/api/admin/user/avatar-upload'
})
const iconHeaders = computed(() => {
return { Authorization: 'Bearer ' + state.token }
})
const iconSuccess = (res: AxiosResponse) => {
state.iconLoading = false
if (!res?.success) {
if (res.msg) {
proxy.$modal.msgError(res.msg)
}
return
}
form.materialIcon = res.data
}
const iconError = (error: any) => {
state.iconLoading = false
console.error('上传失败:', error)
}
defineExpose({
open
})
@ -189,4 +224,13 @@ defineExpose({
font-size: 16px;
font-weight: bold;
}
:deep(.el-upload) {
height: 90px;
width: 90px;
img {
width: 90px;
height: 90px;
}
}
</style>

View File

@ -0,0 +1,149 @@
<template>
<div class="uspreason-form">
<el-dialog
v-model="state.visible"
destroy-on-close
:title="title"
draggable
:close-on-click-modal="false"
:close-on-press-escape="false"
width="600px"
>
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px" size="default">
<div class="form-section">
<el-row :gutter="25">
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<el-form-item label="原因类型" prop="revokeType">
<el-select v-model="form.revokeType" placeholder="请选择原因类型" style="width: 100%">
<el-option
v-for="item in state.revokeTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<el-form-item label="原因描述" prop="revokeReason">
<el-input
v-model="form.revokeReason"
type="textarea"
:rows="3"
placeholder="请输入原因描述"
style="width: 100%"
/>
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="onCancel" size="default"> </el-button>
<el-button type="primary" @click="onSubmit" size="default" :loading="state.loading"> </el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, defineProps, defineExpose, onMounted } from 'vue'
import { RevokeReasonApi } from '/@/api/admin/revoke-reason'
import type { RevokeReasonDto, RevokeTypeEnumItem } from '/@/api/admin/data-contracts'
import eventBus from '/@/utils/mitt'
const props = defineProps<{ title: string }>()
const emit = defineEmits(['ok'])
const formRef = ref()
const state = reactive({
visible: false,
loading: false,
revokeTypeOptions: [] as RevokeTypeEnumItem[]
})
const form = reactive<RevokeReasonDto>({
revokeReason: '',
revokeType: '',
isDown: false,
id: 0
})
const rules = {
revokeReason: [{ required: true, message: '请输入原因描述', trigger: 'blur' }],
revokeType: [{ required: true, message: '请选择类型', trigger: 'change' }]
}
//
const getRevokeTypeOptions = async () => {
try {
const res = await new RevokeReasonApi().getRevokeTypeEnumList()
if (res?.success && res.data) {
state.revokeTypeOptions = res.data
}
} catch (error) {
console.error('获取撤回类型枚举列表失败:', error)
}
}
function open(row?: Partial<RevokeReasonDto>) {
if (row) {
Object.assign(form, row)
} else {
Object.assign(form, { revokeReason: '', revokeType: '', isDown: false, id: 0 })
}
state.visible = true
}
function onCancel() {
state.visible = false
}
function onSubmit() {
formRef.value.validate(async (valid: boolean) => {
if (!valid) return
state.loading = true
try {
if (form.id) {
await new RevokeReasonApi().update(form)
} else {
await new RevokeReasonApi().add(form)
}
state.visible = false
eventBus.emit('refreshUspReason')
} finally {
state.loading = false
}
})
}
onMounted(() => {
getRevokeTypeOptions()
})
defineExpose({ open })
</script>
<style lang="scss" scoped>
.form-section {
margin-bottom: 24px;
&:last-child {
margin-bottom: 0;
}
}
:deep(.el-form-item) {
margin-bottom: 18px;
}
:deep(.el-switch__label) {
font-size: 13px;
}
:deep(.el-textarea__inner) {
resize: none;
}
</style>

View File

@ -0,0 +1,257 @@
<template>
<my-layout>
<el-card class="my-query-box mt8" shadow="never" :body-style="{ paddingBottom: '0' }">
<el-form ref="queryForm" :model="queryParams" :inline="true" label-width="auto" :label-position="'left'" @submit.stop.prevent>
<el-form-item label="原因描述" prop="revokeReason">
<el-input v-model="queryParams.filter.keyWord" placeholder="请输入原因描述" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="原因类型" prop="revokeType">
<el-select v-model="queryParams.filter.revokeType" placeholder="请选择原因类型" clearable style="width: 200px">
<el-option v-for="item in revokeTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="开始时间">
<el-date-picker
v-model="queryParams.filter.startTime"
type="datetime"
placeholder="选择开始时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 180px"
/>
</el-form-item>
<el-form-item label="结束时间">
<el-date-picker
v-model="queryParams.filter.endTime"
type="datetime"
placeholder="选择结束时间"
format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
style="width: 180px"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="ele-Search" @click="handleQuery">查询</el-button>
<el-button v-auth="'api:admin:revoke-reason:add'" type="primary" icon="ele-Plus" @click="handleAdd">新增</el-button>
</el-form-item>
</el-form>
</el-card>
<el-card class="my-fill mt8" shadow="never">
<el-table v-loading="loading" :data="revokeReasonList" row-key="id" style="width: 100%" border>
<el-table-column label="原因类型" min-width="120" show-overflow-tooltip>
<template #default="{ row }">
{{ getRevokeTypeName(row.revokeType) }}
</template>
</el-table-column>
<el-table-column prop="revokeReason" label="原因描述" min-width="150" show-overflow-tooltip />
<el-table-column label="操作" width="180" fixed="right" header-align="center" align="center">
<template #default="{ row }">
<el-button v-auth="'api:admin:revoke-reason:update'" icon="ele-EditPen" size="small" text type="primary" @click="handleEdit(row)">编辑</el-button>
<el-button v-auth="'api:admin:revoke-reason:soft-delete'" icon="ele-Delete" size="small" text type="danger" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="my-flex my-flex-end" style="margin-top: 10px">
<el-pagination
v-model:currentPage="queryParams.currentPage"
v-model:page-size="queryParams.pageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
size="small"
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
layout="total, sizes, prev, pager, next, jumper"
/>
</div>
</el-card>
<uspreason-form ref="formRef" :title="state.formTitle" @ok="getList" />
</my-layout>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, onBeforeMount } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { RevokeReasonApi } from '/@/api/admin/revoke-reason'
import eventBus from '/@/utils/mitt'
import type {
RevokeReasonGetPageOutput,
RevokeTypeEnumItem,
RevokeTypeEnumListOutput,
PageOutputRevokeReasonGetPageOutput
} from '/@/api/admin/data-contracts'
import UspreasonForm from './components/uspreason-form.vue'
import { number } from 'echarts'
const loading = ref(false)
const showSearch = ref(true)
const total = ref(0)
const revokeReasonList = ref<RevokeReasonGetPageOutput[]>([])
const revokeTypeOptions = ref<RevokeTypeEnumItem[]>([])
const formRef = ref()
const dateRange = ref<[string, string] | null>(null)
const state = reactive({
loading: false,
formTitle: ''
})
const queryParams = reactive({
currentPage: 1,
pageSize: 10,
filter: {
keyWord: '',
revokeType: '',
isDown: false,
startTime: '',
endTime: ''
}
})
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const res = await new RevokeReasonApi().getPage(queryParams)
console.log(res)
revokeReasonList.value = res.data?.list || []
total.value = res.data?.total || 0
} catch (error) {
console.error('获取列表失败:', error)
} finally {
loading.value = false
}
}
/** 获取撤销类型枚举列表 */
const getRevokeTypeOptions = async () => {
try {
const res = await new RevokeReasonApi().getRevokeTypeEnumList()
revokeTypeOptions.value = res.data ?? []
} catch (error) {
console.error('获取撤销类型枚举列表失败:', error)
}
}
const getRevokeTypeName = (revokeType: string) => {
const item = revokeTypeOptions.value.find(item => item.value === Number(revokeType))
return item?.label || ''
}
/** 处理日期范围变化 */
const handleDateRangeChange = (val: [string, string] | null) => {
if (val) {
queryParams.filter.startTime = val[0]
queryParams.filter.endTime = val[1]
} else {
queryParams.filter.startTime = ''
queryParams.filter.endTime = ''
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.currentPage = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryParams.filter = {
keyWord: '',
revokeType: '',
isDown: false,
startTime: '',
endTime: ''
}
handleQuery()
}
/** 新增按钮操作 */
const handleAdd = () => {
state.formTitle = '新增原因'
formRef.value?.open()
}
/** 修改按钮操作 */
const handleEdit = (row: RevokeReasonGetPageOutput) => {
state.formTitle = '编辑原因'
formRef.value?.open(row)
}
/** 删除按钮操作 */
const handleDelete = (row: RevokeReasonGetPageOutput) => {
ElMessageBox.confirm('确认要删除该记录吗?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await new RevokeReasonApi().softDelete({ id: row.id! })
if (res.success) {
ElMessage.success('删除成功')
getList()
}
} catch (error) {
console.error('删除失败:', error)
}
})
}
/** 每页条数改变 */
const handleSizeChange = (val: number) => {
queryParams.pageSize = val
getList()
}
/** 当前页改变 */
const handleCurrentChange = (val: number) => {
queryParams.currentPage = val
getList()
}
onMounted(() => {
getRevokeTypeOptions()
getList()
eventBus.on('refreshUspReason', getList)
})
onBeforeMount(() => {
eventBus.off('refreshUspReason')
})
</script>
<style lang="scss" scoped>
.my-query-box {
:deep(.el-form-item) {
margin-bottom: 16px;
}
}
.my-fill {
flex: 1;
display: flex;
flex-direction: column;
}
.my-flex {
display: flex;
}
.my-flex-end {
justify-content: flex-end;
}
.mt8 {
margin-top: 8px;
}
.mb8 {
margin-bottom: 8px;
}
</style>

View File

@ -1,142 +0,0 @@
# 页面开发指南
## 页面结构
1. 列表页面
- 搜索区域:关键词搜索
- 操作按钮:新增、查询
- 表格区域:基础字段展示
- 分页区域:标准分页组件
2. 编辑页面
- 基本信息模块
- 网络配置模块
- 设备配置模块
## 样式规范
1. 对话框配置
```html
<el-dialog
v-model="state.showDialog"
destroy-on-close
:title="title"
draggable
:close-on-click-modal="false"
:close-on-press-escape="false"
width="900px"
>
```
2. 表单布局
```html
<el-form :model="form" ref="formRef" size="default" label-width="120px">
<div class="form-section">
<div class="section-title">模块标题</div>
<el-row :gutter="25">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-form-item label="字段名称" prop="fieldName">
<el-input v-model="form.fieldName" clearable placeholder="请输入" />
</el-form-item>
</el-col>
</el-row>
</div>
</el-form>
```
3. 样式定义
```scss
.form-section {
margin-bottom: 24px;
&:last-child { margin-bottom: 0; }
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin-bottom: 16px;
padding-bottom: 8px;
border-bottom: 2px solid #f0f0f0;
position: relative;
&:before {
content: '';
position: absolute;
left: 0;
bottom: -2px;
width: 40px;
height: 2px;
background: #409eff;
}
}
```
## 开发规范
1. 组件命名
- 目录:小写字母,用横线分隔
- 组件PascalCase
- 组合函数camelCase
2. 代码组织
```typescript
// 1. 导入声明
import { reactive, ref } from 'vue'
// 2. 类型定义
interface State {
showDialog: boolean
form: FormData
}
// 3. 组件定义
const props = defineProps({
title: String
})
// 4. 状态定义
const state = reactive<State>({
showDialog: false,
form: {}
})
// 5. 方法定义
const handleSubmit = async () => {
// 处理逻辑
}
```
3. 表单验证
```typescript
const rules = {
fieldName: [
{ required: true, message: '请输入', trigger: ['blur', 'change'] }
]
}
```
4. API 调用
```typescript
const handleSave = async () => {
try {
const res = await api.save(state.form)
if (res.success) {
ElMessage.success('保存成功')
emit('refresh')
state.showDialog = false
}
} catch (error) {
console.error(error)
}
}
```
## 注意事项
1. 保持与 uspscale 页面风格一致
2. 使用响应式布局适配不同屏幕
3. 统一表单验证规则和错误提示
4. 优化用户交互体验
5. 保持代码风格统一
6. 注意性能优化
7. 遵循 TypeScript 类型规范
---
**注意**:此模板基于 Vue 3 + TypeScript + Element Plus + Pinia 技术栈,使用时请根据实际项目技术栈进行调整。