Merge branch 'main' into dev-tw
This commit is contained in:
commit
9a90569ec5
@ -6,3 +6,4 @@ NODE_ENV='development'
|
|||||||
VITE_APP_TITLE = '水电水利建设项目全过程环境管理信息平台'
|
VITE_APP_TITLE = '水电水利建设项目全过程环境管理信息平台'
|
||||||
VITE_APP_PORT = 3000
|
VITE_APP_PORT = 3000
|
||||||
VITE_APP_BASE_API = '/dev-api'
|
VITE_APP_BASE_API = '/dev-api'
|
||||||
|
VITE_APP_PREVIEW_URL = 'https://211.99.26.225:12125'
|
||||||
|
|||||||
@ -4,3 +4,4 @@ NODE_ENV='production'
|
|||||||
VITE_APP_TITLE = 'qgc-buji-web'
|
VITE_APP_TITLE = 'qgc-buji-web'
|
||||||
VITE_APP_PORT = 3000
|
VITE_APP_PORT = 3000
|
||||||
VITE_APP_BASE_API = '/prod-api'
|
VITE_APP_BASE_API = '/prod-api'
|
||||||
|
VITE_APP_PREVIEW_URL = 'https://211.99.26.225:12125'
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="description" content="qgc-buji-web" />
|
<meta name="description" content="qgc-buji-web" />
|
||||||
<meta name="keywords" content="qgc-buji-web" />
|
<meta name="keywords" content="qgc-buji-web" />
|
||||||
<title>水电水利建设项目全过程环境管理信息平台</title>
|
<title>水电水利建设项目全过程环境管理信息平台数据填报子系统</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
@ -8,26 +8,100 @@ export function getFishDraftPage(data:any) {
|
|||||||
data
|
data
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//新增目录
|
//新增过鱼数据
|
||||||
export function addFishDraft(queryParams:any) {
|
export function addFishDraft(queryParams:any) {
|
||||||
return request({
|
return request({
|
||||||
url: '/data/fishDraft/add',
|
url: '/data/fishDraft/saveDraft',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: queryParams
|
data: queryParams
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//修改目录
|
//修改过鱼数据
|
||||||
export function editFishDraft(queryParams:any) {
|
export function editFishDraft(queryParams:any) {
|
||||||
return request({
|
return request({
|
||||||
url: '/data/fishDraft/update',
|
url: '/data/fishDraft/updateDraft',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: queryParams
|
data: queryParams
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
//删除
|
//删除 过鱼数据
|
||||||
export function delFishDraft(data:any) {
|
export function delFishDraft(data:any) {
|
||||||
return request({
|
return request({
|
||||||
url: '/data/fishDraft/batchDelete',
|
url: '/data/fishDraft/batchRemoveDraft',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//提交过鱼数据
|
||||||
|
export function submitFishDraft(data:any) {
|
||||||
|
return request({
|
||||||
|
url: '/data/fishDraft/submitDrafts',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//审批过鱼数据
|
||||||
|
export function successFishDraft(data:any) {
|
||||||
|
return request({
|
||||||
|
url: '/data/fishDraft/batchApprove',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//驳回过鱼数据
|
||||||
|
export function rejectFishDraft(data:any) {
|
||||||
|
return request({
|
||||||
|
url: '/data/fishDraft/reject',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 导入zip
|
||||||
|
export function importFishZip(data:FormData) {
|
||||||
|
return request({
|
||||||
|
url: '/data/fishDraft/importZip',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
headers: { 'Content-Type': 'multipart/form-data' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 取消导入任务
|
||||||
|
export function cancelImportTask(data:any) {
|
||||||
|
return request({
|
||||||
|
url: '/data/fishDraft/cancelImport',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//检测用户导入状态
|
||||||
|
export function checkImportStatus() {
|
||||||
|
return request({
|
||||||
|
url: '/data/fishDraft/checkImportStatus',
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询用户导入导入结果
|
||||||
|
export function getLastImportResult() {
|
||||||
|
return request({
|
||||||
|
url: '/data/fishDraft/getLastImportResult',
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量保存草稿
|
||||||
|
export function batchSaveDraft(data:any) {
|
||||||
|
return request({
|
||||||
|
url: '/data/fishDraft/batchSaveDraft',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记导入任务为成功
|
||||||
|
export function markImportTaskSuccess(data:any) {
|
||||||
|
return request({
|
||||||
|
url: '/data/importTask/markSuccess',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data
|
data
|
||||||
});
|
});
|
||||||
|
|||||||
33
frontend/src/api/select/index.ts
Normal file
33
frontend/src/api/select/index.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
|
||||||
|
// 基地下拉列表
|
||||||
|
export function getBaseDropdown(data:any) {
|
||||||
|
return request({
|
||||||
|
url: '/env/hydrobase/dropdown',
|
||||||
|
method: 'get',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//电站下拉列表
|
||||||
|
export function getEngInfoDropdown(params:any) {
|
||||||
|
return request({
|
||||||
|
url: '/env/engInfo/dropdown',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//过鱼设施下拉列表
|
||||||
|
export function getFpssDropdown(params:any) {
|
||||||
|
return request({
|
||||||
|
url: '/env/fpss/dropdown',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//鱼类名称下拉列表
|
||||||
|
export function getFishDictoryDropdown() {
|
||||||
|
return request({
|
||||||
|
url: '/env/fishDictory/listByName',
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
27
frontend/src/api/shengPiJiLu/index.ts
Normal file
27
frontend/src/api/shengPiJiLu/index.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
|
||||||
|
export function queryPageList(queryParams:any){
|
||||||
|
return request({
|
||||||
|
url: '/data/approvalMain/queryPageList' ,
|
||||||
|
method: 'post',
|
||||||
|
data:queryParams
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询审批操作日志列表
|
||||||
|
export function getApprovalLogList(params: any) {
|
||||||
|
return request({
|
||||||
|
url: '/data/approvalLog/queryPageList',
|
||||||
|
method: 'post',
|
||||||
|
data: params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询审批变更记录列表
|
||||||
|
export function getApprovalChangeLogList(params: any) {
|
||||||
|
return request({
|
||||||
|
url: '/data/approvalChangeLog/queryPageList',
|
||||||
|
method: 'post',
|
||||||
|
data: params
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -6,6 +6,7 @@
|
|||||||
:rules="rules"
|
:rules="rules"
|
||||||
layout="inline"
|
layout="inline"
|
||||||
class="basic-search-form"
|
class="basic-search-form"
|
||||||
|
@reset="handleReset"
|
||||||
@finish="handleFinish"
|
@finish="handleFinish"
|
||||||
@values-change="handleValuesChange"
|
@values-change="handleValuesChange"
|
||||||
>
|
>
|
||||||
@ -18,14 +19,18 @@
|
|||||||
:name="item.name"
|
:name="item.name"
|
||||||
style="width: 100%; margin-bottom: 0"
|
style="width: 100%; margin-bottom: 0"
|
||||||
>
|
>
|
||||||
<!-- 1. 优先检查是否有具名插槽,或者 type 为 custom -->
|
<!-- 1. 优先检查是否有具名插槽,或者 type 为 custom -->
|
||||||
<slot
|
<slot
|
||||||
v-if="$slots[item.name] || item.type === 'custom'"
|
v-if="$slots[item.name] || item.type === 'custom'"
|
||||||
:name="item.name"
|
:name="item.name"
|
||||||
:value="formData[item.name]"
|
:value="formData[item.name]"
|
||||||
:onChange="(val:any) => { formData[item.name] = val }"
|
:onChange="(val:any) => {
|
||||||
:formModel="formData"
|
formData[item.name] = val;
|
||||||
/>
|
triggerManualValuesChange(item.name, val);
|
||||||
|
}"
|
||||||
|
:formModel="formData"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- 普通日期选择器 -->
|
<!-- 普通日期选择器 -->
|
||||||
<a-date-picker
|
<a-date-picker
|
||||||
v-else-if="item.type === 'DataPicker'"
|
v-else-if="item.type === 'DataPicker'"
|
||||||
@ -38,7 +43,9 @@
|
|||||||
:allow-clear="item.fieldProps?.allowClear"
|
:allow-clear="item.fieldProps?.allowClear"
|
||||||
:presets="item.presets"
|
:presets="item.presets"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
|
@change="(val) => triggerManualValuesChange(item.name, val)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 日期范围选择器 -->
|
<!-- 日期范围选择器 -->
|
||||||
<a-range-picker
|
<a-range-picker
|
||||||
v-else-if="item.type === 'RangePicker'"
|
v-else-if="item.type === 'RangePicker'"
|
||||||
@ -51,6 +58,7 @@
|
|||||||
:allow-clear="item.fieldProps?.allowClear"
|
:allow-clear="item.fieldProps?.allowClear"
|
||||||
:presets="item.presets"
|
:presets="item.presets"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
|
@change="(val) => triggerManualValuesChange(item.name, val)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 普通输入框 -->
|
<!-- 普通输入框 -->
|
||||||
@ -60,38 +68,50 @@
|
|||||||
:placeholder="item.placeholder || '请输入'"
|
:placeholder="item.placeholder || '请输入'"
|
||||||
:allow-clear="item.fieldProps?.allowClear"
|
: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)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 电站下拉框 -->
|
<!-- 电站下拉框 -->
|
||||||
<div class="flex gap-[10px]" v-else-if="item.type === 'waterStation'">
|
<div class="flex gap-[10px]" v-else-if="item.type === 'waterStation'">
|
||||||
<a-form-item-rest>
|
<a-form-item-rest>
|
||||||
<a-select
|
<a-select
|
||||||
:value="formData.stcd?.dataDimensionData"
|
:value="formData.baseId"
|
||||||
placeholder="请选择"
|
placeholder="请选择"
|
||||||
@change="dataDimensionDataChange"
|
@change="dataDimensionDataChange"
|
||||||
style="width: 135px"
|
show-search
|
||||||
>
|
allow-clear
|
||||||
<a-select-option
|
:loading="shuJuTianBaoStore.baseLoading"
|
||||||
v-for="opt in item.options"
|
:filter-option="filterOption"
|
||||||
:key="opt.value"
|
style="width: 135px"
|
||||||
:value="opt.value"
|
|
||||||
>
|
>
|
||||||
{{ opt.label }}
|
<a-select-option
|
||||||
</a-select-option>
|
v-for="opt in shuJuTianBaoStore.baseOption"
|
||||||
</a-select>
|
:key="opt.baseid"
|
||||||
<a-select
|
:value="opt.baseid"
|
||||||
:value="formData.stcd?.stcdId"
|
:label="opt.basename"
|
||||||
placeholder="请选择电站"
|
>
|
||||||
@change="stcdIdChange"
|
{{ opt.basename }}
|
||||||
style="width: 135px"
|
</a-select-option>
|
||||||
>
|
</a-select>
|
||||||
<a-select-option
|
<a-select
|
||||||
v-for="opt in item.options"
|
:value="formData.rstcd"
|
||||||
:key="opt.value"
|
placeholder="请选择电站"
|
||||||
:value="opt.value"
|
@change="stcdIdChange"
|
||||||
|
show-search
|
||||||
|
allow-clear
|
||||||
|
:loading="shuJuTianBaoStore.engLoading"
|
||||||
|
:filter-option="filterOption"
|
||||||
|
style="width: 135px"
|
||||||
>
|
>
|
||||||
{{ opt.label }}
|
<a-select-option
|
||||||
</a-select-option>
|
v-for="opt in shuJuTianBaoStore.engOption"
|
||||||
</a-select>
|
:key="opt.stcd"
|
||||||
|
:value="opt.stcd"
|
||||||
|
:label="opt.ennm"
|
||||||
|
>
|
||||||
|
{{ opt.ennm }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
</a-form-item-rest>
|
</a-form-item-rest>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -102,27 +122,28 @@
|
|||||||
:placeholder="item.placeholder || '请选择'"
|
:placeholder="item.placeholder || '请选择'"
|
||||||
:allow-clear="item.fieldProps?.allowClear"
|
: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)"
|
||||||
|
show-search
|
||||||
|
:filter-option="filterOption"
|
||||||
>
|
>
|
||||||
<a-select-option
|
<a-select-option
|
||||||
v-for="opt in item.options"
|
v-for="opt in item.options"
|
||||||
:key="opt.value"
|
:key="opt[item.values?.value] || opt.value || opt.itemCode"
|
||||||
:value="opt.value"
|
:value="opt[item.values?.value] || opt.value || opt.itemCode"
|
||||||
|
:label="opt[item.values?.name] || opt.label || opt.dictName"
|
||||||
>
|
>
|
||||||
{{ opt.label }}
|
{{ 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-else-if="item.type === 'Radio'"
|
||||||
v-model:value="formData[item.name]"
|
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)"
|
||||||
>
|
>
|
||||||
<a-radio
|
<a-radio v-for="opt in item.options" :key="opt.value" :value="opt.value">
|
||||||
v-for="opt in item.options"
|
|
||||||
:key="opt.value"
|
|
||||||
:value="opt.value"
|
|
||||||
>
|
|
||||||
{{ opt.label }}
|
{{ opt.label }}
|
||||||
</a-radio>
|
</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
@ -149,7 +170,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed, reactive, watch, onMounted } from "vue";
|
import { ref, computed, reactive, watch, onMounted, nextTick } from "vue";
|
||||||
|
import { useShuJuTianBaoStore } from "@/store/modules/shuJuTianBao";
|
||||||
|
const shuJuTianBaoStore = useShuJuTianBaoStore();
|
||||||
|
|
||||||
// --- 类型定义 ---
|
// --- 类型定义 ---
|
||||||
export interface SearchItem {
|
export interface SearchItem {
|
||||||
@ -163,7 +186,13 @@ export interface SearchItem {
|
|||||||
xlSpan?: number;
|
xlSpan?: number;
|
||||||
width?: number;
|
width?: number;
|
||||||
presets?: any[];
|
presets?: any[];
|
||||||
options?: { label: string; value: any }[];
|
values?: any;
|
||||||
|
options?: {
|
||||||
|
itemCode?: string;
|
||||||
|
dictName?: string;
|
||||||
|
label: string;
|
||||||
|
value: any;
|
||||||
|
}[];
|
||||||
component?: any;
|
component?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,10 +219,15 @@ const formRef = ref<any>();
|
|||||||
const formData = reactive<any>({});
|
const formData = reactive<any>({});
|
||||||
const rules = reactive<Record<string, any>>({});
|
const rules = reactive<Record<string, any>>({});
|
||||||
|
|
||||||
|
const filterOption = (inputValue: string, option: any) => {
|
||||||
|
if (!option.label) return false;
|
||||||
|
return option.label.indexOf(inputValue) !== -1;
|
||||||
|
};
|
||||||
// 2. 创建计算属性,自动过滤掉 false/null/undefined 的项
|
// 2. 创建计算属性,自动过滤掉 false/null/undefined 的项
|
||||||
const validSearchList = computed(() => {
|
const validSearchList = computed(() => {
|
||||||
return props.searchList.filter(item => item);
|
return props.searchList.filter((item) => item);
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- 初始化逻辑 ---
|
// --- 初始化逻辑 ---
|
||||||
const initForm = () => {
|
const initForm = () => {
|
||||||
const initial = JSON.parse(JSON.stringify(props.initialValues || {}));
|
const initial = JSON.parse(JSON.stringify(props.initialValues || {}));
|
||||||
@ -205,8 +239,12 @@ const initForm = () => {
|
|||||||
Object.assign(formData, initial);
|
Object.assign(formData, initial);
|
||||||
|
|
||||||
// 3. 过滤掉 searchList 中为 false/null/undefined 的项,并生成规则
|
// 3. 过滤掉 searchList 中为 false/null/undefined 的项,并生成规则
|
||||||
|
|
||||||
validSearchList.value.forEach((item) => {
|
validSearchList.value.forEach((item) => {
|
||||||
|
if (item.type == "waterStation") {
|
||||||
|
// 下拉菜单
|
||||||
|
shuJuTianBaoStore.getBaseOption();
|
||||||
|
shuJuTianBaoStore.getEngOption(formData.baseId);
|
||||||
|
}
|
||||||
if (item.fieldProps?.required) {
|
if (item.fieldProps?.required) {
|
||||||
rules[item.name] = [
|
rules[item.name] = [
|
||||||
{ required: true, message: `${item.label}不能为空`, trigger: "blur" },
|
{ required: true, message: `${item.label}不能为空`, trigger: "blur" },
|
||||||
@ -214,15 +252,35 @@ const initForm = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动触发 valuesChange 事件
|
||||||
|
* 用于处理那些没有被 a-form 直接管理的字段(如 waterStation 内部逻辑)
|
||||||
|
* 或者作为标准控件的备份触发机制
|
||||||
|
*/
|
||||||
|
const triggerManualValuesChange = (changedKey: string, newValue: any) => {
|
||||||
|
// 构造变更对象
|
||||||
|
const changedValues = { [changedKey]: newValue };
|
||||||
|
// 发射事件,传递变更值和当前所有值
|
||||||
|
// 注意:这里使用 {...formData} 是为了传递当前的最新状态
|
||||||
|
emit("valuesChange", changedValues, { ...formData });
|
||||||
|
};
|
||||||
|
|
||||||
const dataDimensionDataChange = (value: any) => {
|
const dataDimensionDataChange = (value: any) => {
|
||||||
formData.stcd.dataDimensionData = value;
|
formData.baseId = value;
|
||||||
|
formData.rstcd = "";
|
||||||
|
shuJuTianBaoStore.getEngOption(formData.baseId);
|
||||||
|
|
||||||
|
// 【关键修改】手动触发 valuesChange,因为 a-form-item-rest 阻断了自动监听
|
||||||
|
triggerManualValuesChange("baseId", formData.baseId);
|
||||||
};
|
};
|
||||||
// const hbrvcdChange = (value: any) => {
|
|
||||||
// formData.stcd.hbrvcd = value;
|
|
||||||
// };
|
|
||||||
const stcdIdChange = (value: any) => {
|
const stcdIdChange = (value: any) => {
|
||||||
formData.stcd.stcdId = value;
|
formData.rstcd = value;
|
||||||
|
// 【关键修改】手动触发 valuesChange
|
||||||
|
triggerManualValuesChange("rstcd", formData.rstcd);
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initForm();
|
initForm();
|
||||||
});
|
});
|
||||||
@ -240,7 +298,8 @@ watch(
|
|||||||
|
|
||||||
// --- 事件处理 ---
|
// --- 事件处理 ---
|
||||||
const handleFinish = (values: any) => {
|
const handleFinish = (values: any) => {
|
||||||
emit("finish", values);
|
const finalValues = { ...formData, ...values };
|
||||||
|
emit("finish", finalValues);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleValuesChange = (changedValues: any, allValues: any) => {
|
const handleValuesChange = (changedValues: any, allValues: any) => {
|
||||||
@ -250,12 +309,16 @@ const handleValuesChange = (changedValues: any, allValues: any) => {
|
|||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
if (formRef.value) {
|
if (formRef.value) {
|
||||||
formRef.value.resetFields();
|
formRef.value.resetFields();
|
||||||
|
nextTick(() => {
|
||||||
|
initForm();
|
||||||
|
});
|
||||||
emit("reset");
|
emit("reset");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
form: formRef,
|
form: formRef,
|
||||||
|
formData,
|
||||||
reset: handleReset,
|
reset: handleReset,
|
||||||
submit: () => formRef.value?.submit(),
|
submit: () => formRef.value?.submit(),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -33,6 +33,8 @@ interface Props {
|
|||||||
searchParams?: Record<string, any>;
|
searchParams?: Record<string, any>;
|
||||||
// 默认每页显示数量
|
// 默认每页显示数量
|
||||||
defaultPageSize?: number;
|
defaultPageSize?: number;
|
||||||
|
getCheckboxProps?: (record: any) => any;
|
||||||
|
transformData?: (res: any) => { records: any[]; total: number };
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
@ -40,6 +42,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
rowKey: "id",
|
rowKey: "id",
|
||||||
searchParams: () => ({}),
|
searchParams: () => ({}),
|
||||||
defaultPageSize: 20,
|
defaultPageSize: 20,
|
||||||
|
getCheckboxProps: undefined,
|
||||||
|
transformData: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@ -66,9 +70,7 @@ const rowSelection = computed(() => ({
|
|||||||
selectedRows.value = rows;
|
selectedRows.value = rows;
|
||||||
emit("selection-change", keys, rows);
|
emit("selection-change", keys, rows);
|
||||||
},
|
},
|
||||||
getCheckboxProps: (record: any) => ({
|
getCheckboxProps: props.getCheckboxProps ? props.getCheckboxProps : (record: any) => ({})
|
||||||
disabled: record.disabled, // 可根据业务逻辑禁用某些行的勾选
|
|
||||||
}),
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- Pagination Config ---
|
// --- Pagination Config ---
|
||||||
@ -88,33 +90,42 @@ const paginationConfig = computed(() => ({
|
|||||||
* 获取列表数据
|
* 获取列表数据
|
||||||
* @param extraParams 额外的临时参数(可选)
|
* @param extraParams 额外的临时参数(可选)
|
||||||
*/
|
*/
|
||||||
const getList = async (extraParams?: Record<string, any>) => {
|
const getList = async (filter?: Record<string, any>) => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
// 合并基础分页参数、外部搜索参数和临时参数
|
// 合并基础分页参数、外部搜索参数和临时参数
|
||||||
const params = {
|
const params = {
|
||||||
...props.searchParams,
|
// ...props.searchParams,
|
||||||
...extraParams,
|
|
||||||
skip: page.value,
|
skip: page.value,
|
||||||
take: size.value,
|
take: size.value,
|
||||||
filter: {}
|
filter: filter,
|
||||||
// 如果后端需要 skip/take 格式,可以在此转换
|
// 如果后端需要 skip/take 格式,可以在此转换
|
||||||
// skip: (page.value - 1) * size.value,
|
// skip: (page.value - 1) * size.value,
|
||||||
// take: size.value,
|
// take: size.value,
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await props.listUrl(params);
|
const res = await props.listUrl(params);
|
||||||
|
let records: any[] = [];
|
||||||
|
let totalCount: number = 0;
|
||||||
|
|
||||||
|
// [!code ++] 核心逻辑:如果父组件传入了 transformData,则使用父组件的逻辑
|
||||||
|
if (props.transformData) {
|
||||||
|
const result = props.transformData(res);
|
||||||
|
records = result.records || [];
|
||||||
|
totalCount = result.total || 0;
|
||||||
|
} else {
|
||||||
|
// [!code ++] 否则使用默认逻辑
|
||||||
|
records = res?.data?.records || res?.data || [];
|
||||||
|
totalCount = res?.data?.total || res?.total || 0;
|
||||||
|
}
|
||||||
|
|
||||||
// 假设后端返回结构为 { data: { records: [], total: 0 } }
|
|
||||||
// 请根据实际后端接口调整以下取值逻辑
|
|
||||||
const records = res?.data?.records || res?.data?.list || res?.data || [];
|
|
||||||
const totalCount = res?.data?.total || res?.total || 0;
|
|
||||||
|
|
||||||
tableData.value = records;
|
tableData.value = records;
|
||||||
total.value = totalCount;
|
total.value = totalCount;
|
||||||
|
|
||||||
// 向父组件暴露当前请求参数和结果
|
// 向父组件暴露当前请求参数和结果
|
||||||
emit("data-loaded", params, { records, total: totalCount });
|
emit("data-loaded", params, { records, total: totalCount });
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Fetch table data error:", error);
|
console.error("Fetch table data error:", error);
|
||||||
tableData.value = [];
|
tableData.value = [];
|
||||||
@ -177,7 +188,6 @@ watch(
|
|||||||
|
|
||||||
// --- Lifecycle ---
|
// --- Lifecycle ---
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList();
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -1,24 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-select
|
<a-select
|
||||||
|
ref="selectRef"
|
||||||
:style="{ width: width }"
|
:style="{ width: width }"
|
||||||
:value="modelValue"
|
:value="modelValue"
|
||||||
|
:options="options"
|
||||||
|
:loading="loading"
|
||||||
@change="handleChange"
|
@change="handleChange"
|
||||||
placeholder="请选择鱼名称"
|
@search="handleSearch"
|
||||||
mode="multiple"
|
placeholder="请选择鱼种类"
|
||||||
|
:mode="multiple ? 'multiple' : undefined"
|
||||||
show-search
|
show-search
|
||||||
:filter-option="filterOption"
|
:filter-option="false"
|
||||||
class="custom-fish-select"
|
class="custom-fish-select"
|
||||||
:dropdownMatchSelectWidth="false"
|
:dropdownMatchSelectWidth="false"
|
||||||
:getPopupContainer="(triggerNode: HTMLElement) => triggerNode.parentNode"
|
:getPopupContainer="(triggerNode: HTMLElement) => triggerNode.parentNode"
|
||||||
@dropdownVisibleChange="handleDropdownVisibleChange"
|
@dropdownVisibleChange="handleDropdownVisibleChange"
|
||||||
|
:max-tag-count="multiple ? 1 : undefined"
|
||||||
|
:open="open"
|
||||||
|
@update:open="open = $event"
|
||||||
|
:field-names="{ label: 'name', value: 'id' }"
|
||||||
>
|
>
|
||||||
<!-- 自定义 Tag 显示名称 -->
|
<!-- 自定义 Tag 显示名称 (仅在多选时生效) -->
|
||||||
<template #tagRender="{ value: tagId, onClose }">
|
<template #tagRender="{ value: tagId, onClose }" v-if="multiple">
|
||||||
<a-tag
|
<a-tag closable @close="onClose" style="margin-right: 3px; max-width: 120px">
|
||||||
closable
|
|
||||||
@close="onClose"
|
|
||||||
style="margin-right: 3px; max-width: 120px"
|
|
||||||
>
|
|
||||||
{{ getFishNameById(tagId) }}
|
{{ getFishNameById(tagId) }}
|
||||||
</a-tag>
|
</a-tag>
|
||||||
</template>
|
</template>
|
||||||
@ -32,7 +36,7 @@
|
|||||||
:key="opt.id"
|
:key="opt.id"
|
||||||
class="dropdown-item"
|
class="dropdown-item"
|
||||||
:class="{
|
:class="{
|
||||||
'is-active': Array.isArray(modelValue) && modelValue.includes(opt.id),
|
'is-active': isSelected(opt.id),
|
||||||
'is-hovered': opt.id === hoveredId,
|
'is-hovered': opt.id === hoveredId,
|
||||||
}"
|
}"
|
||||||
@click.stop="handleSelectOption(opt)"
|
@click.stop="handleSelectOption(opt)"
|
||||||
@ -40,11 +44,9 @@
|
|||||||
>
|
>
|
||||||
<span class="item-name">{{ opt.name }}</span>
|
<span class="item-name">{{ opt.name }}</span>
|
||||||
<!-- 选中对勾 -->
|
<!-- 选中对勾 -->
|
||||||
<span v-if="Array.isArray(modelValue) && modelValue.includes(opt.id)" class="check-icon">✓</span>
|
<span v-if="isSelected(opt.id)" class="check-icon">✓</span>
|
||||||
</div>
|
|
||||||
<div v-if="filteredOptions.length === 0" class="empty-tip">
|
|
||||||
无匹配数据
|
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="filteredOptions.length === 0" class="empty-tip">无匹配数据</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 中间分割线 -->
|
<!-- 中间分割线 -->
|
||||||
@ -66,30 +68,44 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from "vue";
|
import { ref, onMounted, computed, watch } from "vue";
|
||||||
|
import { getFishDictoryDropdown } from "@/api/select";
|
||||||
|
import { useShuJuTianBaoStore } from "@/store/modules/shuJuTianBao";
|
||||||
|
const shuJuTianBaoStore = useShuJuTianBaoStore();
|
||||||
|
|
||||||
// --- Props & Emits ---
|
// --- Props & Emits ---
|
||||||
interface Props {
|
interface Props {
|
||||||
modelValue: string[]; // 接收选中的 ID 数组
|
modelValue: string | string[]; // 支持字符串(单选)或数组(多选)
|
||||||
options: any[];
|
width?: string;
|
||||||
width: string;
|
multiple?: boolean; // 控制是否多选
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
multiple: false, // 默认单选,根据需求调整
|
||||||
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "update:modelValue", value: string[]): void;
|
(e: "update:modelValue", value: string | string[], opt: any): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// --- State ---
|
// --- State ---
|
||||||
// 这里可以改为从 API 获取,目前先保留静态数据作为示例
|
const loading = ref(false);
|
||||||
const options = ref<any>(props.options || []);
|
const options = ref<any[]>([]);
|
||||||
|
const searchKeyword = ref<string>("");
|
||||||
const hoveredId = ref<string | null>(null);
|
const hoveredId = ref<string | null>(null);
|
||||||
|
const open = ref(false); // 控制下拉框显隐
|
||||||
|
|
||||||
// --- Computed ---
|
// --- Computed ---
|
||||||
const filteredOptions = computed(() => {
|
const filteredOptions = computed(() => {
|
||||||
return options.value;
|
if (!searchKeyword.value) {
|
||||||
|
return options.value;
|
||||||
|
}
|
||||||
|
const lowerKeyword = searchKeyword.value.toLowerCase();
|
||||||
|
return options.value.filter((item: any) => {
|
||||||
|
const nameMatch = item.name?.toLowerCase().includes(lowerKeyword);
|
||||||
|
const aliasMatch = item.alias?.toLowerCase().includes(lowerKeyword);
|
||||||
|
return nameMatch || aliasMatch;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const currentDetailData = computed(() => {
|
const currentDetailData = computed(() => {
|
||||||
@ -100,40 +116,60 @@ const currentDetailData = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// --- Methods ---
|
// --- Methods ---
|
||||||
const handleDropdownVisibleChange = (open: boolean) => {
|
|
||||||
if (!open) {
|
// 辅助函数:判断是否选中
|
||||||
hoveredId.value = null;
|
const isSelected = (id: string) => {
|
||||||
|
if (props.multiple) {
|
||||||
|
return Array.isArray(props.modelValue) && props.modelValue.includes(id);
|
||||||
|
} else {
|
||||||
|
return props.modelValue === id;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const filterOption = (input: string, option: any) => {
|
const handleSearch = (value: string) => {
|
||||||
if (!input) return true;
|
searchKeyword.value = value;
|
||||||
const targetOpt = options.value.find((item: any) => item.id === option.value);
|
};
|
||||||
if (!targetOpt) return false;
|
|
||||||
|
|
||||||
const lowerInput = input.toLowerCase();
|
const handleDropdownVisibleChange = (val: boolean) => {
|
||||||
const nameMatch = targetOpt.name?.toLowerCase().includes(lowerInput);
|
open.value = val;
|
||||||
const aliasMatch = targetOpt.alias?.toLowerCase().includes(lowerInput);
|
if (!val) {
|
||||||
return nameMatch || aliasMatch;
|
hoveredId.value = null;
|
||||||
|
searchKeyword.value = "";
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectOption = (opt: any) => {
|
const handleSelectOption = (opt: any) => {
|
||||||
let newValues: string[] = Array.isArray(props.modelValue) ? [...props.modelValue] : [];
|
if (props.multiple) {
|
||||||
const index = newValues.indexOf(opt.id);
|
// --- 多选逻辑 ---
|
||||||
|
let newValues: string[] = Array.isArray(props.modelValue)
|
||||||
|
? [...props.modelValue]
|
||||||
|
: [];
|
||||||
|
const index = newValues.indexOf(opt.id);
|
||||||
|
|
||||||
if (index > -1) {
|
if (index > -1) {
|
||||||
newValues.splice(index, 1);
|
newValues.splice(index, 1); // 取消选中
|
||||||
|
} else {
|
||||||
|
newValues.push(opt.id); // 选中
|
||||||
|
}
|
||||||
|
emit("update:modelValue", newValues, opt);
|
||||||
} else {
|
} else {
|
||||||
newValues.push(opt.id);
|
// --- 单选逻辑 ---
|
||||||
|
// 关键:单选模式下,直接发射当前 ID,覆盖旧值
|
||||||
|
// 如果点击的是已选中的项,则清空(可选行为,视需求而定)
|
||||||
|
if (props.modelValue === opt.id) {
|
||||||
|
emit("update:modelValue", "", opt); // 取消选中
|
||||||
|
} else {
|
||||||
|
emit("update:modelValue", opt.id, opt); // 选中新项
|
||||||
|
// 单选模式下,选择后通常希望关闭下拉框
|
||||||
|
open.value = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 触发更新
|
|
||||||
emit("update:modelValue", newValues);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = (val: any) => {
|
const handleChange = (val: any) => {
|
||||||
// 处理直接通过 a-select 内部机制触发的变化(如删除 tag)
|
// 当 a-select 内部触发 change 时(例如删除 Tag)
|
||||||
emit("update:modelValue", val);
|
// 在单选模式下,如果用户通过键盘或删除操作改变了值,这里也会捕获
|
||||||
|
emit("update:modelValue", val, '');
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFishNameById = (id: string) => {
|
const getFishNameById = (id: string) => {
|
||||||
@ -141,9 +177,46 @@ const getFishNameById = (id: string) => {
|
|||||||
const fish = options.value.find((item: any) => item.id === id);
|
const fish = options.value.find((item: any) => item.id === id);
|
||||||
return fish ? fish.name : id;
|
return fish ? fish.name : id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 监听 multiple 变化,确保模式切换时状态正确
|
||||||
|
watch(
|
||||||
|
() => props.multiple,
|
||||||
|
(newVal) => {
|
||||||
|
// 如果从多选变为单选,且 modelValue 是数组,取第一个值或清空
|
||||||
|
if (!newVal && Array.isArray(props.modelValue)) {
|
||||||
|
emit("update:modelValue", props.modelValue || null, options.value);
|
||||||
|
}
|
||||||
|
// 如果从单选变为多选,且 modelValue 是字符串,转为数组
|
||||||
|
if (newVal && typeof props.modelValue === "string") {
|
||||||
|
emit("update:modelValue", props.modelValue ? [props.modelValue] : [], {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const init = () => {
|
||||||
|
let data = shuJuTianBaoStore.getFishOption();
|
||||||
|
if (data.length === 0) {
|
||||||
|
loading.value = true;
|
||||||
|
getFishDictoryDropdown()
|
||||||
|
.then((res) => {
|
||||||
|
options.value = res.data || [];
|
||||||
|
loading.value = false;
|
||||||
|
shuJuTianBaoStore.setFishOption(options.value);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
loading.value = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
options.value = data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
init();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
/* 样式保持不变 */
|
||||||
.custom-fish-select {
|
.custom-fish-select {
|
||||||
:deep(.ant-select-dropdown) {
|
:deep(.ant-select-dropdown) {
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
@ -157,7 +230,7 @@ const getFishNameById = (id: string) => {
|
|||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 300px; /* 固定高度 */
|
height: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-left-list {
|
.dropdown-left-list {
|
||||||
|
|||||||
@ -56122,41 +56122,42 @@ const fetchPointData = _.debounce(async () => {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// 基础图层
|
// 基础图层
|
||||||
// mapClass.addBaseDataLayer({
|
mapClass.addBaseDataLayer({
|
||||||
// id: "customBaseLayer",
|
id: "customBaseLayer",
|
||||||
// key: "customBaseLayer",
|
key: "customBaseLayer",
|
||||||
// type: "wmts",
|
type: "wmts",
|
||||||
// name: "qgc_sx_gjjdx_arcgistiles_l13",
|
name: "qgc_sx_gjjdx_arcgistiles_l13",
|
||||||
// urlType: "gisurl",
|
urlType: "gisurl",
|
||||||
// url:
|
url:
|
||||||
// "/geoserver/gwc/service/wmts?REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&LAYER=qgc_qsj_arcgistiles_l13&STYLE=&TILEMATRIX=EPSG:3857_qgc_qsj_arcgistiles_l13:{z}&TILEMATRIXSET=EPSG:3857_qgc_qsj_arcgistiles_l13&FORMAT=image/png&TILECOL={x}&TILEROW={y}",
|
"/geoserver/gwc/service/wmts?REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&LAYER=qgc_qsj_arcgistiles_l13&STYLE=&TILEMATRIX=EPSG:3857_qgc_qsj_arcgistiles_l13:{z}&TILEMATRIXSET=EPSG:3857_qgc_qsj_arcgistiles_l13&FORMAT=image/png&TILECOL={x}&TILEROW={y}",
|
||||||
// url_3d:
|
url_3d:
|
||||||
// "/geoserver/gwc/service/wmts?REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&LAYER=qgc_sx_gjjdx_arcgistiles_l13&STYLE=&TILEMATRIX=EPSG:3857_qgc_sx_gjjdx_arcgistiles_l13:{z}&TILEMATRIXSET=EPSG:3857_qgc_sx_gjjdx_arcgistiles_l13&FORMAT=image/png&TILECOL={x}&TILEROW={y}",
|
"/geoserver/gwc/service/wmts?REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&LAYER=qgc_sx_gjjdx_arcgistiles_l13&STYLE=&TILEMATRIX=EPSG:3857_qgc_sx_gjjdx_arcgistiles_l13:{z}&TILEMATRIXSET=EPSG:3857_qgc_sx_gjjdx_arcgistiles_l13&FORMAT=image/png&TILECOL={x}&TILEROW={y}",
|
||||||
// matrixIds_index: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"],
|
matrixIds_index: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"],
|
||||||
// tileMatrixSetID: "EPSG:3857_qgc_sx_gjjdx_arcgistiles_l13",
|
tileMatrixSetID: "EPSG:3857_qgc_sx_gjjdx_arcgistiles_l13",
|
||||||
// });
|
});
|
||||||
// setTimeout(() => {
|
// setTimeout(() => {
|
||||||
mapClass.addBaseDataLayer({
|
// 单个
|
||||||
"id": "hydropBase",
|
// mapClass.addBaseDataLayer({
|
||||||
"key": "hydropBase",
|
// "id": "hydropBase",
|
||||||
"urlType": "gisurl",
|
// "key": "hydropBase",
|
||||||
"url": "https://211.99.26.225:18085/geoserver/gwc/service/tms/1.0.0/qgc%3AstationEra1117@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf",
|
// "urlType": "gisurl",
|
||||||
"geojson_url": "https://211.99.26.225:18085/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:stationEra1117&maxFeatures=50&outputFormat=application/json&token=bearer a9a0f227-1df3-4e68-b380-2eca5bb49bd1",
|
// "url": "https://211.99.26.225:18085/geoserver/gwc/service/tms/1.0.0/qgc%3AstationEra1117@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf",
|
||||||
"url_3d": "https://211.99.26.225:18085/geoserver/qgc/wms",
|
// "geojson_url": "https://211.99.26.225:18085/geoserver/qgc/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=qgc:stationEra1117&maxFeatures=50&outputFormat=application/json&token=bearer a9a0f227-1df3-4e68-b380-2eca5bb49bd1",
|
||||||
"_layer": "stationEra1117",
|
// "url_3d": "https://211.99.26.225:18085/geoserver/qgc/wms",
|
||||||
"layers": "qgc:stationEra1117",
|
// "_layer": "stationEra1117",
|
||||||
"rasteropacity": 0.5,
|
// "layers": "qgc:stationEra1117",
|
||||||
"visible": true,
|
// "rasteropacity": 0.5,
|
||||||
"minZoom": 0,
|
// "visible": true,
|
||||||
"maxZoom": 20,
|
// "minZoom": 0,
|
||||||
"type": "vector",
|
// "maxZoom": 20,
|
||||||
"layerType": "line",
|
// "type": "vector",
|
||||||
"paint": {
|
// "layerType": "line",
|
||||||
"line-color": "#C5C6F3",
|
// "paint": {
|
||||||
"line-width": 1,
|
// "line-color": "#C5C6F3",
|
||||||
"line-opacity": 1
|
// "line-width": 1,
|
||||||
}
|
// "line-opacity": 1
|
||||||
})
|
// }
|
||||||
|
// })
|
||||||
// }, 2000);
|
// }, 2000);
|
||||||
|
|
||||||
// 梯级流域图
|
// 梯级流域图
|
||||||
|
|||||||
@ -439,7 +439,6 @@ export class MapOl implements MapInterface {
|
|||||||
if (layer.key === 'hydropBase') {
|
if (layer.key === 'hydropBase') {
|
||||||
// this.hydropBaseConfig = layer;
|
// this.hydropBaseConfig = layer;
|
||||||
}
|
}
|
||||||
console.log(this.geoJsonData1)
|
|
||||||
// ✅ 1. 创建矢量源,关键是要配置投影转换
|
// ✅ 1. 创建矢量源,关键是要配置投影转换
|
||||||
const vectorSource = new VectorSource({
|
const vectorSource = new VectorSource({
|
||||||
features: new GeoJSON().readFeatures(this.geoJsonData1, {
|
features: new GeoJSON().readFeatures(this.geoJsonData1, {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ export default {
|
|||||||
// 登录页面国际化
|
// 登录页面国际化
|
||||||
login: {
|
login: {
|
||||||
title: '水电水利建设项目全过程环境管理信息平台',
|
title: '水电水利建设项目全过程环境管理信息平台',
|
||||||
|
titleSjtb: '水电水利建设项目全过程环境管理信息平台数据填报子系统',
|
||||||
username: '用户名',
|
username: '用户名',
|
||||||
rulesUsername: '用户账号/身份证号/手机号 不能为空',
|
rulesUsername: '用户账号/身份证号/手机号 不能为空',
|
||||||
password: '密码',
|
password: '密码',
|
||||||
|
|||||||
@ -54,7 +54,9 @@ onBeforeUnmount(() => {
|
|||||||
href="/"
|
href="/"
|
||||||
class="h-[50px] min-w-[350px] flex items-center justify-center text-white"
|
class="h-[50px] min-w-[350px] flex items-center justify-center text-white"
|
||||||
>
|
>
|
||||||
<h1 class="text-blank font-bold text-[16px]">{{ t("login.title") }}</h1></a
|
<!-- <h1 class="text-blank font-bold text-[16px]">{{ t("login.title") }}</h1> -->
|
||||||
|
<h1 class="text-blank font-bold text-[16px]">{{ t("login.titleSjtb") }}</h1>
|
||||||
|
</a
|
||||||
>
|
>
|
||||||
</transition>
|
</transition>
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
|
|||||||
343
frontend/src/modules/EnvironmentalQuality/index.vue
Normal file
343
frontend/src/modules/EnvironmentalQuality/index.vue
Normal file
@ -0,0 +1,343 @@
|
|||||||
|
<!-- SidePanelItem.vue -->
|
||||||
|
<template>
|
||||||
|
<SidePanelItem title="地表水水质达标率">
|
||||||
|
<div class="body_item">
|
||||||
|
<div class="tabs_all">
|
||||||
|
<div :class="tabs == 1 ? 'zhong_tabs' : 'no_tabs'" @click="handleTabChange(1)">自建水质站</div>
|
||||||
|
<div :class="tabs == 2 ? 'zhong_tabs' : 'no_tabs'" @click="handleTabChange(2)">国家水质站</div>
|
||||||
|
</div>
|
||||||
|
<div v-show="tabs == 1 || tabs == 2" class="tabs_body">
|
||||||
|
<div ref="chartRef" class="chart-container"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</SidePanelItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
|
||||||
|
import * as echarts from 'echarts';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
|
||||||
|
// 定义组件名(便于调试和递归)
|
||||||
|
defineOptions({
|
||||||
|
name: 'EnvironmentalQuality'
|
||||||
|
});
|
||||||
|
|
||||||
|
const tabs = ref(1);
|
||||||
|
const chartRef = ref<HTMLElement | null>(null);
|
||||||
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
|
|
||||||
|
// 静态数据 - 自建水质站(第1页)
|
||||||
|
const selfBuiltData = [
|
||||||
|
{ name: '雅砻江\n干流', current: 99, lastYear: 100 },
|
||||||
|
{ name: '大渡河\n干流', current: 99, lastYear: 100 },
|
||||||
|
{ name: '黄河上游\n干流', current: 99, lastYear: 100 }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 静态数据 - 国家水质站(第2页)
|
||||||
|
const nationalData = [
|
||||||
|
{ name: '长江\n干流', current: 98, lastYear: 99 },
|
||||||
|
{ name: '珠江\n干流', current: 97, lastYear: 98 },
|
||||||
|
{ name: '淮河\n干流', current: 96, lastYear: 97 }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 根据tab获取对应数据
|
||||||
|
const getCurrentData = () => {
|
||||||
|
return tabs.value === 1 ? selfBuiltData : nationalData;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理tab切换
|
||||||
|
const handleTabChange = (tab: number) => {
|
||||||
|
tabs.value = tab;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听tabs变化,处理图表刷新
|
||||||
|
watch(tabs, (newVal) => {
|
||||||
|
if (newVal === 1 || newVal === 2) {
|
||||||
|
nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!chartInstance) {
|
||||||
|
initChart();
|
||||||
|
} else {
|
||||||
|
// 更新图表数据
|
||||||
|
updateChartData();
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化图表
|
||||||
|
const initChart = () => {
|
||||||
|
if (!chartRef.value) return;
|
||||||
|
|
||||||
|
chartInstance = echarts.init(chartRef.value);
|
||||||
|
|
||||||
|
const currentData = getCurrentData();
|
||||||
|
|
||||||
|
const option = {
|
||||||
|
// 图例配置
|
||||||
|
legend: {
|
||||||
|
top: 10,
|
||||||
|
itemWidth: 18,
|
||||||
|
itemHeight: 12,
|
||||||
|
itemGap: 20,
|
||||||
|
icon: 'roundRect',
|
||||||
|
data: [
|
||||||
|
{ name: '2026-04 月度', itemStyle: { color: '#4A8BC2' } },
|
||||||
|
{ name: '去年同期', itemStyle: { color: '#9B59B6' } }
|
||||||
|
],
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#333'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 提示框配置
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'shadow',
|
||||||
|
shadowStyle: {
|
||||||
|
color: 'rgba(200, 200, 200, 0.2)'
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderColor: '#e8e8e8',
|
||||||
|
borderWidth: 1,
|
||||||
|
padding: 12,
|
||||||
|
textStyle: {
|
||||||
|
color: '#333',
|
||||||
|
fontSize: 12
|
||||||
|
},
|
||||||
|
formatter: (params: any) => {
|
||||||
|
const name = params[0].name.replace('\n', '');
|
||||||
|
let result = `<div style="font-weight: bold; margin-bottom: 8px;">${name}</div>`;
|
||||||
|
params.forEach((item: any) => {
|
||||||
|
result += `<div style="display: flex; align-items: center; margin-bottom: 4px;">
|
||||||
|
<span style="display: inline-block; width: 10px; height: 10px; border-radius: 50%; background: ${item.color}; margin-right: 8px;"></span>
|
||||||
|
<span>${item.seriesName}:</span>
|
||||||
|
<span style="margin-left: auto; font-weight: bold;">${item.value} %</span>
|
||||||
|
</div>`;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 网格配置
|
||||||
|
grid: {
|
||||||
|
left: 60,
|
||||||
|
right: 10,
|
||||||
|
top: 60,
|
||||||
|
bottom: 50
|
||||||
|
},
|
||||||
|
|
||||||
|
// X轴配置
|
||||||
|
xAxis: {
|
||||||
|
type: 'value',
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
axisLine: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#e8e8e8',
|
||||||
|
type: 'solid'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#666'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Y轴配置
|
||||||
|
yAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: currentData.map(item => item.name),
|
||||||
|
inverse: true,
|
||||||
|
axisLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#333',
|
||||||
|
width: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: true,
|
||||||
|
length: 4,
|
||||||
|
lineStyle: {
|
||||||
|
color: '#333',
|
||||||
|
width: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#333',
|
||||||
|
margin: 12
|
||||||
|
},
|
||||||
|
// 在Y轴底部添加"达标率(%)"标签
|
||||||
|
name: '达标率(%)',
|
||||||
|
nameLocation: 'end',
|
||||||
|
nameGap: 20,
|
||||||
|
nameTextStyle: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: '#666',
|
||||||
|
align: 'left'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 系列配置
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '2026-04 月度',
|
||||||
|
type: 'bar',
|
||||||
|
data: currentData.map(item => item.current),
|
||||||
|
barWidth: 10,
|
||||||
|
barGap: '20%',
|
||||||
|
itemStyle: {
|
||||||
|
color: '#4A8BC2',
|
||||||
|
borderRadius: [0, 3, 3, 0]
|
||||||
|
},
|
||||||
|
markArea: {
|
||||||
|
silent: true,
|
||||||
|
itemStyle: {
|
||||||
|
color: 'rgba(240, 245, 250, 0.5)'
|
||||||
|
},
|
||||||
|
data: [
|
||||||
|
[{ yAxis: 1 }, { yAxis: 2 }]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '去年同期',
|
||||||
|
type: 'bar',
|
||||||
|
data: currentData.map(item => item.lastYear),
|
||||||
|
barWidth: 10,
|
||||||
|
barGap: '20%',
|
||||||
|
itemStyle: {
|
||||||
|
color: '#9B59B6',
|
||||||
|
borderRadius: [0, 3, 3, 0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
chartInstance.setOption(option);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 更新图表数据
|
||||||
|
const updateChartData = () => {
|
||||||
|
if (!chartInstance) return;
|
||||||
|
|
||||||
|
const currentData = getCurrentData();
|
||||||
|
|
||||||
|
chartInstance.setOption({
|
||||||
|
yAxis: {
|
||||||
|
data: currentData.map(item => item.name)
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
data: currentData.map(item => item.current)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: currentData.map(item => item.lastYear)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// 强制重新计算尺寸
|
||||||
|
chartInstance.resize();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面加载时执行
|
||||||
|
onMounted(() => {
|
||||||
|
// 延迟初始化,确保容器已渲染
|
||||||
|
setTimeout(() => {
|
||||||
|
initChart();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
// 监听窗口resize
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
chartInstance?.resize();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 组件卸载时清理
|
||||||
|
onUnmounted(() => {
|
||||||
|
chartInstance?.dispose();
|
||||||
|
window.removeEventListener('resize', () => {
|
||||||
|
chartInstance?.resize();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.body_item {
|
||||||
|
width: 100%;
|
||||||
|
height: 600px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.tabs_all {
|
||||||
|
width: 28px;
|
||||||
|
height: 600px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 2px solid #2f6b98;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
.zhong_tabs {
|
||||||
|
width: 100%;
|
||||||
|
height: 50%;
|
||||||
|
background: #2f6b98;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-shadow: 0 0 .25px currentcolor;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no_tabs {
|
||||||
|
width: 100%;
|
||||||
|
height: 50%;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.no_tabs:hover {
|
||||||
|
color: #40a9ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs_body {
|
||||||
|
width: 368px;
|
||||||
|
height: 600px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.chart-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
124
frontend/src/modules/qixidibaohugongzuokaizhanQK/index.vue
Normal file
124
frontend/src/modules/qixidibaohugongzuokaizhanQK/index.vue
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
<!-- SidePanelItem.vue -->
|
||||||
|
<template>
|
||||||
|
<SidePanelItem title="栖息地保护工作开展情况">
|
||||||
|
<div class="facility-grid">
|
||||||
|
<div v-for="facility in facilities" :key="facility.name" class="facility-card">
|
||||||
|
<div style="width: 60px;height: 62px;display: flex;align-items: center;justify-content: center;">
|
||||||
|
<div class="facility-icon">
|
||||||
|
<i style="color: #fff;" :class="facility.icon" type="icon-shengtailiuliang2"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="facility-info">
|
||||||
|
<div class="facility-name">{{ facility.name }}</div>
|
||||||
|
<div style="font-size: 16px;"> <span class="facility-count">{{ facility.count
|
||||||
|
}}</span><span>个</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SidePanelItem>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue';
|
||||||
|
import SidePanelItem from '@/components/SidePanelItem/index.vue';
|
||||||
|
|
||||||
|
// 定义组件名(便于调试和递归)
|
||||||
|
defineOptions({
|
||||||
|
name: 'qixidibaohugongzuokaizhanQK'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 设施数据
|
||||||
|
const facilities = ref([
|
||||||
|
{
|
||||||
|
name: '栖息地',
|
||||||
|
count: 56,
|
||||||
|
icon: 'icon iconfont icon-qixidi'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '水温监测',
|
||||||
|
count: 1722,
|
||||||
|
icon: 'icon iconfont icon-diwenshuijianhuan'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '水文监测',
|
||||||
|
count: 135,
|
||||||
|
icon: 'icon iconfont icon-shuiwen-line'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '视频监控',
|
||||||
|
count: 135,
|
||||||
|
icon: 'icon iconfont icon-shipinjiankongshebei'
|
||||||
|
},
|
||||||
|
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 页面加载时执行
|
||||||
|
onMounted(() => {
|
||||||
|
// 延迟初始化,确保容器已渲染
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// 组件卸载时清理
|
||||||
|
onUnmounted(() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.facility-grid {
|
||||||
|
width: 406px;
|
||||||
|
flex-flow: wrap;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-card {
|
||||||
|
width: 200px;
|
||||||
|
height: 70px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin: 4px 0px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
border-radius: 2px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
cursor: pointer;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
// margin-right: 8px;
|
||||||
|
background: #5389b5;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
.anticon {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-name {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
// margin-bottom: 4px;
|
||||||
|
// font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.facility-count {
|
||||||
|
font-size: 18px;
|
||||||
|
color: #2f6b98;
|
||||||
|
// font-weight: 600;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,6 +1,6 @@
|
|||||||
<!-- SidePanelItem.vue -->
|
<!-- SidePanelItem.vue -->
|
||||||
<template>
|
<template>
|
||||||
<SidePanelItem title="环保设施情况">
|
<SidePanelItem title="水质监测工作开展情况">
|
||||||
<div class="facility-grid" >
|
<div class="facility-grid" >
|
||||||
<div v-for="facility in facilities" :key="facility.name" class="facility-card">
|
<div v-for="facility in facilities" :key="facility.name" class="facility-card">
|
||||||
<div style="width: 60px;height: 62px;display: flex;align-items: center;justify-content: center;">
|
<div style="width: 60px;height: 62px;display: flex;align-items: center;justify-content: center;">
|
||||||
|
|||||||
@ -9,7 +9,7 @@ NProgress.configure({ showSpinner: false });
|
|||||||
const permissionStore = usePermissionStoreHook();
|
const permissionStore = usePermissionStoreHook();
|
||||||
|
|
||||||
// 白名单路由
|
// 白名单路由
|
||||||
const whiteList = ['/login', '/login-sjtb']; //login
|
const whiteList = ['/login']; //login
|
||||||
|
|
||||||
// 查找第一个可用路由
|
// 查找第一个可用路由
|
||||||
function findFirstAvailableRoute(routes: any[]): string | undefined {
|
function findFirstAvailableRoute(routes: any[]): string | undefined {
|
||||||
@ -35,7 +35,7 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
|
|
||||||
if (userStore.Token) {
|
if (userStore.Token) {
|
||||||
// 登录成功,跳转到首页
|
// 登录成功,跳转到首页
|
||||||
if (to.path === '/login-sjtb' || to.path === '/login') {//login
|
if (to.path === '/login') {//login
|
||||||
next({ path: '/' });
|
next({ path: '/' });
|
||||||
NProgress.done();
|
NProgress.done();
|
||||||
} else {
|
} else {
|
||||||
@ -80,7 +80,7 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
await userStore.resetToken();
|
await userStore.resetToken();
|
||||||
to.path === '/login-sjtb' ? next(`/login-sjtb?redirect=${to.path}`) : next(`/login?redirect=${to.path}`);
|
next(`/login?redirect=${to.path}`);
|
||||||
NProgress.done();
|
NProgress.done();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,7 +91,7 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
if (whiteList.indexOf(to.path) !== -1) {
|
if (whiteList.indexOf(to.path) !== -1) {
|
||||||
next();
|
next();
|
||||||
} else {
|
} else {
|
||||||
to.path === '/login-sjtb' ? next(`/login-sjtb?redirect=${to.path}`) : next(`/login?redirect=${to.path}`);
|
next(`/login?redirect=${to.path}`);
|
||||||
NProgress.done();
|
NProgress.done();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,13 +16,13 @@ export const constantRoutes: RouteRecordRaw[] = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// path: '/login',
|
||||||
|
// component: () => import('@/views/login/index.vue'),
|
||||||
|
// meta: { hidden: true }
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: '/login',
|
||||||
component: () => import('@/views/login/index.vue'),
|
|
||||||
meta: { hidden: true }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/login-sjtb',
|
|
||||||
component: () => import('@/views/login-sjtb/index.vue'),
|
component: () => import('@/views/login-sjtb/index.vue'),
|
||||||
meta: { hidden: true }
|
meta: { hidden: true }
|
||||||
},
|
},
|
||||||
@ -31,11 +31,11 @@ export const constantRoutes: RouteRecordRaw[] = [
|
|||||||
component: () => import('@/views/error-page/404.vue'),
|
component: () => import('@/views/error-page/404.vue'),
|
||||||
meta: { hidden: true }
|
meta: { hidden: true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/401',
|
path: '/401',
|
||||||
component: () => import('@/views/error-page/401.vue'),
|
component: () => import('@/views/error-page/401.vue'),
|
||||||
meta: { hidden: true }
|
meta: { hidden: true }
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// 创建路由
|
// 创建路由
|
||||||
|
|||||||
85
frontend/src/store/modules/shuJuTianBao.ts
Normal file
85
frontend/src/store/modules/shuJuTianBao.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { ref } from 'vue'; // 使用 ref 更简单直观
|
||||||
|
import { getBaseDropdown, getEngInfoDropdown, getFpssDropdown } from '@/api/select';
|
||||||
|
import { set } from 'lodash';
|
||||||
|
|
||||||
|
export const useShuJuTianBaoStore = defineStore('shuJuTianBao', () => {
|
||||||
|
// 1. 直接使用 ref 定义状态,确保响应式
|
||||||
|
const fpssOption = ref<any[]>([]);
|
||||||
|
const fpssLoading = ref(false);
|
||||||
|
const baseOption = ref<any[]>([]);
|
||||||
|
const baseLoading = ref(false);
|
||||||
|
const engOption = ref<any[]>([]);
|
||||||
|
const engLoading = ref(false);
|
||||||
|
const fishOption = ref([]);
|
||||||
|
// 获取水电基地列表
|
||||||
|
const getBaseOption = async () => {
|
||||||
|
try {
|
||||||
|
baseLoading.value = true;
|
||||||
|
const res = await getBaseDropdown({});
|
||||||
|
if (res.data && Array.isArray(res.data)) {
|
||||||
|
const list = [...res.data];
|
||||||
|
list.unshift({
|
||||||
|
baseid: 'all',
|
||||||
|
basename: '当前全部'
|
||||||
|
});
|
||||||
|
// 直接赋值给 ref,触发响应式更新
|
||||||
|
baseOption.value = list;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取水电基地列表失败:', error);
|
||||||
|
} finally {
|
||||||
|
baseLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 获取电站列表
|
||||||
|
const getEngOption = async (baseId: string) => {
|
||||||
|
try {
|
||||||
|
engLoading.value = true;
|
||||||
|
const param = baseId === 'all' ? {} : { baseId };
|
||||||
|
const res = await getEngInfoDropdown(param);
|
||||||
|
if (res.data && Array.isArray(res.data)) {
|
||||||
|
// 直接赋值给 ref
|
||||||
|
engOption.value = res.data;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取电站列表失败:', error);
|
||||||
|
} finally {
|
||||||
|
engLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 获取过鱼设施列表
|
||||||
|
const getFpssOption = async (baseId: string, rstcd: string) => {
|
||||||
|
try {
|
||||||
|
fpssLoading.value = true;
|
||||||
|
const res = await getFpssDropdown({ baseId, rstcd });
|
||||||
|
fpssOption.value = res.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
fpssLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const getFishOption = () => {
|
||||||
|
return fishOption.value;
|
||||||
|
};
|
||||||
|
const setFishOption = (data: any[]) => {
|
||||||
|
fishOption.value = data;
|
||||||
|
};
|
||||||
|
// 3. 直接返回 ref 和方法
|
||||||
|
// 在组件中使用时:store.baseOption 会自动解包为数组
|
||||||
|
return {
|
||||||
|
fpssOption,
|
||||||
|
baseOption,
|
||||||
|
engOption,
|
||||||
|
fishOption,
|
||||||
|
fpssLoading,
|
||||||
|
baseLoading,
|
||||||
|
engLoading,
|
||||||
|
getBaseOption,
|
||||||
|
getEngOption,
|
||||||
|
getFpssOption,
|
||||||
|
getFishOption,
|
||||||
|
setFishOption
|
||||||
|
};
|
||||||
|
});
|
||||||
@ -10,7 +10,7 @@
|
|||||||
<!-- 左侧:背景图区域 -->
|
<!-- 左侧:背景图区域 -->
|
||||||
<div class="left-section">
|
<div class="left-section">
|
||||||
<div class="slogan">
|
<div class="slogan">
|
||||||
<p>采集网站及数据管理子系统</p>
|
<p>{{ $t("login.titleSjtb") }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -168,6 +168,14 @@
|
|||||||
>
|
>
|
||||||
忘记密码
|
忘记密码
|
||||||
</a-button> -->
|
</a-button> -->
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
size="mini"
|
||||||
|
block
|
||||||
|
:style="{ marginTop: '10px', border: 'none' }"
|
||||||
|
>
|
||||||
|
注册
|
||||||
|
</a-button>
|
||||||
|
|
||||||
<!-- 忘记密码 -->
|
<!-- 忘记密码 -->
|
||||||
</a-form>
|
</a-form>
|
||||||
@ -245,11 +253,10 @@ import loginImg from "@/assets/images/logo.png";
|
|||||||
import { UserOutlined, LockOutlined, MobileOutlined } from "@ant-design/icons-vue";
|
import { UserOutlined, LockOutlined, MobileOutlined } from "@ant-design/icons-vue";
|
||||||
import { getCaptcha } from "@/api/auth";
|
import { getCaptcha } from "@/api/auth";
|
||||||
import { message } from "ant-design-vue";
|
import { message } from "ant-design-vue";
|
||||||
import { setPath } from '@/utils/auth';
|
import { setPath } from "@/utils/auth";
|
||||||
// 组件依赖
|
// 组件依赖
|
||||||
|
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
import Cookies from "js-cookie";
|
|
||||||
// API依赖
|
// API依赖
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import { LoginData } from "@/api/auth/types";
|
import { LoginData } from "@/api/auth/types";
|
||||||
@ -288,8 +295,8 @@ const state = reactive({
|
|||||||
redirect: "",
|
redirect: "",
|
||||||
loginData: {
|
loginData: {
|
||||||
uuid: "",
|
uuid: "",
|
||||||
username: "admin",
|
username: "",
|
||||||
password: "123456",
|
password: "",
|
||||||
code: "",
|
code: "",
|
||||||
} as LoginData,
|
} as LoginData,
|
||||||
loginRules: {
|
loginRules: {
|
||||||
@ -322,11 +329,12 @@ const forgotPasswordRules = ref({
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
const { loginData, loginRules, loading,
|
const {
|
||||||
|
loginData,
|
||||||
|
loginRules,
|
||||||
|
loading,
|
||||||
// passwordType, capslockTooltipDisabled
|
// passwordType, capslockTooltipDisabled
|
||||||
} = toRefs(
|
} = toRefs(state);
|
||||||
state
|
|
||||||
);
|
|
||||||
|
|
||||||
// function checkCapslock(e: any) {
|
// function checkCapslock(e: any) {
|
||||||
// const { key } = e;
|
// const { key } = e;
|
||||||
@ -363,10 +371,10 @@ function onFinish() {
|
|||||||
userStore
|
userStore
|
||||||
.login(user)
|
.login(user)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
Cookies.set("username", user.username);
|
setPath("/login-sjtb");
|
||||||
setPath('/login-sjtb')
|
|
||||||
router.push({ path: "/" });
|
router.push({ path: "/" });
|
||||||
state.loading = false;
|
state.loading = false;
|
||||||
|
message.success("登录成功");
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
getCode();
|
getCode();
|
||||||
@ -396,22 +404,6 @@ function getOtherQuery(query: any) {
|
|||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
function getCookie() {
|
|
||||||
const username = Cookies.get("username");
|
|
||||||
let password = Cookies.get("password");
|
|
||||||
const rememberMe = Cookies.get("rememberMe");
|
|
||||||
rememberMe == "true" ? (remember.value = Boolean(rememberMe)) : false;
|
|
||||||
// 保存cookie里面的加密后的密码
|
|
||||||
state.cookiePass = password === undefined ? "" : password;
|
|
||||||
password = password === undefined ? state.loginData.password : password;
|
|
||||||
state.loginData = {
|
|
||||||
username: username === undefined ? state.loginData.username : username,
|
|
||||||
password: decrypt(password),
|
|
||||||
code: "",
|
|
||||||
uuid: "",
|
|
||||||
};
|
|
||||||
remember.value = rememberMe === undefined ? false : Boolean(rememberMe);
|
|
||||||
}
|
|
||||||
function getCode() {
|
function getCode() {
|
||||||
getCaptcha().then((result: any) => {
|
getCaptcha().then((result: any) => {
|
||||||
codeUrl.value = result.data.img;
|
codeUrl.value = result.data.img;
|
||||||
@ -437,7 +429,10 @@ const startCountdown = () => {
|
|||||||
// const showForgotPasswordPage = () => {
|
// const showForgotPasswordPage = () => {
|
||||||
// showForgotPassword.value = true;
|
// showForgotPassword.value = true;
|
||||||
// };
|
// };
|
||||||
|
//注册用户页面
|
||||||
|
const goRegister = () => {
|
||||||
|
router.push({ path: "/register" });
|
||||||
|
};
|
||||||
// 返回登录页面
|
// 返回登录页面
|
||||||
const backToLogin = () => {
|
const backToLogin = () => {
|
||||||
showForgotPassword.value = false;
|
showForgotPassword.value = false;
|
||||||
@ -554,7 +549,6 @@ const handleResetPassword = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getCookie();
|
|
||||||
getCode();
|
getCode();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@ -624,8 +618,8 @@ onMounted(() => {
|
|||||||
.slogan {
|
.slogan {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 20%;
|
top: 20%;
|
||||||
left: 18%;
|
left: 16%;
|
||||||
width: 490px;
|
width: 780px;
|
||||||
height: 112px;
|
height: 112px;
|
||||||
color: #040504;
|
color: #040504;
|
||||||
font-size: 40px;
|
font-size: 40px;
|
||||||
|
|||||||
@ -288,8 +288,8 @@ const state = reactive({
|
|||||||
redirect: "",
|
redirect: "",
|
||||||
loginData: {
|
loginData: {
|
||||||
uuid: "",
|
uuid: "",
|
||||||
username: "admin",
|
username: "",
|
||||||
password: "123456",
|
password: "",
|
||||||
code: "",
|
code: "",
|
||||||
} as LoginData,
|
} as LoginData,
|
||||||
loginRules: {
|
loginRules: {
|
||||||
@ -359,11 +359,9 @@ function onFinish() {
|
|||||||
if (user.password !== state.cookiePass) {
|
if (user.password !== state.cookiePass) {
|
||||||
user.password = encrypt(user.password);
|
user.password = encrypt(user.password);
|
||||||
}
|
}
|
||||||
console.log(user);
|
|
||||||
userStore
|
userStore
|
||||||
.login(user)
|
.login(user)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
Cookies.set("username", user.username);
|
|
||||||
router.push({ path: "/" });
|
router.push({ path: "/" });
|
||||||
setPath('/login')
|
setPath('/login')
|
||||||
state.loading = false;
|
state.loading = false;
|
||||||
@ -396,22 +394,6 @@ function getOtherQuery(query: any) {
|
|||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
function getCookie() {
|
|
||||||
const username = Cookies.get("username");
|
|
||||||
let password = Cookies.get("password");
|
|
||||||
const rememberMe = Cookies.get("rememberMe");
|
|
||||||
rememberMe == "true" ? (remember.value = Boolean(rememberMe)) : false;
|
|
||||||
// 保存cookie里面的加密后的密码
|
|
||||||
state.cookiePass = password === undefined ? "" : password;
|
|
||||||
password = password === undefined ? state.loginData.password : password;
|
|
||||||
state.loginData = {
|
|
||||||
username: username === undefined ? state.loginData.username : username,
|
|
||||||
password: decrypt(password),
|
|
||||||
code: "",
|
|
||||||
uuid: "",
|
|
||||||
};
|
|
||||||
remember.value = rememberMe === undefined ? false : Boolean(rememberMe);
|
|
||||||
}
|
|
||||||
function getCode() {
|
function getCode() {
|
||||||
getCaptcha().then((result: any) => {
|
getCaptcha().then((result: any) => {
|
||||||
codeUrl.value = result.data.img;
|
codeUrl.value = result.data.img;
|
||||||
@ -554,7 +536,6 @@ const handleResetPassword = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getCookie();
|
|
||||||
getCode();
|
getCode();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,5 +1,25 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import JidiSelectorMod from "@/modules/jidiSelectorMod.vue";
|
||||||
|
import RightDrawer from "@/components/RightDrawer/index.vue";
|
||||||
|
import QiXiDiBaoHuGongZuoKaiZhan from "@/modules/qixidibaohugongzuokaizhanQK/index.vue"
|
||||||
|
// import QixidijchuXx from "@/modules/qixidijchuXx"
|
||||||
|
// import QiXiDiShuiWenBianHua from "@/modules/qixidishuiwenbianhua"
|
||||||
|
// import QiXiDiLiuLiangBianHua from "@/modules/qixidiliuliangbianhua"
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="moduleContent">
|
||||||
<h2>栖息地</h2>
|
<div class="leftContent">
|
||||||
|
<JidiSelectorMod />
|
||||||
|
</div>
|
||||||
|
<div class="rightContent">
|
||||||
|
<RightDrawer>
|
||||||
|
<QiXiDiBaoHuGongZuoKaiZhan />
|
||||||
|
<!-- <QiXiDiShuiWenBianHua /> -->
|
||||||
|
<!-- <QiXiDiLiuLiangBianHua /> -->
|
||||||
|
<!-- <QixidijchuXx /> -->
|
||||||
|
</RightDrawer>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<style scoped lang="scss"></style>
|
||||||
|
|||||||
326
frontend/src/views/register/index.vue
Normal file
326
frontend/src/views/register/index.vue
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
<template>
|
||||||
|
<div class="register-container">
|
||||||
|
<div class="register-wrapper">
|
||||||
|
<!-- 左侧:背景图区域 -->
|
||||||
|
<div class="left-section">
|
||||||
|
<div class="slogan">
|
||||||
|
<p>{{ $t("login.titleSjtb") }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右侧:注册表单区域 -->
|
||||||
|
<div class="right-section">
|
||||||
|
<a-tabs v-model:activeKey="activeTab" class="register-tabs">
|
||||||
|
<a-tab-pane key="register" tab="用户注册">
|
||||||
|
<a-form
|
||||||
|
:model="registerData"
|
||||||
|
:rules="registerRules"
|
||||||
|
layout="vertical"
|
||||||
|
class="form-container"
|
||||||
|
@finish="onRegister"
|
||||||
|
>
|
||||||
|
<!-- 登录账号 -->
|
||||||
|
<a-form-item name="username" label="登录账号">
|
||||||
|
<a-input
|
||||||
|
v-model:value="registerData.username"
|
||||||
|
placeholder="请输入登录账号(4-20个字符)"
|
||||||
|
:prefix="h(UserOutlined)"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 真实姓名 -->
|
||||||
|
<a-form-item name="realName" label="真实姓名">
|
||||||
|
<a-input
|
||||||
|
v-model:value="registerData.realName"
|
||||||
|
placeholder="请输入真实姓名"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 手机号 -->
|
||||||
|
<a-form-item name="phone" label="手机号">
|
||||||
|
<a-input
|
||||||
|
v-model:value="registerData.phone"
|
||||||
|
placeholder="请输入11位手机号"
|
||||||
|
:prefix="h(MobileOutlined)"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 密码 -->
|
||||||
|
<a-form-item name="password" label="密码">
|
||||||
|
<a-input-password
|
||||||
|
v-model:value="registerData.password"
|
||||||
|
placeholder="请设置密码(6-20个字符)"
|
||||||
|
:prefix="h(LockOutlined)"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 确认密码 -->
|
||||||
|
<a-form-item name="confirmPassword" label="确认密码">
|
||||||
|
<a-input-password
|
||||||
|
v-model:value="registerData.confirmPassword"
|
||||||
|
placeholder="请再次输入密码"
|
||||||
|
:prefix="h(LockOutlined)"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 验证码 -->
|
||||||
|
<a-form-item name="code" label="验证码">
|
||||||
|
<a-row :gutter="8">
|
||||||
|
<a-col :span="16">
|
||||||
|
<a-input
|
||||||
|
v-model:value="registerData.code"
|
||||||
|
placeholder="请输入验证码"
|
||||||
|
/>
|
||||||
|
</a-col>
|
||||||
|
<a-col :span="8">
|
||||||
|
<img
|
||||||
|
v-if="captchaImg"
|
||||||
|
:src="captchaImg"
|
||||||
|
@click="refreshCaptcha"
|
||||||
|
style="cursor: pointer; width: 100%; height: 36px;"
|
||||||
|
/>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</a-form-item>
|
||||||
|
|
||||||
|
<!-- 注册按钮 -->
|
||||||
|
<a-button
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
block
|
||||||
|
htmlType="submit"
|
||||||
|
:loading="loading"
|
||||||
|
>
|
||||||
|
<span>立即注册</span>
|
||||||
|
</a-button>
|
||||||
|
|
||||||
|
<!-- 返回登录 -->
|
||||||
|
<a-button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
block
|
||||||
|
@click="backToLogin"
|
||||||
|
:style="{ marginTop: '10px' }"
|
||||||
|
>
|
||||||
|
已有账号?返回登录
|
||||||
|
</a-button>
|
||||||
|
</a-form>
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, ref, onMounted, h } from "vue";
|
||||||
|
import { UserOutlined, LockOutlined, MobileOutlined } from "@ant-design/icons-vue";
|
||||||
|
import { getCaptcha,
|
||||||
|
// registerUser
|
||||||
|
} from "@/api/auth";
|
||||||
|
import { message } from "ant-design-vue";
|
||||||
|
import router from "@/router";
|
||||||
|
import { encrypt } from "@/utils/rsaEncrypt";
|
||||||
|
|
||||||
|
// 注册表单数据
|
||||||
|
const registerData = reactive({
|
||||||
|
// 必填字段
|
||||||
|
username: "",
|
||||||
|
realName: "",
|
||||||
|
phone: "",
|
||||||
|
password: "",
|
||||||
|
confirmPassword: "",
|
||||||
|
code: "",
|
||||||
|
uuid: "",
|
||||||
|
|
||||||
|
// 系统字段
|
||||||
|
userType: 1,
|
||||||
|
status: 1,
|
||||||
|
regStatus: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const registerRules = {
|
||||||
|
// 登录账号
|
||||||
|
username: [
|
||||||
|
{ required: true, message: "请输入登录账号", trigger: "blur" },
|
||||||
|
{ min: 4, max: 20, message: "账号长度4-20个字符", trigger: "blur" },
|
||||||
|
{ pattern: /^[a-zA-Z0-9_]+$/, message: "只能包含字母、数字和下划线", trigger: "blur" }
|
||||||
|
],
|
||||||
|
|
||||||
|
// 真实姓名
|
||||||
|
realName: [
|
||||||
|
{ required: true, message: "请输入真实姓名", trigger: "blur" },
|
||||||
|
{ min: 2, max: 20, message: "姓名长度2-20个字符", trigger: "blur" }
|
||||||
|
],
|
||||||
|
|
||||||
|
// 手机号
|
||||||
|
phone: [
|
||||||
|
{ required: true, message: "请输入手机号", trigger: "blur" },
|
||||||
|
{ pattern: /^1[3-9]\d{9}$/, message: "请输入正确的11位手机号", trigger: "blur" }
|
||||||
|
],
|
||||||
|
|
||||||
|
// 密码
|
||||||
|
password: [
|
||||||
|
{ required: true, message: "请输入密码", trigger: "blur" },
|
||||||
|
{ min: 6, max: 20, message: "密码长度6-20个字符", trigger: "blur" }
|
||||||
|
],
|
||||||
|
|
||||||
|
// 确认密码
|
||||||
|
confirmPassword: [
|
||||||
|
{ required: true, message: "请再次输入密码", trigger: "blur" },
|
||||||
|
{
|
||||||
|
validator: (rule: any, value: string) => {
|
||||||
|
if (value && value !== registerData.password) {
|
||||||
|
return Promise.reject("两次输入的密码不一致");
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
trigger: "blur"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
// 验证码
|
||||||
|
code: [
|
||||||
|
{ required: true, message: "请输入验证码", trigger: "blur" }
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const captchaImg = ref("");
|
||||||
|
const activeTab = ref("register");
|
||||||
|
|
||||||
|
// 获取验证码
|
||||||
|
const refreshCaptcha = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getCaptcha();
|
||||||
|
registerData.uuid = res.data.verifyCodeKey;
|
||||||
|
captchaImg.value = res.data.verifyCodeImg; // base64图片
|
||||||
|
} catch (error) {
|
||||||
|
message.error("获取验证码失败");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 注册提交
|
||||||
|
const onRegister = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 密码加密
|
||||||
|
const encryptedPassword = encrypt(registerData.password);
|
||||||
|
|
||||||
|
// 构造注册数据
|
||||||
|
const registerParams = {
|
||||||
|
username: registerData.username,
|
||||||
|
realName: registerData.realName,
|
||||||
|
phone: registerData.phone,
|
||||||
|
password: encryptedPassword,
|
||||||
|
|
||||||
|
// 系统字段
|
||||||
|
userType: 1,
|
||||||
|
status: 1,
|
||||||
|
regStatus: 0,
|
||||||
|
|
||||||
|
// 验证码相关
|
||||||
|
code: registerData.code,
|
||||||
|
uuid: registerData.uuid
|
||||||
|
};
|
||||||
|
|
||||||
|
// 调用注册接口
|
||||||
|
// await registerUser(registerParams);
|
||||||
|
|
||||||
|
message.success("注册成功,等待管理员审核");
|
||||||
|
|
||||||
|
// 延迟跳转到登录页
|
||||||
|
setTimeout(() => {
|
||||||
|
router.push({ path: "/login" });
|
||||||
|
}, 1500);
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.message || "注册失败,请重试");
|
||||||
|
// 刷新验证码
|
||||||
|
refreshCaptcha();
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 返回登录
|
||||||
|
const backToLogin = () => {
|
||||||
|
router.push({ path: "/login" });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(() => {
|
||||||
|
refreshCaptcha();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.register-container {
|
||||||
|
margin: 0 auto;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-width: 1500px;
|
||||||
|
background-color: #fff;
|
||||||
|
|
||||||
|
.register-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 600px;
|
||||||
|
background: url("@/assets/images/bg_sjtb.png");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 左侧背景区域
|
||||||
|
.left-section {
|
||||||
|
.slogan {
|
||||||
|
position: absolute;
|
||||||
|
top: 20%;
|
||||||
|
left: 18%;
|
||||||
|
width: 700px;
|
||||||
|
height: 112px;
|
||||||
|
color: #040504;
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右侧注册卡片区域
|
||||||
|
.right-section {
|
||||||
|
position: absolute;
|
||||||
|
left: 70%;
|
||||||
|
top: 15%;
|
||||||
|
width: 25%;
|
||||||
|
// max-height: 650px;
|
||||||
|
// max-width: 400px;
|
||||||
|
min-height: 650px;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 20px 24px 24px;
|
||||||
|
background-color: #fff;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
:deep(.ant-form-item) {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-form-item-label > label) {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
:deep(.ant-input-prefix) {
|
||||||
|
display: flex;
|
||||||
|
width: 26px;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
66
frontend/src/views/shuJuTianBao/approvalLogSearch.vue
Normal file
66
frontend/src/views/shuJuTianBao/approvalLogSearch.vue
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<template>
|
||||||
|
<div class="approval-log-search">
|
||||||
|
<BasicSearch ref="basicSearchRef" :searchList="searchList" :initial-values="initSearchData"
|
||||||
|
@finish="onSearchFinish" @values-change="onValuesChange" @reset="handleReset">
|
||||||
|
</BasicSearch>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, onMounted } from "vue";
|
||||||
|
import BasicSearch from "@/components/BasicSearch/index.vue";
|
||||||
|
|
||||||
|
// --- Props & Emits ---
|
||||||
|
interface Props {
|
||||||
|
actionTypeDict: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "reset", values: any): void;
|
||||||
|
(e: "searchFinish", values: any): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
|
||||||
|
// 模拟 initSearchData
|
||||||
|
const initSearchData = {
|
||||||
|
action: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchData = ref<any>({ ...initSearchData });
|
||||||
|
|
||||||
|
const searchList: any = computed(() => [
|
||||||
|
{
|
||||||
|
type: "Select",
|
||||||
|
name: "action",
|
||||||
|
label: "操作类型",
|
||||||
|
fieldProps: {
|
||||||
|
allowClear: true,
|
||||||
|
},
|
||||||
|
options: props.actionTypeDict || [],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// --- Methods ---
|
||||||
|
|
||||||
|
// 2. 搜索表单逻辑
|
||||||
|
const onSearchFinish = (values: any) => {
|
||||||
|
console.log(values);
|
||||||
|
emit("searchFinish", values);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
emit("reset", initSearchData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onValuesChange = (changedValues: any, allValues: any) => {
|
||||||
|
// 同步更新本地 searchData,以便其他逻辑使用
|
||||||
|
searchData.value = { ...searchData.value, ...allValues };
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Lifecycle ---
|
||||||
|
onMounted(() => {
|
||||||
|
emit("searchFinish", initSearchData);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss"></style>
|
||||||
66
frontend/src/views/shuJuTianBao/changeLogSearch.vue
Normal file
66
frontend/src/views/shuJuTianBao/changeLogSearch.vue
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<template>
|
||||||
|
<div class="change-log-search">
|
||||||
|
<BasicSearch ref="basicSearchRef" :searchList="searchList" :initial-values="initSearchData"
|
||||||
|
@finish="onSearchFinish" @values-change="onValuesChange" @reset="handleReset">
|
||||||
|
</BasicSearch>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, onMounted } from "vue";
|
||||||
|
import BasicSearch from "@/components/BasicSearch/index.vue";
|
||||||
|
|
||||||
|
// --- Props & Emits ---
|
||||||
|
interface Props {
|
||||||
|
operationTypeDict: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "reset", values: any): void;
|
||||||
|
(e: "searchFinish", values: any): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
|
||||||
|
// 模拟 initSearchData
|
||||||
|
const initSearchData = {
|
||||||
|
operationType: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchData = ref<any>({ ...initSearchData });
|
||||||
|
|
||||||
|
const searchList: any = computed(() => [
|
||||||
|
{
|
||||||
|
type: "Select",
|
||||||
|
name: "operationType",
|
||||||
|
label: "操作类型",
|
||||||
|
fieldProps: {
|
||||||
|
allowClear: true,
|
||||||
|
},
|
||||||
|
options: props.operationTypeDict || [],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// --- Methods ---
|
||||||
|
|
||||||
|
// 2. 搜索表单逻辑
|
||||||
|
const onSearchFinish = (values: any) => {
|
||||||
|
console.log(values);
|
||||||
|
emit("searchFinish", values);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
emit("reset", initSearchData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onValuesChange = (changedValues: any, allValues: any) => {
|
||||||
|
// 同步更新本地 searchData,以便其他逻辑使用
|
||||||
|
searchData.value = { ...searchData.value, ...allValues };
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Lifecycle ---
|
||||||
|
onMounted(() => {
|
||||||
|
emit("searchFinish", initSearchData);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss"></style>
|
||||||
@ -1,546 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="guoYuSheShiShuJuTianBao-page">
|
|
||||||
<!-- 搜索区域组件,具体 props 需根据实际子组件调整 -->
|
|
||||||
<GuoYuSheShiShuJuTianBaoSearch
|
|
||||||
:import-btn="importBtn"
|
|
||||||
:save-btn="saveBtn"
|
|
||||||
:handle-add="handleAdd"
|
|
||||||
:batchData="batchData"
|
|
||||||
:batchDel="batchDel"
|
|
||||||
@search-finish="handleSearchFinish"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 主表格 -->
|
|
||||||
<BasicTable
|
|
||||||
ref="tableRef"
|
|
||||||
:columns="columns"
|
|
||||||
:list-url="getFishDraftPage"
|
|
||||||
:search-params="{}"
|
|
||||||
:enable-row-selection="true"
|
|
||||||
@selection-change="handleSelectionChange"
|
|
||||||
>
|
|
||||||
<!-- 使用 bodyCell 插槽自定义单元格渲染 -->
|
|
||||||
<template #bodyCell="{ column, record }">
|
|
||||||
<template v-if="column.key === 'action' || column.dataIndex === 'action'">
|
|
||||||
<div class="flex">
|
|
||||||
<a-button type="link" size="small" @click="handleEdit(record)">编辑</a-button>
|
|
||||||
<a-button type="link" danger size="small" @click="handleDelete([record.id])"
|
|
||||||
>删除</a-button
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</BasicTable>
|
|
||||||
<!-- <BasicTable :columns="columns" :listUrl="getFishDraftPage" /> -->
|
|
||||||
|
|
||||||
<!-- 导入预览 Modal -->
|
|
||||||
<a-modal
|
|
||||||
title="导入数据预览"
|
|
||||||
ok-text="提交导入"
|
|
||||||
cancel-text="取消"
|
|
||||||
:width="1500"
|
|
||||||
v-model:open="visible"
|
|
||||||
:confirm-loading="fileLoading"
|
|
||||||
@cancel="handleModalCancel"
|
|
||||||
@ok="handleModalOk"
|
|
||||||
>
|
|
||||||
<a-table
|
|
||||||
size="small"
|
|
||||||
:loading="fileLoading"
|
|
||||||
:data-source="fileTableData"
|
|
||||||
:columns="modalColumns"
|
|
||||||
:scroll="{ y: 500, x: '100%' }"
|
|
||||||
row-key="index"
|
|
||||||
>
|
|
||||||
<!-- 如果需要复杂的行内编辑插槽,可在此定义,但目前逻辑主要在 column render 中处理 -->
|
|
||||||
</a-table>
|
|
||||||
</a-modal>
|
|
||||||
|
|
||||||
<!-- 新增/编辑 Modal (对应 React 的 EditModal) -->
|
|
||||||
<!-- 假设已创建对应的 Vue 组件 GuoYuSheShiShuJuTianBaoForm -->
|
|
||||||
<EditModal
|
|
||||||
v-model:visible="editModalVisible"
|
|
||||||
:initial-values="currentRecord"
|
|
||||||
:loading="submitLoading"
|
|
||||||
@cancel="editModalCancel"
|
|
||||||
@ok="handleEditSubmit"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 视频预览 Modal -->
|
|
||||||
<a-modal
|
|
||||||
title="视频预览"
|
|
||||||
v-model:open="videoPreviewVisible"
|
|
||||||
:footer="null"
|
|
||||||
width="800px"
|
|
||||||
@cancel="closeVideoPreview"
|
|
||||||
>
|
|
||||||
<video
|
|
||||||
v-if="currentVideoUrl"
|
|
||||||
controls
|
|
||||||
autoplay
|
|
||||||
style="width: 100%"
|
|
||||||
:src="currentVideoUrl"
|
|
||||||
>
|
|
||||||
您的浏览器不支持视频播放
|
|
||||||
</video>
|
|
||||||
</a-modal>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { ref, computed, onMounted, h } from "vue";
|
|
||||||
import { message, Modal } from "ant-design-vue"; // 假设使用 ant-design-vue
|
|
||||||
import JSZip from "jszip";
|
|
||||||
import * as XLSX from "xlsx";
|
|
||||||
import BasicTable from "@/components/BasicTable/index.vue";
|
|
||||||
import GuoYuSheShiShuJuTianBaoSearch from "./guoYuSheShiShuJuTianBaoSearch.vue";
|
|
||||||
import EditModal from "./guoYuSheShiShuJuTianBaoForm.vue";
|
|
||||||
import {
|
|
||||||
getFishDraftPage,
|
|
||||||
addFishDraft,
|
|
||||||
editFishDraft,
|
|
||||||
delFishDraft,
|
|
||||||
} from "@/api/guoYuSheShiShuJuTianBao";
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
import { Tag } from 'ant-design-vue'; // 确保导入 Tag
|
|
||||||
// import { FileImageOutlined, VideoCameraOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons-vue'
|
|
||||||
|
|
||||||
// --- 类型定义 ---
|
|
||||||
interface FormData {
|
|
||||||
[key: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ColumnConfig {
|
|
||||||
dataIndex: string;
|
|
||||||
key: string;
|
|
||||||
title: string;
|
|
||||||
width?: number;
|
|
||||||
customRender?: (text: any, record: any) => any;
|
|
||||||
}
|
|
||||||
const tableRef = ref<any>(null);
|
|
||||||
// --- 基础配置 ---
|
|
||||||
const baseColumnsConfig: ColumnConfig[] = [
|
|
||||||
{ dataIndex: "engName", key: "engName", title: "水电基地", width: 100 },
|
|
||||||
{ dataIndex: "baseName", key: "baseName", title: "电站名称", width: 120 },
|
|
||||||
{ dataIndex: "fpname", key: "fpname", title: "过鱼设施名称", width: 150 },
|
|
||||||
{ dataIndex: "strdt", key: "strdt", title: "过鱼时间", width: 150 },
|
|
||||||
{ dataIndex: "ftp", key: "ftp", 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', // Antdv Tag 的颜色预设
|
|
||||||
style: { margin: 0 } // 去除默认 margin,使其在表格中对齐更好
|
|
||||||
},
|
|
||||||
() => isYes ? '是' : '否'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ 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: "picpth", key: "level5", title: "图片", width: 100 },
|
|
||||||
{ dataIndex: "vdpth", key: "level6", title: "视频", width: 100 },
|
|
||||||
{ dataIndex: "tm", key: "tm", title: "填报时间", width: 150 },
|
|
||||||
{ dataIndex: "status", key: "status", title: "状态", width: 100 },
|
|
||||||
];
|
|
||||||
|
|
||||||
// --- 状态定义 ---
|
|
||||||
const visible = ref(false); // 导入预览 Modal
|
|
||||||
|
|
||||||
// 编辑相关状态
|
|
||||||
const editModalVisible = ref(false);
|
|
||||||
const currentRecord = ref<FormData | null>(null);
|
|
||||||
const submitLoading = ref(false);
|
|
||||||
|
|
||||||
// 视频预览相关状态
|
|
||||||
const videoPreviewVisible = ref(false);
|
|
||||||
const currentVideoUrl = ref<string>("");
|
|
||||||
|
|
||||||
// 表格数据
|
|
||||||
const tableData = ref<any[]>([]);
|
|
||||||
const fileTableData = ref<any[]>([]);
|
|
||||||
const batchData = ref<any[]>([]);
|
|
||||||
|
|
||||||
const fileLoading = ref(false);
|
|
||||||
|
|
||||||
// 行内编辑 Key (用于导入预览表格)
|
|
||||||
const editingKey = ref<string | number>("");
|
|
||||||
|
|
||||||
|
|
||||||
// --- 辅助函数 ---
|
|
||||||
|
|
||||||
// 从 Zip 获取 Blob URL
|
|
||||||
// const getBlobUrlFromZip = async (zip: JSZip, fileName: string): Promise<string> => {
|
|
||||||
// try {
|
|
||||||
// const file = zip.file(fileName);
|
|
||||||
// if (!file) return "";
|
|
||||||
// const blob = await file.async("blob");
|
|
||||||
// return URL.createObjectURL(blob);
|
|
||||||
// } catch (e) {
|
|
||||||
// console.error("Extract file failed", e);
|
|
||||||
// return "";
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// 渲染媒体单元格 (返回 VNode 或简单结构,实际在 Antdv columns render 中处理)
|
|
||||||
// 在 Vue Antdv 中,render 函数接收 (text, record, index)
|
|
||||||
// const createMediaRender = (type: "image" | "video") => {
|
|
||||||
// return (text: string) => {
|
|
||||||
// if (!text) return "-";
|
|
||||||
// // 这里简化处理,实际项目中可能需要使用 h 函数渲染图标和点击事件
|
|
||||||
// // 由于无法直接在这里绑定 click 事件到简单的字符串返回,建议在 columns 定义中使用 slots 或 h 函数
|
|
||||||
// // 为了保持逻辑清晰,这里仅返回文本提示,实际 UI 需结合 Antdv 的 customRender
|
|
||||||
// return type === "image" ? "查看图片" : "播放视频";
|
|
||||||
// };
|
|
||||||
// };
|
|
||||||
|
|
||||||
// --- Columns 定义 ---
|
|
||||||
|
|
||||||
// 主表格 Columns
|
|
||||||
const columns = computed(() => {
|
|
||||||
return [
|
|
||||||
...baseColumnsConfig.map((col) => {
|
|
||||||
if (col.dataIndex === "level5") {
|
|
||||||
return {
|
|
||||||
...col,
|
|
||||||
customRender: ({ text }: any) => {
|
|
||||||
if (!text) return "-";
|
|
||||||
// 实际应渲染 Icon 和点击事件,此处简化
|
|
||||||
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",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
// 导入预览表格 Columns (包含行内编辑逻辑)
|
|
||||||
const modalColumns = computed(() => {
|
|
||||||
const isEditing = (_record: any, index: number) => index === editingKey.value;
|
|
||||||
|
|
||||||
// const save = async (index: number) => {
|
|
||||||
// // 保存逻辑:实际上 fileTableData 是响应式的,input change 时已经更新
|
|
||||||
// editingKey.value = "";
|
|
||||||
// message.success("行数据已更新");
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const deleteRow = (index: number) => {
|
|
||||||
// fileTableData.value = fileTableData.value.filter((_, i) => i !== index);
|
|
||||||
// message.success("行数据已删除");
|
|
||||||
// };
|
|
||||||
|
|
||||||
return baseColumnsConfig
|
|
||||||
.map((col) => ({
|
|
||||||
...col,
|
|
||||||
customRender: ({ text, record, index }: any) => {
|
|
||||||
const editing = isEditing(record, index);
|
|
||||||
|
|
||||||
// 如果是媒体列
|
|
||||||
if (col.dataIndex === "level5" || col.dataIndex === "level6") {
|
|
||||||
if (editing) {
|
|
||||||
// 返回 Input 组件的 VNode 或标识,实际需用 slot 或 h 函数
|
|
||||||
return "Input编辑中";
|
|
||||||
}
|
|
||||||
return col.dataIndex === "level5" ? "查看图片" : "播放视频";
|
|
||||||
}
|
|
||||||
|
|
||||||
// 普通列
|
|
||||||
if (editing) {
|
|
||||||
// 返回 Input 组件标识
|
|
||||||
return "Input编辑中";
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
},
|
|
||||||
// Antdv 支持通过 slots 自定义单元格内容以实现交互
|
|
||||||
slots: { customRender: `cell-${col.dataIndex}` },
|
|
||||||
}))
|
|
||||||
// .concat({
|
|
||||||
// title: "操作",
|
|
||||||
// dataIndex: "operation",
|
|
||||||
// fixed: "right",
|
|
||||||
// width: 140,
|
|
||||||
// align: "center",
|
|
||||||
// customRender: ({ record, index }: any) => {
|
|
||||||
// const editable = isEditing(record, index);
|
|
||||||
// return editable ? "保存/取消" : "修改/删除";
|
|
||||||
// },
|
|
||||||
// slots: { customRender: "cell-operation" },
|
|
||||||
// });
|
|
||||||
});
|
|
||||||
|
|
||||||
// --- 业务逻辑方法 ---
|
|
||||||
|
|
||||||
const handleAdd = () => {
|
|
||||||
currentRecord.value = null;
|
|
||||||
editModalVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEdit = (record: any) => {
|
|
||||||
currentRecord.value = { ...record };
|
|
||||||
editModalVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 删除过鱼数据
|
|
||||||
const handleDelete = (ids: any[]) => {
|
|
||||||
console.log(ids)
|
|
||||||
Modal.confirm({
|
|
||||||
title: "是否确认删除选中数据吗?",
|
|
||||||
onOk: async () => {
|
|
||||||
let res: any = await delFishDraft(ids);
|
|
||||||
if (res && res?.code == 0) {
|
|
||||||
message.success("删除成功");
|
|
||||||
tableRef.value?.getList();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
// 批量删除
|
|
||||||
const batchDel = () => {
|
|
||||||
handleDelete(batchData.value);
|
|
||||||
};
|
|
||||||
// 多选
|
|
||||||
const handleSelectionChange = (keys: any) => {
|
|
||||||
batchData.value = keys;
|
|
||||||
};
|
|
||||||
const editModalCancel = () => {
|
|
||||||
editModalVisible.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEditSubmit = async (values: FormData) => {
|
|
||||||
submitLoading.value = true;
|
|
||||||
console.log(values);
|
|
||||||
// 模拟异步请求
|
|
||||||
// setTimeout(() => {
|
|
||||||
if (currentRecord.value) {
|
|
||||||
// 编辑逻辑
|
|
||||||
|
|
||||||
let res: any = await editFishDraft({
|
|
||||||
...values
|
|
||||||
});
|
|
||||||
if (res && res?.code == 0) {
|
|
||||||
message.success("编辑成功");
|
|
||||||
editModalVisible.value = false;
|
|
||||||
tableRef.value?.getList();
|
|
||||||
}
|
|
||||||
submitLoading.value = false;
|
|
||||||
// const newData = tableData.value.map((item) => {
|
|
||||||
// // 简单比较,实际建议用 ID
|
|
||||||
// if (JSON.stringify(item) === JSON.stringify(currentRecord.value)) {
|
|
||||||
// // 注意:浅比较可能不够,需根据实际 ID
|
|
||||||
// return { ...item, ...values };
|
|
||||||
// }
|
|
||||||
// return item;
|
|
||||||
// });
|
|
||||||
// // 更稳妥的方式是通过 key 查找
|
|
||||||
// const targetIndex = tableData.value.findIndex(
|
|
||||||
// (item) => item.key === currentRecord.value?.key
|
|
||||||
// );
|
|
||||||
// if (targetIndex > -1) {
|
|
||||||
// tableData.value[targetIndex] = { ...tableData.value[targetIndex], ...values };
|
|
||||||
// }
|
|
||||||
} else {
|
|
||||||
// 新增逻辑
|
|
||||||
let res: any = await addFishDraft({
|
|
||||||
...values,
|
|
||||||
tm: dayjs().startOf("day").format("YYYY-MM-DD HH:mm:ss"),
|
|
||||||
stcd: 1,
|
|
||||||
});
|
|
||||||
if (res && res?.code == 0) {
|
|
||||||
message.success("新增成功");
|
|
||||||
editModalVisible.value = false;
|
|
||||||
tableRef.value?.getList();
|
|
||||||
}
|
|
||||||
submitLoading.value = false;
|
|
||||||
}
|
|
||||||
// }, 500);
|
|
||||||
};
|
|
||||||
|
|
||||||
const parseExcelFile = async (fileName: string, arrayBuffer: ArrayBuffer) => {
|
|
||||||
try {
|
|
||||||
const workbook = XLSX.read(arrayBuffer, {
|
|
||||||
type: "array",
|
|
||||||
cellDates: true, // 【关键】将日期单元格解析为 JS Date 对象
|
|
||||||
dateNF: "yyyy-mm-dd", // 【关键】指定日期输出格式
|
|
||||||
});
|
|
||||||
const firstSheetName = workbook.SheetNames[0];
|
|
||||||
if (!firstSheetName) throw new Error("Excel文件中没有工作表");
|
|
||||||
const worksheet = workbook.Sheets[firstSheetName];
|
|
||||||
const jsonData: any[] = XLSX.utils.sheet_to_json(worksheet);
|
|
||||||
return jsonData;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`解析文件 ${fileName} 失败:`, error);
|
|
||||||
message.error(`文件 ${fileName} 解析失败`);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleModalOk = () => {
|
|
||||||
tableData.value = [...fileTableData.value];
|
|
||||||
visible.value = false;
|
|
||||||
message.success("数据已导入至列表");
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleModalCancel = () => {
|
|
||||||
visible.value = false;
|
|
||||||
editingKey.value = "";
|
|
||||||
};
|
|
||||||
|
|
||||||
const importBtn = async (file: File) => {
|
|
||||||
fileLoading.value = true;
|
|
||||||
editingKey.value = "";
|
|
||||||
const hideMessage = message.loading("正在解析压缩包...", 0);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const zip = await JSZip.loadAsync(file);
|
|
||||||
const zipPathMap: Record<string, string> = {};
|
|
||||||
|
|
||||||
// 构建路径映射
|
|
||||||
zip.forEach((relativePath, zipEntry) => {
|
|
||||||
if (!zipEntry.dir) {
|
|
||||||
const lowerPath = relativePath.toLowerCase();
|
|
||||||
zipPathMap[lowerPath] = relativePath;
|
|
||||||
const pathParts = relativePath.split("/");
|
|
||||||
for (let i = 0; i < pathParts.length; i++) {
|
|
||||||
const subPath = pathParts.slice(i).join("/");
|
|
||||||
if (subPath) zipPathMap[subPath.toLowerCase()] = relativePath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const fileNames = Object.keys(zip.files);
|
|
||||||
if (fileNames.length === 0) {
|
|
||||||
hideMessage();
|
|
||||||
message.warning("压缩包为空");
|
|
||||||
fileLoading.value = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let allExcelData: any[] = [];
|
|
||||||
for (const fileName of fileNames) {
|
|
||||||
const zipEntry = zip.files[fileName];
|
|
||||||
if (zipEntry.dir) continue;
|
|
||||||
if (!fileName.match(/\.(xls|xlsx)$/i)) continue;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const arrayBuffer = await zipEntry.async("arraybuffer");
|
|
||||||
const data = await parseExcelFile(fileName, arrayBuffer);
|
|
||||||
if (!data || data.length === 0) continue;
|
|
||||||
|
|
||||||
const transformedData = await Promise.all(
|
|
||||||
data.map(async (item: any) => {
|
|
||||||
const newObj: any = {};
|
|
||||||
for (const excelKey in item) {
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(item, excelKey)) continue;
|
|
||||||
const value = item[excelKey];
|
|
||||||
// 模糊匹配列标题
|
|
||||||
const matchedCol = baseColumnsConfig.find(
|
|
||||||
(col) => excelKey.includes(col.title) || col.title.includes(excelKey)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (matchedCol) {
|
|
||||||
let finalValue = value;
|
|
||||||
// 处理图片和视频路径提取
|
|
||||||
if (
|
|
||||||
(matchedCol.dataIndex === "level5" ||
|
|
||||||
matchedCol.dataIndex === "level6") &&
|
|
||||||
value &&
|
|
||||||
typeof value === "string"
|
|
||||||
) {
|
|
||||||
const trimPath = value.trim().replace(/\\/g, "/");
|
|
||||||
if (trimPath) {
|
|
||||||
const searchKey = trimPath.toLowerCase();
|
|
||||||
const realPath = zipPathMap[searchKey];
|
|
||||||
if (realPath) {
|
|
||||||
try {
|
|
||||||
const zipFile = zip.file(realPath);
|
|
||||||
if (zipFile) {
|
|
||||||
const blob = await zipFile.async("blob");
|
|
||||||
finalValue = URL.createObjectURL(blob);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`Failed to extract blob for: ${realPath}`, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newObj[matchedCol.dataIndex] = finalValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newObj;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
allExcelData = [...allExcelData, ...transformedData];
|
|
||||||
} catch (err) {
|
|
||||||
console.error(`读取文件 ${fileName} 失败`, err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fileTableData.value = allExcelData;
|
|
||||||
visible.value = true;
|
|
||||||
hideMessage();
|
|
||||||
message.success(`解析完成,共获取 ${allExcelData.length} 条数据`);
|
|
||||||
} catch (error) {
|
|
||||||
hideMessage();
|
|
||||||
console.error("ZIP 解析失败:", error);
|
|
||||||
message.error("文件格式错误或解析失败");
|
|
||||||
} finally {
|
|
||||||
fileLoading.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const saveBtn = async () => {
|
|
||||||
// TODO: 实现保存逻辑
|
|
||||||
console.log("Save button clicked");
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSearchFinish = (values: any) => {
|
|
||||||
console.log(values);
|
|
||||||
// const newSearchData = { ...searchData.value, ...e };
|
|
||||||
// searchData.value = newSearchData;
|
|
||||||
// getData(newSearchData, label);
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeVideoPreview = () => {
|
|
||||||
videoPreviewVisible.value = false;
|
|
||||||
currentVideoUrl.value = "";
|
|
||||||
};
|
|
||||||
|
|
||||||
// --- 生命周期 ---
|
|
||||||
onMounted(() => {});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.guoYuSheShiShuJuTianBao-page {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: #ffffff;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,10 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<a-modal
|
<a-modal
|
||||||
:title="isEdit ? '编辑数据' : '新增数据'"
|
:title="isView ? '查看数据' : isEdit ? '编辑数据' : '新增数据'"
|
||||||
v-model:open="modalVisible"
|
v-model:open="modalVisible"
|
||||||
:confirm-loading="loading"
|
:confirm-loading="loading"
|
||||||
width="800px"
|
width="800px"
|
||||||
:destroy-on-close="true"
|
:destroy-on-close="true"
|
||||||
|
:footer="isView ? null : undefined"
|
||||||
@cancel="handleCancel"
|
@cancel="handleCancel"
|
||||||
@ok="handleOk"
|
@ok="handleOk"
|
||||||
>
|
>
|
||||||
@ -17,31 +18,86 @@
|
|||||||
>
|
>
|
||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item label="水电基地" name="engName">
|
<a-form-item label="流域" name="baseId">
|
||||||
<a-input v-model:value="formData.engName" placeholder="请输入水电基地" />
|
<a-select
|
||||||
|
v-model:value="formData.baseId"
|
||||||
|
:loading="baseLoading"
|
||||||
|
placeholder="请选择流域"
|
||||||
|
:disabled="isView"
|
||||||
|
show-search
|
||||||
|
allowClear
|
||||||
|
:filter-option="filterOption"
|
||||||
|
@change="baseChange"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="opt in baseOption"
|
||||||
|
:key="opt.baseid"
|
||||||
|
:value="opt.baseid"
|
||||||
|
:label="opt.basename"
|
||||||
|
>
|
||||||
|
{{ opt.basename }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item label="电站名称" name="baseName">
|
<a-form-item label="电站名称" name="rstcd">
|
||||||
<a-input v-model:value="formData.baseName" placeholder="请输入电站名称" />
|
<a-select
|
||||||
|
v-model:value="formData.rstcd"
|
||||||
|
:loading="engLoading"
|
||||||
|
placeholder="请选择电站名称"
|
||||||
|
:disabled="isView"
|
||||||
|
show-search
|
||||||
|
allowClear
|
||||||
|
:filter-option="filterOption"
|
||||||
|
@change="engChange"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="opt in engOption"
|
||||||
|
:key="opt.stcd"
|
||||||
|
:value="opt.stcd"
|
||||||
|
:label="opt.ennm"
|
||||||
|
>
|
||||||
|
{{ opt.ennm }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
</a-row>
|
</a-row>
|
||||||
|
|
||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item label="过鱼设施名称" name="fpname">
|
<a-form-item label="过鱼设施" name="stcd">
|
||||||
<a-input v-model:value="formData.fpname" placeholder="请输入过鱼设施名称" />
|
<a-select
|
||||||
|
v-model:value="formData.stcd"
|
||||||
|
:loading="fpssLoading"
|
||||||
|
placeholder="请选择过鱼设施"
|
||||||
|
:disabled="isView"
|
||||||
|
show-search
|
||||||
|
allowClear
|
||||||
|
:filter-option="filterOption"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="opt in fpssOption"
|
||||||
|
:key="opt.stcd"
|
||||||
|
:value="opt.stcd"
|
||||||
|
:label="opt.stnm"
|
||||||
|
>
|
||||||
|
{{ opt.stnm }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item label="过鱼时间" name="strdt">
|
<a-form-item label="过鱼时间" name="strdt">
|
||||||
<a-date-picker
|
<a-date-picker
|
||||||
v-model:value="formData.strdt"
|
v-model:value="formData.strdt"
|
||||||
|
show-time
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
format="YYYY-MM-DD"
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
value-format="YYYY-MM-DD HH:mm:ss"
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
placeholder="选择日期"
|
placeholder="选择日期"
|
||||||
|
:disabled="isView"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
@ -50,12 +106,12 @@
|
|||||||
<a-row :gutter="16">
|
<a-row :gutter="16">
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item label="鱼种类" name="ftp">
|
<a-form-item label="鱼种类" name="ftp">
|
||||||
<a-input v-model:value="formData.ftp" placeholder="请输入鱼种类" />
|
<fishSearch v-model="formData.ftp" :disabled="isView" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
<a-col :span="12">
|
<a-col :span="12">
|
||||||
<a-form-item label="是否鱼苗" name="isfs">
|
<a-form-item label="是否鱼苗" name="isfs">
|
||||||
<a-radio-group v-model:value="formData.isfs">
|
<a-radio-group v-model:value="formData.isfs" :disabled="isView">
|
||||||
<a-radio :value="1">是</a-radio>
|
<a-radio :value="1">是</a-radio>
|
||||||
<a-radio :value="0">否</a-radio>
|
<a-radio :value="0">否</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
@ -70,11 +126,14 @@
|
|||||||
v-model:value="formData.direction"
|
v-model:value="formData.direction"
|
||||||
placeholder="请选择游向"
|
placeholder="请选择游向"
|
||||||
allow-clear
|
allow-clear
|
||||||
|
:disabled="isView"
|
||||||
>
|
>
|
||||||
<a-select-option value="上行">上行</a-select-option>
|
<a-select-option
|
||||||
<a-select-option value="下行">下行</a-select-option>
|
v-for="item in direction"
|
||||||
<a-select-option value="上行折返">上行折返</a-select-option>
|
:key="item.itemCode"
|
||||||
<a-select-option value="下行折返">下行折返</a-select-option>
|
:value="item.itemCode"
|
||||||
|
>{{ item.dictName }}</a-select-option
|
||||||
|
>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
@ -85,6 +144,7 @@
|
|||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
placeholder="数量"
|
placeholder="数量"
|
||||||
:min="0"
|
:min="0"
|
||||||
|
:disabled="isView"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
@ -104,6 +164,7 @@
|
|||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
:min="0"
|
:min="0"
|
||||||
@change="validateBodyLength"
|
@change="validateBodyLength"
|
||||||
|
:disabled="isView"
|
||||||
/>
|
/>
|
||||||
<span class="px-[10px]">~</span>
|
<span class="px-[10px]">~</span>
|
||||||
<a-input-number
|
<a-input-number
|
||||||
@ -112,6 +173,7 @@
|
|||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
:min="0"
|
:min="0"
|
||||||
@change="validateBodyLength"
|
@change="validateBodyLength"
|
||||||
|
:disabled="isView"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@ -129,6 +191,7 @@
|
|||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
:min="0"
|
:min="0"
|
||||||
@change="validateWeight"
|
@change="validateWeight"
|
||||||
|
:disabled="isView"
|
||||||
/>
|
/>
|
||||||
<span class="px-[10px]">~</span>
|
<span class="px-[10px]">~</span>
|
||||||
<a-input-number
|
<a-input-number
|
||||||
@ -137,6 +200,7 @@
|
|||||||
placeholder="请输入"
|
placeholder="请输入"
|
||||||
:min="0"
|
:min="0"
|
||||||
@change="validateWeight"
|
@change="validateWeight"
|
||||||
|
:disabled="isView"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
@ -150,6 +214,7 @@
|
|||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
placeholder="水温"
|
placeholder="水温"
|
||||||
:min="0"
|
:min="0"
|
||||||
|
:disabled="isView"
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-col>
|
</a-col>
|
||||||
@ -172,22 +237,75 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, reactive, watch, computed } from "vue";
|
import { ref, reactive, watch, computed } from "vue";
|
||||||
|
import dayjs from "dayjs";
|
||||||
import { message } from "ant-design-vue";
|
import { message } from "ant-design-vue";
|
||||||
import type { Rule } from "ant-design-vue/es/form";
|
import type { Rule } from "ant-design-vue/es/form";
|
||||||
|
import fishSearch from "@/components/fishSearch/index.vue";
|
||||||
|
import { getBaseDropdown, getEngInfoDropdown, getFpssDropdown } from "@/api/select";
|
||||||
|
|
||||||
// 定义 Props
|
// 定义 Props
|
||||||
interface Props {
|
interface Props {
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
|
direction: any[];
|
||||||
initialValues?: any | null;
|
initialValues?: any | null;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
|
isView?: boolean;
|
||||||
}
|
}
|
||||||
|
const baseLoading = ref(false);
|
||||||
|
const engLoading = ref(false);
|
||||||
|
const fpssLoading = ref(false);
|
||||||
|
const baseOption = ref<any[]>([]);
|
||||||
|
const engOption = ref<any[]>([]);
|
||||||
|
const fpssOption = ref<any[]>([]);
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
visible: false,
|
visible: false,
|
||||||
initialValues: null,
|
initialValues: null,
|
||||||
loading: false,
|
loading: false,
|
||||||
});
|
});
|
||||||
// --- 核心修复:创建计算属性处理 v-model ---
|
const getBaseDropdownSelect = async () => {
|
||||||
|
try {
|
||||||
|
baseLoading.value = true;
|
||||||
|
const res = await getBaseDropdown({});
|
||||||
|
baseOption.value = res.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取流域列表失败:", error);
|
||||||
|
} finally {
|
||||||
|
baseLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const baseChange = async (baseId: string) => {
|
||||||
|
formData.rstcd = undefined;
|
||||||
|
formData.stcd = undefined;
|
||||||
|
await getEngInfoDropdownSelect(baseId);
|
||||||
|
await getFpssDropdownSelect(formData.rstcd, baseId);
|
||||||
|
};
|
||||||
|
const getEngInfoDropdownSelect = async (baseId: string) => {
|
||||||
|
try {
|
||||||
|
engLoading.value = true;
|
||||||
|
const res = await getEngInfoDropdown({ baseId });
|
||||||
|
engOption.value = res.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取电站列表失败", error);
|
||||||
|
} finally {
|
||||||
|
engLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const engChange = async (rstcd: string) => {
|
||||||
|
formData.stcd = undefined;
|
||||||
|
await getFpssDropdownSelect(rstcd, formData.baseId);
|
||||||
|
};
|
||||||
|
const getFpssDropdownSelect = async (rstcd: string, baseId: string) => {
|
||||||
|
try {
|
||||||
|
fpssLoading.value = true;
|
||||||
|
const res = await getFpssDropdown({ rstcd, baseId });
|
||||||
|
fpssOption.value = res.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取流量列表失败", error);
|
||||||
|
} finally {
|
||||||
|
fpssLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
const modalVisible = computed({
|
const modalVisible = computed({
|
||||||
get: () => props.visible,
|
get: () => props.visible,
|
||||||
set: (val) => emit("update:visible", val),
|
set: (val) => emit("update:visible", val),
|
||||||
@ -208,10 +326,9 @@ const weightError = ref<string>("");
|
|||||||
// 表单数据模型
|
// 表单数据模型
|
||||||
const defaultFormData = reactive({
|
const defaultFormData = reactive({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
engName: undefined,
|
baseId: undefined,
|
||||||
baseName: undefined,
|
|
||||||
fpname: undefined,
|
|
||||||
stcd: undefined,
|
stcd: undefined,
|
||||||
|
rstcd: undefined,
|
||||||
strdt: undefined,
|
strdt: undefined,
|
||||||
ftp: undefined,
|
ftp: undefined,
|
||||||
isfs: 0,
|
isfs: 0,
|
||||||
@ -228,42 +345,19 @@ const defaultFormData = reactive({
|
|||||||
weightMax: undefined,
|
weightMax: undefined,
|
||||||
});
|
});
|
||||||
const formData: any = reactive({ ...defaultFormData });
|
const formData: any = reactive({ ...defaultFormData });
|
||||||
|
const filterOption = (inputValue: string, option: any) => {
|
||||||
// 自定义验证器:检查最小值是否小于最大值
|
if (!option.label) return false;
|
||||||
// const validateBodyLengthRange = (rule: any, value: any) => {
|
return option.label.indexOf(inputValue) !== -1;
|
||||||
// const min = formData.bodyLengthMin;
|
};
|
||||||
// const max = formData.bodyLengthMax;
|
|
||||||
|
|
||||||
// // 如果两个值都存在,则进行比较
|
|
||||||
// if (min !== undefined && min !== null && max !== undefined && max !== null) {
|
|
||||||
// if (Number(min) >= Number(max)) {
|
|
||||||
// return Promise.reject("最小体长必须小于最大体长");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return Promise.resolve();
|
|
||||||
// };
|
|
||||||
// const validateWeightRange = (rule: any, value: any) => {
|
|
||||||
// const min = formData.weightMin;
|
|
||||||
// const max = formData.weightMax;
|
|
||||||
|
|
||||||
// // 如果两个值都存在,则进行比较
|
|
||||||
// if (min !== undefined && min !== null && max !== undefined && max !== null) {
|
|
||||||
// if (Number(min) >= Number(max)) {
|
|
||||||
// return Promise.reject("最小体重必须小于最大体重");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
// 验证规则
|
// 验证规则
|
||||||
const rules: Record<string, Rule[]> = {
|
const rules: Record<string, Rule[]> = {
|
||||||
// engName: [{ required: true, message: "请输入水电基地", trigger: "blur" }],
|
baseId: [{ required: true, message: "请选择流域", trigger: "change" }],
|
||||||
// baseName: [{ required: true, message: "请输入电站名称", trigger: "blur" }],
|
rstcd: [{ required: true, message: "请选择电站", trigger: "change" }],
|
||||||
// fpname: [{ required: true, message: "请输入过鱼设施名称", trigger: "blur" }],
|
stcd: [{ required: true, message: "请选择过鱼设施", trigger: "change" }],
|
||||||
// strdt: [{ required: true, message: "请选择过鱼时间", trigger: "change" }],
|
strdt: [{ required: true, message: "请选择过鱼时间", trigger: "change" }],
|
||||||
// // 添加体长的验证规则
|
ftp: [{ required: true, message: "请选择鱼种类", trigger: "change" }],
|
||||||
// bodyLengthMin: [{ validator: validateBodyLengthRange, trigger: "change" }],
|
direction: [{ required: true, message: "请选择游向", trigger: "change" }],
|
||||||
// bodyLengthMax: [{ validator: validateBodyLengthRange, trigger: "change" }],
|
fcnt: [{ required: true, message: "请输入过鱼数量", trigger: "change" }],
|
||||||
// weightMin: [{ validator: validateWeightRange, trigger: "change" }],
|
|
||||||
// weightMax: [{ validator: validateWeightRange, trigger: "change" }],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 计算是否为编辑模式
|
// 计算是否为编辑模式
|
||||||
@ -276,14 +370,25 @@ const validateBodyLength = () => {
|
|||||||
// 重置错误
|
// 重置错误
|
||||||
bodyLengthError.value = "";
|
bodyLengthError.value = "";
|
||||||
|
|
||||||
// 如果两个值都有,才进行比对
|
// 判断是否有值 (排除 undefined, null, 和空字符串)
|
||||||
if (min !== undefined && min !== null && max !== undefined && max !== null) {
|
const hasMin = min !== undefined && min !== null && min !== "";
|
||||||
if (Number(min) >= Number(max)) {
|
const hasMax = max !== undefined && max !== null && max !== "";
|
||||||
bodyLengthError.value = "最小体长必须小于最大体长";
|
|
||||||
// 如果需要阻止提交,可以在 handleOk 中检查这个变量
|
// 1. 如果只填了一个值,提示两个都必填
|
||||||
|
if ((hasMin && !hasMax) || (!hasMin && hasMax)) {
|
||||||
|
bodyLengthError.value = "最小体长和最大体长均需填写";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 如果两个值都有,才进行比对
|
||||||
|
if (hasMin && hasMax) {
|
||||||
|
// 允许相等,只禁止 min > max
|
||||||
|
if (Number(min) > Number(max)) {
|
||||||
|
bodyLengthError.value = "最小体长不能大于最大体长";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -294,12 +399,25 @@ const validateWeight = () => {
|
|||||||
// 重置错误
|
// 重置错误
|
||||||
weightError.value = "";
|
weightError.value = "";
|
||||||
|
|
||||||
if (min !== undefined && min !== null && max !== undefined && max !== null) {
|
// 判断是否有值
|
||||||
if (Number(min) >= Number(max)) {
|
const hasMin = min !== undefined && min !== null && min !== "";
|
||||||
weightError.value = "最小体重必须小于最大体重";
|
const hasMax = max !== undefined && max !== null && max !== "";
|
||||||
|
|
||||||
|
// 1. 如果只填了一个值,提示两个都必填
|
||||||
|
if ((hasMin && !hasMax) || (!hasMin && hasMax)) {
|
||||||
|
weightError.value = "最小体重和最大体重均需填写";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 如果两个值都有,才进行比对
|
||||||
|
if (hasMin && hasMax) {
|
||||||
|
// 允许相等,只禁止 min > max
|
||||||
|
if (Number(min) > Number(max)) {
|
||||||
|
weightError.value = "最小体重不能大于最大体重";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -308,12 +426,15 @@ const initForm = () => {
|
|||||||
if (props.initialValues) {
|
if (props.initialValues) {
|
||||||
// --- 编辑模式:回填数据 ---
|
// --- 编辑模式:回填数据 ---
|
||||||
const values = props.initialValues;
|
const values = props.initialValues;
|
||||||
|
|
||||||
// 处理特殊字段拆分
|
|
||||||
if (values.fwet) {
|
if (values.fwet) {
|
||||||
const weights = values.fwet.split("~");
|
const weights = values.fwet.split("~");
|
||||||
formData.weightMin = weights[0];
|
if (weights.length === 2) {
|
||||||
formData.weightMax = weights[1];
|
formData.weightMin = weights[0];
|
||||||
|
formData.weightMax = weights[1];
|
||||||
|
} else {
|
||||||
|
formData.weightMin = weights[0];
|
||||||
|
formData.weightMax = weights[0];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
formData.weightMin = undefined;
|
formData.weightMin = undefined;
|
||||||
formData.weightMax = undefined;
|
formData.weightMax = undefined;
|
||||||
@ -321,8 +442,13 @@ const initForm = () => {
|
|||||||
|
|
||||||
if (values.fsz) {
|
if (values.fsz) {
|
||||||
const sizes = values.fsz.split("~");
|
const sizes = values.fsz.split("~");
|
||||||
formData.bodyLengthMin = sizes[0];
|
if (sizes.length === 2) {
|
||||||
formData.bodyLengthMax = sizes[1];
|
formData.bodyLengthMin = sizes[0];
|
||||||
|
formData.bodyLengthMax = sizes[1];
|
||||||
|
} else {
|
||||||
|
formData.bodyLengthMin = sizes[0];
|
||||||
|
formData.bodyLengthMax = sizes[0];
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
formData.bodyLengthMin = undefined;
|
formData.bodyLengthMin = undefined;
|
||||||
formData.bodyLengthMax = undefined;
|
formData.bodyLengthMax = undefined;
|
||||||
@ -345,32 +471,24 @@ const initForm = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// --- 新增模式:重置表单 ---
|
|
||||||
resetForm();
|
resetForm();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 2. 修改 watch,只监听 visible 的变化
|
|
||||||
watch(
|
watch(
|
||||||
() => props.visible,
|
() => props.visible,
|
||||||
(newVisible) => {
|
(newVisible) => {
|
||||||
if (newVisible) {
|
if (newVisible) {
|
||||||
// 弹窗打开时,初始化数据
|
// 弹窗打开时,初始化数据
|
||||||
|
getBaseDropdownSelect();
|
||||||
|
getEngInfoDropdownSelect(formData.baseId);
|
||||||
|
getFpssDropdownSelect(formData.rstcd, formData.baseId);
|
||||||
initForm();
|
initForm();
|
||||||
} else {
|
|
||||||
// 弹窗关闭时,可以选择是否重置,或者留给下次打开时处理
|
|
||||||
// 通常建议在关闭时重置,或者在下次打开时根据 initialValues 判断
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: false } // 不需要 immediate,因为初始状态通常是 false
|
{ immediate: false } // 不需要 immediate,因为初始状态通常是 false
|
||||||
);
|
);
|
||||||
|
|
||||||
// 3. 删除原来的复杂 watch
|
|
||||||
// 删除这段代码:
|
|
||||||
// watch(
|
|
||||||
// () => [props.visible, props.initialValues],
|
|
||||||
// ...
|
|
||||||
// );
|
|
||||||
// 重置表单
|
// 重置表单
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
if (formRef.value) {
|
if (formRef.value) {
|
||||||
@ -378,8 +496,8 @@ const resetForm = () => {
|
|||||||
}
|
}
|
||||||
Object.assign(formData, defaultFormData);
|
Object.assign(formData, defaultFormData);
|
||||||
// 清空手动验证的错误信息
|
// 清空手动验证的错误信息
|
||||||
bodyLengthError.value = '';
|
bodyLengthError.value = "";
|
||||||
weightError.value = '';
|
weightError.value = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
// 取消操作
|
// 取消操作
|
||||||
@ -402,13 +520,41 @@ const handleOk = async () => {
|
|||||||
}
|
}
|
||||||
// 验证表单
|
// 验证表单
|
||||||
await formRef.value.validate();
|
await formRef.value.validate();
|
||||||
|
let fwet = "";
|
||||||
|
if (
|
||||||
|
formData.weightMin == formData.weightMax &&
|
||||||
|
formData.weightMin != undefined &&
|
||||||
|
formData.weightMax != undefined
|
||||||
|
) {
|
||||||
|
fwet = formData.weightMin;
|
||||||
|
} else if (formData.weightMin == undefined && formData.weightMax == undefined) {
|
||||||
|
fwet = "-";
|
||||||
|
} else {
|
||||||
|
fwet = formData.weightMin + "~" + formData.weightMax;
|
||||||
|
}
|
||||||
|
let fsz = "";
|
||||||
|
if (
|
||||||
|
formData.bodyLengthMin == formData.bodyLengthMax &&
|
||||||
|
formData.bodyLengthMin != undefined &&
|
||||||
|
formData.bodyLengthMax != undefined
|
||||||
|
) {
|
||||||
|
fsz = formData.bodyLengthMin;
|
||||||
|
} else if (
|
||||||
|
formData.bodyLengthMin == undefined &&
|
||||||
|
formData.bodyLengthMax == undefined
|
||||||
|
) {
|
||||||
|
fsz = "-";
|
||||||
|
} else {
|
||||||
|
fsz = formData.bodyLengthMin + "~" + formData.bodyLengthMax;
|
||||||
|
}
|
||||||
// 准备提交数据
|
// 准备提交数据
|
||||||
const submitValues = {
|
const submitValues = {
|
||||||
...formData,
|
...formData,
|
||||||
fwet: formData.weightMin + "~" + formData.weightMax,
|
fwet: fwet,
|
||||||
fsz: formData.bodyLengthMin + "~" + formData.bodyLengthMax,
|
fsz: fsz,
|
||||||
};
|
};
|
||||||
|
if (!formData.id) submitValues.tm = dayjs().format("YYYY-MM-DD HH:mm:ss");
|
||||||
|
console.log(submitValues);
|
||||||
emit("ok", submitValues);
|
emit("ok", submitValues);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Validate Failed:", error);
|
console.error("Validate Failed:", error);
|
||||||
@ -418,5 +564,4 @@ const handleOk = async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* 如有需要,添加局部样式 */
|
|
||||||
</style>
|
</style>
|
||||||
@ -0,0 +1,222 @@
|
|||||||
|
<template>
|
||||||
|
<div class="guoYuSheShiShuJuTianBao-search">
|
||||||
|
<BasicSearch
|
||||||
|
ref="basicSearchRef"
|
||||||
|
:searchList="searchList"
|
||||||
|
:initial-values="initSearchData"
|
||||||
|
@reset="handleReset"
|
||||||
|
@finish="onSearchFinish"
|
||||||
|
@values-change="onValuesChange"
|
||||||
|
>
|
||||||
|
<template #ftp="{ onChange }">
|
||||||
|
<fishSearch v-model="localTypeDate" width="280px" @update:modelValue="onChange" />
|
||||||
|
</template>
|
||||||
|
<template #actions>
|
||||||
|
<a-tooltip title="新增">
|
||||||
|
<a-button v-hasPerm="['sjtb:import-add']" @click="props.handleAdd">
|
||||||
|
新增
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip title="导入zip">
|
||||||
|
<a-button v-hasPerm="['sjtb:import-add']" @click="props.importBtn">
|
||||||
|
导入zip
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-button
|
||||||
|
v-hasPerm="['sjtb:import-add']"
|
||||||
|
@click="props.batchDelBtn"
|
||||||
|
:disabled="batchData.length === 0"
|
||||||
|
>
|
||||||
|
批量删除
|
||||||
|
</a-button>
|
||||||
|
<a-tooltip title="提交数据">
|
||||||
|
<a-button
|
||||||
|
v-hasPerm="['sjtb:import-add']"
|
||||||
|
@click="props.submitBtn"
|
||||||
|
:disabled="batchData.length === 0"
|
||||||
|
>
|
||||||
|
<template #icon><SaveOutlined /></template>
|
||||||
|
提交数据
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
|
||||||
|
<a-tooltip title="批量审批">
|
||||||
|
<a-button
|
||||||
|
v-hasPerm="['sjtb:edit-review']"
|
||||||
|
@click="props.successBtn"
|
||||||
|
:disabled="batchData.length === 0"
|
||||||
|
>
|
||||||
|
<template #icon><CheckSquareOutlined /></template>
|
||||||
|
批量审批
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip title="下载模板">
|
||||||
|
<a-button v-hasPerm="['sjtb:import-add']">
|
||||||
|
下载模板
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-tooltip placement="leftBottom">
|
||||||
|
<template #title>
|
||||||
|
<div>1.</div>
|
||||||
|
</template>
|
||||||
|
<a-button>
|
||||||
|
<template #icon><QuestionOutlined /></template>
|
||||||
|
</a-button>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
</BasicSearch>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, onMounted, watch } from "vue";
|
||||||
|
import {
|
||||||
|
SaveOutlined,
|
||||||
|
CheckSquareOutlined,
|
||||||
|
QuestionOutlined,
|
||||||
|
} from "@ant-design/icons-vue";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import BasicSearch from "@/components/BasicSearch/index.vue"; // 确保路径正确
|
||||||
|
import { DateSetting } from "@/utils/enumeration";
|
||||||
|
import { checkPerm } from "@/directive/permission";
|
||||||
|
import fishSearch from "@/components/fishSearch/index.vue";
|
||||||
|
import { useShuJuTianBaoStore } from "@/store/modules/shuJuTianBao";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
direction: any[];
|
||||||
|
guoyuStatus: any[];
|
||||||
|
importBtn: () => void;
|
||||||
|
batchDelBtn: () => void;
|
||||||
|
submitBtn: () => void;
|
||||||
|
successBtn: () => void;
|
||||||
|
batchData: any[];
|
||||||
|
handleAdd: () => void;
|
||||||
|
}
|
||||||
|
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 = {
|
||||||
|
baseId: "all",
|
||||||
|
stcd: null,
|
||||||
|
rstcd: null,
|
||||||
|
ftp: null,
|
||||||
|
status: null,
|
||||||
|
direction: null,
|
||||||
|
strdt: [
|
||||||
|
dayjs().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: "baseId",
|
||||||
|
label: "流域",
|
||||||
|
fieldProps: {
|
||||||
|
allowClear: true,
|
||||||
|
},
|
||||||
|
options: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "Select",
|
||||||
|
name: "stcd",
|
||||||
|
label: "过鱼设施",
|
||||||
|
values: { name: "stnm", value: "rstcd" },
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
// --- Methods ---
|
||||||
|
|
||||||
|
// 2. 搜索表单逻辑
|
||||||
|
const onSearchFinish = (values: any) => {
|
||||||
|
emit("searchFinish", values);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onValuesChange = (changedValues: any, allValues: any) => {
|
||||||
|
searchData.value = { ...searchData.value, ...allValues };
|
||||||
|
// 同步更新本地 searchData,以便其他逻辑使用
|
||||||
|
if (
|
||||||
|
Object.keys(changedValues)[0] == "rstcd" ||
|
||||||
|
Object.keys(changedValues)[0] == "baseId"
|
||||||
|
) {
|
||||||
|
shuJuTianBaoStore.getFpssOption(
|
||||||
|
allValues.baseId == "all" ? "" : allValues.baseId,
|
||||||
|
allValues.rstcd
|
||||||
|
);
|
||||||
|
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"></style>
|
||||||
@ -0,0 +1,552 @@
|
|||||||
|
<template>
|
||||||
|
<a-table
|
||||||
|
size="small"
|
||||||
|
:loading="fileLoading"
|
||||||
|
:data-source="fileTableData"
|
||||||
|
:columns="modalColumns"
|
||||||
|
:scroll="{ y: 500, x: '100%' }"
|
||||||
|
:pagination="false"
|
||||||
|
:row-key="(record, index) => index"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record, index }">
|
||||||
|
<!-- 1. 操作列 -->
|
||||||
|
<template v-if="column.key === 'action' || column.dataIndex === 'action'">
|
||||||
|
<div class="flex">
|
||||||
|
<template v-if="editingRowIndex === index">
|
||||||
|
<a-button type="link" size="small" @click="saveEdit(index)">保存</a-button>
|
||||||
|
<a-button type="link" size="small" @click="cancelEdit">取消</a-button>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<a-button type="link" size="small" @click="startEdit(index)">编辑</a-button>
|
||||||
|
<a-button type="link" danger size="small" @click="handlePreviewDelete(index)"
|
||||||
|
>删除</a-button
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 2. 警告提示列 (非编辑状态) -->
|
||||||
|
<template
|
||||||
|
v-else-if="
|
||||||
|
!isEditing(index) &&
|
||||||
|
column.dataIndexKey &&
|
||||||
|
record._warnings &&
|
||||||
|
record._warnings.includes(column.dataIndexKey)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div style="color: red; display: flex; align-items: center">
|
||||||
|
<span>{{ record[column.dataIndex] }}</span>
|
||||||
|
<exclamation-circle-outlined style="margin-left: 4px" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 3. 编辑状态下的单元格 (绑定到 editingData) -->
|
||||||
|
<template v-else-if="isEditing(index) && column.dataIndex != 'picpth' && column.dataIndex != 'vdpth'">
|
||||||
|
<template v-if="column.dataIndex === 'baseName'">
|
||||||
|
<a-select
|
||||||
|
v-model:value="editingData.baseId"
|
||||||
|
placeholder="请选择"
|
||||||
|
show-search
|
||||||
|
:filter-option="filterOption"
|
||||||
|
:loading="rowStates[index]?.baseLoading"
|
||||||
|
style="width: 100%"
|
||||||
|
@change="(val) => handleBaseChange(val, index)"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="opt in baseOptions"
|
||||||
|
:key="opt.baseid"
|
||||||
|
:value="opt.baseid"
|
||||||
|
:label="opt.basename"
|
||||||
|
>
|
||||||
|
{{ opt.basename }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 电站名称 -->
|
||||||
|
<template v-else-if="column.dataIndex === 'ennm'">
|
||||||
|
<a-select
|
||||||
|
v-model:value="editingData.rstcd"
|
||||||
|
placeholder="请选择"
|
||||||
|
show-search
|
||||||
|
:filter-option="filterOption"
|
||||||
|
:loading="rowStates[index]?.engLoading"
|
||||||
|
style="width: 100%"
|
||||||
|
:disabled="!editingData.baseId"
|
||||||
|
@change="(val) => handleEngChange(val, index)"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="opt in rowStates[index]?.engOptions || []"
|
||||||
|
:key="opt.stcd"
|
||||||
|
:value="opt.stcd"
|
||||||
|
:label="opt.ennm"
|
||||||
|
>
|
||||||
|
{{ opt.ennm }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 过鱼设施 -->
|
||||||
|
<template v-else-if="column.dataIndex === 'stnm'">
|
||||||
|
<a-select
|
||||||
|
v-model:value="editingData.stcd"
|
||||||
|
placeholder="请选择"
|
||||||
|
show-search
|
||||||
|
:filter-option="filterOption"
|
||||||
|
:loading="rowStates[index]?.fpssLoading"
|
||||||
|
style="width: 100%"
|
||||||
|
:disabled="!editingData.rstcd"
|
||||||
|
@change="(val) => handleFpssChange(val, index)"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="opt in rowStates[index]?.fpssOptions || []"
|
||||||
|
:key="opt.stcd"
|
||||||
|
:value="opt.stcd"
|
||||||
|
:label="opt.stnm"
|
||||||
|
>
|
||||||
|
{{ opt.stnm }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 过鱼时间 -->
|
||||||
|
<template v-else-if="column.dataIndex === 'strdt'">
|
||||||
|
<a-date-picker
|
||||||
|
v-model:value="editingData.strdt"
|
||||||
|
show-time
|
||||||
|
style="width: 100%"
|
||||||
|
format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 鱼种类 -->
|
||||||
|
<template v-else-if="column.dataIndex === 'ftpName'">
|
||||||
|
<fishSearch
|
||||||
|
v-model="editingData.ftp"
|
||||||
|
style="width: 100%"
|
||||||
|
@update:modelValue="handleFtpChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 游向 -->
|
||||||
|
<template v-else-if="column.dataIndex === 'direction'">
|
||||||
|
<a-select
|
||||||
|
v-model:value="editingData.direction"
|
||||||
|
placeholder="请选择"
|
||||||
|
style="width: 100%"
|
||||||
|
>
|
||||||
|
<a-select-option
|
||||||
|
v-for="item in direction"
|
||||||
|
:key="item.itemCode"
|
||||||
|
:value="item.itemCode"
|
||||||
|
>
|
||||||
|
{{ item.dictName }}
|
||||||
|
</a-select-option>
|
||||||
|
</a-select>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 数字输入框 -->
|
||||||
|
<template v-else-if="['fcnt', 'wt'].includes(column.dataIndex)">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="editingData[column.dataIndex]"
|
||||||
|
style="width: 100%"
|
||||||
|
:min="0"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 是否鱼苗 -->
|
||||||
|
<template v-else-if="column.dataIndex === 'isfs'">
|
||||||
|
<a-radio-group v-model:value="editingData.isfs">
|
||||||
|
<a-radio :value="1">是</a-radio>
|
||||||
|
<a-radio :value="0">否</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 体长 (编辑态使用 Min/Max) -->
|
||||||
|
<template v-else-if="column.dataIndex === 'fsz'">
|
||||||
|
<div class="flex">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="editingData.bodyLengthMin"
|
||||||
|
style="width: 50%"
|
||||||
|
:min="0"
|
||||||
|
/>
|
||||||
|
<span class="px-[2px]">~</span>
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="editingData.bodyLengthMax"
|
||||||
|
style="width: 50%"
|
||||||
|
:min="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 体重 (编辑态使用 Min/Max) -->
|
||||||
|
<template v-else-if="column.dataIndex === 'fwet'">
|
||||||
|
<div class="flex align-center">
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="editingData.weightMin"
|
||||||
|
style="width: 50%"
|
||||||
|
:min="0"
|
||||||
|
/>
|
||||||
|
<span class="px-[2px]">~</span>
|
||||||
|
<a-input-number
|
||||||
|
v-model:value="editingData.weightMax"
|
||||||
|
style="width: 50%"
|
||||||
|
:min="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template
|
||||||
|
v-else-if="column.dataIndex === 'picpth'"
|
||||||
|
>
|
||||||
|
<div class="preview" v-for="(item, index) in record.picpthList">
|
||||||
|
<div class="text" :class="{'text_warning': record.picpthsWarnings.includes(item.name)}" @click="emit('preview-click', record, 'image' ,index)">
|
||||||
|
{{ item.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="record.picpthList.length==0">暂无图片</div>
|
||||||
|
</template>
|
||||||
|
<template
|
||||||
|
v-else-if="column.dataIndex === 'vdpth'"
|
||||||
|
>
|
||||||
|
<div class="preview" v-for="(item, index) in record.vdpthList">
|
||||||
|
<div class="text" :class="{'text_warning': record.vdpthsWarnings.includes(item.name)}" @click="emit('preview-click', record, 'vdpth' ,index)">
|
||||||
|
{{ item.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="record.vdpthList.length==0">暂无视频</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</a-table>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted, h } from "vue";
|
||||||
|
import { message, Tag } from "ant-design-vue";
|
||||||
|
import { ExclamationCircleOutlined } from "@ant-design/icons-vue";
|
||||||
|
import fishSearch from "@/components/fishSearch/index.vue";
|
||||||
|
import { getBaseDropdown, getEngInfoDropdown, getFpssDropdown } from "@/api/select";
|
||||||
|
import { CloseCircleOutlined } from "@ant-design/icons-vue";
|
||||||
|
import { es } from "element-plus/es/locale/index.mjs";
|
||||||
|
|
||||||
|
const props: any = defineProps({
|
||||||
|
fileTableData: { type: Array, default: () => [] },
|
||||||
|
fileLoading: { type: Boolean, default: false },
|
||||||
|
direction: { type: Array, default: () => [] },
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(["update:fileTableData", "preview-click"]);
|
||||||
|
|
||||||
|
// --- 状态管理 ---
|
||||||
|
const editingRowIndex = ref<number | null>(null);
|
||||||
|
const baseOptions = ref<any[]>([]);
|
||||||
|
const rowStates = reactive<Record<number, any>>({});
|
||||||
|
|
||||||
|
// 【核心】临时编辑数据,只在编辑模式下使用
|
||||||
|
const editingData = ref<any>(null);
|
||||||
|
|
||||||
|
const modalColumns = ref([
|
||||||
|
{
|
||||||
|
dataIndex: "baseName",
|
||||||
|
key: "baseName",
|
||||||
|
dataIndexKey: "baseId",
|
||||||
|
title: "流域",
|
||||||
|
width: 140,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: "ennm",
|
||||||
|
key: "ennm",
|
||||||
|
dataIndexKey: "rstcd",
|
||||||
|
title: "电站名称",
|
||||||
|
width: 140,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dataIndex: "stnm",
|
||||||
|
key: "stnm",
|
||||||
|
dataIndexKey: "stcd",
|
||||||
|
title: "过鱼设施名称",
|
||||||
|
width: 150,
|
||||||
|
},
|
||||||
|
{ dataIndex: "strdt", key: "strdt", title: "过鱼时间", width: 190 },
|
||||||
|
{ dataIndex: "ftpName", key: "ftpName", title: "鱼种类", width: 120 },
|
||||||
|
{
|
||||||
|
dataIndex: "isfs",
|
||||||
|
key: "isfs",
|
||||||
|
title: "是否鱼苗",
|
||||||
|
width: 130,
|
||||||
|
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: 120,
|
||||||
|
customRender: ({ text }: any) => props.direction.find((item: any) => item.itemCode === text)?.dictName || "-"
|
||||||
|
,
|
||||||
|
},
|
||||||
|
{ dataIndex: "fcnt", key: "fcnt", title: "过鱼数量(尾)", width: 120 },
|
||||||
|
{ dataIndex: "fsz", key: "fsz", title: "体长(cm)", width: 160 },
|
||||||
|
{ dataIndex: "fwet", key: "fwet", title: "平均体重(g)", width: 160 },
|
||||||
|
{ dataIndex: "wt", key: "wt", title: "水温(℃)", width: 80 },
|
||||||
|
{ dataIndex: "picpth", key: "picpth", title: "图片", width: 160 },
|
||||||
|
{ dataIndex: "vdpth", key: "vdpth", title: "视频", width: 160 },
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
key: "action",
|
||||||
|
dataIndex: "action",
|
||||||
|
fixed: "right",
|
||||||
|
width: 100,
|
||||||
|
align: "center",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// --- 初始化 ---
|
||||||
|
onMounted(() => {
|
||||||
|
loadBaseOptions();
|
||||||
|
});
|
||||||
|
|
||||||
|
const loadBaseOptions = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getBaseDropdown({});
|
||||||
|
baseOptions.value = res.data || [];
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Load base options failed", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ensureRowState = (index: number) => {
|
||||||
|
if (!rowStates[index]) {
|
||||||
|
rowStates[index] = {
|
||||||
|
engOptions: [],
|
||||||
|
fpssOptions: [],
|
||||||
|
baseLoading: false,
|
||||||
|
engLoading: false,
|
||||||
|
fpssLoading: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return rowStates[index];
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- 级联逻辑 (操作 editingData) ---
|
||||||
|
|
||||||
|
const handleBaseChange = async (baseId: string, index: number) => {
|
||||||
|
console.log(baseId);
|
||||||
|
editingData.value.baseName = baseOptions.value.find(
|
||||||
|
(item: any) => item.baseid == baseId
|
||||||
|
)?.basename;
|
||||||
|
if (baseId && editingData.value._warnings) {
|
||||||
|
editingData.value._warnings = editingData.value._warnings.filter(
|
||||||
|
(w: string) => w !== "baseName"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const state = ensureRowState(index);
|
||||||
|
// 清空后续字段
|
||||||
|
editingData.value.rstcd = undefined;
|
||||||
|
editingData.value.stcd = undefined;
|
||||||
|
state.engOptions = [];
|
||||||
|
state.fpssOptions = [];
|
||||||
|
|
||||||
|
if (!baseId) return;
|
||||||
|
state.engLoading = true;
|
||||||
|
try {
|
||||||
|
const res = await getEngInfoDropdown({ baseId });
|
||||||
|
state.engOptions = res.data || [];
|
||||||
|
} catch (e) {
|
||||||
|
message.error("获取电站列表失败");
|
||||||
|
} finally {
|
||||||
|
state.engLoading = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEngChange = async (rstcd: string, index: number) => {
|
||||||
|
const state = ensureRowState(index);
|
||||||
|
if (rstcd && editingData.value._warnings) {
|
||||||
|
editingData.value._warnings = editingData.value._warnings.filter(
|
||||||
|
(w: string) => w !== "ennm"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
editingData.value.ennm = state.engOptions.find(
|
||||||
|
(item: any) => item.stcd === rstcd
|
||||||
|
)?.ennm;
|
||||||
|
editingData.value.stcd = undefined;
|
||||||
|
state.fpssOptions = [];
|
||||||
|
|
||||||
|
if (!rstcd || !editingData.value.baseId) return;
|
||||||
|
state.fpssLoading = true;
|
||||||
|
try {
|
||||||
|
const res = await getFpssDropdown({ rstcd, baseId: editingData.value.baseId });
|
||||||
|
state.fpssOptions = res.data || [];
|
||||||
|
} catch (e) {
|
||||||
|
message.error("获取设施列表失败");
|
||||||
|
} finally {
|
||||||
|
state.fpssLoading = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const handleFpssChange = (stcd: string, index: number) => {
|
||||||
|
const state = ensureRowState(index);
|
||||||
|
if (stcd && editingData.value._warnings) {
|
||||||
|
editingData.value._warnings = editingData.value._warnings.filter(
|
||||||
|
(w: string) => w !== "stnm"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
editingData.value.stnm = state.fpssOptions.find(
|
||||||
|
(item: any) => item.stcd === stcd
|
||||||
|
)?.stnm;
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- 编辑控制 ---
|
||||||
|
|
||||||
|
const isEditing = (index: number) => editingRowIndex.value === index;
|
||||||
|
const startEdit = (index: number) => {
|
||||||
|
const originalRecord = props.fileTableData[index];
|
||||||
|
|
||||||
|
// 1. 深拷贝原始数据到临时编辑区
|
||||||
|
editingData.value = JSON.parse(JSON.stringify(originalRecord));
|
||||||
|
|
||||||
|
// 2. 预处理:将 fsz/fwet 字符串拆分为 Min/Max 供输入框使用
|
||||||
|
processStringToMinMax(editingData.value);
|
||||||
|
|
||||||
|
editingRowIndex.value = index;
|
||||||
|
|
||||||
|
// 3. 预加载下拉选项 (基于 editingData 的值)
|
||||||
|
if (editingData.value.baseId && !editingData.value.rstcd) {
|
||||||
|
handleBaseChange(editingData.value.baseId, index);
|
||||||
|
} else if (editingData.value.baseId && editingData.value.rstcd) {
|
||||||
|
handleBaseChange(editingData.value.baseId, index).then(() => {
|
||||||
|
handleEngChange(editingData.value.rstcd, index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 辅助:字符串转 Min/Max
|
||||||
|
const processStringToMinMax = (data: any) => {
|
||||||
|
if (data.fsz) {
|
||||||
|
const sizes = String(data.fsz).split("~");
|
||||||
|
data.bodyLengthMin = sizes[0] || "";
|
||||||
|
data.bodyLengthMax = sizes[1] || sizes[0] || "";
|
||||||
|
} else {
|
||||||
|
data.bodyLengthMin = "";
|
||||||
|
data.bodyLengthMax = "";
|
||||||
|
}
|
||||||
|
if (data.fwet) {
|
||||||
|
const weights = String(data.fwet).split("~");
|
||||||
|
data.weightMin = weights[0] || "";
|
||||||
|
data.weightMax = weights[1] || weights[0] || "";
|
||||||
|
} else {
|
||||||
|
data.weightMin = "";
|
||||||
|
data.weightMax = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 辅助:Min/Max 转字符串
|
||||||
|
const processMinMaxToString = (data: any) => {
|
||||||
|
// 处理体长
|
||||||
|
if (data.bodyLengthMin !== "" || data.bodyLengthMax !== "") {
|
||||||
|
if (data.bodyLengthMin == data.bodyLengthMax) {
|
||||||
|
data.fsz = data.bodyLengthMin;
|
||||||
|
} else {
|
||||||
|
data.fsz = `${data.bodyLengthMin}~${data.bodyLengthMax}`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data.fsz = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理体重
|
||||||
|
if (data.weightMin !== "" || data.weightMax !== "") {
|
||||||
|
if (data.weightMin == data.weightMax) {
|
||||||
|
data.fwet = data.weightMin;
|
||||||
|
} else {
|
||||||
|
data.fwet = `${data.weightMin}~${data.weightMax}`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data.fwet = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理临时字段,避免污染原数据
|
||||||
|
delete data.bodyLengthMin;
|
||||||
|
delete data.bodyLengthMax;
|
||||||
|
delete data.weightMin;
|
||||||
|
delete data.weightMax;
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveEdit = (index: number) => {
|
||||||
|
// 1. 后处理:将 Min/Max 合并回 fsz/fwet
|
||||||
|
processMinMaxToString(editingData.value);
|
||||||
|
|
||||||
|
// 2. 创建新数组,替换对应索引的数据
|
||||||
|
const newData = [...props.fileTableData];
|
||||||
|
newData[index] = { ...editingData.value };
|
||||||
|
|
||||||
|
// 3. 通知父组件更新
|
||||||
|
emit("update:fileTableData", newData);
|
||||||
|
|
||||||
|
// 4. 重置状态
|
||||||
|
editingRowIndex.value = null;
|
||||||
|
editingData.value = null;
|
||||||
|
message.success("保存成功");
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelEdit = () => {
|
||||||
|
editingRowIndex.value = null;
|
||||||
|
editingData.value = null;
|
||||||
|
// 由于我们从未修改过 props.fileTableData,所以不需要恢复操作,直接退出即可
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePreviewDelete = (index: number) => {
|
||||||
|
const newData = [...props.fileTableData];
|
||||||
|
newData.splice(index, 1);
|
||||||
|
emit("update:fileTableData", newData);
|
||||||
|
|
||||||
|
if (editingRowIndex.value === index) {
|
||||||
|
editingRowIndex.value = null;
|
||||||
|
editingData.value = null;
|
||||||
|
} else if (editingRowIndex.value !== null && editingRowIndex.value > index) {
|
||||||
|
editingRowIndex.value--;
|
||||||
|
}
|
||||||
|
message.success("删除成功");
|
||||||
|
};
|
||||||
|
// 鱼种类编辑 修改名称
|
||||||
|
const handleFtpChange = (val: any, opt: any) => {
|
||||||
|
editingData.value.ftpName = opt.name;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// --- 辅助函数 ---
|
||||||
|
const filterOption = (input: string, option: any) => {
|
||||||
|
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||||
|
};
|
||||||
|
defineExpose({
|
||||||
|
editingRowIndex,
|
||||||
|
editingData,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.preview {
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #1890ff;
|
||||||
|
&:hover {
|
||||||
|
color: #40a9ff;
|
||||||
|
}
|
||||||
|
display: flex;
|
||||||
|
justify-content: justify-between;
|
||||||
|
align-items: center;
|
||||||
|
.text {
|
||||||
|
width: 120px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.text_warning {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
1060
frontend/src/views/shuJuTianBao/guoYuSheShiShuJuTianBao/index.vue
Normal file
1060
frontend/src/views/shuJuTianBao/guoYuSheShiShuJuTianBao/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,472 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="guoYuSheShiShuJuTianBao-search">
|
|
||||||
<!-- 隐藏的文件输入框 -->
|
|
||||||
<input
|
|
||||||
ref="fileInputRef"
|
|
||||||
type="file"
|
|
||||||
accept=".zip,application/zip,application/x-zip-compressed"
|
|
||||||
style="display: none"
|
|
||||||
@change="handleFileSelect"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<BasicSearch
|
|
||||||
ref="basicSearchRef"
|
|
||||||
:searchList="searchList"
|
|
||||||
:initial-values="initSearchData"
|
|
||||||
@finish="onSearchFinish"
|
|
||||||
@values-change="onValuesChange"
|
|
||||||
>
|
|
||||||
<template #typeDate="{ onChange }">
|
|
||||||
<fishSearch
|
|
||||||
v-model="localTypeDate"
|
|
||||||
width="280px"
|
|
||||||
:options="options"
|
|
||||||
@update:modelValue="onChange"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<!-- 自定义重置及操作按钮区域 -->
|
|
||||||
<template #actions>
|
|
||||||
<a-tooltip title="新增">
|
|
||||||
<a-button @click="props.handleAdd"> 新增 </a-button>
|
|
||||||
</a-tooltip>
|
|
||||||
|
|
||||||
<a-tooltip title="导入zip">
|
|
||||||
<a-button v-hasPerm="['sjtb:import-zip']" @click="triggerFileInput">
|
|
||||||
导入zip
|
|
||||||
</a-button>
|
|
||||||
</a-tooltip>
|
|
||||||
<a-button @click="props.batchDel" :disabled="batchData.length === 0">
|
|
||||||
批量删除
|
|
||||||
</a-button>
|
|
||||||
<a-tooltip title="提交数据">
|
|
||||||
<a-button @click="props.saveBtn">
|
|
||||||
<template #icon><SaveOutlined /></template>
|
|
||||||
提交数据
|
|
||||||
</a-button>
|
|
||||||
</a-tooltip>
|
|
||||||
|
|
||||||
<a-tooltip title="批量审批">
|
|
||||||
<a-button @click="props.saveBtn">
|
|
||||||
<template #icon><CheckSquareOutlined /></template>
|
|
||||||
批量审批
|
|
||||||
</a-button>
|
|
||||||
</a-tooltip>
|
|
||||||
<a-tooltip placement="leftBottom">
|
|
||||||
<template #title>
|
|
||||||
<div>1.</div>
|
|
||||||
</template>
|
|
||||||
<a-button>
|
|
||||||
<template #icon><QuestionOutlined /></template>
|
|
||||||
</a-button>
|
|
||||||
</a-tooltip>
|
|
||||||
</template>
|
|
||||||
</BasicSearch>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { ref, computed, onMounted, watch } from "vue";
|
|
||||||
import { message } from "ant-design-vue";
|
|
||||||
import {
|
|
||||||
SaveOutlined,
|
|
||||||
CheckSquareOutlined,
|
|
||||||
QuestionOutlined,
|
|
||||||
} from "@ant-design/icons-vue";
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
import BasicSearch from "@/components/BasicSearch/index.vue"; // 确保路径正确
|
|
||||||
import { DateSetting } from "@/utils/enumeration";
|
|
||||||
import { checkPerm } from "@/directive/permission";
|
|
||||||
import fishSearch from "@/components/fishSearch/index.vue";
|
|
||||||
|
|
||||||
// --- Props & Emits ---
|
|
||||||
interface Props {
|
|
||||||
importBtn: (file: File) => void;
|
|
||||||
batchDel: () => void;
|
|
||||||
saveBtn: () => void;
|
|
||||||
batchData: any[];
|
|
||||||
handleAdd: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(e: "searchFinish", values: any, label: string): void;
|
|
||||||
}>();
|
|
||||||
const localTypeDate = ref<string[]>([]);
|
|
||||||
// --- State ---
|
|
||||||
const fileInputRef = ref<HTMLInputElement>();
|
|
||||||
const options = ref<any>([
|
|
||||||
{
|
|
||||||
_tls: {},
|
|
||||||
id: "00DDF2A72147B2115384F64DDFE26A5E",
|
|
||||||
recordUser: null,
|
|
||||||
recordTime: null,
|
|
||||||
modifyTime: null,
|
|
||||||
displayRecordUser: null,
|
|
||||||
departmentId: null,
|
|
||||||
displayDepartment: null,
|
|
||||||
index: 1,
|
|
||||||
name: "异唇裂腹鱼",
|
|
||||||
code: null,
|
|
||||||
nameEn: null,
|
|
||||||
alias: null,
|
|
||||||
description: null,
|
|
||||||
logo: null,
|
|
||||||
introduce: null,
|
|
||||||
inffile: null,
|
|
||||||
genus: null,
|
|
||||||
family: null,
|
|
||||||
species: null,
|
|
||||||
fsz: null,
|
|
||||||
type: 1,
|
|
||||||
typeName: "淡水",
|
|
||||||
rare: null,
|
|
||||||
specOrigin: null,
|
|
||||||
specOriginName: null,
|
|
||||||
ptype: null,
|
|
||||||
ptypeName: null,
|
|
||||||
rvcd: "null",
|
|
||||||
rvcdName: "",
|
|
||||||
zyFishId: "00DDF2A72147B2115384F64DDFE26A5E",
|
|
||||||
habitMigrat: null,
|
|
||||||
feedingHabit: null,
|
|
||||||
spawnCharact: null,
|
|
||||||
spawnMonth: null,
|
|
||||||
food: null,
|
|
||||||
timeFeed: null,
|
|
||||||
orignDate: null,
|
|
||||||
pretemp: null,
|
|
||||||
flowRate: null,
|
|
||||||
depth: null,
|
|
||||||
botmMater: null,
|
|
||||||
wqtq: null,
|
|
||||||
habitat: null,
|
|
||||||
situation: null,
|
|
||||||
resourceType: null,
|
|
||||||
shapedesc: null,
|
|
||||||
protectlvl: null,
|
|
||||||
habitation: null,
|
|
||||||
fid: null,
|
|
||||||
enable: null,
|
|
||||||
internal: null,
|
|
||||||
orderIndex: null,
|
|
||||||
filterContent: null,
|
|
||||||
platformId: null,
|
|
||||||
isTempStorage: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
_tls: {},
|
|
||||||
id: "0249006974f34c288d6cb4df54e3b19d",
|
|
||||||
recordUser: null,
|
|
||||||
recordTime: null,
|
|
||||||
modifyTime: null,
|
|
||||||
displayRecordUser: null,
|
|
||||||
departmentId: null,
|
|
||||||
displayDepartment: null,
|
|
||||||
index: 2,
|
|
||||||
name: "匙吻鲟",
|
|
||||||
code: null,
|
|
||||||
nameEn: "Polyodon spathula",
|
|
||||||
alias: "美国匙吻鲟、鸭嘴鲟",
|
|
||||||
description:
|
|
||||||
"匙吻鲟(Polyodonspathula)亦称匙吻猫鱼(spoonbillcat)。产于北美洲的原始鱼,为桨吻鲟(paddlefish)的一种。属鲟形目、匙吻鲟科,是北美洲的一种名贵大型淡水经济鱼类。匙吻鲟的显著特点是吻呈扁平桨状,特别长。鱼的体表光滑无鳞,背部黑蓝灰色,有一些斑点在其间,体侧有点状赭色,腹部白色。个体大,这种大型淡水鱼可以长到220厘米,重达90公斤以上。",
|
|
||||||
logo: "20240527221754634033127655455265",
|
|
||||||
introduce: null,
|
|
||||||
inffile:
|
|
||||||
"20240527221811830658320352201158,20240527221805865127213075311524,20240527221822527347221377607671,20240527221828072460253583084314,20240527221800311481326028334838,20240527221817630761245563388673",
|
|
||||||
genus: "匙吻鲟属",
|
|
||||||
family: "匙吻鲟科",
|
|
||||||
species: "匙吻鲟",
|
|
||||||
fsz: "85~220",
|
|
||||||
type: 1,
|
|
||||||
typeName: "淡水",
|
|
||||||
rare: null,
|
|
||||||
specOrigin: 2,
|
|
||||||
specOriginName: "外来鱼类",
|
|
||||||
ptype: 4,
|
|
||||||
ptypeName: "易危",
|
|
||||||
rvcd: "SJLY148",
|
|
||||||
rvcdName: "大渡河",
|
|
||||||
zyFishId: "0249006974f34c288d6cb4df54e3b19d",
|
|
||||||
habitMigrat: "繁殖洄游",
|
|
||||||
feedingHabit: "肉食性",
|
|
||||||
spawnCharact: "粘性卵类型",
|
|
||||||
spawnMonth: "4-5",
|
|
||||||
food: "主要以浮游动物,也以甲壳类和双壳类生物为食",
|
|
||||||
timeFeed: "夜间觅食",
|
|
||||||
orignDate:
|
|
||||||
"匙吻鲟在美国密西西比河流域的22个洲均有发现。包括密苏里河到蒙大拿州,俄亥俄河,和它的主要支流流域。雄鱼在7~9龄达到性成熟,雌鱼晚一年,相对怀卵量约为每克体重3.5粒。匙吻鲟多在4~5月繁殖,适宜水温为16~18℃,繁殖期会游到江河上游产卵,受精卵灰黑色,直径2~2.5毫米,有黏性,往往粘在砾石上孵化,孵化期6~7天。",
|
|
||||||
pretemp: "0~37℃",
|
|
||||||
flowRate: "0.3m/s",
|
|
||||||
depth: "2~2.5",
|
|
||||||
botmMater: "泥质",
|
|
||||||
wqtq: "适宜的pH范围为6.5~8,对溶解氧要求较高,应在5毫克/升以上。",
|
|
||||||
habitat: null,
|
|
||||||
situation: null,
|
|
||||||
resourceType: null,
|
|
||||||
shapedesc:
|
|
||||||
"匙吻鲟有一个形如匙柄的长吻,长约为体长的三分之一。身体流线型,体表光滑无鳞。眼小,口较大,位于吻末端的腹面,不能伸缩;上颌背面具有粗糙的颗粒感觉器。鳃盖骨大而向后延伸,鳃盖膜长达胸鳍至腹鳍的1/2处。头部有一喷水孔和喷水腔。胸鳍较小,下位;腹鳍腹位,背鳍起点在腹鳍之后。尾鳍分叉,歪尾型,上叶长于下叶,尾柄披有梗栉状的甲鳞。背部黑蓝灰色,常有一些斑点间于其中,两侧逐渐变浅,体侧有点状褐色,腹部白色。",
|
|
||||||
protectlvl: null,
|
|
||||||
habitation: "缓流型;广温性;中上层水域",
|
|
||||||
fid: null,
|
|
||||||
enable: null,
|
|
||||||
internal: null,
|
|
||||||
orderIndex: null,
|
|
||||||
filterContent: null,
|
|
||||||
platformId: null,
|
|
||||||
isTempStorage: null,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
_tls: {},
|
|
||||||
id: "02A23B169BF240589B2C37C5E81A8DC2",
|
|
||||||
recordUser: null,
|
|
||||||
recordTime: null,
|
|
||||||
modifyTime: null,
|
|
||||||
displayRecordUser: null,
|
|
||||||
departmentId: null,
|
|
||||||
displayDepartment: null,
|
|
||||||
index: 3,
|
|
||||||
name: "南方马口鱼",
|
|
||||||
code: null,
|
|
||||||
nameEn: "Chinese hooksnout carp",
|
|
||||||
alias:
|
|
||||||
"午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公午仔鱼、山涟仔、桃花鱼、山鳡、坑爬、宽口、大口扒、扯口婆、红车公",
|
|
||||||
description:
|
|
||||||
"南方马口鱼,Opsariichthys uncirostris bidens (Gunther,1873),是鲤科马口鱼属的一种生活的溪流中的小型鱼类。体长,稍侧扁,腹部圆。头稍尖,头长大于体高。吻钝,吻长远比其宽为大。口特大,下颌前端突起,两侧面各有一凹陷,恰与上颌突出部分吻合。下咽齿3行。鳞圆形,背鳍条2,7,无硬刺。臀鳍条3,8-10。背部黑灰色,体侧下半部及腹面银白色,喉部、口唇及各鳍橙黄,背鳍上有黑色的小斑点,眼上部有一红色斑点,体两侧具有浅蓝色的垂直条纹。生殖季节时,雄鱼体色更为鲜艳。",
|
|
||||||
logo: "20240527192500111683624865306342",
|
|
||||||
introduce: null,
|
|
||||||
inffile:
|
|
||||||
"20240527192505300717052825727341,20240527192533035616525871580354,20240527192510217883087850433201,20240527192516128514164206355182,20240527192522835236402141341053,20240527192527583177528213025212",
|
|
||||||
genus: "马口鱼属",
|
|
||||||
family: "鲤科",
|
|
||||||
species: "南方马口鱼",
|
|
||||||
fsz: "7~20",
|
|
||||||
type: 1,
|
|
||||||
typeName: "淡水",
|
|
||||||
rare: null,
|
|
||||||
specOrigin: 1,
|
|
||||||
specOriginName: "本土",
|
|
||||||
ptype: 4,
|
|
||||||
ptypeName: "易危",
|
|
||||||
rvcd: "null",
|
|
||||||
rvcdName: "",
|
|
||||||
zyFishId: "02A23B169BF240589B2C37C5E81A8DC2",
|
|
||||||
habitMigrat: "定居型",
|
|
||||||
feedingHabit: "肉食性",
|
|
||||||
spawnCharact: "沉性卵类型",
|
|
||||||
spawnMonth: "6-8",
|
|
||||||
food: "摄食小型鱼类和水生昆虫。",
|
|
||||||
timeFeed: "白天觅食",
|
|
||||||
orignDate:
|
|
||||||
"产卵期在6~8月份。第一年生长较迅速,可达7~11厘米。1龄鱼即有繁殖能力,系小型鱼类。",
|
|
||||||
pretemp: "0~30℃",
|
|
||||||
flowRate: "0.3m/s",
|
|
||||||
depth: "1~1.5",
|
|
||||||
botmMater: "砂砾底质",
|
|
||||||
wqtq: "pH在7.2~7.8之内,凉爽清洁、溶氧丰富的水质",
|
|
||||||
habitat: null,
|
|
||||||
situation: null,
|
|
||||||
resourceType: null,
|
|
||||||
shapedesc:
|
|
||||||
"背鳍条3,7;臀鳍条3,9;侧线鳞45~47;下咽齿3行,1·4·5-4·4·1。鳃耙外侧10,脊椎骨35。体长为体高的3.1~4.3倍,为头长的3.5~3.9倍,为尾柄长的4.7~5.2倍,为尾柄高的10.2~11.3倍。头长为吻长的2.7~3.2倍,为眼径的5.0~6.2倍,为眼间距的3.1~3.3倍。体延长,侧扁。吻长,其长略大于宽,口大,端位,口裂向上倾斜,下颌后端延长达眼前缘,其前缘凸起,两侧凹陷,恰与上颌前端和两侧嵌合。眼中等大,位于头侧上方。鳃耙短小而稀疏。下咽齿圆柱性,顶端尖而长。侧线完全,前端弯向体侧腹方,后端向上延至尾柄正中。背鳍无硬刺,其起点至吻端稍大于至尾鳍基部的距离,胸鳍不达腹鳍,其末端可达胸、腹鳍间距的3/5处。腹鳍外缘略钝圆,起点约与背鳍不分支鳍条相对。鳔2视,后室约为前室的2倍,腹腔膜银白色。体背部灰黑色,腹部银白色,体侧有浅蓝色的垂直条纹,胸鳍、腹鳍和臀鳍为橙黄色。雄鱼在生殖期出现婚装,头部、吻部和臀部有显眼的珠星,臀鳍的第1~4根分支鳍条特别延长,全身具有很鲜艳的婚姻色。",
|
|
||||||
protectlvl: null,
|
|
||||||
habitation: "流水型;冷水性;中上水层",
|
|
||||||
fid: null,
|
|
||||||
enable: null,
|
|
||||||
internal: null,
|
|
||||||
orderIndex: null,
|
|
||||||
filterContent: null,
|
|
||||||
platformId: null,
|
|
||||||
isTempStorage: null,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 模拟 initSearchData
|
|
||||||
const initSearchData = {
|
|
||||||
dmStcd: "008660306300000079", // 默认水电站ID,实际可能需要动态获取
|
|
||||||
stcd: {
|
|
||||||
dataDimensionData: "all",
|
|
||||||
dataDimensionType: "hyBase",
|
|
||||||
hbrvcd: "",
|
|
||||||
stcdId: "",
|
|
||||||
},
|
|
||||||
mway: "1",
|
|
||||||
typeDate: [],
|
|
||||||
strdt: [
|
|
||||||
dayjs().startOf("month").format("YYYY-MM-DD"),
|
|
||||||
dayjs().endOf("day").format("YYYY-MM-DD"),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const searchData = ref<any>({ ...initSearchData });
|
|
||||||
|
|
||||||
// --- Search List Configuration ---
|
|
||||||
// 电站(下拉框)、STCD:过鱼设施编码(下拉框)、STRDT:开始时间、ENDDT:结束时间、鱼名称(字符串模糊查询)、DIRECTION:游向(0:上行、1:下行、2:上下行)
|
|
||||||
const searchList: any = computed(() => [
|
|
||||||
{
|
|
||||||
type: "waterStation",
|
|
||||||
name: "engName",
|
|
||||||
label: "水电基地",
|
|
||||||
fieldProps: {
|
|
||||||
allowClear: true,
|
|
||||||
},
|
|
||||||
options: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "Select",
|
|
||||||
name: "fpname",
|
|
||||||
label: "过鱼设施",
|
|
||||||
fieldProps: {
|
|
||||||
allowClear: true,
|
|
||||||
},
|
|
||||||
options: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "Select",
|
|
||||||
name: "direction",
|
|
||||||
label: "游向",
|
|
||||||
width: 120,
|
|
||||||
options: [
|
|
||||||
{ label: "上行", value: "上行" },
|
|
||||||
{ label: "下行", value: "下行" },
|
|
||||||
{ label: "上行折返", value: "上行折返" },
|
|
||||||
{ label: "下行折返", value: "下行折返" },
|
|
||||||
],
|
|
||||||
fieldProps: {
|
|
||||||
allowClear: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "custom",
|
|
||||||
name: "ftp",
|
|
||||||
label: "鱼种类",
|
|
||||||
fieldProps: {
|
|
||||||
allowClear: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
checkPerm(["sjtb:edit-ztcx"]) && {
|
|
||||||
width: 120,
|
|
||||||
type: "Select",
|
|
||||||
name: "status",
|
|
||||||
label: "审批状态",
|
|
||||||
fieldProps: {
|
|
||||||
allowClear: true,
|
|
||||||
},
|
|
||||||
options: [
|
|
||||||
{ label: "正常", value: "01" },
|
|
||||||
{ label: "异常", value: "02" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
span: 12,
|
|
||||||
type: "RangePicker",
|
|
||||||
name: "strdt",
|
|
||||||
label: "过鱼时间",
|
|
||||||
picker: "date",
|
|
||||||
fieldProps: {
|
|
||||||
format: "YYYY-MM-DD",
|
|
||||||
valueFormat: "YYYY-MM-DD",
|
|
||||||
allowClear: false,
|
|
||||||
// disabledDate: disabledDateFn, // 如果需要禁用日期,在此传入函数
|
|
||||||
},
|
|
||||||
presets: DateSetting.RangeButton.days,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
// --- Methods ---
|
|
||||||
|
|
||||||
// 1. 文件处理逻辑
|
|
||||||
const handleFileSelect = (e: Event) => {
|
|
||||||
const target = e.target as HTMLInputElement;
|
|
||||||
const file = target.files?.[0];
|
|
||||||
if (!file) return;
|
|
||||||
|
|
||||||
// 校验文件大小 (50MB)
|
|
||||||
const maxSize = 50 * 1024 * 1024;
|
|
||||||
if (file.size > maxSize) {
|
|
||||||
message.error("文件大小不能超过50MB");
|
|
||||||
resetFileInput();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 校验文件类型
|
|
||||||
const isZip =
|
|
||||||
file.name.toLowerCase().endsWith(".zip") ||
|
|
||||||
file.type === "application/zip" ||
|
|
||||||
file.type === "application/x-zip-compressed";
|
|
||||||
|
|
||||||
if (!isZip) {
|
|
||||||
message.error("请选择.zip格式的压缩包");
|
|
||||||
resetFileInput();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
props.importBtn(file);
|
|
||||||
resetFileInput();
|
|
||||||
};
|
|
||||||
|
|
||||||
const resetFileInput = () => {
|
|
||||||
if (fileInputRef.value) {
|
|
||||||
fileInputRef.value.value = "";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const triggerFileInput = () => {
|
|
||||||
fileInputRef.value?.click();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 2. 搜索表单逻辑
|
|
||||||
const onSearchFinish = (values: any) => {
|
|
||||||
console.log(values);
|
|
||||||
// 模拟获取 label,实际可能需要根据 dmStcd 查找名称
|
|
||||||
// 在原 React 代码中,label 来自 options.find(...)
|
|
||||||
// 这里简化处理,直接传递 ID 或固定名称,或者你需要维护一个电站列表映射
|
|
||||||
const label = "默认水温站"; // TODO: 根据 values.dmStcd 查找真实名称
|
|
||||||
// const params: any = {};
|
|
||||||
// if (values.strdt) {
|
|
||||||
// params.startDate = values.strdt[0].format("YYYY-MM-DD");
|
|
||||||
// params.endDate = values.strdt[1].format("YYYY-MM-DD");
|
|
||||||
// }
|
|
||||||
|
|
||||||
emit("searchFinish", values, label);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onValuesChange = (changedValues: any, allValues: any) => {
|
|
||||||
// 同步更新本地 searchData,以便其他逻辑使用
|
|
||||||
searchData.value = { ...searchData.value, ...allValues };
|
|
||||||
|
|
||||||
if (changedValues.strdt) {
|
|
||||||
// 如果需要在时间改变时做额外处理
|
|
||||||
console.log("Time changed:", changedValues.strdt);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// const handleReset = (form: any) => {
|
|
||||||
// // 重置表单
|
|
||||||
// if (form) {
|
|
||||||
// form.resetFields();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 重置后重新设置默认值并触发搜索
|
|
||||||
// nextTick(() => {
|
|
||||||
// if (form) {
|
|
||||||
// form.setFieldsValue(initSearchData);
|
|
||||||
// }
|
|
||||||
// // 触发初始搜索
|
|
||||||
// emit("searchFinish", initSearchData, "两河口出库水温站");
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
watch(
|
|
||||||
() => initSearchData.typeDate,
|
|
||||||
(newVal) => {
|
|
||||||
localTypeDate.value = newVal || [];
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
);
|
|
||||||
// --- Lifecycle ---
|
|
||||||
onMounted(() => {
|
|
||||||
// 初始请求
|
|
||||||
emit("searchFinish", initSearchData, "两河口出库水温站");
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style lang="scss"></style>
|
|
||||||
446
frontend/src/views/shuJuTianBao/shengPiJiLu.vue
Normal file
446
frontend/src/views/shuJuTianBao/shengPiJiLu.vue
Normal file
@ -0,0 +1,446 @@
|
|||||||
|
<template>
|
||||||
|
<div class="shengPiJiLu-page">
|
||||||
|
<!-- 搜索区域组件,具体 props 需根据实际子组件调整 -->
|
||||||
|
<GuoYuSheShiShuJuTianBaoSearch @search-finish="handleSearchFinish" @reset="handleReset" />
|
||||||
|
<!-- 主表格 -->
|
||||||
|
<BasicTable ref="tableRef" :columns="columns" :list-url="queryPageList" :search-params="{}">
|
||||||
|
<!-- 使用 bodyCell 插槽自定义单元格渲染 -->
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.key === 'action' || column.dataIndex === 'action'">
|
||||||
|
<div class="flex">
|
||||||
|
<a-button type="link" size="small" @click="handleShowApprovalLog(record)">审批详情</a-button>
|
||||||
|
<a-button type="link" size="small" @click="handleShowChangeLog(record)">变更详情</a-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-if="column.dataIndex === 'bizType'">
|
||||||
|
{{ handName(record.bizType, yeWuType) }}
|
||||||
|
</template>
|
||||||
|
<template v-if="column.dataIndex === 'status'">
|
||||||
|
{{ handName(record.status, shenStatus) }}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
|
||||||
|
<!-- 审批操作日志弹框 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="approvalLogVisible"
|
||||||
|
title="审批操作日志"
|
||||||
|
width="1600px"
|
||||||
|
:footer="null"
|
||||||
|
destroy-on-close="false"
|
||||||
|
>
|
||||||
|
<div class="approval-log-modal-content">
|
||||||
|
<ApprovalLogSearch
|
||||||
|
:action-type-dict="actionTypeDict"
|
||||||
|
@search-finish="handleApprovalLogSearch"
|
||||||
|
@reset="handleApprovalLogReset"
|
||||||
|
/>
|
||||||
|
<BasicTable
|
||||||
|
ref="approvalLogTableRef"
|
||||||
|
:columns="approvalLogColumns"
|
||||||
|
:list-url="getApprovalLogList"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.dataIndex === 'action'">
|
||||||
|
{{ handName(record.action, actionTypeDict) }}
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
|
||||||
|
<!-- 数据变更记录弹框 -->
|
||||||
|
<a-modal
|
||||||
|
v-model:open="changeLogVisible"
|
||||||
|
title="数据变更记录"
|
||||||
|
width="1600px"
|
||||||
|
:footer="null"
|
||||||
|
destroy-on-close="false"
|
||||||
|
>
|
||||||
|
<div class="change-log-modal-content">
|
||||||
|
<ChangeLogSearch
|
||||||
|
:operation-type-dict="operationTypeDict"
|
||||||
|
@search-finish="handleChangeLogSearch"
|
||||||
|
@reset="handleChangeLogReset"
|
||||||
|
/>
|
||||||
|
<BasicTable
|
||||||
|
ref="changeLogTableRef"
|
||||||
|
:columns="changeLogColumns"
|
||||||
|
:list-url="getApprovalChangeLogList"
|
||||||
|
>
|
||||||
|
<template #bodyCell="{ column, record }">
|
||||||
|
<template v-if="column.dataIndex === 'operationType'">
|
||||||
|
{{ handName(record.operationType, operationTypeDict) }}
|
||||||
|
</template>
|
||||||
|
<template v-if="column.dataIndex === 'bizType'">
|
||||||
|
{{ handName(record.bizType, yeWuType) }}
|
||||||
|
</template>
|
||||||
|
<template v-if="column.dataIndex === 'changeJson'">
|
||||||
|
<pre style="max-height: 200px; overflow: auto; margin: 0;">{{ record.changeJson }}</pre>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</BasicTable>
|
||||||
|
</div>
|
||||||
|
</a-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, reactive, onMounted } from 'vue';
|
||||||
|
import { queryPageList, getApprovalLogList, getApprovalChangeLogList } from '@/api/shengPiJiLu';
|
||||||
|
import BasicTable from "@/components/BasicTable/index.vue";
|
||||||
|
import GuoYuSheShiShuJuTianBaoSearch from "./shengPiJiLuSearch.vue";
|
||||||
|
import ApprovalLogSearch from "./approvalLogSearch.vue";
|
||||||
|
import ChangeLogSearch from "./changeLogSearch.vue";
|
||||||
|
import { getDictItemsByCode } from '@/api/dict';
|
||||||
|
let columns = ref([
|
||||||
|
{
|
||||||
|
title: '审批批次号',
|
||||||
|
dataIndex: 'approvalNo',
|
||||||
|
key: 'approvalNo',
|
||||||
|
width: 160,
|
||||||
|
fixed: 'left'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '业务类型',
|
||||||
|
dataIndex: 'bizType',
|
||||||
|
key: 'bizType',
|
||||||
|
width: 100,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '数据条数',
|
||||||
|
dataIndex: 'dataCount',
|
||||||
|
key: 'dataCount',
|
||||||
|
width: 100,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '提交人',
|
||||||
|
dataIndex: 'applyUserName',
|
||||||
|
key: 'applyUserName',
|
||||||
|
width: 120,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '提交时间',
|
||||||
|
dataIndex: 'applyTime',
|
||||||
|
key: 'applyTime',
|
||||||
|
width: 160,
|
||||||
|
align: 'center'
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '审批状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
width: 80,
|
||||||
|
align: 'center',
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '审批人',
|
||||||
|
dataIndex: 'approverName',
|
||||||
|
key: 'approverName',
|
||||||
|
width: 120,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '审批时间',
|
||||||
|
dataIndex: 'approveTime',
|
||||||
|
key: 'approveTime',
|
||||||
|
width: 160,
|
||||||
|
align: 'center'
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '审批备注',
|
||||||
|
dataIndex: 'remark',
|
||||||
|
key: 'remark',
|
||||||
|
ellipsis: true,
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
width: 160,
|
||||||
|
fixed: 'right',
|
||||||
|
align: 'center'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
const tableRef = ref()
|
||||||
|
const handleSearchFinish = (values: any) => {
|
||||||
|
console.log(values);
|
||||||
|
const filters = [
|
||||||
|
values.approvalNo && {
|
||||||
|
field: "approvalNo",
|
||||||
|
operator: "contains",
|
||||||
|
dataType: "string",
|
||||||
|
value: values.approvalNo,
|
||||||
|
},
|
||||||
|
values.status && {
|
||||||
|
field: "status",
|
||||||
|
operator: "eq",
|
||||||
|
dataType: "string",
|
||||||
|
value: values.status,
|
||||||
|
},
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
|
||||||
|
const filter = {
|
||||||
|
logic: "and",
|
||||||
|
filters: filters,
|
||||||
|
};
|
||||||
|
tableRef.value?.getList(filter);
|
||||||
|
};
|
||||||
|
const handleReset = (values) => {
|
||||||
|
handleSearchFinish(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 审批操作日志弹框相关
|
||||||
|
const approvalLogVisible = ref(false);
|
||||||
|
const approvalLogTableRef = ref();
|
||||||
|
|
||||||
|
const approvalLogColumns = ref([
|
||||||
|
{
|
||||||
|
title: '操作类型',
|
||||||
|
dataIndex: 'action',
|
||||||
|
key: 'action',
|
||||||
|
width: 120,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作人',
|
||||||
|
dataIndex: 'operatorName',
|
||||||
|
key: 'operatorName',
|
||||||
|
width: 150,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作时间',
|
||||||
|
dataIndex: 'operateTime',
|
||||||
|
key: 'operateTime',
|
||||||
|
width: 180
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
dataIndex: 'createdAt',
|
||||||
|
key: 'createdAt',
|
||||||
|
width: 180
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '审批意见',
|
||||||
|
dataIndex: 'commentInfo',
|
||||||
|
key: 'commentInfo',
|
||||||
|
ellipsis: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 数据变更记录弹框相关
|
||||||
|
const changeLogVisible = ref(false);
|
||||||
|
const changeLogTableRef = ref();
|
||||||
|
|
||||||
|
const changeLogColumns = ref([
|
||||||
|
{
|
||||||
|
title: '业务类型',
|
||||||
|
dataIndex: 'bizType',
|
||||||
|
key: 'bizType',
|
||||||
|
width: 120,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作类型',
|
||||||
|
dataIndex: 'operationType',
|
||||||
|
key: 'operationType',
|
||||||
|
width: 120,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作人',
|
||||||
|
dataIndex: 'operatorName',
|
||||||
|
key: 'operatorName',
|
||||||
|
width: 150,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作时间',
|
||||||
|
dataIndex: 'operateTime',
|
||||||
|
key: 'operateTime',
|
||||||
|
width: 180
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
dataIndex: 'createdAt',
|
||||||
|
key: 'createdAt',
|
||||||
|
width: 180
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '变更内容',
|
||||||
|
dataIndex: 'changeJson',
|
||||||
|
key: 'changeJson',
|
||||||
|
ellipsis: true
|
||||||
|
},
|
||||||
|
|
||||||
|
]);
|
||||||
|
|
||||||
|
const currentApprovalId = ref('');
|
||||||
|
const actionTypeDict = ref([]);
|
||||||
|
const operationTypeDict = ref([]);
|
||||||
|
|
||||||
|
// 显示审批操作日志弹框
|
||||||
|
const handleShowApprovalLog = (record: any) => {
|
||||||
|
currentApprovalId.value = record.id;
|
||||||
|
approvalLogVisible.value = true;
|
||||||
|
// 延迟调用,确保弹框和 BasicTable 组件完全渲染
|
||||||
|
setTimeout(() => {
|
||||||
|
const filter = {
|
||||||
|
logic: "and",
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: "approvalId",
|
||||||
|
operator: "eq",
|
||||||
|
dataType: "string",
|
||||||
|
value: record.id,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
approvalLogTableRef.value?.getList(filter);
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 审批日志搜索处理
|
||||||
|
const handleApprovalLogSearch = (values: any) => {
|
||||||
|
console.log('审批日志搜索:', values);
|
||||||
|
const filters = [
|
||||||
|
{
|
||||||
|
field: "approvalId",
|
||||||
|
operator: "eq",
|
||||||
|
dataType: "string",
|
||||||
|
value: currentApprovalId.value,
|
||||||
|
},
|
||||||
|
values.action && {
|
||||||
|
field: "action",
|
||||||
|
operator: "eq",
|
||||||
|
dataType: "string",
|
||||||
|
value: values.action,
|
||||||
|
},
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
const filter = {
|
||||||
|
logic: "and",
|
||||||
|
filters: filters,
|
||||||
|
};
|
||||||
|
approvalLogTableRef.value?.getList(filter);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 审批日志重置处理
|
||||||
|
const handleApprovalLogReset = (values: any) => {
|
||||||
|
console.log('审批日志重置:', values);
|
||||||
|
handleApprovalLogSearch(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 显示数据变更记录弹框
|
||||||
|
const handleShowChangeLog = (record: any) => {
|
||||||
|
currentApprovalId.value = record.id;
|
||||||
|
changeLogVisible.value = true;
|
||||||
|
// 延迟调用,确保弹框和 BasicTable 组件完全渲染
|
||||||
|
setTimeout(() => {
|
||||||
|
const filter = {
|
||||||
|
logic: "and",
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
field: "approvalId",
|
||||||
|
operator: "eq",
|
||||||
|
dataType: "string",
|
||||||
|
value: record.id,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
changeLogTableRef.value?.getList(filter);
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 变更日志搜索处理
|
||||||
|
const handleChangeLogSearch = (values: any) => {
|
||||||
|
console.log('变更日志搜索:', values);
|
||||||
|
const filters = [
|
||||||
|
{
|
||||||
|
field: "approvalId",
|
||||||
|
operator: "eq",
|
||||||
|
dataType: "string",
|
||||||
|
value: currentApprovalId.value,
|
||||||
|
},
|
||||||
|
values.operationType && {
|
||||||
|
field: "operationType",
|
||||||
|
operator: "eq",
|
||||||
|
dataType: "string",
|
||||||
|
value: values.operationType,
|
||||||
|
},
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
const filter = {
|
||||||
|
logic: "and",
|
||||||
|
filters: filters,
|
||||||
|
};
|
||||||
|
changeLogTableRef.value?.getList(filter);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 变更日志重置处理
|
||||||
|
const handleChangeLogReset = (values: any) => {
|
||||||
|
console.log('变更日志重置:', values);
|
||||||
|
handleChangeLogSearch(values);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化
|
||||||
|
onMounted(() => {
|
||||||
|
dictNmae()
|
||||||
|
});
|
||||||
|
const shenStatus = ref([])
|
||||||
|
const yeWuType = ref([])
|
||||||
|
const dictNmae = () => {
|
||||||
|
getDictItemsByCode({ dictCode: 'shenStatus' }).then((res) => {
|
||||||
|
shenStatus.value = res.data;
|
||||||
|
});
|
||||||
|
getDictItemsByCode({ dictCode: 'yeWuType' }).then((res) => {
|
||||||
|
yeWuType.value = res.data;
|
||||||
|
});
|
||||||
|
// TODO: 待补充操作类型字典
|
||||||
|
getDictItemsByCode({ dictCode: 'caoType' }).then((res) => {
|
||||||
|
actionTypeDict.value = res.data;
|
||||||
|
});
|
||||||
|
getDictItemsByCode({ dictCode: 'caoTypeTwo' }).then((res) => {
|
||||||
|
operationTypeDict.value = res.data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const handName = (val: any, arr: any) => {
|
||||||
|
let dictName1 = ''
|
||||||
|
arr.forEach((item: any) => {
|
||||||
|
if (item.itemCode == val) {
|
||||||
|
dictName1 = item.dictName
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return dictName1
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.shengPiJiLu-page {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #ffffff;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.approval-log-modal-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-log-modal-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
85
frontend/src/views/shuJuTianBao/shengPiJiLuSearch.vue
Normal file
85
frontend/src/views/shuJuTianBao/shengPiJiLuSearch.vue
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<template>
|
||||||
|
<div class="guoYuSheShiShuJuTianBao-search">
|
||||||
|
<BasicSearch ref="basicSearchRef" :searchList="searchList" :initial-values="initSearchData"
|
||||||
|
@finish="onSearchFinish" @values-change="onValuesChange" @reset="handleReset">
|
||||||
|
</BasicSearch>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, computed, onMounted } from "vue";
|
||||||
|
import BasicSearch from "@/components/BasicSearch/index.vue"; // 确保路径正确
|
||||||
|
import { getDictItemsByCode } from '@/api/dict';
|
||||||
|
// --- Props & Emits ---
|
||||||
|
interface Props {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "reset", values: any): void;
|
||||||
|
(e: "searchFinish", values: any): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
|
||||||
|
// 模拟 initSearchData
|
||||||
|
const initSearchData = {
|
||||||
|
approvalNo:'',
|
||||||
|
status:'',
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchData = ref<any>({ ...initSearchData });
|
||||||
|
|
||||||
|
const searchList: any = computed(() => [
|
||||||
|
{
|
||||||
|
type: "Input",
|
||||||
|
name: "approvalNo",
|
||||||
|
label: "审批批次号",
|
||||||
|
fieldProps: {
|
||||||
|
allowClear: true,
|
||||||
|
},
|
||||||
|
options: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "Select",
|
||||||
|
name: "status",
|
||||||
|
label: "审批状态",
|
||||||
|
fieldProps: {
|
||||||
|
allowClear: true,
|
||||||
|
},
|
||||||
|
options: statusData.value,
|
||||||
|
},
|
||||||
|
|
||||||
|
]);
|
||||||
|
// --- Methods ---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 2. 搜索表单逻辑
|
||||||
|
const onSearchFinish = (values: any) => {
|
||||||
|
console.log(values);
|
||||||
|
|
||||||
|
emit("searchFinish", values);
|
||||||
|
};
|
||||||
|
const handleReset = () => {
|
||||||
|
emit("reset", initSearchData);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onValuesChange = (changedValues: any, allValues: any) => {
|
||||||
|
// 同步更新本地 searchData,以便其他逻辑使用
|
||||||
|
searchData.value = { ...searchData.value, ...allValues };
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Lifecycle ---
|
||||||
|
onMounted(() => {
|
||||||
|
emit("searchFinish", initSearchData);
|
||||||
|
getstatusData()
|
||||||
|
});
|
||||||
|
const statusData = ref(false)
|
||||||
|
const getstatusData = () => {
|
||||||
|
getDictItemsByCode({ dictCode: "shenStatus" }).then((res) => {
|
||||||
|
statusData.value = res.data;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss"></style>
|
||||||
@ -2,7 +2,7 @@
|
|||||||
import JidiSelectorMod from "@/modules/jidiSelectorMod.vue";
|
import JidiSelectorMod from "@/modules/jidiSelectorMod.vue";
|
||||||
import RightDrawer from "@/components/RightDrawer/index.vue";
|
import RightDrawer from "@/components/RightDrawer/index.vue";
|
||||||
import ShuiZhiJianCeGongZuoQingKuang from "@/modules/shuizhijiancegongzuoQK/index.vue"
|
import ShuiZhiJianCeGongZuoQingKuang from "@/modules/shuizhijiancegongzuoQK/index.vue"
|
||||||
// import EnvironmentalQuality from "@/modules/environmentalQuality/index.vue" // 环境质量满足度
|
import EnvironmentalQuality from "@/modules/EnvironmentalQuality/index.vue" // 环境质量满足度
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -13,7 +13,7 @@ import ShuiZhiJianCeGongZuoQingKuang from "@/modules/shuizhijiancegongzuoQK/inde
|
|||||||
<div class="rightContent">
|
<div class="rightContent">
|
||||||
<RightDrawer>
|
<RightDrawer>
|
||||||
<ShuiZhiJianCeGongZuoQingKuang />
|
<ShuiZhiJianCeGongZuoQingKuang />
|
||||||
<!-- <EnvironmentalQuality /> -->
|
<EnvironmentalQuality />
|
||||||
</RightDrawer>
|
</RightDrawer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -68,7 +68,7 @@ const input = ref("");
|
|||||||
const title = ref("");
|
const title = ref("");
|
||||||
const info: any = ref({
|
const info: any = ref({
|
||||||
rolename: "",
|
rolename: "",
|
||||||
level: "系统管理员",
|
level: "2",
|
||||||
description: "",
|
description: "",
|
||||||
});
|
});
|
||||||
const faultList: any = [
|
const faultList: any = [
|
||||||
@ -90,7 +90,7 @@ function addClick() {
|
|||||||
title.value = "新增角色";
|
title.value = "新增角色";
|
||||||
info.value = {
|
info.value = {
|
||||||
rolename: "",
|
rolename: "",
|
||||||
level: "系统管理员",
|
level: "2",
|
||||||
description: "",
|
description: "",
|
||||||
};
|
};
|
||||||
dialogVisible.value = true;
|
dialogVisible.value = true;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": false,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": false,
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
|
|||||||
@ -27,7 +27,7 @@ export default ({ mode }: ConfigEnv): UserConfig => {
|
|||||||
// 线上API地址
|
// 线上API地址
|
||||||
// target: 'http://localhost:8093/',
|
// target: 'http://localhost:8093/',
|
||||||
// 本地API地址
|
// 本地API地址
|
||||||
target: 'http://10.84.121.21:8093',
|
target: 'http://10.84.121.21:8093',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: path =>
|
rewrite: path =>
|
||||||
path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '')
|
path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '')
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user