5.6页面样式bug更改
This commit is contained in:
parent
3b0eef5bbb
commit
7b04cb0076
9
frontend/src/api/changePassWord/index.ts
Normal file
9
frontend/src/api/changePassWord/index.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
//修改密码
|
||||||
|
export function updatePassword(params:any) {
|
||||||
|
return request({
|
||||||
|
url: '/user/updatePassword',
|
||||||
|
method: 'post',
|
||||||
|
data: params
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -40,6 +40,13 @@ export function submitFishDraft(data:any) {
|
|||||||
data
|
data
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
//提交全部过鱼数据
|
||||||
|
export function batchApproveAll() {
|
||||||
|
return request({
|
||||||
|
url: '/data/fishDraft/submitDraftsAll',
|
||||||
|
method: 'post',
|
||||||
|
});
|
||||||
|
}
|
||||||
//审批过鱼数据
|
//审批过鱼数据
|
||||||
export function successFishDraft(data:any) {
|
export function successFishDraft(data:any) {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
@ -25,3 +25,12 @@ export function getApprovalChangeLogList(params: any) {
|
|||||||
data: params
|
data: params
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量审批
|
||||||
|
export function batchApproveByApprovalId(data: any) {
|
||||||
|
return request({
|
||||||
|
url: '/data/fishDraft/batchApproveByApprovalId',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -1,75 +1,38 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="basic-search-container">
|
<div class="basic-search-container">
|
||||||
<a-form
|
<a-form ref="formRef" :model="formData" :rules="rules" layout="inline" class="basic-search-form"
|
||||||
ref="formRef"
|
@reset="handleReset" @finish="handleFinish" @values-change="handleValuesChange">
|
||||||
:model="formData"
|
|
||||||
:rules="rules"
|
|
||||||
layout="inline"
|
|
||||||
class="basic-search-form"
|
|
||||||
@reset="handleReset"
|
|
||||||
@finish="handleFinish"
|
|
||||||
@values-change="handleValuesChange"
|
|
||||||
>
|
|
||||||
<div class="form-content-wrapper">
|
<div class="form-content-wrapper">
|
||||||
<!-- 1. 搜索项 + 查询/重置按钮 (栅格布局) -->
|
<!-- 1. 搜索项 + 查询/重置按钮 (栅格布局) -->
|
||||||
<a-row :gutter="[16, 0]" class="search-row" wrap>
|
<a-row :gutter="[16, 0]" class="search-row" wrap>
|
||||||
<a-col v-for="item in validSearchList" :key="item.name">
|
<a-col v-for="item in validSearchList" :key="item.name">
|
||||||
<a-form-item
|
<a-form-item :label="item.label" :name="item.name" style="width: 100%; margin-bottom: 0">
|
||||||
:label="item.label"
|
|
||||||
:name="item.name"
|
|
||||||
style="width: 100%; margin-bottom: 0"
|
|
||||||
>
|
|
||||||
<!-- 1. 优先检查是否有具名插槽,或者 type 为 custom -->
|
<!-- 1. 优先检查是否有具名插槽,或者 type 为 custom -->
|
||||||
<slot
|
<slot v-if="$slots[item.name] || item.type === 'custom'" :name="item.name" :value="formData[item.name]"
|
||||||
v-if="$slots[item.name] || item.type === 'custom'"
|
|
||||||
:name="item.name"
|
|
||||||
:value="formData[item.name]"
|
|
||||||
:onChange="(val: any) => {
|
:onChange="(val: any) => {
|
||||||
formData[item.name] = val;
|
formData[item.name] = val;
|
||||||
triggerManualValuesChange(item.name, val);
|
triggerManualValuesChange(item.name, val);
|
||||||
}"
|
}" :formModel="formData" />
|
||||||
:formModel="formData"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 普通日期选择器 -->
|
<!-- 普通日期选择器 -->
|
||||||
<a-date-picker
|
<a-date-picker v-else-if="item.type === 'DataPicker'" v-model:value="formData[item.name]"
|
||||||
v-else-if="item.type === 'DataPicker'"
|
:picker="item.picker" :format="item.fieldProps?.format" :value-format="item.fieldProps?.valueFormat"
|
||||||
v-model:value="formData[item.name]"
|
:disabled-date="item.fieldProps?.disabledDate" :show-time="item.fieldProps?.showTime"
|
||||||
:picker="item.picker"
|
:allow-clear="item.fieldProps?.allowClear" :presets="item.presets" style="width: 100%"
|
||||||
:format="item.fieldProps?.format"
|
@change="(val) => triggerManualValuesChange(item.name, val)" />
|
||||||
:value-format="item.fieldProps?.valueFormat"
|
|
||||||
:disabled-date="item.fieldProps?.disabledDate"
|
|
||||||
:show-time="item.fieldProps?.showTime"
|
|
||||||
:allow-clear="item.fieldProps?.allowClear"
|
|
||||||
:presets="item.presets"
|
|
||||||
style="width: 100%"
|
|
||||||
@change="(val) => triggerManualValuesChange(item.name, val)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 日期范围选择器 -->
|
<!-- 日期范围选择器 -->
|
||||||
<a-range-picker
|
<a-range-picker v-else-if="item.type === 'RangePicker'" v-model:value="formData[item.name]"
|
||||||
v-else-if="item.type === 'RangePicker'"
|
:picker="item.picker" :format="item.fieldProps?.format" :value-format="item.fieldProps?.valueFormat"
|
||||||
v-model:value="formData[item.name]"
|
:disabled-date="item.fieldProps?.disabledDate" :show-time="item.fieldProps?.showTime"
|
||||||
:picker="item.picker"
|
:allow-clear="item.fieldProps?.allowClear" :presets="item.presets" style="width: 100%"
|
||||||
:format="item.fieldProps?.format"
|
@change="(val) => triggerManualValuesChange(item.name, val)" />
|
||||||
:value-format="item.fieldProps?.valueFormat"
|
|
||||||
:disabled-date="item.fieldProps?.disabledDate"
|
|
||||||
:show-time="item.fieldProps?.showTime"
|
|
||||||
:allow-clear="item.fieldProps?.allowClear"
|
|
||||||
:presets="item.presets"
|
|
||||||
style="width: 100%"
|
|
||||||
@change="(val) => triggerManualValuesChange(item.name, val)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 普通输入框 -->
|
<!-- 普通输入框 -->
|
||||||
<a-input
|
<a-input v-else-if="!item.type || item.type === 'Input'" v-model:value="formData[item.name]"
|
||||||
v-else-if="!item.type || item.type === 'Input'"
|
:placeholder="item.placeholder || '请输入'" :allow-clear="item.fieldProps?.allowClear"
|
||||||
v-model:value="formData[item.name]"
|
|
||||||
:placeholder="item.placeholder || '请输入'"
|
|
||||||
:allow-clear="item.fieldProps?.allowClear"
|
|
||||||
:style="{ width: item.width ? item.width + 'px' : '200px' }"
|
:style="{ width: item.width ? item.width + 'px' : '200px' }"
|
||||||
@change="(e) => triggerManualValuesChange(item.name, e.target.value)"
|
@change="(e) => triggerManualValuesChange(item.name, e.target.value)" />
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 电站下拉框 -->
|
<!-- 电站下拉框 -->
|
||||||
<div class="flex gap-[10px]" v-else-if="item.type === 'waterStation'">
|
<div class="flex gap-[10px]" v-else-if="item.type === 'waterStation'">
|
||||||
@ -95,42 +58,28 @@
|
|||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select> -->
|
</a-select> -->
|
||||||
<!-- 流域下拉框 -->
|
<!-- 流域下拉框 -->
|
||||||
<a-select
|
<a-select :value="formData.hbrvcd" placeholder="请选择" @change="lyChange" show-search
|
||||||
:value="formData.hbrvcd"
|
:loading="shuJuTianBaoStore.lyLoading" :filter-option="filterOption" style="width: 135px">
|
||||||
placeholder="请选择"
|
<a-select-option v-for="opt in shuJuTianBaoStore.lyOption" :key="opt.hbrvcd" :value="opt.hbrvcd"
|
||||||
@change="lyChange"
|
:label="opt.hbrvnm">
|
||||||
show-search
|
|
||||||
allow-clear
|
|
||||||
:loading="shuJuTianBaoStore.lyLoading"
|
|
||||||
:filter-option="filterOption"
|
|
||||||
style="width: 135px"
|
|
||||||
>
|
|
||||||
<a-select-option
|
|
||||||
v-for="opt in shuJuTianBaoStore.lyOption"
|
|
||||||
:key="opt.hbrvcd"
|
|
||||||
:value="opt.hbrvcd"
|
|
||||||
:label="opt.hbrvnm"
|
|
||||||
>
|
|
||||||
{{ opt.hbrvnm }}
|
{{ opt.hbrvnm }}
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
<!-- 电站下拉框 -->
|
<!-- 电站下拉框 -->
|
||||||
<a-select
|
<a-select v-if="props.zhujianfujian == 'fu'" :value="formData.rstcd" placeholder="请选择电站"
|
||||||
:value="formData.rstcd"
|
@change="stcdIdChange" show-search allow-clear :loading="shuJuTianBaoStore.engLoading"
|
||||||
placeholder="请选择电站"
|
:filter-option="filterOption" style="width: 135px">
|
||||||
@change="stcdIdChange"
|
<a-select-option v-for="opt in shuJuTianBaoStore.engOption" :key="opt.stcd" :value="opt.stcd"
|
||||||
show-search
|
:label="opt.ennm">
|
||||||
allow-clear
|
{{ opt.ennm }}
|
||||||
:loading="shuJuTianBaoStore.engLoading"
|
</a-select-option>
|
||||||
:filter-option="filterOption"
|
</a-select>
|
||||||
style="width: 135px"
|
<!-- 电站下拉框 -->
|
||||||
>
|
<a-select v-if="props.zhujianfujian == 'zhu'" :value="formData.stcd" placeholder="请选择电站"
|
||||||
<a-select-option
|
@change="stcdIdChange" show-search allow-clear :loading="shuJuTianBaoStore.engLoading"
|
||||||
v-for="opt in shuJuTianBaoStore.engOption"
|
:filter-option="filterOption" style="width: 135px">
|
||||||
:key="opt.stcd"
|
<a-select-option v-for="opt in shuJuTianBaoStore.engOption" :key="opt.stcd" :value="opt.stcd"
|
||||||
:value="opt.stcd"
|
:label="opt.ennm">
|
||||||
:label="opt.ennm"
|
|
||||||
>
|
|
||||||
{{ opt.ennm }}
|
{{ opt.ennm }}
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
@ -141,33 +90,21 @@
|
|||||||
<!-- <div v-else-if="item.type === 'Select'">
|
<!-- <div v-else-if="item.type === 'Select'">
|
||||||
<div v-for="i in item.options"> {{ i[item.values?.name] }} {{ i[item.values?.value] }}</div>
|
<div v-for="i in item.options"> {{ i[item.values?.name] }} {{ i[item.values?.value] }}</div>
|
||||||
</div> -->
|
</div> -->
|
||||||
<a-select
|
<a-select v-else-if="item.type === 'Select'" v-model:value="formData[item.name]"
|
||||||
v-else-if="item.type === 'Select'"
|
:placeholder="item.placeholder || '请选择'" :allow-clear="item.fieldProps?.allowClear"
|
||||||
v-model:value="formData[item.name]"
|
|
||||||
:placeholder="item.placeholder || '请选择'"
|
|
||||||
:allow-clear="item.fieldProps?.allowClear"
|
|
||||||
:style="{ width: item.width ? item.width + 'px' : '200px' }"
|
:style="{ width: item.width ? item.width + 'px' : '200px' }"
|
||||||
@change="(val) => triggerManualValuesChange(item.name, val)"
|
@change="(val) => triggerManualValuesChange(item.name, val)" show-search :filter-option="filterOption">
|
||||||
show-search
|
<a-select-option v-for="opt in item.options" :key="opt[item.values?.value] || opt.value || opt.itemCode"
|
||||||
:filter-option="filterOption"
|
|
||||||
>
|
|
||||||
<a-select-option
|
|
||||||
v-for="opt in item.options"
|
|
||||||
:key="opt[item.values?.value] || opt.value || opt.itemCode"
|
|
||||||
:value="opt[item.values?.value] || opt.value || opt.itemCode"
|
:value="opt[item.values?.value] || opt.value || opt.itemCode"
|
||||||
:label="opt[item.values?.name] || opt.label || opt.dictName"
|
:label="opt[item.values?.name] || opt.label || opt.dictName">
|
||||||
>
|
|
||||||
{{ opt[item.values?.name] || opt.label || opt.dictName }}
|
{{ opt[item.values?.name] || opt.label || opt.dictName }}
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
|
|
||||||
<!-- 单选框 -->
|
<!-- 单选框 -->
|
||||||
<a-radio-group
|
<a-radio-group v-else-if="item.type === 'Radio'" v-model:value="formData[item.name]"
|
||||||
v-else-if="item.type === 'Radio'"
|
|
||||||
v-model:value="formData[item.name]"
|
|
||||||
:style="{ width: item.width ? item.width + 'px' : '200px' }"
|
:style="{ width: item.width ? item.width + 'px' : '200px' }"
|
||||||
@change="(e) => triggerManualValuesChange(item.name, e.target.value)"
|
@change="(e) => triggerManualValuesChange(item.name, e.target.value)">
|
||||||
>
|
|
||||||
<a-radio v-for="opt in item.options" :key="opt.value" :value="opt.value">
|
<a-radio v-for="opt in item.options" :key="opt.value" :value="opt.value">
|
||||||
{{ opt.label }}
|
{{ opt.label }}
|
||||||
</a-radio>
|
</a-radio>
|
||||||
@ -181,14 +118,21 @@
|
|||||||
<a-button type="primary" html-type="submit" v-if="showSearch">
|
<a-button type="primary" html-type="submit" v-if="showSearch">
|
||||||
查询
|
查询
|
||||||
</a-button>
|
</a-button>
|
||||||
|
|
||||||
<a-button v-if="showReset" @click="handleReset"> 重置 </a-button>
|
<a-button v-if="showReset" @click="handleReset"> 重置 </a-button>
|
||||||
|
</a-space>
|
||||||
|
</a-col>
|
||||||
|
<a-col class="custom-action-col">
|
||||||
|
<a-space>
|
||||||
<slot name="actions" :form="formRef" :values="formData">
|
<slot name="actions" :form="formRef" :values="formData">
|
||||||
<!-- 默认无内容 -->
|
<!-- 默认无内容 -->
|
||||||
</slot>
|
</slot>
|
||||||
</a-space>
|
</a-space>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
|
||||||
|
<!-- <div class="custom-action-col">
|
||||||
|
|
||||||
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
</a-form>
|
</a-form>
|
||||||
</div>
|
</div>
|
||||||
@ -226,6 +170,7 @@ interface Props {
|
|||||||
initialValues?: any;
|
initialValues?: any;
|
||||||
showSearch?: boolean;
|
showSearch?: boolean;
|
||||||
showReset?: boolean;
|
showReset?: boolean;
|
||||||
|
zhujianfujian: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
@ -311,10 +256,18 @@ const lyChange = (value: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const stcdIdChange = (value: any) => {
|
const stcdIdChange = (value: any) => {
|
||||||
|
if (props.zhujianfujian == 'fu') {
|
||||||
formData.rstcd = value;
|
formData.rstcd = value;
|
||||||
shuJuTianBaoStore.getFpssOption(formData.hbrvcd, value);
|
shuJuTianBaoStore.getFpssOption(formData.hbrvcd, value);
|
||||||
// 【关键修改】手动触发 valuesChange
|
// 【关键修改】手动触发 valuesChange
|
||||||
triggerManualValuesChange("rstcd", formData.rstcd);
|
triggerManualValuesChange("rstcd", formData.rstcd);
|
||||||
|
} else {
|
||||||
|
formData.stcd = value;
|
||||||
|
shuJuTianBaoStore.getFpssOption(formData.hbrvcd, value);
|
||||||
|
// 【关键修改】手动触发 valuesChange
|
||||||
|
triggerManualValuesChange("stcd", formData.stcd);
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@ -365,13 +318,15 @@ defineExpose({
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.ant-form-item) {
|
:deep(.ant-form-item) {
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
margin-inline-end: 0px;
|
margin-inline-end: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 关键代码:让自定义按钮列自动占据剩余空间并靠右 */
|
/* 关键代码:让自定义按钮列自动占据剩余空间并靠右 */
|
||||||
.custom-action-col {
|
.custom-action-col {
|
||||||
margin-left: auto;
|
// margin-left: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
|||||||
@ -35,6 +35,9 @@ function logout() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function changePassword() {
|
||||||
|
router.push('/changePassword');
|
||||||
|
}
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
});
|
});
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
@ -67,7 +70,7 @@ onBeforeUnmount(() => {
|
|||||||
</a-space>
|
</a-space>
|
||||||
<template #overlay>
|
<template #overlay>
|
||||||
<a-menu>
|
<a-menu>
|
||||||
<a-menu-item key="changePassword">
|
<a-menu-item key="changePassword" @click="changePassword" >
|
||||||
<UserOutlined />
|
<UserOutlined />
|
||||||
修改密码
|
修改密码
|
||||||
</a-menu-item>
|
</a-menu-item>
|
||||||
|
|||||||
@ -40,7 +40,12 @@ export const constantRoutes: RouteRecordRaw[] = [
|
|||||||
path: '/401',
|
path: '/401',
|
||||||
component: () => import('@/views/error-page/401.vue'),
|
component: () => import('@/views/error-page/401.vue'),
|
||||||
meta: { hidden: true }
|
meta: { hidden: true }
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
path: '/changePassword',
|
||||||
|
component: () => import('@/views/changePassword/index.vue'),
|
||||||
|
meta: { hidden: true }
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// 创建路由
|
// 创建路由
|
||||||
|
|||||||
@ -13,6 +13,7 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
// state
|
// state
|
||||||
const Token = ref<string>(getToken() || '');
|
const Token = ref<string>(getToken() || '');
|
||||||
const username = ref<string>('');
|
const username = ref<string>('');
|
||||||
|
const userid = ref<string>('');
|
||||||
const nickname = ref<string>('');
|
const nickname = ref<string>('');
|
||||||
const avatar = ref<string>('');
|
const avatar = ref<string>('');
|
||||||
const roles = ref<Array<string>>([]); // 用户角色编码集合 → 判断路由权限
|
const roles = ref<Array<string>>([]); // 用户角色编码集合 → 判断路由权限
|
||||||
@ -49,6 +50,7 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
reject('getUserInfo: roles must be a non-null array!');
|
reject('getUserInfo: roles must be a non-null array!');
|
||||||
}
|
}
|
||||||
username.value = data.userInfo.username;
|
username.value = data.userInfo.username;
|
||||||
|
userid.value = data.userInfo.id
|
||||||
nickname.value = data.userInfo.nickname;
|
nickname.value = data.userInfo.nickname;
|
||||||
avatar.value = data.userInfo.avatar;
|
avatar.value = data.userInfo.avatar;
|
||||||
roles.value = data.roles;
|
roles.value = data.roles;
|
||||||
@ -88,6 +90,7 @@ export const useUserStore = defineStore('user', () => {
|
|||||||
return {
|
return {
|
||||||
Token,
|
Token,
|
||||||
username,
|
username,
|
||||||
|
userid,
|
||||||
nickname,
|
nickname,
|
||||||
avatar,
|
avatar,
|
||||||
roles,
|
roles,
|
||||||
|
|||||||
491
frontend/src/views/changePassword/index.vue
Normal file
491
frontend/src/views/changePassword/index.vue
Normal file
@ -0,0 +1,491 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { message } from 'ant-design-vue';
|
||||||
|
import type { FormInstance } from 'ant-design-vue';
|
||||||
|
import { ArrowLeftOutlined } from '@ant-design/icons-vue';
|
||||||
|
import { useUserStore } from '@/store/modules/user';
|
||||||
|
import { encrypt } from '@/utils/rsaEncrypt';
|
||||||
|
import {
|
||||||
|
updatePassword,
|
||||||
|
} from "@/api/changePassWord";
|
||||||
|
const router = useRouter();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
|
// 表单引用
|
||||||
|
const formRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
// 提交状态
|
||||||
|
const submitting = ref(false);
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
interface ChangePasswordForm {
|
||||||
|
oldPassword: string;
|
||||||
|
newPassword: string;
|
||||||
|
confirmPassword: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formState = reactive<ChangePasswordForm>({
|
||||||
|
oldPassword: '',
|
||||||
|
newPassword: '',
|
||||||
|
confirmPassword: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==================== 密码校验函数 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验密码复杂度:至少包含四类字符中的三类
|
||||||
|
*/
|
||||||
|
const checkComplexity = (password: string): { valid: boolean; message: string } => {
|
||||||
|
let count = 0;
|
||||||
|
const hasUpper = /[A-Z]/.test(password);
|
||||||
|
const hasLower = /[a-z]/.test(password);
|
||||||
|
const hasNumber = /[0-9]/.test(password);
|
||||||
|
const hasSpecial = /[^A-Za-z0-9]/.test(password);
|
||||||
|
|
||||||
|
if (hasUpper) count++;
|
||||||
|
if (hasLower) count++;
|
||||||
|
if (hasNumber) count++;
|
||||||
|
if (hasSpecial) count++;
|
||||||
|
|
||||||
|
if (count >= 3) {
|
||||||
|
return { valid: true, message: '' };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
message: '密码必须包含大写字母、小写字母、数字、特殊字符中的至少三类'
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验连续重复字符
|
||||||
|
*/
|
||||||
|
const checkConsecutiveRepeat = (password: string): { valid: boolean; message: string } => {
|
||||||
|
for (let i = 0; i < password.length - 1; i++) {
|
||||||
|
if (password[i] === password[i + 1]) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
message: '密码不能包含2位及以上相同字符的连续重复(如11、aa)'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { valid: true, message: '' };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验连续递增/递减数字
|
||||||
|
*/
|
||||||
|
const checkConsecutiveNumbers = (password: string): { valid: boolean; message: string } => {
|
||||||
|
for (let i = 0; i < password.length - 1; i++) {
|
||||||
|
const curr = password.charCodeAt(i);
|
||||||
|
const next = password.charCodeAt(i + 1);
|
||||||
|
|
||||||
|
// 检查是否都是数字
|
||||||
|
if (curr >= 48 && curr <= 57 && next >= 48 && next <= 57) {
|
||||||
|
if (Math.abs(next - curr) === 1) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
message: '密码不能包含2位及以上连续递增或递减的数字(如12、21)'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { valid: true, message: '' };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验连续递增/递减字母
|
||||||
|
*/
|
||||||
|
const checkConsecutiveLetters = (password: string): { valid: boolean; message: string } => {
|
||||||
|
for (let i = 0; i < password.length - 1; i++) {
|
||||||
|
const curr = password.charCodeAt(i);
|
||||||
|
const next = password.charCodeAt(i + 1);
|
||||||
|
|
||||||
|
// 检查是否都是字母(大写或小写)
|
||||||
|
const isCurrLetter = (curr >= 65 && curr <= 90) || (curr >= 97 && curr <= 122);
|
||||||
|
const isNextLetter = (next >= 65 && next <= 90) || (next >= 97 && next <= 122);
|
||||||
|
|
||||||
|
if (isCurrLetter && isNextLetter) {
|
||||||
|
if (Math.abs(next - curr) === 1) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
message: '密码不能包含2位及以上连续递增或递减的字母(如ab、ba)'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { valid: true, message: '' };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验用户名关联
|
||||||
|
*/
|
||||||
|
const checkUsernameRelation = (password: string, username: string): { valid: boolean; message: string } => {
|
||||||
|
if (!username) {
|
||||||
|
return { valid: true, message: '' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const lowerPassword = password.toLowerCase();
|
||||||
|
const lowerUsername = username.toLowerCase();
|
||||||
|
|
||||||
|
if (lowerPassword.includes(lowerUsername)) {
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
message: '密码不能包含用户名或其核心部分'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { valid: true, message: '' };
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 表单验证规则 ====================
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
oldPassword: [
|
||||||
|
{ required: true, message: '请输入旧密码', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
newPassword: [
|
||||||
|
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||||
|
{ min: 10, message: '密码长度不能少于10位', trigger: 'blur' },
|
||||||
|
{
|
||||||
|
validator: async (_rule: any, value: string) => {
|
||||||
|
if (!value) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验复杂度
|
||||||
|
const complexity = checkComplexity(value);
|
||||||
|
if (!complexity.valid) {
|
||||||
|
return Promise.reject(new Error(complexity.message));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验连续重复
|
||||||
|
const repeat = checkConsecutiveRepeat(value);
|
||||||
|
if (!repeat.valid) {
|
||||||
|
return Promise.reject(new Error(repeat.message));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验连续数字
|
||||||
|
const numbers = checkConsecutiveNumbers(value);
|
||||||
|
if (!numbers.valid) {
|
||||||
|
return Promise.reject(new Error(numbers.message));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验连续字母
|
||||||
|
const letters = checkConsecutiveLetters(value);
|
||||||
|
if (!letters.valid) {
|
||||||
|
return Promise.reject(new Error(letters.message));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 校验用户名关联
|
||||||
|
const username = userStore.username || '';
|
||||||
|
const usernameCheck = checkUsernameRelation(value, username);
|
||||||
|
if (!usernameCheck.valid) {
|
||||||
|
return Promise.reject(new Error(usernameCheck.message));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
confirmPassword: [
|
||||||
|
{ required: true, message: '请再次输入新密码', trigger: 'blur' },
|
||||||
|
{
|
||||||
|
validator: async (_rule: any, value: string) => {
|
||||||
|
if (!value) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
if (value !== formState.newPassword) {
|
||||||
|
return Promise.reject(new Error('两次输入的密码不一致'));
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
trigger: 'blur'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 接口占位符 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改密码接口(待补充实际后端接口)
|
||||||
|
* @param oldPassword 旧密码(已加密)
|
||||||
|
* @param newPassword 新密码(已加密)
|
||||||
|
*/
|
||||||
|
// const changePasswordApi = async (oldPassword: string, newPassword: string) => {
|
||||||
|
// // TODO: 替换为实际的 API 调用
|
||||||
|
// // 示例:
|
||||||
|
// // return request({
|
||||||
|
// // url: '/user/changePassword',
|
||||||
|
// // method: 'post',
|
||||||
|
// // data: { oldPassword, newPassword }
|
||||||
|
// // });
|
||||||
|
|
||||||
|
// // 模拟延迟
|
||||||
|
// return new Promise((resolve) => {
|
||||||
|
// setTimeout(() => {
|
||||||
|
// resolve({ code: 0, msg: '修改成功' });
|
||||||
|
// }, 1000);
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
|
||||||
|
// ==================== 事件处理 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交表单
|
||||||
|
*/
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
// 表单验证
|
||||||
|
await formRef.value?.validate();
|
||||||
|
|
||||||
|
submitting.value = true;
|
||||||
|
|
||||||
|
// RSA 加密密码
|
||||||
|
const encryptedOldPassword = encrypt(formState.oldPassword);
|
||||||
|
const encryptedNewPassword = encrypt(formState.newPassword);
|
||||||
|
|
||||||
|
if (!encryptedOldPassword || !encryptedNewPassword) {
|
||||||
|
message.error('密码加密失败');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 调用接口
|
||||||
|
const res: any = await updatePassword({id:userStore.userid,password:encryptedNewPassword,oldPassword:encryptedOldPassword});
|
||||||
|
|
||||||
|
// 兼容字符串和数字类型的状态码
|
||||||
|
if (res.code === 0 || res.code === '0') {
|
||||||
|
message.success('密码修改成功,请重新登录');
|
||||||
|
|
||||||
|
// 清空 token 和用户信息
|
||||||
|
userStore.resetToken();
|
||||||
|
|
||||||
|
// 跳转到登录页
|
||||||
|
setTimeout(() => {
|
||||||
|
router.push('/login-sjtb');
|
||||||
|
}, 1500);
|
||||||
|
} else {
|
||||||
|
message.error(res.msg || '密码修改失败');
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.errorFields) {
|
||||||
|
// 表单验证错误
|
||||||
|
message.error('请检查表单填写是否正确');
|
||||||
|
} else {
|
||||||
|
// 其他错误
|
||||||
|
message.error(error.message || '密码修改失败,请稍后重试');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
submitting.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置表单
|
||||||
|
*/
|
||||||
|
const handleReset = () => {
|
||||||
|
formRef.value?.resetFields();
|
||||||
|
formState.oldPassword = '';
|
||||||
|
formState.newPassword = '';
|
||||||
|
formState.confirmPassword = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回上一页
|
||||||
|
*/
|
||||||
|
const handleBack = () => {
|
||||||
|
router.back();
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="change-password-container">
|
||||||
|
<a-card :bordered="false" class="password-card">
|
||||||
|
<!-- 标题区域 -->
|
||||||
|
<div class="card-header">
|
||||||
|
<a-button type="text" @click="handleBack" class="back-btn">
|
||||||
|
<ArrowLeftOutlined />
|
||||||
|
</a-button>
|
||||||
|
<h2 class="card-title">修改密码</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-form ref="formRef" :model="formState" :rules="rules" layout="vertical" class="password-form">
|
||||||
|
<!-- 旧密码 -->
|
||||||
|
<a-form-item label="旧密码" name="oldPassword">
|
||||||
|
<a-input-password v-model:value="formState.oldPassword" placeholder="请输入旧密码" size="large"
|
||||||
|
allow-clear />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 新密码 -->
|
||||||
|
<a-form-item label="新密码" name="newPassword" :dependencies="['username']">
|
||||||
|
<a-input-password v-model:value="formState.newPassword" placeholder="请输入新密码" size="large"
|
||||||
|
allow-clear />
|
||||||
|
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 确认新密码 -->
|
||||||
|
<a-form-item label="确认新密码" name="confirmPassword">
|
||||||
|
<a-input-password v-model:value="formState.confirmPassword" placeholder="请再次输入新密码" size="large"
|
||||||
|
allow-clear />
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 按钮区域 -->
|
||||||
|
<a-form-item class="button-group">
|
||||||
|
<a-button type="primary" size="large" @click="handleSubmit" :loading="submitting"
|
||||||
|
class="submit-btn">
|
||||||
|
提交
|
||||||
|
</a-button>
|
||||||
|
<a-button size="large" @click="handleReset" class="reset-btn">
|
||||||
|
重置
|
||||||
|
</a-button>
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.change-password-container {
|
||||||
|
min-height: calc(100vh - 110px);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.password-card {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 0px;
|
||||||
|
border-bottom: 2px solid #005293;
|
||||||
|
|
||||||
|
.back-btn {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #005293;
|
||||||
|
padding: 4px 8px;
|
||||||
|
margin-right: 12px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #003d6e;
|
||||||
|
background-color: rgba(0, 82, 147, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #005293;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-card-body) {
|
||||||
|
padding: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.password-form {
|
||||||
|
:deep(.ant-form-item-label) {
|
||||||
|
label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-input-password) {
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #005293;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: #005293;
|
||||||
|
box-shadow: 0 0 0 2px rgba(0, 82, 147, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.password-tips {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
background-color: #f5f7fa;
|
||||||
|
border-left: 3px solid #005293;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.8;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin: 4px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
margin-top: 24px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
width: 120px;
|
||||||
|
margin-right: 16px;
|
||||||
|
background-color: #005293;
|
||||||
|
border-color: #005293;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #003d6e;
|
||||||
|
border-color: #003d6e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-btn {
|
||||||
|
width: 120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应式适配
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.change-password-container {
|
||||||
|
padding: 20px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.password-card {
|
||||||
|
:deep(.ant-card-body) {
|
||||||
|
padding: 24px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.submit-btn,
|
||||||
|
.reset-btn {
|
||||||
|
width: 100%;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,154 @@
|
|||||||
|
<template>
|
||||||
|
<div class="guoYuSheShiShuJuHistory-search">
|
||||||
|
<BasicSearch ref="basicSearchRef" :searchList="searchList" :initial-values="initSearchData" :zhujianfujian="'fu'" @reset="handleReset"
|
||||||
|
@finish="onSearchFinish" @values-change="onValuesChange">
|
||||||
|
<template #ftp="{ onChange }">
|
||||||
|
<fishSearch v-model="localTypeDate" width="200px" @update:modelValue="onChange" />
|
||||||
|
</template>
|
||||||
|
</BasicSearch>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, onMounted, watch } from "vue";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import BasicSearch from "@/components/BasicSearch/index.vue";
|
||||||
|
import { DateSetting } from "@/utils/enumeration";
|
||||||
|
import fishSearch from "@/components/fishSearch/index.vue";
|
||||||
|
import { useShuJuTianBaoStore } from "@/store/modules/shuJuTianBao";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
direction: any[];
|
||||||
|
guoyuStatus: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const shuJuTianBaoStore = useShuJuTianBaoStore();
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "reset", values: any): void;
|
||||||
|
(e: "searchFinish", values: any): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const localTypeDate = ref<string>(null);
|
||||||
|
const basicSearchRef = ref<any>();
|
||||||
|
|
||||||
|
const initSearchData = {
|
||||||
|
hbrvcd: "all",
|
||||||
|
stcd: null,
|
||||||
|
rstcd: null,
|
||||||
|
ftp: null,
|
||||||
|
status: 'APPROVED',
|
||||||
|
direction: null,
|
||||||
|
strdt: [
|
||||||
|
dayjs().subtract(1, 'month').startOf('month').format("YYYY-MM-DD"),
|
||||||
|
dayjs().format("YYYY-MM-DD"),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchData = ref<any>({ ...initSearchData });
|
||||||
|
|
||||||
|
const searchList: any = computed(() => [
|
||||||
|
{
|
||||||
|
type: "waterStation",
|
||||||
|
name: "hbrvcd",
|
||||||
|
label: "流域",
|
||||||
|
fieldProps: {
|
||||||
|
allowClear: true,
|
||||||
|
},
|
||||||
|
options: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "Select",
|
||||||
|
name: "stcd",
|
||||||
|
label: "过鱼设施",
|
||||||
|
values: { name: "stnm", value: "stcd" },
|
||||||
|
fieldProps: {
|
||||||
|
allowClear: true,
|
||||||
|
},
|
||||||
|
options: shuJuTianBaoStore.fpssOption,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "Select",
|
||||||
|
name: "direction",
|
||||||
|
label: "游向",
|
||||||
|
width: 120,
|
||||||
|
options: props.direction,
|
||||||
|
fieldProps: {
|
||||||
|
allowClear: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "custom",
|
||||||
|
name: "ftp",
|
||||||
|
label: "鱼种类",
|
||||||
|
fieldProps: {
|
||||||
|
allowClear: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
width: 120,
|
||||||
|
type: "Select",
|
||||||
|
name: "status",
|
||||||
|
label: "数据状态",
|
||||||
|
fieldProps: {
|
||||||
|
// allowClear: true,
|
||||||
|
},
|
||||||
|
options: props.guoyuStatus,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
span: 12,
|
||||||
|
type: "RangePicker",
|
||||||
|
name: "strdt",
|
||||||
|
label: "过鱼时间",
|
||||||
|
picker: "date",
|
||||||
|
fieldProps: {
|
||||||
|
format: "YYYY-MM-DD",
|
||||||
|
valueFormat: "YYYY-MM-DD",
|
||||||
|
allowClear: false,
|
||||||
|
},
|
||||||
|
presets: DateSetting.RangeButton.days,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const onSearchFinish = (values: any) => {
|
||||||
|
emit("searchFinish", values);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onValuesChange = (changedValues: any, allValues: any) => {
|
||||||
|
searchData.value = { ...searchData.value, ...allValues };
|
||||||
|
if (
|
||||||
|
Object.keys(changedValues)[0] == "rstcd" ||
|
||||||
|
Object.keys(changedValues)[0] == "baseId" ||
|
||||||
|
Object.keys(changedValues)[0] == "hbrvcd"
|
||||||
|
) {
|
||||||
|
const formInstance = basicSearchRef.value?.formData;
|
||||||
|
formInstance.stcd = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
localTypeDate.value = null;
|
||||||
|
emit("reset", initSearchData);
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => initSearchData.ftp,
|
||||||
|
(newVal) => {
|
||||||
|
localTypeDate.value = newVal || "";
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
emit("searchFinish", initSearchData);
|
||||||
|
shuJuTianBaoStore.getFpssOption("", "");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.guoYuSheShiShuJuHistory-search {
|
||||||
|
// 继承原有样式
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,494 @@
|
|||||||
|
<template>
|
||||||
|
<div class="guoYuSheShiShuJuHistory-page">
|
||||||
|
<GuoYuSheShiShuJuHistorySearch ref="searchRef" class="search-container" :guoyuStatus="guoyuStatus"
|
||||||
|
:direction="direction" @reset="handleReset" @search-finish="handleSearchFinish" />
|
||||||
|
|
||||||
|
<div class="table-container" ref="tableContainerRef">
|
||||||
|
<BasicTable ref="tableRef" :columns="columns" :scroll-y="tableScrollY" :list-url="getFishDraftPage"
|
||||||
|
:search-params="{}" :transform-data="customTransform">
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action' || column.dataIndex === 'action'">
|
||||||
|
<div class="flex">
|
||||||
|
<a-button type="link" size="small" @click="handleView(record)">查看</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-modal v-model:open="mediaPreviewVisible" :title="videoPreviewTitle" :footer="null" width="900px"
|
||||||
|
@cancel="closeMediaPreview" :zIndex="2000">
|
||||||
|
<div class="flex h-[60vh] gap-4">
|
||||||
|
<div class="w-1/4 overflow-y-auto border-r pr-2 media-list-container">
|
||||||
|
<div v-for="(item, index) in previewList" :key="index" class="mb-2 cursor-pointer list-item"
|
||||||
|
:class="{ select: index === currentMediaIndex }" @click="currentMediaIndex = index">
|
||||||
|
<span class="file-name">{{ item.name }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="w-3/4 flex flex-col items-center justify-center bg-gray-100 relative p-4">
|
||||||
|
<a-image v-if="
|
||||||
|
currentMediaItem &&
|
||||||
|
currentMediaItem.url != '' &&
|
||||||
|
(currentMediaItem.type === 'image' || currentMediaItem.type === 'formImage')
|
||||||
|
" :src="currentMediaItem.url" class="max-w-full max-h-full object-contain" />
|
||||||
|
<div v-else-if="
|
||||||
|
currentMediaItem &&
|
||||||
|
currentMediaItem.url == '' &&
|
||||||
|
(currentMediaItem.type === 'image' || currentMediaItem.type === 'formImage')
|
||||||
|
" class="text-gray-400">
|
||||||
|
暂无图片预览
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<video v-else-if="
|
||||||
|
currentMediaItem &&
|
||||||
|
currentMediaItem.url != '' &&
|
||||||
|
(currentMediaItem.type === 'video' || currentMediaItem.type === 'formVideo')
|
||||||
|
" :src="currentMediaItem.url" controls autoplay class="max-w-full max-h-[50vh]">
|
||||||
|
您的浏览器不支持视频播放
|
||||||
|
</video>
|
||||||
|
<div v-else-if="
|
||||||
|
currentMediaItem &&
|
||||||
|
currentMediaItem.url == '' &&
|
||||||
|
(currentMediaItem.type === 'video' || currentMediaItem.type === 'formVideo')
|
||||||
|
" class="text-gray-400">
|
||||||
|
暂无视频预览
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="absolute bottom-4 flex gap-4 z-10">
|
||||||
|
<a-button shape="circle" :icon="h(LeftOutlined)" @click="prevMedia" :disabled="currentMediaIndex === 0" />
|
||||||
|
<span class="self-center text-gray-600 bg-white px-2 rounded shadow-sm">
|
||||||
|
{{ currentMediaIndex + 1 }} / {{ previewList.length }}
|
||||||
|
</span>
|
||||||
|
<a-button shape="circle" :icon="h(RightOutlined)" @click="nextMedia"
|
||||||
|
:disabled="currentMediaIndex === previewList.length - 1" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
|
<EditModal ref="editModalRef" v-model:visible="editModalVisible" :is-view="true" :direction="direction"
|
||||||
|
:initial-values="currentRecord" @cancel="editModalCancel" @preview-click="handlePreviewClick" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, onMounted, h, nextTick } from "vue";
|
||||||
|
import { message } from "ant-design-vue";
|
||||||
|
import { LeftOutlined, RightOutlined } from "@ant-design/icons-vue";
|
||||||
|
import BasicTable from "@/components/BasicTable/index.vue";
|
||||||
|
import GuoYuSheShiShuJuHistorySearch from "./guoYuSheShiShuJuHistorySearch.vue";
|
||||||
|
import EditModal from "../guoYuSheShiShuJuTianBao/guoYuSheShiShuJuTianBaoForm.vue";
|
||||||
|
import { getFishDraftPage } from "@/api/guoYuSheShiShuJuTianBao";
|
||||||
|
import { Tag } from "ant-design-vue";
|
||||||
|
import { getDictItemsByCode } from "@/api/dict";
|
||||||
|
|
||||||
|
const baseUrl = import.meta.env.VITE_APP_PREVIEW_URL;
|
||||||
|
const baseUrlPreview = import.meta.env.VITE_APP_BASE_URL;
|
||||||
|
|
||||||
|
interface FormData {
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ColumnConfig {
|
||||||
|
dataIndex: string;
|
||||||
|
key: string;
|
||||||
|
title: string;
|
||||||
|
width?: number;
|
||||||
|
fixed?: string;
|
||||||
|
customRender?: (text: any, record: any) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tableRef = ref<any>(null);
|
||||||
|
const searchRef = ref<any>(null);
|
||||||
|
const editModalRef = ref<any>(null);
|
||||||
|
|
||||||
|
const direction = ref<any>([]);
|
||||||
|
const guoyuStatus = ref<any>([]);
|
||||||
|
|
||||||
|
const baseColumnsConfig: ColumnConfig[] = [
|
||||||
|
{
|
||||||
|
dataIndex: "hbrvnm",
|
||||||
|
key: "hbrvnm",
|
||||||
|
title: "流域",
|
||||||
|
width: 120,
|
||||||
|
fixed: "left",
|
||||||
|
},
|
||||||
|
{ dataIndex: "ennm", key: "ennm", title: "电站名称", width: 120, fixed: "left" },
|
||||||
|
{ dataIndex: "stnm", key: "stnm", title: "过鱼设施名称", width: 150, fixed: "left" },
|
||||||
|
{ dataIndex: "strdt", key: "strdt", title: "过鱼时间", width: 150 },
|
||||||
|
{ dataIndex: "ftpName", key: "ftpName", title: "鱼种类", width: 120 },
|
||||||
|
{
|
||||||
|
dataIndex: "isfs",
|
||||||
|
key: "isfs",
|
||||||
|
title: "是否鱼苗",
|
||||||
|
width: 74,
|
||||||
|
customRender: ({ text }: any) => {
|
||||||
|
const isYes = text === 1 || text === "1";
|
||||||
|
return h(
|
||||||
|
Tag,
|
||||||
|
{
|
||||||
|
color: isYes ? "success" : "error",
|
||||||
|
style: { margin: 0 },
|
||||||
|
},
|
||||||
|
() => (isYes ? "是" : "否")
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: "direction",
|
||||||
|
key: "direction",
|
||||||
|
title: "游向",
|
||||||
|
width: 80,
|
||||||
|
customRender: ({ text }: any) =>
|
||||||
|
direction.value.find((item: any) => item.itemCode === text)?.dictName || "-",
|
||||||
|
},
|
||||||
|
{ dataIndex: "fcnt", key: "fcnt", title: "过鱼数量(尾)", width: 120 },
|
||||||
|
{ dataIndex: "fsz", key: "fsz", title: "体长(cm)", width: 110 },
|
||||||
|
{ dataIndex: "fwet", key: "fwet", title: "平均体重(g)", width: 110 },
|
||||||
|
{ dataIndex: "wt", key: "wt", title: "水温(℃)", width: 80 },
|
||||||
|
{ dataIndex: "tm", key: "tm", title: "填报时间", width: 150 },
|
||||||
|
{
|
||||||
|
dataIndex: "status",
|
||||||
|
key: "status",
|
||||||
|
title: "状态",
|
||||||
|
width: 74,
|
||||||
|
customRender: ({ text }: any) => {
|
||||||
|
let data = guoyuStatus.value.find((item: any) => item.itemCode === text);
|
||||||
|
return h(
|
||||||
|
Tag,
|
||||||
|
{
|
||||||
|
color: data?.custom1 || "error",
|
||||||
|
},
|
||||||
|
() => data?.dictName || "-"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const editModalVisible = ref(false);
|
||||||
|
const currentRecord = ref<FormData | null>(null);
|
||||||
|
|
||||||
|
const mediaPreviewVisible = ref(false);
|
||||||
|
const videoPreviewTitle = ref("视频预览");
|
||||||
|
|
||||||
|
interface MediaItem {
|
||||||
|
id: string;
|
||||||
|
type: "image" | "video" | "formVideo" | "formImage";
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const previewList = ref<MediaItem[]>([]);
|
||||||
|
const currentMediaIndex = ref(0);
|
||||||
|
const tablePreviewRecord = ref<any>({});
|
||||||
|
|
||||||
|
const currentMediaItem = computed(() => previewList.value[currentMediaIndex.value]);
|
||||||
|
|
||||||
|
const columns = computed(() => {
|
||||||
|
return [
|
||||||
|
...baseColumnsConfig.map((col) => {
|
||||||
|
if (col.dataIndex === "level5") {
|
||||||
|
return {
|
||||||
|
...col,
|
||||||
|
customRender: ({ text }: any) => {
|
||||||
|
if (!text) return "-";
|
||||||
|
return `<span style="color:#52c41a; cursor:pointer">查看图片</span>`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (col.dataIndex === "level6") {
|
||||||
|
return {
|
||||||
|
...col,
|
||||||
|
customRender: ({ text }: any) => {
|
||||||
|
if (!text) return "-";
|
||||||
|
return `<span style="color:#1890ff; cursor:pointer">播放视频</span>`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { ...col, visible: true };
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
key: "action",
|
||||||
|
dataIndex: "action",
|
||||||
|
fixed: "right",
|
||||||
|
width: 100,
|
||||||
|
align: "center",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleView = (record: any) => {
|
||||||
|
currentRecord.value = { ...record };
|
||||||
|
editModalVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const editModalCancel = () => {
|
||||||
|
editModalVisible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePreviewClick = (record: any, type: string, index: number) => {
|
||||||
|
const mixedList: MediaItem[] = [];
|
||||||
|
if (type === "image") {
|
||||||
|
tablePreviewRecord.value = record;
|
||||||
|
videoPreviewTitle.value = "图片预览";
|
||||||
|
const nameList = record.picpthList;
|
||||||
|
nameList.forEach((item: any) => {
|
||||||
|
mixedList.push({
|
||||||
|
id: record.id,
|
||||||
|
type: "image",
|
||||||
|
name: item.name,
|
||||||
|
url:
|
||||||
|
baseUrlPreview +
|
||||||
|
"/data/fishDraft/previewFile?filename=" +
|
||||||
|
item.name +
|
||||||
|
"&taskId=" +
|
||||||
|
"" +
|
||||||
|
"&type=1",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else if (type === "video") {
|
||||||
|
tablePreviewRecord.value = record;
|
||||||
|
videoPreviewTitle.value = "视频预览";
|
||||||
|
const nameList = record.vdpthList;
|
||||||
|
nameList.forEach((item: any) => {
|
||||||
|
mixedList.push({
|
||||||
|
id: record.id,
|
||||||
|
type: "video",
|
||||||
|
name: item.name,
|
||||||
|
url:
|
||||||
|
baseUrlPreview +
|
||||||
|
"/data/fishDraft/previewFile?filename=" +
|
||||||
|
item.name +
|
||||||
|
"&taskId=" +
|
||||||
|
"" +
|
||||||
|
"&type=2",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else if (type === "formImage") {
|
||||||
|
videoPreviewTitle.value = "图片预览";
|
||||||
|
const nameList = JSON.parse(JSON.stringify(record)).picpthList;
|
||||||
|
nameList.forEach((item: any) => {
|
||||||
|
mixedList.push({
|
||||||
|
id: record.id,
|
||||||
|
type: "formImage",
|
||||||
|
name: item.name,
|
||||||
|
url: item.url,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else if (type === "formVideo") {
|
||||||
|
videoPreviewTitle.value = "视频预览";
|
||||||
|
const nameList = JSON.parse(JSON.stringify(record)).vdpthList;
|
||||||
|
nameList.forEach((item: any) => {
|
||||||
|
mixedList.push({
|
||||||
|
id: record.id,
|
||||||
|
type: "formVideo",
|
||||||
|
name: item.name,
|
||||||
|
url: item.url,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaPreviewVisible.value = true;
|
||||||
|
currentMediaIndex.value = index;
|
||||||
|
nextTick(() => {
|
||||||
|
previewList.value = mixedList;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const prevMedia = () => {
|
||||||
|
if (currentMediaIndex.value > 0) {
|
||||||
|
currentMediaIndex.value--;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const nextMedia = () => {
|
||||||
|
if (currentMediaIndex.value < previewList.value.length - 1) {
|
||||||
|
currentMediaIndex.value++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeMediaPreview = () => {
|
||||||
|
mediaPreviewVisible.value = false;
|
||||||
|
nextTick(() => {
|
||||||
|
previewList.value = [];
|
||||||
|
currentMediaIndex.value = 0;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const customTransform = (res: any) => {
|
||||||
|
const rawRecords = res?.data?.records || [];
|
||||||
|
const modifiedRecords = rawRecords.map((item: any) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
picpthList: item.picpth
|
||||||
|
? item.picpth.split(",").map((item: string) => baseUrl + "/?" + item) || []
|
||||||
|
: [],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
records: modifiedRecords,
|
||||||
|
total: res?.data?.total || 0,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = (values) => {
|
||||||
|
handleSearchFinish(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchFinish = (values: any) => {
|
||||||
|
const filters = [
|
||||||
|
values.ftp && {
|
||||||
|
field: "ftp",
|
||||||
|
operator: "eq",
|
||||||
|
dataType: "string",
|
||||||
|
value: values.ftp,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "strdt",
|
||||||
|
operator: "gte",
|
||||||
|
dataType: "date",
|
||||||
|
value: values.strdt[0],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "strdt",
|
||||||
|
operator: "lte",
|
||||||
|
dataType: "date",
|
||||||
|
value: values.strdt[1],
|
||||||
|
},
|
||||||
|
values.direction && {
|
||||||
|
field: "direction",
|
||||||
|
operator: "eq",
|
||||||
|
dataType: "string",
|
||||||
|
value: values.direction,
|
||||||
|
},
|
||||||
|
values.status && {
|
||||||
|
field: "status",
|
||||||
|
operator: "eq",
|
||||||
|
dataType: "string",
|
||||||
|
value: values.status,
|
||||||
|
},
|
||||||
|
values.stcd && {
|
||||||
|
field: "stcd",
|
||||||
|
operator: "eq",
|
||||||
|
dataType: "string",
|
||||||
|
value: values.stcd,
|
||||||
|
},
|
||||||
|
values.rstcd && {
|
||||||
|
field: "rstcd",
|
||||||
|
operator: "eq",
|
||||||
|
dataType: "string",
|
||||||
|
value: values.rstcd,
|
||||||
|
},
|
||||||
|
values.hbrvcd !== "all" && {
|
||||||
|
field: "hbrvcd",
|
||||||
|
operator: "eq",
|
||||||
|
dataType: "string",
|
||||||
|
value: values.hbrvcd,
|
||||||
|
},
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
const filter = {
|
||||||
|
logic: "and",
|
||||||
|
filters: filters,
|
||||||
|
};
|
||||||
|
tableRef.value?.getList(filter);
|
||||||
|
};
|
||||||
|
|
||||||
|
const tableContainerRef = ref<HTMLElement | null>(null);
|
||||||
|
const tableScrollY = ref<number | undefined>(undefined);
|
||||||
|
|
||||||
|
const calcTableScrollY = () => {
|
||||||
|
if (!tableContainerRef.value) return;
|
||||||
|
const containerHeight = tableContainerRef.value.clientHeight;
|
||||||
|
const otherHeight = 112;
|
||||||
|
const y = containerHeight - otherHeight;
|
||||||
|
tableScrollY.value = y > 0 ? y : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const observer = new ResizeObserver(() => {
|
||||||
|
calcTableScrollY();
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
calcTableScrollY();
|
||||||
|
if (tableContainerRef.value) {
|
||||||
|
observer.observe(tableContainerRef.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
getDictItemsByCode({ dictCode: "direction" }).then((res) => {
|
||||||
|
direction.value = res.data;
|
||||||
|
});
|
||||||
|
|
||||||
|
getDictItemsByCode({ dictCode: "approvalStatus" }).then((res) => {
|
||||||
|
guoyuStatus.value.length = 0;
|
||||||
|
res.data.forEach((item: any) => {
|
||||||
|
if (item.itemCode == "APPROVED") {
|
||||||
|
guoyuStatus.value.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.guoYuSheShiShuJuHistory-page {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #ffffff;
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-list-container {
|
||||||
|
background-color: rgb(234, 241, 251);
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
.list-item {
|
||||||
|
width: 100%;
|
||||||
|
height: 36px;
|
||||||
|
line-height: 36px;
|
||||||
|
padding: 0 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.select {
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: rgb(37, 93, 138);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,66 +1,73 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="guoYuSheShiShuJuTianBao-search">
|
<div class="guoYuSheShiShuJuTianBao-search">
|
||||||
<BasicSearch
|
<BasicSearch ref="basicSearchRef" :searchList="searchList" :initial-values="initSearchData" :zhujianfujian="'fu'" @reset="handleReset"
|
||||||
ref="basicSearchRef"
|
@finish="onSearchFinish" @values-change="onValuesChange">
|
||||||
:searchList="searchList"
|
|
||||||
:initial-values="initSearchData"
|
|
||||||
@reset="handleReset"
|
|
||||||
@finish="onSearchFinish"
|
|
||||||
@values-change="onValuesChange"
|
|
||||||
>
|
|
||||||
<template #ftp="{ onChange }">
|
<template #ftp="{ onChange }">
|
||||||
<fishSearch v-model="localTypeDate" width="200px" @update:modelValue="onChange" />
|
<fishSearch v-model="localTypeDate" width="200px" @update:modelValue="onChange" />
|
||||||
</template>
|
</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<a-tooltip title="新增">
|
<a-tooltip title="手动新增" >
|
||||||
<a-button v-hasPerm="['sjtb:import-add']" @click="props.handleAdd">
|
<a-button v-hasPerm="['sjtb:import-add']" @click="props.handleAdd" type="primary">
|
||||||
新增
|
手动新增
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip title="导入zip">
|
<a-tooltip title="数据批量导入zip">
|
||||||
<a-button v-hasPerm="['sjtb:import-add']" @click="props.importBtn">
|
<a-button v-hasPerm="['sjtb:import-add']" @click="props.importBtn" type="primary">
|
||||||
导入zip
|
数据批量导入zip
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-button
|
<!-- <a-button v-hasPerm="['sjtb:import-add']" @click="props.batchDelBtn" :disabled="batchData.length === 0">
|
||||||
v-hasPerm="['sjtb:import-add']"
|
|
||||||
@click="props.batchDelBtn"
|
|
||||||
:disabled="batchData.length === 0"
|
|
||||||
>
|
|
||||||
批量删除
|
批量删除
|
||||||
</a-button>
|
</a-button> -->
|
||||||
<a-tooltip title="提交数据">
|
<!-- <a-tooltip title="提交数据">
|
||||||
<a-button
|
<a-button v-hasPerm="['sjtb:import-add']" @click="props.submitBtn" :disabled="batchData.length === 0">
|
||||||
v-hasPerm="['sjtb:import-add']"
|
<template #icon>
|
||||||
@click="props.submitBtn"
|
<SaveOutlined />
|
||||||
:disabled="batchData.length === 0"
|
</template>
|
||||||
>
|
|
||||||
<template #icon><SaveOutlined /></template>
|
|
||||||
提交数据
|
提交数据
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
<a-tooltip title="全部提交">
|
||||||
|
<a-button v-hasPerm="['sjtb:import-add']" @click="props.allSubmitBtn" :disabled="batchData.length === 0">
|
||||||
|
<template #icon>
|
||||||
|
<SaveOutlined />
|
||||||
|
</template>
|
||||||
|
全部提交
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip> -->
|
||||||
|
|
||||||
<a-tooltip title="批量审批">
|
<!-- <a-tooltip title="批量审批">
|
||||||
<a-button
|
<a-button v-hasPerm="['sjtb:edit-review']" @click="props.successBtn" :disabled="batchData.length === 0">
|
||||||
v-hasPerm="['sjtb:edit-review']"
|
<template #icon>
|
||||||
@click="props.successBtn"
|
<CheckSquareOutlined />
|
||||||
:disabled="batchData.length === 0"
|
</template>
|
||||||
>
|
|
||||||
<template #icon><CheckSquareOutlined /></template>
|
|
||||||
批量审批
|
批量审批
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip> -->
|
||||||
<a-tooltip title="下载模板">
|
<a-tooltip title="批量导入模板下载">
|
||||||
<a-button v-hasPerm="['sjtb:import-add']" @click="downloadTemplate">
|
<a-button v-hasPerm="['sjtb:import-add']" @click="downloadTemplate">
|
||||||
下载模板
|
批量导入模板下载
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-tooltip placement="left">
|
<!-- <a-tooltip placement="left">
|
||||||
<template #title>
|
<template #title>
|
||||||
<div>导入须知:仅支持 ZIP 压缩包上传,压缩包内需包含 images、video 文件夹,根目录需放置 xlsx 格式表格,单包大小请勿超过 300MB,Excel 单次最大支持 10000 行数据,超出请分批导入,具体请参考下载模版格式。</div>
|
<div>导入须知:仅支持 ZIP 压缩包上传,压缩包内需包含 images、video 文件夹,根目录需放置 xlsx 格式表格,单包大小请勿超过 300MB,Excel 单次最大支持 10000
|
||||||
|
行数据,超出请分批导入,具体请参考下载模版格式。</div>
|
||||||
</template>
|
</template>
|
||||||
<a-button>
|
<a-button>
|
||||||
<template #icon><QuestionOutlined /></template>
|
<template #icon>
|
||||||
|
<QuestionOutlined />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip> -->
|
||||||
|
<a-tooltip title="鱼种类字典数据下载">
|
||||||
|
<a-button>
|
||||||
|
鱼种类字典数据下载
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip title="操作手册下载">
|
||||||
|
<a-button>
|
||||||
|
操作手册下载
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
@ -88,6 +95,7 @@ interface Props {
|
|||||||
importBtn: () => void;
|
importBtn: () => void;
|
||||||
batchDelBtn: () => void;
|
batchDelBtn: () => void;
|
||||||
submitBtn: () => void;
|
submitBtn: () => void;
|
||||||
|
allSubmitBtn:()=> void;
|
||||||
successBtn: () => void;
|
successBtn: () => void;
|
||||||
batchData: any[];
|
batchData: any[];
|
||||||
handleAdd: () => void;
|
handleAdd: () => void;
|
||||||
@ -109,11 +117,11 @@ const initSearchData = {
|
|||||||
stcd: null,
|
stcd: null,
|
||||||
rstcd: null,
|
rstcd: null,
|
||||||
ftp: null,
|
ftp: null,
|
||||||
status: null,
|
status: 'DRAFT',
|
||||||
direction: null,
|
direction: null,
|
||||||
strdt: [
|
strdt: [
|
||||||
dayjs().startOf("month").format("YYYY-MM-DD HH:mm:ss"),
|
dayjs().subtract(1, 'month').startOf('month').format("YYYY-MM-DD"),
|
||||||
dayjs().endOf("day").format("YYYY-MM-DD HH:mm:ss"),
|
dayjs().format("YYYY-MM-DD"),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -160,7 +168,7 @@ const searchList: any = computed(() => [
|
|||||||
width: 120,
|
width: 120,
|
||||||
type: "Select",
|
type: "Select",
|
||||||
name: "status",
|
name: "status",
|
||||||
label: "审批状态",
|
label: "数据状态",
|
||||||
fieldProps: {
|
fieldProps: {
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,254 +1,152 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="guoYuSheShiShuJuTianBao-page">
|
<div class="guoYuSheShiShuJuTianBao-page">
|
||||||
<GuoYuSheShiShuJuTianBaoSearch
|
<GuoYuSheShiShuJuTianBaoSearch ref="searchRef" class="search-container" :guoyuStatus="guoyuStatus"
|
||||||
ref="searchRef"
|
:direction="direction" :handle-add="handleAdd" :batchData="batchData" :importBtn="importBtn"
|
||||||
class="search-container"
|
:batchDelBtn="batchDelBtn" :submitBtn="submitBtn" :allSubmitBtn="allSubmitBtn" :successBtn="successBtn"
|
||||||
:guoyuStatus="guoyuStatus"
|
@reset="handleReset" @search-finish="handleSearchFinish" />
|
||||||
:direction="direction"
|
|
||||||
:handle-add="handleAdd"
|
|
||||||
:batchData="batchData"
|
|
||||||
:importBtn="importBtn"
|
|
||||||
:batchDelBtn="batchDelBtn"
|
|
||||||
:submitBtn="submitBtn"
|
|
||||||
:successBtn="successBtn"
|
|
||||||
@reset="handleReset"
|
|
||||||
@search-finish="handleSearchFinish"
|
|
||||||
/>
|
|
||||||
<!-- 主表格 -->
|
<!-- 主表格 -->
|
||||||
<div class="table-container" ref="tableContainerRef">
|
<div class="table-container" ref="tableContainerRef">
|
||||||
<BasicTable
|
<BasicTable ref="tableRef" :columns="columns" :scroll-y="tableScrollY" :list-url="getFishDraftPage"
|
||||||
ref="tableRef"
|
:search-params="{}" :enable-row-selection="true" :get-checkbox-props="getCheckboxProps"
|
||||||
:columns="columns"
|
:transform-data="customTransform" @selection-change="handleSelectionChange">
|
||||||
:scroll-y="tableScrollY"
|
|
||||||
:list-url="getFishDraftPage"
|
|
||||||
:search-params="{}"
|
|
||||||
:enable-row-selection="true"
|
|
||||||
:get-checkbox-props="getCheckboxProps"
|
|
||||||
:transform-data="customTransform"
|
|
||||||
@selection-change="handleSelectionChange"
|
|
||||||
>
|
|
||||||
<!-- 使用 bodyCell 插槽自定义单元格渲染 -->
|
<!-- 使用 bodyCell 插槽自定义单元格渲染 -->
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.key === 'action' || column.dataIndex === 'action'">
|
<template v-if="column.key === 'action' || column.dataIndex === 'action'">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<a-button
|
<a-button v-hasPerm="['sjtb:import-add']" type="link" size="small" @click="handleSubmit([record.id])"
|
||||||
v-hasPerm="['sjtb:import-add']"
|
v-if="record.status === 'DRAFT' || record.status === 'REJECTED'">提交</a-button>
|
||||||
type="link"
|
<a-button v-hasPerm="['sjtb:import-add']" type="link" size="small" @click="handleEdit(record, 'edit')"
|
||||||
size="small"
|
v-if="record.status === 'DRAFT' || record.status === 'REJECTED'">编辑</a-button>
|
||||||
@click="handleSubmit([record.id])"
|
<a-button v-hasPerm="['sjtb:edit-review']" type="link" size="small" @click="handleEdit(record, 'edit')"
|
||||||
v-if="record.status === 'DRAFT' || record.status === 'REJECTED'"
|
v-if="record.status === 'PENDING'">编辑</a-button>
|
||||||
>提交</a-button
|
<a-button v-hasPerm="['sjtb:import-add']" type="link" danger size="small"
|
||||||
>
|
|
||||||
<a-button
|
|
||||||
v-hasPerm="['sjtb:import-add']"
|
|
||||||
type="link"
|
|
||||||
size="small"
|
|
||||||
@click="handleEdit(record, 'edit')"
|
|
||||||
v-if="record.status === 'DRAFT' || record.status === 'REJECTED'"
|
|
||||||
>编辑</a-button
|
|
||||||
>
|
|
||||||
<a-button
|
|
||||||
v-hasPerm="['sjtb:edit-review']"
|
|
||||||
type="link"
|
|
||||||
size="small"
|
|
||||||
@click="handleEdit(record, 'edit')"
|
|
||||||
v-if="record.status === 'PENDING'"
|
|
||||||
>编辑</a-button
|
|
||||||
>
|
|
||||||
<a-button
|
|
||||||
v-hasPerm="['sjtb:import-add']"
|
|
||||||
type="link"
|
|
||||||
danger
|
|
||||||
size="small"
|
|
||||||
@click="handleDelete([record.id])"
|
@click="handleDelete([record.id])"
|
||||||
v-if="record.status === 'DRAFT' || record.status === 'REJECTED'"
|
v-if="record.status === 'DRAFT' || record.status === 'REJECTED'">删除</a-button>
|
||||||
>删除</a-button
|
<a-button type="link" size="small" @click="handleEdit(record, 'view')" v-if="
|
||||||
>
|
|
||||||
<a-button
|
|
||||||
type="link"
|
|
||||||
size="small"
|
|
||||||
@click="handleEdit(record, 'view')"
|
|
||||||
v-if="
|
|
||||||
(checkPerm(['sjtb:edit-review']) && record.status === 'DRAFT') ||
|
(checkPerm(['sjtb:edit-review']) && record.status === 'DRAFT') ||
|
||||||
(checkPerm(['sjtb:import-add']) && record.status === 'PENDING') ||
|
(checkPerm(['sjtb:import-add']) && record.status === 'PENDING') ||
|
||||||
(checkPerm(['sjtb:edit-review']) && record.status === 'REJECTED') ||
|
(checkPerm(['sjtb:edit-review']) && record.status === 'REJECTED') ||
|
||||||
record.status === 'APPROVED'
|
record.status === 'APPROVED'
|
||||||
"
|
">查看</a-button>
|
||||||
>查看</a-button
|
<a-button v-hasPerm="['sjtb:edit-review']" type="link" size="small" @click="handleSuccess([record.id])"
|
||||||
>
|
v-if="record.status === 'PENDING'">审批</a-button>
|
||||||
<a-button
|
<a-button v-hasPerm="['sjtb:edit-review']" type="link" danger size="small"
|
||||||
v-hasPerm="['sjtb:edit-review']"
|
@click="handleReject(record.id)" v-if="record.status === 'PENDING'">驳回</a-button>
|
||||||
type="link"
|
|
||||||
size="small"
|
|
||||||
@click="handleSuccess([record.id])"
|
|
||||||
v-if="record.status === 'PENDING'"
|
|
||||||
>审批</a-button
|
|
||||||
>
|
|
||||||
<a-button
|
|
||||||
v-hasPerm="['sjtb:edit-review']"
|
|
||||||
type="link"
|
|
||||||
danger
|
|
||||||
size="small"
|
|
||||||
@click="handleReject(record.id)"
|
|
||||||
v-if="record.status === 'PENDING'"
|
|
||||||
>驳回</a-button
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</BasicTable>
|
</BasicTable>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="buttonStyle">
|
||||||
|
|
||||||
|
<div class="button">
|
||||||
|
<a-tooltip title="提交数据">
|
||||||
|
<a-button v-hasPerm="['sjtb:import-add']" @click="submitBtn" :disabled="batchData.length === 0" size="large">
|
||||||
|
<template #icon>
|
||||||
|
<SaveOutlined />
|
||||||
|
</template>
|
||||||
|
提交数据
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="button">
|
||||||
|
<a-tooltip title="全部提交">
|
||||||
|
<a-button v-hasPerm="['sjtb:import-add']" @click="allSubmitBtn" size="large">
|
||||||
|
<template #icon>
|
||||||
|
<SaveOutlined />
|
||||||
|
</template>
|
||||||
|
全部提交
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
<div class="button">
|
||||||
|
<a-tooltip title="批量删除">
|
||||||
|
<a-button v-hasPerm="['sjtb:import-add']" @click="batchDelBtn" :disabled="batchData.length === 0" size="large">
|
||||||
|
批量删除
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
<!-- 隐藏的文件输入框 -->
|
<!-- 隐藏的文件输入框 -->
|
||||||
<input
|
<input ref="fileInputRef" type="file" accept=".zip,application/zip,application/x-zip-compressed"
|
||||||
ref="fileInputRef"
|
style="display: none" @change="handleFileSelect" />
|
||||||
type="file"
|
|
||||||
accept=".zip,application/zip,application/x-zip-compressed"
|
|
||||||
style="display: none"
|
|
||||||
@change="handleFileSelect"
|
|
||||||
/>
|
|
||||||
<!-- 导入预览 Modal -->
|
<!-- 导入预览 Modal -->
|
||||||
<a-modal
|
<a-modal title="导入数据预览" ok-text="提交导入" cancel-text="取消导入" :width="1500" v-model:open="visible" :maskClosable="false"
|
||||||
title="导入数据预览"
|
:confirm-loading="fileLoading" @cancel="taskId = ''">
|
||||||
ok-text="提交导入"
|
<GuoYuSheShiShuJuTianBaoTable ref="modalTableRef" :taskId="taskId" :fileTableData="fileTableData"
|
||||||
cancel-text="取消导入"
|
:direction="direction" @preview-click="handlePreviewClick" v-model:file-loading="fileLoading"
|
||||||
:width="1500"
|
@update:file-table-data="(val) => (fileTableData = val)" />
|
||||||
v-model:open="visible"
|
|
||||||
:maskClosable="false"
|
|
||||||
:confirm-loading="fileLoading"
|
|
||||||
@cancel="taskId = ''"
|
|
||||||
>
|
|
||||||
<GuoYuSheShiShuJuTianBaoTable
|
|
||||||
ref="modalTableRef"
|
|
||||||
:taskId="taskId"
|
|
||||||
:fileTableData="fileTableData"
|
|
||||||
:direction="direction"
|
|
||||||
@preview-click="handlePreviewClick"
|
|
||||||
v-model:file-loading="fileLoading"
|
|
||||||
@update:file-table-data="(val) => (fileTableData = val)"
|
|
||||||
/>
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<a-button key="back" @click="handleCustomCancel">取消导入</a-button>
|
<a-button key="back" @click="handleCustomCancel">取消导入</a-button>
|
||||||
<a-button
|
<a-button key="submit" type="primary" :loading="fileLoading" @click="handleModalOk">
|
||||||
key="submit"
|
|
||||||
type="primary"
|
|
||||||
:loading="fileLoading"
|
|
||||||
@click="handleModalOk"
|
|
||||||
>
|
|
||||||
提交导入
|
提交导入
|
||||||
</a-button>
|
</a-button>
|
||||||
</template>
|
</template>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
<!-- 新增/编辑 Modal (对应 React 的 EditModal) -->
|
<!-- 新增/编辑 Modal (对应 React 的 EditModal) -->
|
||||||
<EditModal
|
<EditModal ref="editModalRef" v-model:visible="editModalVisible" :is-view="isView" :direction="direction"
|
||||||
ref="editModalRef"
|
:initial-values="currentRecord" @cancel="editModalCancel" @ok="handleEditSubmit"
|
||||||
v-model:visible="editModalVisible"
|
@preview-click="handlePreviewClick" />
|
||||||
:is-view="isView"
|
|
||||||
:direction="direction"
|
|
||||||
:initial-values="currentRecord"
|
|
||||||
@cancel="editModalCancel"
|
|
||||||
@ok="handleEditSubmit"
|
|
||||||
@preview-click="handlePreviewClick"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 媒体预览 Modal -->
|
<!-- 媒体预览 Modal -->
|
||||||
<a-modal
|
<a-modal v-model:open="mediaPreviewVisible" :title="videoPreviewTitle" :footer="null" width="900px"
|
||||||
v-model:open="mediaPreviewVisible"
|
@cancel="closeMediaPreview" :zIndex="2000">
|
||||||
:title="videoPreviewTitle"
|
|
||||||
:footer="null"
|
|
||||||
width="900px"
|
|
||||||
@cancel="closeMediaPreview"
|
|
||||||
:zIndex="2000"
|
|
||||||
>
|
|
||||||
<div class="flex h-[60vh] gap-4">
|
<div class="flex h-[60vh] gap-4">
|
||||||
<!-- 左侧:混合列表 (图片+视频) -->
|
<!-- 左侧:混合列表 (图片+视频) -->
|
||||||
<div class="w-1/4 overflow-y-auto border-r pr-2 media-list-container">
|
<div class="w-1/4 overflow-y-auto border-r pr-2 media-list-container">
|
||||||
<div
|
<div v-for="(item, index) in previewList" :key="index" class="mb-2 cursor-pointer list-item"
|
||||||
v-for="(item, index) in previewList"
|
:class="{ select: index === currentMediaIndex }" @click="currentMediaIndex = index">
|
||||||
:key="index"
|
|
||||||
class="mb-2 cursor-pointer list-item"
|
|
||||||
:class="{ select: index === currentMediaIndex }"
|
|
||||||
@click="currentMediaIndex = index"
|
|
||||||
>
|
|
||||||
<span class="file-name">{{ item.name }}</span>
|
<span class="file-name">{{ item.name }}</span>
|
||||||
<!-- 删除按钮 -->
|
<!-- 删除按钮 -->
|
||||||
<div
|
<div class="list-item-delete" v-if="item.type != 'formVideo' && item.type != 'formImage'"
|
||||||
class="list-item-delete"
|
@click.stop="handleDeleteMedia(item, index)">
|
||||||
v-if="item.type != 'formVideo' && item.type != 'formImage'"
|
|
||||||
@click.stop="handleDeleteMedia(item, index)"
|
|
||||||
>
|
|
||||||
<CloseCircleOutlined />
|
<CloseCircleOutlined />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右侧:动态预览区域 -->
|
<!-- 右侧:动态预览区域 -->
|
||||||
<div
|
<div class="w-3/4 flex flex-col items-center justify-center bg-gray-100 relative p-4">
|
||||||
class="w-3/4 flex flex-col items-center justify-center bg-gray-100 relative p-4"
|
|
||||||
>
|
|
||||||
<!-- 图片预览 -->
|
<!-- 图片预览 -->
|
||||||
<a-image
|
<a-image v-if="
|
||||||
v-if="
|
|
||||||
currentMediaItem &&
|
currentMediaItem &&
|
||||||
currentMediaItem.url != '' &&
|
currentMediaItem.url != '' &&
|
||||||
(currentMediaItem.type === 'image' || currentMediaItem.type === 'formImage')
|
(currentMediaItem.type === 'image' || currentMediaItem.type === 'formImage')
|
||||||
"
|
" :src="currentMediaItem.url" class="max-w-full max-h-full object-contain" />
|
||||||
:src="currentMediaItem.url"
|
<div v-else-if="
|
||||||
class="max-w-full max-h-full object-contain"
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
v-else-if="
|
|
||||||
currentMediaItem &&
|
currentMediaItem &&
|
||||||
currentMediaItem.url == '' &&
|
currentMediaItem.url == '' &&
|
||||||
(currentMediaItem.type === 'image' || currentMediaItem.type === 'formImage')
|
(currentMediaItem.type === 'image' || currentMediaItem.type === 'formImage')
|
||||||
"
|
" class="text-gray-400">
|
||||||
class="text-gray-400"
|
|
||||||
>
|
|
||||||
暂无图片预览
|
暂无图片预览
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 视频预览 -->
|
<!-- 视频预览 -->
|
||||||
<video
|
<video v-else-if="
|
||||||
v-else-if="
|
|
||||||
currentMediaItem &&
|
currentMediaItem &&
|
||||||
currentMediaItem.url != '' &&
|
currentMediaItem.url != '' &&
|
||||||
(currentMediaItem.type === 'video' || currentMediaItem.type === 'formVideo')
|
(currentMediaItem.type === 'video' || currentMediaItem.type === 'formVideo')
|
||||||
"
|
" :src="currentMediaItem.url" controls autoplay class="max-w-full max-h-[50vh]">
|
||||||
:src="currentMediaItem.url"
|
|
||||||
controls
|
|
||||||
autoplay
|
|
||||||
class="max-w-full max-h-[50vh]"
|
|
||||||
>
|
|
||||||
您的浏览器不支持视频播放
|
您的浏览器不支持视频播放
|
||||||
</video>
|
</video>
|
||||||
<div
|
<div v-else-if="
|
||||||
v-else-if="
|
|
||||||
currentMediaItem &&
|
currentMediaItem &&
|
||||||
currentMediaItem.url == '' &&
|
currentMediaItem.url == '' &&
|
||||||
(currentMediaItem.type === 'video' || currentMediaItem.type === 'formVideo')
|
(currentMediaItem.type === 'video' || currentMediaItem.type === 'formVideo')
|
||||||
"
|
" class="text-gray-400">
|
||||||
class="text-gray-400"
|
|
||||||
>
|
|
||||||
暂无视频预览
|
暂无视频预览
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 底部切换控制 -->
|
<!-- 底部切换控制 -->
|
||||||
<div class="absolute bottom-4 flex gap-4 z-10">
|
<div class="absolute bottom-4 flex gap-4 z-10">
|
||||||
<a-button
|
<a-button shape="circle" :icon="h(LeftOutlined)" @click="prevMedia" :disabled="currentMediaIndex === 0" />
|
||||||
shape="circle"
|
|
||||||
:icon="h(LeftOutlined)"
|
|
||||||
@click="prevMedia"
|
|
||||||
:disabled="currentMediaIndex === 0"
|
|
||||||
/>
|
|
||||||
<span class="self-center text-gray-600 bg-white px-2 rounded shadow-sm">
|
<span class="self-center text-gray-600 bg-white px-2 rounded shadow-sm">
|
||||||
{{ currentMediaIndex + 1 }} / {{ previewList.length }}
|
{{ currentMediaIndex + 1 }} / {{ previewList.length }}
|
||||||
</span>
|
</span>
|
||||||
<a-button
|
<a-button shape="circle" :icon="h(RightOutlined)" @click="nextMedia"
|
||||||
shape="circle"
|
:disabled="currentMediaIndex === previewList.length - 1" />
|
||||||
:icon="h(RightOutlined)"
|
|
||||||
@click="nextMedia"
|
|
||||||
:disabled="currentMediaIndex === previewList.length - 1"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -280,6 +178,7 @@ import {
|
|||||||
getLastImportResult,
|
getLastImportResult,
|
||||||
markImportTaskSuccess,
|
markImportTaskSuccess,
|
||||||
deleteFile,
|
deleteFile,
|
||||||
|
batchApproveAll
|
||||||
} from "@/api/guoYuSheShiShuJuTianBao";
|
} from "@/api/guoYuSheShiShuJuTianBao";
|
||||||
import { Tag } from "ant-design-vue"; // 确保导入 Tag
|
import { Tag } from "ant-design-vue"; // 确保导入 Tag
|
||||||
import { getDictItemsByCode } from "@/api/dict";
|
import { getDictItemsByCode } from "@/api/dict";
|
||||||
@ -487,6 +386,19 @@ const handleSubmit = (ids: any[]) => {
|
|||||||
const submitBtn = async () => {
|
const submitBtn = async () => {
|
||||||
handleSubmit(batchData.value);
|
handleSubmit(batchData.value);
|
||||||
};
|
};
|
||||||
|
//全部提交按钮
|
||||||
|
const allSubmitBtn = async () => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: "是否 提交 全部数据吗?",
|
||||||
|
onOk: async () => {
|
||||||
|
let res: any = await batchApproveAll();
|
||||||
|
if (res && res?.code == 0) {
|
||||||
|
message.success("提交成功");
|
||||||
|
tableRef.value?.refresh();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// 单个/ 批量审批过鱼数据
|
// 单个/ 批量审批过鱼数据
|
||||||
const handleSuccess = (ids: any[]) => {
|
const handleSuccess = (ids: any[]) => {
|
||||||
@ -881,6 +793,7 @@ const handleSearchFinish = (values: any) => {
|
|||||||
dataType: "string",
|
dataType: "string",
|
||||||
value: values.direction,
|
value: values.direction,
|
||||||
},
|
},
|
||||||
|
// status: 'DRAFT',
|
||||||
values.status && {
|
values.status && {
|
||||||
field: "status",
|
field: "status",
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
@ -1105,10 +1018,17 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
getDictItemsByCode({ dictCode: "direction" }).then((res) => {
|
getDictItemsByCode({ dictCode: "direction" }).then((res) => {
|
||||||
direction.value = res.data;
|
direction.value = res.data
|
||||||
|
|
||||||
});
|
});
|
||||||
getDictItemsByCode({ dictCode: "approvalStatus" }).then((res) => {
|
getDictItemsByCode({ dictCode: "approvalStatus" }).then((res) => {
|
||||||
guoyuStatus.value = res.data;
|
guoyuStatus.value.length = 0
|
||||||
|
res.data.forEach((item: any) => {
|
||||||
|
if(item.itemCode != "APPROVED" ){
|
||||||
|
guoyuStatus.value.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// guoyuStatus.value = res.data;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@ -1122,17 +1042,26 @@ onMounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
box-sizing: border-box; /* 防止 padding 撑大高度 */
|
box-sizing: border-box;
|
||||||
overflow: hidden; /* 防止整个页面出现滚动条 */
|
/* 防止 padding 撑大高度 */
|
||||||
|
overflow: hidden;
|
||||||
|
/* 防止整个页面出现滚动条 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-container {
|
.search-container {
|
||||||
flex-shrink: 0; /* 搜索栏不压缩 */
|
flex-shrink: 0;
|
||||||
|
/* 搜索栏不压缩 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-container {
|
.table-container {
|
||||||
flex: 1; /* 占据剩余所有空间 */
|
flex: 1;
|
||||||
overflow: hidden; /* 关键:防止表格溢出容器 */
|
/* 占据剩余所有空间 */
|
||||||
position: relative; /* 为绝对定位元素提供参考(如果需要) */
|
overflow: hidden;
|
||||||
|
/* 关键:防止表格溢出容器 */
|
||||||
|
position: relative;
|
||||||
|
/* 为绝对定位元素提供参考(如果需要) */
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-list-container {
|
.media-list-container {
|
||||||
background-color: rgb(234, 241, 251);
|
background-color: rgb(234, 241, 251);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
@ -1152,6 +1081,7 @@ onMounted(() => {
|
|||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba(255, 255, 255, 0.5);
|
background-color: rgba(255, 255, 255, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-name {
|
.file-name {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@ -1182,4 +1112,15 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.buttonStyle {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
margin: 0px 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -0,0 +1,137 @@
|
|||||||
|
<template>
|
||||||
|
<div class="approval-detail-search">
|
||||||
|
<BasicSearch ref="basicSearchRef" :searchList="searchList" :initial-values="initSearchData" :zhujianfujian="'fu'" @reset="handleReset"
|
||||||
|
@finish="onSearchFinish" @values-change="onValuesChange">
|
||||||
|
<template #ftp="{ onChange }">
|
||||||
|
<fishSearch v-model="localTypeDate" width="200px" @update:modelValue="onChange" />
|
||||||
|
</template>
|
||||||
|
</BasicSearch>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, onMounted, watch } from "vue";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import BasicSearch from "@/components/BasicSearch/index.vue";
|
||||||
|
import { DateSetting } from "@/utils/enumeration";
|
||||||
|
import fishSearch from "@/components/fishSearch/index.vue";
|
||||||
|
import { useShuJuTianBaoStore } from "@/store/modules/shuJuTianBao";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
direction: any[];
|
||||||
|
guoyuStatus: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "reset", values: any): void;
|
||||||
|
(e: "search-finish", values: any): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const shuJuTianBaoStore = useShuJuTianBaoStore();
|
||||||
|
const localTypeDate = ref<string>(null);
|
||||||
|
const basicSearchRef = ref<any>();
|
||||||
|
|
||||||
|
const initSearchData = {
|
||||||
|
hbrvcd: "all",
|
||||||
|
stcd: null,
|
||||||
|
rstcd: null,
|
||||||
|
ftp: null,
|
||||||
|
direction: null,
|
||||||
|
strdt: [
|
||||||
|
dayjs().subtract(1, "month").startOf("month").format("YYYY-MM-DD HH:mm:ss"),
|
||||||
|
dayjs().endOf("day").format("YYYY-MM-DD HH:mm:ss"),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchData = ref<any>({ ...initSearchData });
|
||||||
|
|
||||||
|
const searchList: any = computed(() => [
|
||||||
|
{
|
||||||
|
type: "waterStation",
|
||||||
|
name: "hbrvcd",
|
||||||
|
label: "流域",
|
||||||
|
fieldProps: {
|
||||||
|
allowClear: true,
|
||||||
|
},
|
||||||
|
options: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "Select",
|
||||||
|
name: "stcd",
|
||||||
|
label: "过鱼设施",
|
||||||
|
values: { name: "stnm", value: "stcd" },
|
||||||
|
fieldProps: {
|
||||||
|
allowClear: true,
|
||||||
|
},
|
||||||
|
options: shuJuTianBaoStore.fpssOption,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "Select",
|
||||||
|
name: "direction",
|
||||||
|
label: "游向",
|
||||||
|
width: 120,
|
||||||
|
options: props.direction,
|
||||||
|
fieldProps: {
|
||||||
|
allowClear: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "custom",
|
||||||
|
name: "ftp",
|
||||||
|
label: "鱼种类",
|
||||||
|
fieldProps: {
|
||||||
|
allowClear: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
span: 12,
|
||||||
|
type: "RangePicker",
|
||||||
|
name: "strdt",
|
||||||
|
label: "过鱼时间",
|
||||||
|
picker: "date",
|
||||||
|
fieldProps: {
|
||||||
|
format: "YYYY-MM-DD",
|
||||||
|
valueFormat: "YYYY-MM-DD",
|
||||||
|
allowClear: false,
|
||||||
|
},
|
||||||
|
presets: DateSetting.RangeButton.days,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const onSearchFinish = (values: any) => {
|
||||||
|
emit("search-finish", values);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onValuesChange = (changedValues: any, allValues: any) => {
|
||||||
|
searchData.value = { ...searchData.value, ...allValues };
|
||||||
|
if (
|
||||||
|
Object.keys(changedValues)[0] == "rstcd" ||
|
||||||
|
Object.keys(changedValues)[0] == "hbrvcd"
|
||||||
|
) {
|
||||||
|
const formInstance = basicSearchRef.value?.formData;
|
||||||
|
formInstance.stcd = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
localTypeDate.value = null;
|
||||||
|
emit("reset", initSearchData);
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => initSearchData.ftp,
|
||||||
|
(newVal) => {
|
||||||
|
localTypeDate.value = newVal || "";
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
emit("search-finish", initSearchData);
|
||||||
|
shuJuTianBaoStore.getFpssOption("", "");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss"></style>
|
||||||
@ -1,15 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="shengPiJiLu-page">
|
<div class="shengPiJiLu-page">
|
||||||
<!-- 搜索区域组件,具体 props 需根据实际子组件调整 -->
|
<!-- 搜索区域组件,具体 props 需根据实际子组件调整 -->
|
||||||
<GuoYuSheShiShuJuTianBaoSearch @search-finish="handleSearchFinish" @reset="handleReset" />
|
<GuoYuSheShiShuJuTianBaoSearch @search-finish="handleSearchFinish" @reset="handleReset"
|
||||||
|
:selected-count="selectedRows.length" @batch-approve="handleBatchApprove" />
|
||||||
<!-- 主表格 -->
|
<!-- 主表格 -->
|
||||||
<BasicTable ref="tableRef" :columns="columns" :list-url="queryPageList" :search-params="{sort:paramsTab.sort}" :scroll-y="500">
|
<BasicTable ref="tableRef" :columns="columns" :list-url="queryPageList" :search-params="computedSearchParams"
|
||||||
|
:scroll-y="500" :enable-row-selection="true" @selection-change="handleSelectionChange">
|
||||||
<!-- 使用 bodyCell 插槽自定义单元格渲染 -->
|
<!-- 使用 bodyCell 插槽自定义单元格渲染 -->
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.key === 'action' || column.dataIndex === 'action'">
|
<template v-if="column.key === 'action' || column.dataIndex === 'action'">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<a-button type="link" size="small" @click="handleShowApprovalLog(record)">审批详情</a-button>
|
<a-button type="link" size="small" @click="handleShowDetail(record)">详情</a-button>
|
||||||
<a-button type="link" size="small" @click="handleShowChangeLog(record)">变更详情</a-button>
|
<a-button type="link" size="small" @click="handleShowApprovalLog(record)">审批记录</a-button>
|
||||||
|
<!-- <a-button type="link" size="small" @click="handleShowChangeLog(record)">变更详情</a-button> -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="column.dataIndex === 'bizType'">
|
<template v-if="column.dataIndex === 'bizType'">
|
||||||
@ -59,19 +62,139 @@
|
|||||||
</BasicTable>
|
</BasicTable>
|
||||||
</div>
|
</div>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
|
|
||||||
|
<!-- 详情信息弹框 -->
|
||||||
|
<a-modal v-model:open="detailVisible" title="详情信息" width="1800px" :footer="null" destroy-on-close="false">
|
||||||
|
<div class="detail-modal-content">
|
||||||
|
<!-- 搜索组件 -->
|
||||||
|
<ApprovalDetailSearch :guoyu-status="guoyuStatus" :direction="direction"
|
||||||
|
@search-finish="handleDetailSearchFinish" @reset="handleDetailReset" />
|
||||||
|
|
||||||
|
<!-- 表格组件 -->
|
||||||
|
<BasicTable ref="detailTableRef" :columns="detailColumns" :list-url="getFishDraftPage"
|
||||||
|
:scroll-y="'500px'" :transform-data="customTransform">
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.dataIndex === 'isfs'">
|
||||||
|
<a-tag :color="record.isfs === 1 || record.isfs === '1' ? 'success' : 'error'">
|
||||||
|
{{ record.isfs === 1 || record.isfs === '1' ? '是' : '否' }}
|
||||||
|
</a-tag>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.dataIndex === 'direction'">
|
||||||
|
{{ handName(record.direction, direction) }}
|
||||||
|
</template>
|
||||||
|
<template v-if="column.key === 'action' || column.dataIndex === 'action'">
|
||||||
|
<a-button type="link" size="small" @click="handleView(record)">查看</a-button>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
|
<!-- 查看详情弹框 -->
|
||||||
|
<EditModal ref="editModalRef" v-model:visible="viewModalVisible" :is-view="true" :direction="direction"
|
||||||
|
:initial-values="currentViewRecord" @cancel="handleViewCancel" @preview-click="handlePreviewClick" />
|
||||||
|
|
||||||
|
<!-- 媒体预览 Modal -->
|
||||||
|
<a-modal v-model:open="mediaPreviewVisible" :title="videoPreviewTitle" :footer="null" width="900px"
|
||||||
|
@cancel="closeMediaPreview" :z-index="2000">
|
||||||
|
<div class="flex h-[60vh] gap-4">
|
||||||
|
<!-- 左侧:混合列表 (图片+视频) -->
|
||||||
|
<div class="w-1/4 overflow-y-auto border-r pr-2 media-list-container">
|
||||||
|
<div v-for="(item, index) in previewList" :key="index" class="mb-2 cursor-pointer list-item"
|
||||||
|
:class="{ select: index === currentMediaIndex }" @click="currentMediaIndex = index">
|
||||||
|
<span class="file-name">{{ item.name }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧:动态预览区域 -->
|
||||||
|
<div class="w-3/4 flex flex-col items-center justify-center bg-gray-100 relative p-4">
|
||||||
|
<!-- 图片预览 -->
|
||||||
|
<a-image v-if="
|
||||||
|
currentMediaItem &&
|
||||||
|
currentMediaItem.url != '' &&
|
||||||
|
(currentMediaItem.type === 'image' || currentMediaItem.type === 'formImage')
|
||||||
|
" :src="currentMediaItem.url" class="max-w-full max-h-full object-contain" />
|
||||||
|
<div v-else-if="
|
||||||
|
currentMediaItem &&
|
||||||
|
currentMediaItem.url == '' &&
|
||||||
|
(currentMediaItem.type === 'image' || currentMediaItem.type === 'formImage')
|
||||||
|
" class="text-gray-400">
|
||||||
|
暂无图片预览
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 视频预览 -->
|
||||||
|
<video v-else-if="
|
||||||
|
currentMediaItem &&
|
||||||
|
currentMediaItem.url != '' &&
|
||||||
|
(currentMediaItem.type === 'video' || currentMediaItem.type === 'formVideo')
|
||||||
|
" :src="currentMediaItem.url" controls autoplay class="max-w-full max-h-[50vh]">
|
||||||
|
您的浏览器不支持视频播放
|
||||||
|
</video>
|
||||||
|
<div v-else-if="
|
||||||
|
currentMediaItem &&
|
||||||
|
currentMediaItem.url == '' &&
|
||||||
|
(currentMediaItem.type === 'video' || currentMediaItem.type === 'formVideo')
|
||||||
|
" class="text-gray-400">
|
||||||
|
暂无视频预览
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 底部切换控制 -->
|
||||||
|
<div class="absolute bottom-4 flex gap-4 z-10">
|
||||||
|
<a-button shape="circle" :icon="h(LeftOutlined)" @click="prevMedia"
|
||||||
|
:disabled="currentMediaIndex === 0" />
|
||||||
|
<span class="self-center text-gray-600 bg-white px-2 rounded shadow-sm">
|
||||||
|
{{ currentMediaIndex + 1 }} / {{ previewList.length }}
|
||||||
|
</span>
|
||||||
|
<a-button shape="circle" :icon="h(RightOutlined)" @click="nextMedia"
|
||||||
|
:disabled="currentMediaIndex === previewList.length - 1" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
|
<!-- 批量审批弹框 -->
|
||||||
|
<a-modal v-model:open="batchApproveVisible" title="批量审批确认" :confirm-loading="batchApproveLoading"
|
||||||
|
@ok="handleBatchApproveConfirm" @cancel="handleBatchApproveCancel" width="600px">
|
||||||
|
<a-form :model="batchApproveForm">
|
||||||
|
<a-form-item label="选中记录数">
|
||||||
|
<span>{{ selectedRows.length }} 条</span>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item label="审批意见" required>
|
||||||
|
<a-textarea v-model:value="batchApproveForm.approveComment" :rows="4" placeholder="请输入审批意见(必填)"
|
||||||
|
:maxlength="500" show-count />
|
||||||
|
</a-form-item>
|
||||||
|
</a-form>
|
||||||
|
</a-modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, reactive, onMounted, h } from 'vue';
|
import { ref, reactive, onMounted, h, computed, nextTick } from 'vue';
|
||||||
import { queryPageList, getApprovalLogList, getApprovalChangeLogList } from '@/api/shengPiJiLu';
|
import dayjs from "dayjs";
|
||||||
import { Tag } from "ant-design-vue"; // 确保导入 Tag
|
import { queryPageList, getApprovalLogList, getApprovalChangeLogList, batchApproveByApprovalId } from '@/api/shengPiJiLu';
|
||||||
|
import { Tag, message } from "ant-design-vue";
|
||||||
|
import { LeftOutlined, RightOutlined } from "@ant-design/icons-vue";
|
||||||
import BasicTable from "@/components/BasicTable/index.vue";
|
import BasicTable from "@/components/BasicTable/index.vue";
|
||||||
import GuoYuSheShiShuJuTianBaoSearch from "./shengPiJiLuSearch.vue";
|
import GuoYuSheShiShuJuTianBaoSearch from "./shengPiJiLuSearch.vue";
|
||||||
import ApprovalLogSearch from "./approvalLogSearch.vue";
|
import ApprovalLogSearch from "./approvalLogSearch.vue";
|
||||||
import ChangeLogSearch from "./changeLogSearch.vue";
|
import ChangeLogSearch from "./changeLogSearch.vue";
|
||||||
|
import ApprovalDetailSearch from "./approvalDetailSearch.vue";
|
||||||
|
import EditModal from "@/views/shuJuTianBao/guoYuSheShiShuJuTianBao/guoYuSheShiShuJuTianBaoForm.vue";
|
||||||
import { getDictItemsByCode } from '@/api/dict';
|
import { getDictItemsByCode } from '@/api/dict';
|
||||||
|
import { getFishDraftPage } from "@/api/guoYuSheShiShuJuTianBao";
|
||||||
|
|
||||||
|
const baseUrl = import.meta.env.VITE_APP_PREVIEW_URL;
|
||||||
|
const baseUrlPreview = import.meta.env.VITE_APP_BASE_URL;
|
||||||
|
|
||||||
let columns = ref([
|
let columns = ref([
|
||||||
|
{
|
||||||
|
dataIndex: "hbrvnm",
|
||||||
|
key: "hbrvnm",
|
||||||
|
title: "流域",
|
||||||
|
width: 120,
|
||||||
|
fixed: "left",
|
||||||
|
},
|
||||||
|
{ dataIndex: "ennm", key: "ennm", title: "电站名称", width: 120, fixed: "left" },
|
||||||
{
|
{
|
||||||
title: '审批批次号',
|
title: '审批批次号',
|
||||||
dataIndex: 'approvalNo',
|
dataIndex: 'approvalNo',
|
||||||
@ -79,13 +202,6 @@ let columns = ref([
|
|||||||
width: 160,
|
width: 160,
|
||||||
fixed: 'left'
|
fixed: 'left'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: '业务类型',
|
|
||||||
dataIndex: 'bizType',
|
|
||||||
key: 'bizType',
|
|
||||||
width: 100,
|
|
||||||
align: 'center'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: '数据条数',
|
title: '数据条数',
|
||||||
dataIndex: 'dataCount',
|
dataIndex: 'dataCount',
|
||||||
@ -153,25 +269,30 @@ let columns = ref([
|
|||||||
key: 'action',
|
key: 'action',
|
||||||
width: 160,
|
width: 160,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
align: 'center'
|
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
const tableRef = ref()
|
const tableRef = ref()
|
||||||
const handleSearchFinish = (values: any) => {
|
const handleSearchFinish = (values: any) => {
|
||||||
console.log(values);
|
console.log(values);
|
||||||
const filters = [
|
const filters = [
|
||||||
values.approvalNo && {
|
|
||||||
field: "approvalNo",
|
|
||||||
operator: "contains",
|
|
||||||
dataType: "string",
|
|
||||||
value: values.approvalNo,
|
|
||||||
},
|
|
||||||
values.status && {
|
values.status && {
|
||||||
field: "status",
|
field: "status",
|
||||||
operator: "eq",
|
operator: "eq",
|
||||||
dataType: "string",
|
dataType: "string",
|
||||||
value: values.status,
|
value: values.status,
|
||||||
},
|
},
|
||||||
|
values.hbrvcd !== "all" && {
|
||||||
|
field: "hbrvcd",
|
||||||
|
operator: "eq",
|
||||||
|
dataType: "string",
|
||||||
|
value: values.hbrvcd,
|
||||||
|
},
|
||||||
|
values.stcd && {
|
||||||
|
field: "stcd",
|
||||||
|
operator: "eq",
|
||||||
|
dataType: "string",
|
||||||
|
value: values.stcd,
|
||||||
|
},
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
|
|
||||||
|
|
||||||
@ -277,6 +398,255 @@ const currentApprovalId = ref('');
|
|||||||
const actionTypeDict = ref([]);
|
const actionTypeDict = ref([]);
|
||||||
const operationTypeDict = ref([]);
|
const operationTypeDict = ref([]);
|
||||||
|
|
||||||
|
// 详情弹框相关
|
||||||
|
const detailVisible = ref(false);
|
||||||
|
const detailTableRef = ref();
|
||||||
|
const guoyuStatus = ref([]);
|
||||||
|
const direction = ref([]);
|
||||||
|
|
||||||
|
// 查看详情弹框相关
|
||||||
|
const viewModalVisible = ref(false);
|
||||||
|
const editModalRef = ref<any>(null);
|
||||||
|
const currentViewRecord = ref<any>(null);
|
||||||
|
|
||||||
|
// 媒体预览相关
|
||||||
|
const mediaPreviewVisible = ref(false);
|
||||||
|
const videoPreviewTitle = ref("预览");
|
||||||
|
interface MediaItem {
|
||||||
|
id: string;
|
||||||
|
type: "image" | "video" | "formVideo" | "formImage";
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
const previewList = ref<MediaItem[]>([]);
|
||||||
|
const currentMediaIndex = ref(0);
|
||||||
|
const tablePreviewRecord = ref<any>({});
|
||||||
|
|
||||||
|
const currentMediaItem = computed(() => previewList.value[currentMediaIndex.value]);
|
||||||
|
|
||||||
|
// 详情表格列定义(删除状态列)
|
||||||
|
const detailColumns = ref([
|
||||||
|
{ dataIndex: "hbrvnm", key: "hbrvnm", title: "流域", width: 120, fixed: "left" },
|
||||||
|
{ dataIndex: "ennm", key: "ennm", title: "电站名称", width: 120, fixed: "left" },
|
||||||
|
{ dataIndex: "stnm", key: "stnm", title: "过鱼设施名称", width: 150, fixed: "left" },
|
||||||
|
{ dataIndex: "strdt", key: "strdt", title: "过鱼时间", width: 150 },
|
||||||
|
{ dataIndex: "ftpName", key: "ftpName", title: "鱼种类", width: 120 },
|
||||||
|
{
|
||||||
|
dataIndex: "isfs",
|
||||||
|
key: "isfs",
|
||||||
|
title: "是否鱼苗",
|
||||||
|
width: 74,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: "direction",
|
||||||
|
key: "direction",
|
||||||
|
title: "游向",
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{ dataIndex: "fcnt", key: "fcnt", title: "过鱼数量(尾)", width: 120 },
|
||||||
|
{ dataIndex: "fsz", key: "fsz", title: "体长(cm)", width: 110 },
|
||||||
|
{ dataIndex: "fwet", key: "fwet", title: "平均体重(g)", width: 110 },
|
||||||
|
{ dataIndex: "wt", key: "wt", title: "水温(℃)", width: 80 },
|
||||||
|
{ dataIndex: "tm", key: "tm", title: "填报时间", width: 150 },
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
key: "action",
|
||||||
|
dataIndex: "action",
|
||||||
|
fixed: "right",
|
||||||
|
width: 100,
|
||||||
|
align: "center",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 数据转换函数
|
||||||
|
const customTransform = (res: any) => {
|
||||||
|
const rawRecords = res?.data?.records || [];
|
||||||
|
const modifiedRecords = rawRecords.map((item: any) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
picpthList: item.picpth
|
||||||
|
? item.picpth.split(",").map((item: string) => baseUrl + "/?" + item) || []
|
||||||
|
: [],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
records: modifiedRecords,
|
||||||
|
total: res?.data?.total || 0,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 详情搜索处理
|
||||||
|
const handleDetailSearchFinish = (values: any) => {
|
||||||
|
console.log('详情搜索:', values);
|
||||||
|
const filters = [
|
||||||
|
values.ftp && {
|
||||||
|
field: "ftp",
|
||||||
|
operator: "eq",
|
||||||
|
dataType: "string",
|
||||||
|
value: values.ftp,
|
||||||
|
},
|
||||||
|
values.strdt && {
|
||||||
|
field: "strdt",
|
||||||
|
operator: "gte",
|
||||||
|
dataType: "date",
|
||||||
|
value: values.strdt[0],
|
||||||
|
},
|
||||||
|
values.strdt && {
|
||||||
|
field: "strdt",
|
||||||
|
operator: "lte",
|
||||||
|
dataType: "date",
|
||||||
|
value: values.strdt[1],
|
||||||
|
},
|
||||||
|
values.direction && {
|
||||||
|
field: "direction",
|
||||||
|
operator: "eq",
|
||||||
|
dataType: "string",
|
||||||
|
value: values.direction,
|
||||||
|
},
|
||||||
|
values.rstcd && {
|
||||||
|
field: "rstcd",
|
||||||
|
operator: "eq",
|
||||||
|
dataType: "string",
|
||||||
|
value: values.rstcd,
|
||||||
|
},
|
||||||
|
values.hbrvcd !== "all" && {
|
||||||
|
field: "hbrvcd",
|
||||||
|
operator: "eq",
|
||||||
|
dataType: "string",
|
||||||
|
value: values.hbrvcd,
|
||||||
|
},
|
||||||
|
approvalId.value && {
|
||||||
|
field: "approvalId", // 字段名
|
||||||
|
operator: "eq", // 等于操作符
|
||||||
|
dataType: "string", // 字符串类型
|
||||||
|
value: approvalId.value // 从 record 中获取审批批次号
|
||||||
|
}
|
||||||
|
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
const filter = {
|
||||||
|
logic: "and",
|
||||||
|
filters: filters,
|
||||||
|
};
|
||||||
|
detailTableRef.value?.getList(filter);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 详情重置处理
|
||||||
|
const handleDetailReset = (values: any) => {
|
||||||
|
console.log('详情重置:', values);
|
||||||
|
handleDetailSearchFinish(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 显示详情弹框
|
||||||
|
const approvalId = ref('');
|
||||||
|
const handleShowDetail = (record: any) => {
|
||||||
|
detailVisible.value = true;
|
||||||
|
// 延迟调用,确保弹框和 BasicTable 组件完全渲染 approvalNo:record.approvalNo
|
||||||
|
setTimeout(() => {
|
||||||
|
approvalId.value = record.id;
|
||||||
|
|
||||||
|
|
||||||
|
// 传入 approvalNo 的筛选条件
|
||||||
|
const filters = [
|
||||||
|
{
|
||||||
|
field: "strdt",
|
||||||
|
operator: "gte",
|
||||||
|
dataType: "date",
|
||||||
|
value: dayjs().subtract(1, "month").startOf("month").format("YYYY-MM-DD HH:mm:ss"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: "strdt",
|
||||||
|
operator: "lte",
|
||||||
|
dataType: "date",
|
||||||
|
value: dayjs().endOf("day").format("YYYY-MM-DD HH:mm:ss"),
|
||||||
|
},
|
||||||
|
approvalId.value && {
|
||||||
|
field: "approvalId", // 字段名
|
||||||
|
operator: "eq", // 等于操作符
|
||||||
|
dataType: "string", // 字符串类型
|
||||||
|
value: approvalId.value // 从 record 中获取审批批次号
|
||||||
|
}
|
||||||
|
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
const filter = {
|
||||||
|
logic: "and",
|
||||||
|
filters: filters,
|
||||||
|
};
|
||||||
|
detailTableRef.value?.getList(filter);
|
||||||
|
// detailTableRef.value?.getList();
|
||||||
|
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查看单条记录
|
||||||
|
const handleView = (record: any) => {
|
||||||
|
currentViewRecord.value = { ...record };
|
||||||
|
viewModalVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 关闭查看弹框
|
||||||
|
const handleViewCancel = () => {
|
||||||
|
viewModalVisible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理预览点击
|
||||||
|
const handlePreviewClick = (record: any, type: string, index: number) => {
|
||||||
|
const mixedList: MediaItem[] = [];
|
||||||
|
if (type === "formImage") {
|
||||||
|
videoPreviewTitle.value = "图片预览";
|
||||||
|
const nameList = JSON.parse(JSON.stringify(record)).picpthList;
|
||||||
|
nameList.forEach((item: any) => {
|
||||||
|
mixedList.push({
|
||||||
|
id: record.id,
|
||||||
|
type: "formImage",
|
||||||
|
name: item.name,
|
||||||
|
url: item.url,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else if (type === "formVideo") {
|
||||||
|
videoPreviewTitle.value = "视频预览";
|
||||||
|
const nameList = JSON.parse(JSON.stringify(record)).vdpthList;
|
||||||
|
nameList.forEach((item: any) => {
|
||||||
|
mixedList.push({
|
||||||
|
id: record.id,
|
||||||
|
type: "formVideo",
|
||||||
|
name: item.name,
|
||||||
|
url: item.url,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaPreviewVisible.value = true;
|
||||||
|
currentMediaIndex.value = index;
|
||||||
|
nextTick(() => {
|
||||||
|
previewList.value = mixedList;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 上一个媒体
|
||||||
|
const prevMedia = () => {
|
||||||
|
if (currentMediaIndex.value > 0) {
|
||||||
|
currentMediaIndex.value--;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 下一个媒体
|
||||||
|
const nextMedia = () => {
|
||||||
|
if (currentMediaIndex.value < previewList.value.length - 1) {
|
||||||
|
currentMediaIndex.value++;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 关闭媒体预览
|
||||||
|
const closeMediaPreview = () => {
|
||||||
|
mediaPreviewVisible.value = false;
|
||||||
|
nextTick(() => {
|
||||||
|
previewList.value = [];
|
||||||
|
currentMediaIndex.value = 0;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// 显示审批操作日志弹框
|
// 显示审批操作日志弹框
|
||||||
const handleShowApprovalLog = (record: any) => {
|
const handleShowApprovalLog = (record: any) => {
|
||||||
currentApprovalId.value = record.id;
|
currentApprovalId.value = record.id;
|
||||||
@ -383,8 +753,17 @@ const handleChangeLogReset = (values: any) => {
|
|||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
dictNmae()
|
dictNmae();
|
||||||
|
|
||||||
|
// 获取过鱼设施相关字典
|
||||||
|
getDictItemsByCode({ dictCode: 'direction' }).then((res) => {
|
||||||
|
direction.value = res.data;
|
||||||
});
|
});
|
||||||
|
getDictItemsByCode({ dictCode: 'approvalStatus' }).then((res) => {
|
||||||
|
guoyuStatus.value = res.data;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const shenStatus = ref([])
|
const shenStatus = ref([])
|
||||||
const yeWuType = ref([])
|
const yeWuType = ref([])
|
||||||
const dictNmae = () => {
|
const dictNmae = () => {
|
||||||
@ -416,6 +795,92 @@ const handName = (val: any, arr: any) => {
|
|||||||
})
|
})
|
||||||
return dictName1
|
return dictName1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量审批相关状态
|
||||||
|
const batchApproveVisible = ref(false);
|
||||||
|
const batchApproveLoading = ref(false);
|
||||||
|
const batchApproveForm = ref({
|
||||||
|
approveComment: ''
|
||||||
|
});
|
||||||
|
const selectedRows = ref<any[]>([]);
|
||||||
|
|
||||||
|
// 监听表格选中变化
|
||||||
|
const handleSelectionChange = (rows: any[], data: any) => {
|
||||||
|
selectedRows.value = data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 打开批量审批弹框
|
||||||
|
const handleBatchApprove = () => {
|
||||||
|
if (selectedRows.value.length === 0) {
|
||||||
|
message.warning('请至少选择一条记录');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
batchApproveForm.value.approveComment = '';
|
||||||
|
batchApproveVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 确认批量审批
|
||||||
|
const handleBatchApproveConfirm = async () => {
|
||||||
|
// 验证审批意见
|
||||||
|
if (!batchApproveForm.value.approveComment || batchApproveForm.value.approveComment.trim() === '') {
|
||||||
|
message.warning('请填写审批意见');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有已通过的记录
|
||||||
|
const hasApprovedRecord = selectedRows.value.some(row => row.status != 'PENDING');
|
||||||
|
if (hasApprovedRecord) {
|
||||||
|
message.warning('当前有选中的数据中含有已通过或已驳回的记录,请重试。');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
batchApproveLoading.value = true;
|
||||||
|
try {
|
||||||
|
// 获取选中行的主键ID作为 approvalIds
|
||||||
|
const approvalIds = selectedRows.value.map(row => row.id).filter(id => id);
|
||||||
|
|
||||||
|
if (approvalIds.length === 0) {
|
||||||
|
message.warning('选中的记录缺少有效的ID');
|
||||||
|
batchApproveLoading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res: any = await batchApproveByApprovalId({
|
||||||
|
approvalIds,
|
||||||
|
approveComment: batchApproveForm.value.approveComment
|
||||||
|
});
|
||||||
|
if (res.code == '0') {
|
||||||
|
message.success('批量审批成功');
|
||||||
|
batchApproveVisible.value = false;
|
||||||
|
batchApproveForm.value.approveComment = '';
|
||||||
|
|
||||||
|
// 清空选中状态
|
||||||
|
tableRef.value?.clearSelection();
|
||||||
|
|
||||||
|
// 刷新表格(保持当前搜索条件)
|
||||||
|
handleSearchFinish(searchData.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// console.error('批量审批失败:', error);
|
||||||
|
// message.error('批量审批失败,请稍后重试');
|
||||||
|
} finally {
|
||||||
|
batchApproveLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 取消批量审批
|
||||||
|
const handleBatchApproveCancel = () => {
|
||||||
|
batchApproveVisible.value = false;
|
||||||
|
batchApproveForm.value.approveComment = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 搜索相关数据
|
||||||
|
const searchData = ref<any>({
|
||||||
|
hbrvcd: 'all',
|
||||||
|
status: '',
|
||||||
|
});
|
||||||
|
|
||||||
// 搜索相关弹框
|
// 搜索相关弹框
|
||||||
const paramsTab = ref({
|
const paramsTab = ref({
|
||||||
sort: [
|
sort: [
|
||||||
@ -426,6 +891,11 @@ const paramsTab = ref({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 使用 computed 包装 searchParams,避免深度监听误触发
|
||||||
|
const computedSearchParams = computed(() => ({
|
||||||
|
sort: paramsTab.value.sort
|
||||||
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@ -447,4 +917,53 @@ const paramsTab = ref({
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detail-modal-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.caozuo {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media-list-container {
|
||||||
|
background-color: rgb(234, 241, 251);
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
.list-item {
|
||||||
|
width: 100%;
|
||||||
|
height: 36px;
|
||||||
|
line-height: 36px;
|
||||||
|
padding: 0 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.select {
|
||||||
|
color: #ffffff;
|
||||||
|
background-color: rgb(37, 93, 138);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -1,7 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="guoYuSheShiShuJuTianBao-search">
|
<div class="guoYuSheShiShuJuTianBao-search">
|
||||||
<BasicSearch ref="basicSearchRef" :searchList="searchList" :initial-values="initSearchData"
|
<BasicSearch ref="basicSearchRef" :searchList="searchList" :initial-values="initSearchData" :zhujianfujian="'zhu'"
|
||||||
@finish="onSearchFinish" @values-change="onValuesChange" @reset="handleReset">
|
@finish="onSearchFinish" @values-change="onValuesChange" @reset="handleReset">
|
||||||
|
<template #actions>
|
||||||
|
<a-tooltip title="批量审批">
|
||||||
|
<a-button :disabled="selectedCount === 0" @click="$emit('batch-approve')">
|
||||||
|
<template #icon>
|
||||||
|
<CheckSquareOutlined />
|
||||||
|
</template>
|
||||||
|
批量审批
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
|
||||||
|
</template>
|
||||||
</BasicSearch>
|
</BasicSearch>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -10,21 +21,29 @@
|
|||||||
import { ref, computed, onMounted } from "vue";
|
import { ref, computed, onMounted } from "vue";
|
||||||
import BasicSearch from "@/components/BasicSearch/index.vue"; // 确保路径正确
|
import BasicSearch from "@/components/BasicSearch/index.vue"; // 确保路径正确
|
||||||
import { getDictItemsByCode } from '@/api/dict';
|
import { getDictItemsByCode } from '@/api/dict';
|
||||||
|
import {
|
||||||
|
CheckSquareOutlined,
|
||||||
|
} from "@ant-design/icons-vue";
|
||||||
// --- Props & Emits ---
|
// --- Props & Emits ---
|
||||||
interface Props {
|
interface Props {
|
||||||
|
selectedCount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
selectedCount: 0
|
||||||
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "reset", values: any): void;
|
(e: "reset", values: any): void;
|
||||||
(e: "searchFinish", values: any): void;
|
(e: "searchFinish", values: any): void;
|
||||||
|
(e: "batch-approve"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
||||||
// 模拟 initSearchData
|
// 模拟 initSearchData
|
||||||
const initSearchData = {
|
const initSearchData = {
|
||||||
approvalNo:'',
|
hbrvcd:'all',
|
||||||
|
stcd:'',
|
||||||
status: '',
|
status: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -32,14 +51,15 @@ const searchData = ref<any>({ ...initSearchData });
|
|||||||
|
|
||||||
const searchList: any = computed(() => [
|
const searchList: any = computed(() => [
|
||||||
{
|
{
|
||||||
type: "Input",
|
type: "waterStation",
|
||||||
name: "approvalNo",
|
name: "hbrvcd",
|
||||||
label: "审批批次号",
|
label: "流域",
|
||||||
fieldProps: {
|
fieldProps: {
|
||||||
allowClear: true,
|
allowClear: true,
|
||||||
},
|
},
|
||||||
options: [],
|
options: [],
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
type: "Select",
|
type: "Select",
|
||||||
name: "status",
|
name: "status",
|
||||||
|
|||||||
@ -279,6 +279,13 @@ watch([fishway, isFishDataLoaded], ([newFishway, newDataLoaded]) => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
fishTreeRef.value.setCheckedKeys(fishhui.value, false);
|
fishTreeRef.value.setCheckedKeys(fishhui.value, false);
|
||||||
|
|
||||||
|
// 查找需要展开的父节点
|
||||||
|
const parentCodesToExpand = findParentCodesForCheckedNodes(fishhui.value, fishData.value);
|
||||||
|
console.log('需要展开的父节点codes:', parentCodesToExpand);
|
||||||
|
|
||||||
|
// 控制展开/折叠状态
|
||||||
|
setTreeExpandState(parentCodesToExpand);
|
||||||
|
|
||||||
// 验证回显结果
|
// 验证回显结果
|
||||||
const checkedKeys = fishTreeRef.value.getCheckedKeys();
|
const checkedKeys = fishTreeRef.value.getCheckedKeys();
|
||||||
const halfCheckedKeys = fishTreeRef.value.getHalfCheckedKeys();
|
const halfCheckedKeys = fishTreeRef.value.getHalfCheckedKeys();
|
||||||
@ -384,7 +391,7 @@ function isNodeContainedByOtherParent(nodeId: number, parentIds: number[], allCh
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 统一处理选中IDs的过滤逻辑:如果父节点的所有子节点都被选中,只保留父节点ID
|
// 统一处理选中IDs的过滤逻辑:如果父节点的所有子节点都被选中,只保留父节点ID
|
||||||
function filterSelectedIds(checkedKeysArray: number[], allCheckedNodes: any[]): number[] {
|
function filterSelectedIds(checkedKeysArray: number[], allCheckedNodes: any[]): number[] {
|
||||||
const resultIds: number[] = [];
|
const resultIds: number[] = [];
|
||||||
const fullySelectedParentIds: number[] = [];
|
const fullySelectedParentIds: number[] = [];
|
||||||
@ -424,6 +431,45 @@ function filterSelectedIds(checkedKeysArray: number[], allCheckedNodes: any[]):
|
|||||||
return resultIds;
|
return resultIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 查找选中节点的所有父节点code(用于控制展开状态)
|
||||||
|
function findParentCodesForCheckedNodes(checkedKeys: number[], treeData: any[]): Set<number> {
|
||||||
|
const parentCodesToExpand = new Set<number>();
|
||||||
|
|
||||||
|
function traverse(nodes: any[], parentCode: number | null = null) {
|
||||||
|
for (const node of nodes) {
|
||||||
|
// 如果当前节点是选中节点,记录其父节点
|
||||||
|
if (checkedKeys.includes(node.code) && parentCode !== null) {
|
||||||
|
parentCodesToExpand.add(parentCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 递归处理子节点
|
||||||
|
if (node.children && node.children.length > 0) {
|
||||||
|
traverse(node.children, node.code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
traverse(treeData);
|
||||||
|
return parentCodesToExpand;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 控制树节点展开/折叠状态
|
||||||
|
function setTreeExpandState(parentCodesToExpand: Set<number>) {
|
||||||
|
if (!fishTreeRef.value || !fishTreeRef.value.store) return;
|
||||||
|
|
||||||
|
// 遍历树的所有节点
|
||||||
|
Object.values(fishTreeRef.value.store.nodesMap).forEach((node: any) => {
|
||||||
|
// 只对父节点(有子节点的)进行展开/折叠控制
|
||||||
|
if (node.data.children && node.data.children.length > 0) {
|
||||||
|
if (parentCodesToExpand.has(node.data.code)) {
|
||||||
|
node.expanded = true; // 展开包含选中子节点的父节点
|
||||||
|
} else {
|
||||||
|
node.expanded = false; // 收起没有选中子节点的父节点
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 过鱼设施权限维护 - 处理复选框选中事件
|
// 过鱼设施权限维护 - 处理复选框选中事件
|
||||||
function handleFishCheckChange(checkedNode: any, checkedInfo: any) {
|
function handleFishCheckChange(checkedNode: any, checkedInfo: any) {
|
||||||
const treeInstance = fishTreeRef.value;
|
const treeInstance = fishTreeRef.value;
|
||||||
@ -1045,7 +1091,7 @@ function handleClearSelection() {
|
|||||||
|
|
||||||
<el-scrollbar height="450px" style="margin-top: 10px;">
|
<el-scrollbar height="450px" style="margin-top: 10px;">
|
||||||
<el-tree ref="fishTreeRef" style="max-width: 300px" :data="fishData" show-checkbox
|
<el-tree ref="fishTreeRef" style="max-width: 300px" :data="fishData" show-checkbox
|
||||||
default-expand-all v-loading="fishTreeDialog" node-key="code" highlight-current
|
v-loading="fishTreeDialog" node-key="code" highlight-current
|
||||||
:props="fishProps" @check="handleFishCheckChange" />
|
:props="fishProps" @check="handleFishCheckChange" />
|
||||||
</el-scrollbar>
|
</el-scrollbar>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user