数据填报修改
This commit is contained in:
parent
f01e6d926f
commit
6e600d6c72
@ -8,27 +8,90 @@ export function getFishDraftPage(data:any) {
|
||||
data
|
||||
});
|
||||
}
|
||||
//新增目录
|
||||
//新增过鱼数据
|
||||
export function addFishDraft(queryParams:any) {
|
||||
return request({
|
||||
url: '/data/fishDraft/add',
|
||||
url: '/data/fishDraft/saveDraft',
|
||||
method: 'post',
|
||||
data: queryParams
|
||||
});
|
||||
}
|
||||
//修改目录
|
||||
//修改过鱼数据
|
||||
export function editFishDraft(queryParams:any) {
|
||||
return request({
|
||||
url: '/data/fishDraft/update',
|
||||
url: '/data/fishDraft/updateDraft',
|
||||
method: 'post',
|
||||
data: queryParams
|
||||
});
|
||||
}
|
||||
//删除
|
||||
//删除 过鱼数据
|
||||
export function delFishDraft(data:any) {
|
||||
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 submitImportTask(data:any) {
|
||||
return request({
|
||||
url: '/data/fishDraft/submitDrafts',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
// 取消导入任务
|
||||
export function cancelImportTask(data:any) {
|
||||
return request({
|
||||
url: '/data/fishDraft/cancelImport',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
// 查询导入任务
|
||||
export function getImportTask() {
|
||||
return request({
|
||||
url: '/data/importTask/list',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
//检测用户导入状态
|
||||
export function checkImportStatus() {
|
||||
return request({
|
||||
url: '/data/fishDraft/checkImportStatus',
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
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'
|
||||
});
|
||||
}
|
||||
@ -6,6 +6,7 @@
|
||||
:rules="rules"
|
||||
layout="inline"
|
||||
class="basic-search-form"
|
||||
@reset="handleReset"
|
||||
@finish="handleFinish"
|
||||
@values-change="handleValuesChange"
|
||||
>
|
||||
@ -18,14 +19,18 @@
|
||||
:name="item.name"
|
||||
style="width: 100%; margin-bottom: 0"
|
||||
>
|
||||
<!-- 1. 优先检查是否有具名插槽,或者 type 为 custom -->
|
||||
<slot
|
||||
v-if="$slots[item.name] || item.type === 'custom'"
|
||||
:name="item.name"
|
||||
:value="formData[item.name]"
|
||||
:onChange="(val:any) => { formData[item.name] = val }"
|
||||
:formModel="formData"
|
||||
/>
|
||||
<!-- 1. 优先检查是否有具名插槽,或者 type 为 custom -->
|
||||
<slot
|
||||
v-if="$slots[item.name] || item.type === 'custom'"
|
||||
:name="item.name"
|
||||
:value="formData[item.name]"
|
||||
:onChange="(val:any) => {
|
||||
formData[item.name] = val;
|
||||
triggerManualValuesChange(item.name, val);
|
||||
}"
|
||||
:formModel="formData"
|
||||
/>
|
||||
|
||||
<!-- 普通日期选择器 -->
|
||||
<a-date-picker
|
||||
v-else-if="item.type === 'DataPicker'"
|
||||
@ -38,7 +43,9 @@
|
||||
:allow-clear="item.fieldProps?.allowClear"
|
||||
:presets="item.presets"
|
||||
style="width: 100%"
|
||||
@change="(val) => triggerManualValuesChange(item.name, val)"
|
||||
/>
|
||||
|
||||
<!-- 日期范围选择器 -->
|
||||
<a-range-picker
|
||||
v-else-if="item.type === 'RangePicker'"
|
||||
@ -51,6 +58,7 @@
|
||||
:allow-clear="item.fieldProps?.allowClear"
|
||||
:presets="item.presets"
|
||||
style="width: 100%"
|
||||
@change="(val) => triggerManualValuesChange(item.name, val)"
|
||||
/>
|
||||
|
||||
<!-- 普通输入框 -->
|
||||
@ -60,38 +68,40 @@
|
||||
:placeholder="item.placeholder || '请输入'"
|
||||
:allow-clear="item.fieldProps?.allowClear"
|
||||
: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'">
|
||||
<a-form-item-rest>
|
||||
<a-select
|
||||
:value="formData.stcd?.dataDimensionData"
|
||||
placeholder="请选择"
|
||||
@change="dataDimensionDataChange"
|
||||
style="width: 135px"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="opt in item.options"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
<a-select
|
||||
:value="formData.baseId"
|
||||
placeholder="请选择"
|
||||
@change="dataDimensionDataChange"
|
||||
style="width: 135px"
|
||||
>
|
||||
{{ opt.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
:value="formData.stcd?.stcdId"
|
||||
placeholder="请选择电站"
|
||||
@change="stcdIdChange"
|
||||
style="width: 135px"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="opt in item.options"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
<a-select-option
|
||||
v-for="opt in shuJuTianBaoStore.baseOption"
|
||||
:key="opt.baseid"
|
||||
:value="opt.baseid"
|
||||
>
|
||||
{{ opt.basename }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-select
|
||||
:value="formData.rstcd"
|
||||
placeholder="请选择电站"
|
||||
@change="stcdIdChange"
|
||||
style="width: 135px"
|
||||
>
|
||||
{{ opt.label }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
<a-select-option
|
||||
v-for="opt in shuJuTianBaoStore.engOption"
|
||||
:key="opt.stcd"
|
||||
:value="opt.stcd"
|
||||
>
|
||||
{{ opt.ennm }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item-rest>
|
||||
</div>
|
||||
|
||||
@ -102,27 +112,25 @@
|
||||
:placeholder="item.placeholder || '请选择'"
|
||||
:allow-clear="item.fieldProps?.allowClear"
|
||||
:style="{ width: item.width ? item.width + 'px' : '200px' }"
|
||||
@change="(val) => triggerManualValuesChange(item.name, val)"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="opt in item.options"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
:key="opt[item.values?.value] || opt.value || opt.itemCode"
|
||||
:value="opt[item.values?.value] || opt.value || opt.itemCode"
|
||||
>
|
||||
{{ opt.label }}
|
||||
{{ opt[item.values?.name] || opt.label || opt.dictName }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
|
||||
<!-- 单选 框 -->
|
||||
<!-- 单选框 -->
|
||||
<a-radio-group
|
||||
v-else-if="item.type === 'Radio'"
|
||||
v-model:value="formData[item.name]"
|
||||
:style="{ width: item.width ? item.width + 'px' : '200px' }"
|
||||
@change="(e) => triggerManualValuesChange(item.name, e.target.value)"
|
||||
>
|
||||
<a-radio
|
||||
v-for="opt in item.options"
|
||||
:key="opt.value"
|
||||
:value="opt.value"
|
||||
>
|
||||
<a-radio v-for="opt in item.options" :key="opt.value" :value="opt.value">
|
||||
{{ opt.label }}
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
@ -149,7 +157,10 @@
|
||||
</template>
|
||||
|
||||
<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();
|
||||
// import { nextTick } from "process";
|
||||
|
||||
// --- 类型定义 ---
|
||||
export interface SearchItem {
|
||||
@ -163,7 +174,13 @@ export interface SearchItem {
|
||||
xlSpan?: number;
|
||||
width?: number;
|
||||
presets?: any[];
|
||||
options?: { label: string; value: any }[];
|
||||
values?: any;
|
||||
options?: {
|
||||
itemCode?: string;
|
||||
dictName?: string;
|
||||
label: string;
|
||||
value: any;
|
||||
}[];
|
||||
component?: any;
|
||||
}
|
||||
|
||||
@ -192,8 +209,9 @@ const rules = reactive<Record<string, any>>({});
|
||||
|
||||
// 2. 创建计算属性,自动过滤掉 false/null/undefined 的项
|
||||
const validSearchList = computed(() => {
|
||||
return props.searchList.filter(item => item);
|
||||
return props.searchList.filter((item) => item);
|
||||
});
|
||||
|
||||
// --- 初始化逻辑 ---
|
||||
const initForm = () => {
|
||||
const initial = JSON.parse(JSON.stringify(props.initialValues || {}));
|
||||
@ -205,8 +223,12 @@ const initForm = () => {
|
||||
Object.assign(formData, initial);
|
||||
|
||||
// 3. 过滤掉 searchList 中为 false/null/undefined 的项,并生成规则
|
||||
|
||||
validSearchList.value.forEach((item) => {
|
||||
if (item.type == "waterStation") {
|
||||
// 下拉菜单
|
||||
shuJuTianBaoStore.getBaseOption();
|
||||
shuJuTianBaoStore.getEngOption(formData.baseId);
|
||||
}
|
||||
if (item.fieldProps?.required) {
|
||||
rules[item.name] = [
|
||||
{ required: true, message: `${item.label}不能为空`, trigger: "blur" },
|
||||
@ -214,15 +236,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) => {
|
||||
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) => {
|
||||
formData.stcd.stcdId = value;
|
||||
formData.rstcd = value;
|
||||
// 【关键修改】手动触发 valuesChange
|
||||
triggerManualValuesChange("rstcd", formData.rstcd);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initForm();
|
||||
});
|
||||
@ -240,7 +282,8 @@ watch(
|
||||
|
||||
// --- 事件处理 ---
|
||||
const handleFinish = (values: any) => {
|
||||
emit("finish", values);
|
||||
const finalValues = { ...formData, ...values };
|
||||
emit("finish", finalValues);
|
||||
};
|
||||
|
||||
const handleValuesChange = (changedValues: any, allValues: any) => {
|
||||
@ -250,12 +293,16 @@ const handleValuesChange = (changedValues: any, allValues: any) => {
|
||||
const handleReset = () => {
|
||||
if (formRef.value) {
|
||||
formRef.value.resetFields();
|
||||
nextTick(() => {
|
||||
initForm();
|
||||
});
|
||||
emit("reset");
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
form: formRef,
|
||||
formData,
|
||||
reset: handleReset,
|
||||
submit: () => formRef.value?.submit(),
|
||||
});
|
||||
|
||||
@ -67,7 +67,7 @@ const rowSelection = computed(() => ({
|
||||
emit("selection-change", keys, rows);
|
||||
},
|
||||
getCheckboxProps: (record: any) => ({
|
||||
disabled: record.disabled, // 可根据业务逻辑禁用某些行的勾选
|
||||
// disabled: record.status === 'SUBMITTED'
|
||||
}),
|
||||
}));
|
||||
|
||||
@ -88,20 +88,20 @@ const paginationConfig = computed(() => ({
|
||||
* 获取列表数据
|
||||
* @param extraParams 额外的临时参数(可选)
|
||||
*/
|
||||
const getList = async (extraParams?: Record<string, any>) => {
|
||||
const getList = async (filter?: Record<string, any>) => {
|
||||
loading.value = true;
|
||||
try {
|
||||
// 合并基础分页参数、外部搜索参数和临时参数
|
||||
const params = {
|
||||
...props.searchParams,
|
||||
...extraParams,
|
||||
// ...props.searchParams,
|
||||
skip: page.value,
|
||||
take: size.value,
|
||||
filter: {}
|
||||
filter: filter,
|
||||
// 如果后端需要 skip/take 格式,可以在此转换
|
||||
// skip: (page.value - 1) * size.value,
|
||||
// take: size.value,
|
||||
};
|
||||
console.log(params);
|
||||
|
||||
const res = await props.listUrl(params);
|
||||
|
||||
@ -177,7 +177,6 @@ watch(
|
||||
|
||||
// --- Lifecycle ---
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,24 +1,28 @@
|
||||
<template>
|
||||
<a-select
|
||||
ref="selectRef"
|
||||
:style="{ width: width }"
|
||||
:value="modelValue"
|
||||
:options="options"
|
||||
:loading="loading"
|
||||
@change="handleChange"
|
||||
placeholder="请选择鱼名称"
|
||||
mode="multiple"
|
||||
@search="handleSearch"
|
||||
placeholder="请选择鱼种类"
|
||||
:mode="multiple ? 'multiple' : undefined"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
:filter-option="false"
|
||||
class="custom-fish-select"
|
||||
:dropdownMatchSelectWidth="false"
|
||||
:getPopupContainer="(triggerNode: HTMLElement) => triggerNode.parentNode"
|
||||
@dropdownVisibleChange="handleDropdownVisibleChange"
|
||||
:max-tag-count="multiple ? 1 : undefined"
|
||||
:open="open"
|
||||
@update:open="open = $event"
|
||||
:field-names="{ label: 'name', value: 'id' }"
|
||||
>
|
||||
<!-- 自定义 Tag 显示名称 -->
|
||||
<template #tagRender="{ value: tagId, onClose }">
|
||||
<a-tag
|
||||
closable
|
||||
@close="onClose"
|
||||
style="margin-right: 3px; max-width: 120px"
|
||||
>
|
||||
<!-- 自定义 Tag 显示名称 (仅在多选时生效) -->
|
||||
<template #tagRender="{ value: tagId, onClose }" v-if="multiple">
|
||||
<a-tag closable @close="onClose" style="margin-right: 3px; max-width: 120px">
|
||||
{{ getFishNameById(tagId) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
@ -32,7 +36,7 @@
|
||||
:key="opt.id"
|
||||
class="dropdown-item"
|
||||
:class="{
|
||||
'is-active': Array.isArray(modelValue) && modelValue.includes(opt.id),
|
||||
'is-active': isSelected(opt.id),
|
||||
'is-hovered': opt.id === hoveredId,
|
||||
}"
|
||||
@click.stop="handleSelectOption(opt)"
|
||||
@ -40,11 +44,9 @@
|
||||
>
|
||||
<span class="item-name">{{ opt.name }}</span>
|
||||
<!-- 选中对勾 -->
|
||||
<span v-if="Array.isArray(modelValue) && modelValue.includes(opt.id)" class="check-icon">✓</span>
|
||||
</div>
|
||||
<div v-if="filteredOptions.length === 0" class="empty-tip">
|
||||
无匹配数据
|
||||
<span v-if="isSelected(opt.id)" class="check-icon">✓</span>
|
||||
</div>
|
||||
<div v-if="filteredOptions.length === 0" class="empty-tip">无匹配数据</div>
|
||||
</div>
|
||||
|
||||
<!-- 中间分割线 -->
|
||||
@ -66,30 +68,42 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from "vue";
|
||||
import { ref, onMounted, computed, watch } from "vue";
|
||||
import { getFishDictoryDropdown } from "@/api/select";
|
||||
|
||||
// --- Props & Emits ---
|
||||
interface Props {
|
||||
modelValue: string[]; // 接收选中的 ID 数组
|
||||
options: any[];
|
||||
width: string;
|
||||
modelValue: string | string[]; // 支持字符串(单选)或数组(多选)
|
||||
width?: string;
|
||||
multiple?: boolean; // 控制是否多选
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
multiple: false, // 默认单选,根据需求调整
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "update:modelValue", value: string[]): void;
|
||||
(e: "update:modelValue", value: string | string[]): void;
|
||||
}>();
|
||||
|
||||
// --- State ---
|
||||
// 这里可以改为从 API 获取,目前先保留静态数据作为示例
|
||||
const options = ref<any>(props.options || []);
|
||||
|
||||
const loading = ref(false);
|
||||
const options = ref<any[]>([]);
|
||||
const searchKeyword = ref<string>("");
|
||||
const hoveredId = ref<string | null>(null);
|
||||
const open = ref(false); // 控制下拉框显隐
|
||||
|
||||
// --- 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(() => {
|
||||
@ -100,39 +114,59 @@ const currentDetailData = computed(() => {
|
||||
});
|
||||
|
||||
// --- 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) => {
|
||||
if (!input) return true;
|
||||
const targetOpt = options.value.find((item: any) => item.id === option.value);
|
||||
if (!targetOpt) return false;
|
||||
const handleSearch = (value: string) => {
|
||||
searchKeyword.value = value;
|
||||
};
|
||||
|
||||
const lowerInput = input.toLowerCase();
|
||||
const nameMatch = targetOpt.name?.toLowerCase().includes(lowerInput);
|
||||
const aliasMatch = targetOpt.alias?.toLowerCase().includes(lowerInput);
|
||||
return nameMatch || aliasMatch;
|
||||
const handleDropdownVisibleChange = (val: boolean) => {
|
||||
open.value = val;
|
||||
if (!val) {
|
||||
hoveredId.value = null;
|
||||
searchKeyword.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectOption = (opt: any) => {
|
||||
let newValues: string[] = Array.isArray(props.modelValue) ? [...props.modelValue] : [];
|
||||
const index = newValues.indexOf(opt.id);
|
||||
if (props.multiple) {
|
||||
// --- 多选逻辑 ---
|
||||
let newValues: string[] = Array.isArray(props.modelValue)
|
||||
? [...props.modelValue]
|
||||
: [];
|
||||
const index = newValues.indexOf(opt.id);
|
||||
|
||||
if (index > -1) {
|
||||
newValues.splice(index, 1);
|
||||
if (index > -1) {
|
||||
newValues.splice(index, 1); // 取消选中
|
||||
} else {
|
||||
newValues.push(opt.id); // 选中
|
||||
}
|
||||
emit("update:modelValue", newValues);
|
||||
} else {
|
||||
newValues.push(opt.id);
|
||||
// --- 单选逻辑 ---
|
||||
// 关键:单选模式下,直接发射当前 ID,覆盖旧值
|
||||
// 如果点击的是已选中的项,则清空(可选行为,视需求而定)
|
||||
if (props.modelValue === opt.id) {
|
||||
emit("update:modelValue", ""); // 取消选中
|
||||
} else {
|
||||
emit("update:modelValue", opt.id); // 选中新项
|
||||
// 单选模式下,选择后通常希望关闭下拉框
|
||||
open.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 触发更新
|
||||
emit("update:modelValue", newValues);
|
||||
};
|
||||
|
||||
const handleChange = (val: any) => {
|
||||
// 处理直接通过 a-select 内部机制触发的变化(如删除 tag)
|
||||
// 当 a-select 内部触发 change 时(例如删除 Tag)
|
||||
// 在单选模式下,如果用户通过键盘或删除操作改变了值,这里也会捕获
|
||||
emit("update:modelValue", val);
|
||||
};
|
||||
|
||||
@ -141,9 +175,37 @@ const getFishNameById = (id: string) => {
|
||||
const fish = options.value.find((item: any) => item.id === id);
|
||||
return fish ? fish.name : id;
|
||||
};
|
||||
|
||||
// 监听 multiple 变化,确保模式切换时状态正确
|
||||
watch(
|
||||
() => props.multiple,
|
||||
(newVal) => {
|
||||
// 如果从多选变为单选,且 modelValue 是数组,取第一个值或清空
|
||||
if (!newVal && Array.isArray(props.modelValue)) {
|
||||
emit("update:modelValue", props.modelValue || null);
|
||||
}
|
||||
// 如果从单选变为多选,且 modelValue 是字符串,转为数组
|
||||
if (newVal && typeof props.modelValue === "string") {
|
||||
emit("update:modelValue", props.modelValue ? [props.modelValue] : []);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
loading.value = true;
|
||||
getFishDictoryDropdown()
|
||||
.then((res) => {
|
||||
options.value = res.data || [];
|
||||
loading.value = false;
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 样式保持不变 */
|
||||
.custom-fish-select {
|
||||
:deep(.ant-select-dropdown) {
|
||||
padding: 0 !important;
|
||||
@ -157,7 +219,7 @@ const getFishNameById = (id: string) => {
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
height: 300px; /* 固定高度 */
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.dropdown-left-list {
|
||||
|
||||
61
frontend/src/store/modules/shuJuTianBao.ts
Normal file
61
frontend/src/store/modules/shuJuTianBao.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue'; // 使用 ref 更简单直观
|
||||
import { getBaseDropdown, getEngInfoDropdown, getFpssDropdown } from '@/api/select';
|
||||
|
||||
export const useShuJuTianBaoStore = defineStore('shuJuTianBao', () => {
|
||||
// 1. 直接使用 ref 定义状态,确保响应式
|
||||
const fpssOption = ref<any[]>([]);
|
||||
const baseOption = ref<any[]>([]);
|
||||
const engOption = ref<any[]>([]);
|
||||
|
||||
// 2. 业务逻辑方法
|
||||
const getBaseOption = async () => {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
const getEngOption = async (baseId: string) => {
|
||||
try {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const getFpssOption = async (baseId: string, rstcd: string) => {
|
||||
try {
|
||||
const res = await getFpssDropdown({ baseId, rstcd });
|
||||
fpssOption.value = res.data;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
// 3. 直接返回 ref 和方法
|
||||
// 在组件中使用时:store.baseOption 会自动解包为数组
|
||||
return {
|
||||
fpssOption,
|
||||
baseOption,
|
||||
engOption,
|
||||
getBaseOption,
|
||||
getEngOption,
|
||||
getFpssOption,
|
||||
};
|
||||
});
|
||||
@ -2,11 +2,16 @@
|
||||
<div class="guoYuSheShiShuJuTianBao-page">
|
||||
<!-- 搜索区域组件,具体 props 需根据实际子组件调整 -->
|
||||
<GuoYuSheShiShuJuTianBaoSearch
|
||||
:import-btn="importBtn"
|
||||
:save-btn="saveBtn"
|
||||
ref="searchRef"
|
||||
:guoyuStatus="guoyuStatus"
|
||||
:direction="direction"
|
||||
:handle-add="handleAdd"
|
||||
:batchData="batchData"
|
||||
:batchDel="batchDel"
|
||||
:importBtn="importBtn"
|
||||
:batchDelBtn="batchDelBtn"
|
||||
:submitBtn="submitBtn"
|
||||
:successBtn="successBtn"
|
||||
@reset="handleReset"
|
||||
@search-finish="handleSearchFinish"
|
||||
/>
|
||||
|
||||
@ -23,21 +28,68 @@
|
||||
<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
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleSubmit([record.id])"
|
||||
v-if="record.status === 'DRAFT' || record.status === 'REJECTED'"
|
||||
>提交</a-button
|
||||
>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleEdit(record, 'edit')"
|
||||
v-if="record.status === 'DRAFT' || record.status === 'REJECTED'|| record.status === 'SUBMITTED'"
|
||||
>编辑</a-button
|
||||
>
|
||||
<a-button
|
||||
type="link"
|
||||
danger
|
||||
size="small"
|
||||
@click="handleDelete([record.id])"
|
||||
v-if="record.status === 'DRAFT' || record.status === 'REJECTED'"
|
||||
>删除</a-button
|
||||
>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleEdit(record, 'view')"
|
||||
v-if="record.status === 'SUBMITTED'"
|
||||
>查看</a-button
|
||||
>
|
||||
<a-button
|
||||
type="link"
|
||||
size="small"
|
||||
@click="handleSuccess([record.id])"
|
||||
v-if="record.status === 'SUBMITTED'"
|
||||
>审批</a-button
|
||||
>
|
||||
<a-button
|
||||
type="link"
|
||||
danger
|
||||
size="small"
|
||||
@click="handleReject(record.id)"
|
||||
v-if="record.status === 'SUBMITTED'"
|
||||
>驳回</a-button
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</BasicTable>
|
||||
<!-- <BasicTable :columns="columns" :listUrl="getFishDraftPage" /> -->
|
||||
|
||||
<!-- 隐藏的文件输入框 -->
|
||||
<input
|
||||
ref="fileInputRef"
|
||||
type="file"
|
||||
accept=".zip,application/zip,application/x-zip-compressed"
|
||||
style="display: none"
|
||||
@change="handleFileSelect"
|
||||
/>
|
||||
<!-- 导入预览 Modal -->
|
||||
<a-modal
|
||||
title="导入数据预览"
|
||||
ok-text="提交导入"
|
||||
cancel-text="取消"
|
||||
cancel-text="取消导入"
|
||||
:width="1500"
|
||||
v-model:open="visible"
|
||||
:confirm-loading="fileLoading"
|
||||
@ -60,6 +112,8 @@
|
||||
<!-- 假设已创建对应的 Vue 组件 GuoYuSheShiShuJuTianBaoForm -->
|
||||
<EditModal
|
||||
v-model:visible="editModalVisible"
|
||||
:is-view="isView"
|
||||
:direction="direction"
|
||||
:initial-values="currentRecord"
|
||||
:loading="submitLoading"
|
||||
@cancel="editModalCancel"
|
||||
@ -88,10 +142,8 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted, h } from "vue";
|
||||
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";
|
||||
@ -100,9 +152,17 @@ import {
|
||||
addFishDraft,
|
||||
editFishDraft,
|
||||
delFishDraft,
|
||||
submitFishDraft,
|
||||
successFishDraft,
|
||||
rejectFishDraft,
|
||||
importFishZip,
|
||||
submitImportTask,
|
||||
cancelImportTask,
|
||||
getImportTask,
|
||||
checkImportStatus
|
||||
} from "@/api/guoYuSheShiShuJuTianBao";
|
||||
import dayjs from "dayjs";
|
||||
import { Tag } from 'ant-design-vue'; // 确保导入 Tag
|
||||
import { Tag } from "ant-design-vue"; // 确保导入 Tag
|
||||
import { getDictItemsByCode } from "@/api/dict";
|
||||
// import { FileImageOutlined, VideoCameraOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons-vue'
|
||||
|
||||
// --- 类型定义 ---
|
||||
@ -115,34 +175,52 @@ interface ColumnConfig {
|
||||
key: string;
|
||||
title: string;
|
||||
width?: number;
|
||||
fixed?: string;
|
||||
customRender?: (text: any, record: any) => any;
|
||||
}
|
||||
const fileInputRef = ref<any>(null);
|
||||
const tableRef = ref<any>(null);
|
||||
// 字典项
|
||||
const direction = ref<any>([]);
|
||||
const guoyuStatus = ref<any>([]);
|
||||
// --- 基础配置 ---
|
||||
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: "baseName",
|
||||
key: "baseName",
|
||||
title: "水电基地",
|
||||
width: 120,
|
||||
fixed: "left",
|
||||
},
|
||||
{ dataIndex: "ennm", key: "ennm", title: "电站名称", width: 120, fixed: "left" },
|
||||
{ dataIndex: "stnm", key: "stnm", title: "过鱼设施名称", width: 150, fixed: "left" },
|
||||
{ dataIndex: "strdt", key: "strdt", title: "过鱼时间", width: 150 },
|
||||
{ dataIndex: "ftp", key: "ftp", title: "鱼种类", width: 120 },
|
||||
{ dataIndex: "ftpName", key: "ftpName", title: "鱼种类", width: 120 },
|
||||
{
|
||||
dataIndex: "isfs",
|
||||
key: "isfs",
|
||||
title: "是否鱼苗",
|
||||
width: 74,
|
||||
customRender: ({ text }: any) => {
|
||||
const isYes = text === 1 || text === '1';
|
||||
return h(
|
||||
Tag,
|
||||
{
|
||||
color: isYes ? 'success' : 'error', // Antdv Tag 的颜色预设
|
||||
style: { margin: 0 } // 去除默认 margin,使其在表格中对齐更好
|
||||
},
|
||||
() => isYes ? '是' : '否'
|
||||
);
|
||||
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,
|
||||
customRender: ({ text }: any) =>
|
||||
direction.value.find((item: any) => item.itemCode === text)?.dictName || "-",
|
||||
},
|
||||
{ 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 },
|
||||
@ -150,7 +228,22 @@ const baseColumnsConfig: ColumnConfig[] = [
|
||||
{ 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 },
|
||||
{
|
||||
dataIndex: "status",
|
||||
key: "status",
|
||||
title: "状态",
|
||||
width: 100,
|
||||
customRender: ({ text }: any) => {
|
||||
let data = guoyuStatus.value.find((item: any) => item.itemCode === text);
|
||||
return h(
|
||||
Tag,
|
||||
{
|
||||
color: data?.custom1 || "error", // Antdv Tag 的颜色预设
|
||||
},
|
||||
() => data?.dictName || "-"
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// --- 状态定义 ---
|
||||
@ -158,6 +251,7 @@ const visible = ref(false); // 导入预览 Modal
|
||||
|
||||
// 编辑相关状态
|
||||
const editModalVisible = ref(false);
|
||||
const isView = ref(false);
|
||||
const currentRecord = ref<FormData | null>(null);
|
||||
const submitLoading = ref(false);
|
||||
|
||||
@ -175,7 +269,6 @@ const fileLoading = ref(false);
|
||||
// 行内编辑 Key (用于导入预览表格)
|
||||
const editingKey = ref<string | number>("");
|
||||
|
||||
|
||||
// --- 辅助函数 ---
|
||||
|
||||
// 从 Zip 获取 Blob URL
|
||||
@ -235,7 +328,7 @@ const columns = computed(() => {
|
||||
key: "action",
|
||||
dataIndex: "action",
|
||||
fixed: "right",
|
||||
width: 100,
|
||||
width: 200,
|
||||
align: "center",
|
||||
},
|
||||
];
|
||||
@ -256,43 +349,42 @@ const modalColumns = computed(() => {
|
||||
// message.success("行数据已删除");
|
||||
// };
|
||||
|
||||
return baseColumnsConfig
|
||||
.map((col) => ({
|
||||
...col,
|
||||
customRender: ({ text, record, index }: any) => {
|
||||
const editing = isEditing(record, index);
|
||||
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 (col.dataIndex === "level5" || col.dataIndex === "level6") {
|
||||
if (editing) {
|
||||
// 返回 Input 组件标识
|
||||
// 返回 Input 组件的 VNode 或标识,实际需用 slot 或 h 函数
|
||||
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" },
|
||||
// });
|
||||
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" },
|
||||
// });
|
||||
});
|
||||
|
||||
// --- 业务逻辑方法 ---
|
||||
@ -301,17 +393,21 @@ const handleAdd = () => {
|
||||
currentRecord.value = null;
|
||||
editModalVisible.value = true;
|
||||
};
|
||||
|
||||
const handleEdit = (record: any) => {
|
||||
// 修改
|
||||
const handleEdit = (record: any, type: string) => {
|
||||
if (type == "view") {
|
||||
isView.value = true;
|
||||
} else {
|
||||
isView.value = false;
|
||||
}
|
||||
currentRecord.value = { ...record };
|
||||
editModalVisible.value = true;
|
||||
};
|
||||
|
||||
// 删除过鱼数据
|
||||
// 单个/批量 删除过鱼数据
|
||||
const handleDelete = (ids: any[]) => {
|
||||
console.log(ids)
|
||||
Modal.confirm({
|
||||
title: "是否确认删除选中数据吗?",
|
||||
title: "是否确认 删除 选中数据吗?",
|
||||
onOk: async () => {
|
||||
let res: any = await delFishDraft(ids);
|
||||
if (res && res?.code == 0) {
|
||||
@ -321,10 +417,118 @@ const handleDelete = (ids: any[]) => {
|
||||
},
|
||||
});
|
||||
};
|
||||
// 批量删除
|
||||
const batchDel = () => {
|
||||
// 批量删除 - 按钮
|
||||
const batchDelBtn = () => {
|
||||
handleDelete(batchData.value);
|
||||
};
|
||||
//单个/ 批量提交过鱼数据
|
||||
const handleSubmit = (ids: any[]) => {
|
||||
Modal.confirm({
|
||||
title: "是否 提交 选中数据吗?",
|
||||
onOk: async () => {
|
||||
let res: any = await submitFishDraft(ids);
|
||||
if (res && res?.code == 0) {
|
||||
message.success("提交成功");
|
||||
tableRef.value?.getList();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
// 批量提交-按钮
|
||||
const submitBtn = async () => {
|
||||
handleSubmit(batchData.value);
|
||||
};
|
||||
|
||||
// 单个/ 批量审批过鱼数据
|
||||
const handleSuccess = (ids: any[]) => {
|
||||
let rejectReason = "";
|
||||
Modal.confirm({
|
||||
title: "是否确认 审批通过 选中数据?",
|
||||
// 使用 h 函数创建内容
|
||||
content: h("div", { style: "margin-top: 10px;" }, [
|
||||
h("div", { style: "margin-bottom: 8px;" }, [
|
||||
h("span", { style: "color: red;" }, "* "),
|
||||
h("span", "审批原因:"),
|
||||
]),
|
||||
h("textarea", {
|
||||
class: "ant-input", // 使用 antdv 的样式类,使其看起来像 Antdv 输入框
|
||||
placeholder: "请输入审批原因",
|
||||
rows: 4,
|
||||
style: {
|
||||
width: "100%",
|
||||
resize: "none",
|
||||
border: "1px solid #d9d9d9",
|
||||
padding: "8px",
|
||||
borderRadius: "4px",
|
||||
},
|
||||
// 监听输入事件,更新局部变量
|
||||
onInput: (e: Event) => {
|
||||
rejectReason = (e.target as HTMLTextAreaElement).value;
|
||||
},
|
||||
}),
|
||||
]),
|
||||
okText: "确认审批",
|
||||
cancelText: "取消",
|
||||
onOk: async () => {
|
||||
let res: any = await successFishDraft({ ids: ids, approveComment: rejectReason });
|
||||
if (res && res?.code == 0) {
|
||||
message.success("审批成功");
|
||||
tableRef.value?.getList();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
// 批量审批-按钮
|
||||
const successBtn = async () => {
|
||||
handleSuccess(batchData.value);
|
||||
};
|
||||
|
||||
// 批量驳回过鱼数据
|
||||
const handleReject = (id: any) => {
|
||||
let rejectReason = "";
|
||||
|
||||
Modal.confirm({
|
||||
title: "是否确认 驳回 选中数据?",
|
||||
// 使用 h 函数创建内容
|
||||
content: h("div", { style: "margin-top: 10px;" }, [
|
||||
h("div", { style: "margin-bottom: 8px;" }, [
|
||||
h("span", { style: "color: red;" }, "* "),
|
||||
h("span", "驳回原因:"),
|
||||
]),
|
||||
h("textarea", {
|
||||
class: "ant-input", // 使用 antdv 的样式类,使其看起来像 Antdv 输入框
|
||||
placeholder: "请输入驳回原因",
|
||||
rows: 4,
|
||||
style: {
|
||||
width: "100%",
|
||||
resize: "none",
|
||||
border: "1px solid #d9d9d9",
|
||||
padding: "8px",
|
||||
borderRadius: "4px",
|
||||
},
|
||||
// 监听输入事件,更新局部变量
|
||||
onInput: (e: Event) => {
|
||||
rejectReason = (e.target as HTMLTextAreaElement).value;
|
||||
},
|
||||
}),
|
||||
]),
|
||||
okText: "确认驳回",
|
||||
cancelText: "取消",
|
||||
onOk: async () => {
|
||||
// 校验驳回原因不能为空
|
||||
if (!rejectReason || rejectReason.trim() === "") {
|
||||
message.warning("请输入驳回原因");
|
||||
return Promise.reject(); // 阻止 Modal 关闭
|
||||
}
|
||||
|
||||
let res: any = await rejectFishDraft({ id: id, rejectReason: rejectReason });
|
||||
if (res && res?.code == 0) {
|
||||
message.success("驳回成功");
|
||||
tableRef.value?.getList();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
// 多选
|
||||
const handleSelectionChange = (keys: any) => {
|
||||
batchData.value = keys;
|
||||
@ -332,17 +536,13 @@ const handleSelectionChange = (keys: any) => {
|
||||
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
|
||||
...values,
|
||||
});
|
||||
if (res && res?.code == 0) {
|
||||
message.success("编辑成功");
|
||||
@ -350,27 +550,10 @@ const handleEditSubmit = async (values: FormData) => {
|
||||
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("新增成功");
|
||||
@ -379,152 +562,154 @@ const handleEditSubmit = async (values: FormData) => {
|
||||
}
|
||||
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("数据已导入至列表");
|
||||
Modal.confirm({
|
||||
title: "是否提交导入数据?",
|
||||
onOk: async () => {
|
||||
// tableData.value = [...fileTableData.value];
|
||||
visible.value = false;
|
||||
editingKey.value = "";
|
||||
message.success("数据已导入至列表");
|
||||
// let res: any = await submitImportTask(ids);
|
||||
// if (res && res?.code == 0) {
|
||||
// message.success("审批成功");
|
||||
// tableRef.value?.getList();
|
||||
// }
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleModalCancel = () => {
|
||||
visible.value = false;
|
||||
editingKey.value = "";
|
||||
Modal.confirm({
|
||||
title: "是否取消导入数据?",
|
||||
onOk: async () => {
|
||||
visible.value = false;
|
||||
editingKey.value = "";
|
||||
// let res: any = await cancelImportTask(ids);
|
||||
// if (res && res?.code == 0) {
|
||||
// message.success("审批成功");
|
||||
// tableRef.value?.getList();
|
||||
// }
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
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> = {};
|
||||
const handleFileSelect = (e: Event) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
const file = target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
// 构建路径映射
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
// 校验文件大小 (50MB)
|
||||
const maxSize = 50 * 1024 * 1024;
|
||||
if (file.size > maxSize) {
|
||||
message.error("文件大小不能超过50MB");
|
||||
resetFileInput();
|
||||
return;
|
||||
}
|
||||
|
||||
const fileNames = Object.keys(zip.files);
|
||||
if (fileNames.length === 0) {
|
||||
hideMessage();
|
||||
message.warning("压缩包为空");
|
||||
fileLoading.value = false;
|
||||
return;
|
||||
}
|
||||
// 校验文件类型
|
||||
const isZip =
|
||||
file.name.toLowerCase().endsWith(".zip") ||
|
||||
file.type === "application/zip" ||
|
||||
file.type === "application/x-zip-compressed";
|
||||
|
||||
let allExcelData: any[] = [];
|
||||
for (const fileName of fileNames) {
|
||||
const zipEntry = zip.files[fileName];
|
||||
if (zipEntry.dir) continue;
|
||||
if (!fileName.match(/\.(xls|xlsx)$/i)) continue;
|
||||
if (!isZip) {
|
||||
message.error("请选择.zip格式的压缩包");
|
||||
resetFileInput();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const arrayBuffer = await zipEntry.async("arraybuffer");
|
||||
const data = await parseExcelFile(fileName, arrayBuffer);
|
||||
if (!data || data.length === 0) continue;
|
||||
// props.importBtn(file);
|
||||
resetFileInput();
|
||||
};
|
||||
|
||||
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 resetFileInput = () => {
|
||||
if (fileInputRef.value) {
|
||||
fileInputRef.value.value = "";
|
||||
}
|
||||
};
|
||||
const importBtn = async (file: File) => {
|
||||
let res: any = await checkImportStatus();
|
||||
console.log(res)
|
||||
// fileInputRef.value?.click();
|
||||
// fileLoading.value = true;
|
||||
// editingKey.value = "";
|
||||
|
||||
const saveBtn = async () => {
|
||||
// TODO: 实现保存逻辑
|
||||
console.log("Save button clicked");
|
||||
// try {
|
||||
// const formData = new FormData();
|
||||
// formData.append("file", file);
|
||||
// let res: any = await importFishZip(formData);
|
||||
// console.log(res);
|
||||
// visible.value = true;
|
||||
// } catch (error) {
|
||||
// } finally {
|
||||
// // fileLoading.value = false;
|
||||
// }
|
||||
};
|
||||
|
||||
const handleReset = (values) => {
|
||||
handleSearchFinish(values);
|
||||
};
|
||||
// 搜索-按钮
|
||||
const handleSearchFinish = (values: any) => {
|
||||
console.log(values);
|
||||
// const newSearchData = { ...searchData.value, ...e };
|
||||
// searchData.value = newSearchData;
|
||||
// getData(newSearchData, label);
|
||||
const filters = [
|
||||
values.ftp && {
|
||||
field: "ftp",
|
||||
operator: "eq",
|
||||
dataType: "string",
|
||||
value: values.ftp,
|
||||
},
|
||||
{
|
||||
field: "TM",
|
||||
operator: "gte",
|
||||
dataType: "date",
|
||||
value: values.strdt[0],
|
||||
},
|
||||
{
|
||||
field: "TM",
|
||||
operator: "lte",
|
||||
dataType: "date",
|
||||
value: values.strdt[1],
|
||||
},
|
||||
values.direction && {
|
||||
field: "direction",
|
||||
operator: "eq",
|
||||
dataType: "string",
|
||||
value: values.direction,
|
||||
},
|
||||
values.status && {
|
||||
field: "status",
|
||||
operator: "eq",
|
||||
dataType: "string",
|
||||
value: values.status,
|
||||
},
|
||||
values.stcd && {
|
||||
field: "stcd",
|
||||
operator: "eq",
|
||||
dataType: "string",
|
||||
value: values.stcd,
|
||||
},
|
||||
values.rstcd && {
|
||||
field: "rstcd",
|
||||
operator: "eq",
|
||||
dataType: "string",
|
||||
value: values.rstcd,
|
||||
},
|
||||
values.baseId !== "all" && {
|
||||
field: "baseId",
|
||||
operator: "eq",
|
||||
dataType: "string",
|
||||
value: values.baseId,
|
||||
},
|
||||
].filter(Boolean);
|
||||
|
||||
const filter = {
|
||||
logic: "and",
|
||||
filters: filters,
|
||||
};
|
||||
tableRef.value?.getList(filter);
|
||||
};
|
||||
|
||||
const closeVideoPreview = () => {
|
||||
@ -533,7 +718,14 @@ const closeVideoPreview = () => {
|
||||
};
|
||||
|
||||
// --- 生命周期 ---
|
||||
onMounted(() => {});
|
||||
onMounted(() => {
|
||||
getDictItemsByCode({ dictCode: "direction" }).then((res) => {
|
||||
direction.value = res.data;
|
||||
});
|
||||
getDictItemsByCode({ dictCode: "guoyuStatus" }).then((res) => {
|
||||
guoyuStatus.value = res.data;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:title="isEdit ? '编辑数据' : '新增数据'"
|
||||
:title="isView ? '查看数据' : isEdit ? '编辑数据' : '新增数据'"
|
||||
v-model:open="modalVisible"
|
||||
:confirm-loading="loading"
|
||||
width="800px"
|
||||
:destroy-on-close="true"
|
||||
:footer="isView ? null : undefined"
|
||||
@cancel="handleCancel"
|
||||
@ok="handleOk"
|
||||
>
|
||||
@ -17,21 +18,58 @@
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="水电基地" name="engName">
|
||||
<a-input v-model:value="formData.engName" placeholder="请输入水电基地" />
|
||||
<a-form-item label="水电基地" name="baseId">
|
||||
<a-select
|
||||
v-model:value="formData.baseId"
|
||||
:loading="baseLoading"
|
||||
placeholder="请选择水电基地"
|
||||
:disabled="isView"
|
||||
@change="baseChange"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="opt in baseOption"
|
||||
:key="opt.baseid"
|
||||
:value="opt.baseid"
|
||||
>
|
||||
{{ opt.basename }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
<a-form-item label="电站名称" name="baseName">
|
||||
<a-input v-model:value="formData.baseName" placeholder="请输入电站名称" />
|
||||
<a-form-item label="电站名称" name="rstcd">
|
||||
<a-select
|
||||
v-model:value="formData.rstcd"
|
||||
:loading="engLoading"
|
||||
placeholder="请选择电站名称"
|
||||
:disabled="isView"
|
||||
@change="engChange"
|
||||
>
|
||||
<a-select-option v-for="opt in engOption" :key="opt.stcd" :value="opt.stcd">
|
||||
{{ opt.ennm }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<a-form-item label="过鱼设施名称" name="fpname">
|
||||
<a-input v-model:value="formData.fpname" placeholder="请输入过鱼设施名称" />
|
||||
<a-form-item label="过鱼设施" name="stcd">
|
||||
<a-select
|
||||
v-model:value="formData.stcd"
|
||||
:loading="fpssLoading"
|
||||
placeholder="请选择过鱼设施"
|
||||
:disabled="isView"
|
||||
>
|
||||
<a-select-option
|
||||
v-for="opt in fpssOption"
|
||||
:key="opt.stcd"
|
||||
:value="opt.stcd"
|
||||
>
|
||||
{{ opt.stnm }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="12">
|
||||
@ -42,6 +80,7 @@
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="选择日期"
|
||||
:disabled="isView"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
@ -50,12 +89,12 @@
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="12">
|
||||
<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-col>
|
||||
<a-col :span="12">
|
||||
<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="0">否</a-radio>
|
||||
</a-radio-group>
|
||||
@ -70,11 +109,14 @@
|
||||
v-model:value="formData.direction"
|
||||
placeholder="请选择游向"
|
||||
allow-clear
|
||||
:disabled="isView"
|
||||
>
|
||||
<a-select-option value="上行">上行</a-select-option>
|
||||
<a-select-option value="下行">下行</a-select-option>
|
||||
<a-select-option value="上行折返">上行折返</a-select-option>
|
||||
<a-select-option value="下行折返">下行折返</a-select-option>
|
||||
<a-select-option
|
||||
v-for="item in direction"
|
||||
:key="item.itemCode"
|
||||
:value="item.itemCode"
|
||||
>{{ item.dictName }}</a-select-option
|
||||
>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
@ -85,6 +127,7 @@
|
||||
style="width: 100%"
|
||||
placeholder="数量"
|
||||
:min="0"
|
||||
:disabled="isView"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
@ -104,6 +147,7 @@
|
||||
placeholder="请输入"
|
||||
:min="0"
|
||||
@change="validateBodyLength"
|
||||
:disabled="isView"
|
||||
/>
|
||||
<span class="px-[10px]">~</span>
|
||||
<a-input-number
|
||||
@ -112,6 +156,7 @@
|
||||
placeholder="请输入"
|
||||
:min="0"
|
||||
@change="validateBodyLength"
|
||||
:disabled="isView"
|
||||
/>
|
||||
</div>
|
||||
</a-form-item>
|
||||
@ -129,6 +174,7 @@
|
||||
placeholder="请输入"
|
||||
:min="0"
|
||||
@change="validateWeight"
|
||||
:disabled="isView"
|
||||
/>
|
||||
<span class="px-[10px]">~</span>
|
||||
<a-input-number
|
||||
@ -137,6 +183,7 @@
|
||||
placeholder="请输入"
|
||||
:min="0"
|
||||
@change="validateWeight"
|
||||
:disabled="isView"
|
||||
/>
|
||||
</div>
|
||||
</a-form-item>
|
||||
@ -150,6 +197,7 @@
|
||||
style="width: 100%"
|
||||
placeholder="水温"
|
||||
:min="0"
|
||||
:disabled="isView"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
@ -172,21 +220,77 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch, computed } from "vue";
|
||||
import dayjs from "dayjs";
|
||||
import { message } from "ant-design-vue";
|
||||
import type { Rule } from "ant-design-vue/es/form";
|
||||
import fishSearch from "@/components/fishSearch/index.vue";
|
||||
import { getBaseDropdown, getEngInfoDropdown, getFpssDropdown } from "@/api/select";
|
||||
|
||||
// 定义 Props
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
direction: any[];
|
||||
initialValues?: any | null;
|
||||
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>(), {
|
||||
visible: false,
|
||||
initialValues: null,
|
||||
loading: false,
|
||||
});
|
||||
// 1. 在表单内部定义独立的电站列表状态
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
// --- 核心修复:创建计算属性处理 v-model ---
|
||||
const modalVisible = computed({
|
||||
get: () => props.visible,
|
||||
@ -208,10 +312,9 @@ const weightError = ref<string>("");
|
||||
// 表单数据模型
|
||||
const defaultFormData = reactive({
|
||||
id: undefined,
|
||||
engName: undefined,
|
||||
baseName: undefined,
|
||||
fpname: undefined,
|
||||
baseId: undefined,
|
||||
stcd: undefined,
|
||||
rstcd: undefined,
|
||||
strdt: undefined,
|
||||
ftp: undefined,
|
||||
isfs: 0,
|
||||
@ -229,41 +332,15 @@ const defaultFormData = reactive({
|
||||
});
|
||||
const formData: any = reactive({ ...defaultFormData });
|
||||
|
||||
// 自定义验证器:检查最小值是否小于最大值
|
||||
// const validateBodyLengthRange = (rule: any, value: any) => {
|
||||
// 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[]> = {
|
||||
// engName: [{ required: true, message: "请输入水电基地", trigger: "blur" }],
|
||||
// baseName: [{ required: true, message: "请输入电站名称", trigger: "blur" }],
|
||||
// fpname: [{ required: true, message: "请输入过鱼设施名称", trigger: "blur" }],
|
||||
// baseId: [{ required: true, message: "请选择水电基地", trigger: "change" }],
|
||||
// rstcd: [{ required: true, message: "请选择电站", trigger: "change" }],
|
||||
// stcd: [{ required: true, message: "请选择过鱼设施", trigger: "change" }],
|
||||
// strdt: [{ required: true, message: "请选择过鱼时间", trigger: "change" }],
|
||||
// // 添加体长的验证规则
|
||||
// bodyLengthMin: [{ validator: validateBodyLengthRange, trigger: "change" }],
|
||||
// bodyLengthMax: [{ validator: validateBodyLengthRange, trigger: "change" }],
|
||||
// weightMin: [{ validator: validateWeightRange, trigger: "change" }],
|
||||
// weightMax: [{ validator: validateWeightRange, trigger: "change" }],
|
||||
// ftp: [{ required: true, message: "请选择鱼种类", trigger: "change" }],
|
||||
// direction: [{ required: true, message: "请选择游向", trigger: "change" }],
|
||||
// fcnt: [{ required: true, message: "请输入过鱼数量", trigger: "change" }],
|
||||
};
|
||||
|
||||
// 计算是否为编辑模式
|
||||
@ -276,14 +353,25 @@ const validateBodyLength = () => {
|
||||
// 重置错误
|
||||
bodyLengthError.value = "";
|
||||
|
||||
// 如果两个值都有,才进行比对
|
||||
if (min !== undefined && min !== null && max !== undefined && max !== null) {
|
||||
if (Number(min) >= Number(max)) {
|
||||
bodyLengthError.value = "最小体长必须小于最大体长";
|
||||
// 如果需要阻止提交,可以在 handleOk 中检查这个变量
|
||||
// 判断是否有值 (排除 undefined, null, 和空字符串)
|
||||
const hasMin = min !== undefined && min !== null && min !== "";
|
||||
const hasMax = max !== undefined && max !== null && max !== "";
|
||||
|
||||
// 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 true;
|
||||
};
|
||||
|
||||
@ -294,12 +382,25 @@ const validateWeight = () => {
|
||||
// 重置错误
|
||||
weightError.value = "";
|
||||
|
||||
if (min !== undefined && min !== null && max !== undefined && max !== null) {
|
||||
if (Number(min) >= Number(max)) {
|
||||
weightError.value = "最小体重必须小于最大体重";
|
||||
// 判断是否有值
|
||||
const hasMin = min !== undefined && min !== null && min !== "";
|
||||
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 true;
|
||||
};
|
||||
|
||||
@ -312,8 +413,13 @@ const initForm = () => {
|
||||
// 处理特殊字段拆分
|
||||
if (values.fwet) {
|
||||
const weights = values.fwet.split("~");
|
||||
formData.weightMin = weights[0];
|
||||
formData.weightMax = weights[1];
|
||||
if (weights.length === 2) {
|
||||
formData.weightMin = weights[0];
|
||||
formData.weightMax = weights[1];
|
||||
} else {
|
||||
formData.weightMin = weights[0];
|
||||
formData.weightMax = weights[0];
|
||||
}
|
||||
} else {
|
||||
formData.weightMin = undefined;
|
||||
formData.weightMax = undefined;
|
||||
@ -321,8 +427,13 @@ const initForm = () => {
|
||||
|
||||
if (values.fsz) {
|
||||
const sizes = values.fsz.split("~");
|
||||
formData.bodyLengthMin = sizes[0];
|
||||
formData.bodyLengthMax = sizes[1];
|
||||
if (sizes.length === 2) {
|
||||
formData.bodyLengthMin = sizes[0];
|
||||
formData.bodyLengthMax = sizes[1];
|
||||
} else {
|
||||
formData.bodyLengthMin = sizes[0];
|
||||
formData.bodyLengthMax = sizes[0];
|
||||
}
|
||||
} else {
|
||||
formData.bodyLengthMin = undefined;
|
||||
formData.bodyLengthMax = undefined;
|
||||
@ -356,6 +467,9 @@ watch(
|
||||
(newVisible) => {
|
||||
if (newVisible) {
|
||||
// 弹窗打开时,初始化数据
|
||||
getBaseDropdownSelect();
|
||||
getEngInfoDropdownSelect(formData.baseId);
|
||||
getFpssDropdownSelect(formData.rstcd, formData.baseId);
|
||||
initForm();
|
||||
} else {
|
||||
// 弹窗关闭时,可以选择是否重置,或者留给下次打开时处理
|
||||
@ -378,8 +492,8 @@ const resetForm = () => {
|
||||
}
|
||||
Object.assign(formData, defaultFormData);
|
||||
// 清空手动验证的错误信息
|
||||
bodyLengthError.value = '';
|
||||
weightError.value = '';
|
||||
bodyLengthError.value = "";
|
||||
weightError.value = "";
|
||||
};
|
||||
|
||||
// 取消操作
|
||||
@ -402,13 +516,41 @@ const handleOk = async () => {
|
||||
}
|
||||
// 验证表单
|
||||
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 = {
|
||||
...formData,
|
||||
fwet: formData.weightMin + "~" + formData.weightMax,
|
||||
fsz: formData.bodyLengthMin + "~" + formData.bodyLengthMax,
|
||||
fwet: fwet,
|
||||
fsz: fsz,
|
||||
};
|
||||
|
||||
if (!formData.id) submitValues.tm = dayjs().format("YYYY-MM-DD HH:mm:ss")
|
||||
console.log(submitValues);
|
||||
emit("ok", submitValues);
|
||||
} catch (error) {
|
||||
console.error("Validate Failed:", error);
|
||||
|
||||
@ -1,52 +1,38 @@
|
||||
<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"
|
||||
@reset="handleReset"
|
||||
@finish="onSearchFinish"
|
||||
@values-change="onValuesChange"
|
||||
>
|
||||
<template #typeDate="{ onChange }">
|
||||
<fishSearch
|
||||
v-model="localTypeDate"
|
||||
width="280px"
|
||||
:options="options"
|
||||
@update:modelValue="onChange"
|
||||
/>
|
||||
<template #ftp="{ onChange }">
|
||||
<fishSearch v-model="localTypeDate" width="280px" @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">
|
||||
<a-button v-hasPerm="['sjtb:import-zip']" @click="props.importBtn">
|
||||
导入zip
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
<a-button @click="props.batchDel" :disabled="batchData.length === 0">
|
||||
<a-button @click="props.batchDelBtn" :disabled="batchData.length === 0">
|
||||
批量删除
|
||||
</a-button>
|
||||
<a-tooltip title="提交数据">
|
||||
<a-button @click="props.saveBtn">
|
||||
<a-button @click="props.submitBtn" :disabled="batchData.length === 0">
|
||||
<template #icon><SaveOutlined /></template>
|
||||
提交数据
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
|
||||
<a-tooltip title="批量审批">
|
||||
<a-button @click="props.saveBtn">
|
||||
<a-button @click="props.successBtn" :disabled="batchData.length === 0">
|
||||
<template #icon><CheckSquareOutlined /></template>
|
||||
批量审批
|
||||
</a-button>
|
||||
@ -77,224 +63,43 @@ 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";
|
||||
|
||||
// --- Props & Emits ---
|
||||
interface Props {
|
||||
direction: any[];
|
||||
guoyuStatus: any[];
|
||||
importBtn: (file: File) => void;
|
||||
batchDel: () => void;
|
||||
saveBtn: () => void;
|
||||
batchDelBtn: () => void;
|
||||
submitBtn: () => void;
|
||||
successBtn: () => void;
|
||||
batchData: any[];
|
||||
handleAdd: () => void;
|
||||
}
|
||||
const shuJuTianBaoStore = useShuJuTianBaoStore();
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "searchFinish", values: any, label: string): void;
|
||||
(e: "reset", values: any): void;
|
||||
(e: "searchFinish", values: any): void;
|
||||
}>();
|
||||
const localTypeDate = ref<string[]>([]);
|
||||
const localTypeDate = ref<string>(null);
|
||||
// --- State ---
|
||||
const basicSearchRef = ref<any>(); // 1. 定义 ref
|
||||
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: [],
|
||||
baseId: "all",
|
||||
stcd: null,
|
||||
rstcd: null,
|
||||
ftp: null,
|
||||
status: null,
|
||||
direction: null,
|
||||
strdt: [
|
||||
dayjs().startOf("month").format("YYYY-MM-DD"),
|
||||
dayjs().endOf("day").format("YYYY-MM-DD"),
|
||||
dayjs().startOf("month").format("YYYY-MM-DD HH:mm:ss"),
|
||||
dayjs().endOf("day").format("YYYY-MM-DD HH:mm:ss"),
|
||||
],
|
||||
};
|
||||
|
||||
@ -305,7 +110,7 @@ const searchData = ref<any>({ ...initSearchData });
|
||||
const searchList: any = computed(() => [
|
||||
{
|
||||
type: "waterStation",
|
||||
name: "engName",
|
||||
name: "baseId",
|
||||
label: "水电基地",
|
||||
fieldProps: {
|
||||
allowClear: true,
|
||||
@ -314,24 +119,20 @@ const searchList: any = computed(() => [
|
||||
},
|
||||
{
|
||||
type: "Select",
|
||||
name: "fpname",
|
||||
name: "stcd",
|
||||
label: "过鱼设施",
|
||||
values: { name: "stnm", value: "rstcd" },
|
||||
fieldProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
options: [],
|
||||
options: shuJuTianBaoStore.fpssOption,
|
||||
},
|
||||
{
|
||||
type: "Select",
|
||||
name: "direction",
|
||||
label: "游向",
|
||||
width: 120,
|
||||
options: [
|
||||
{ label: "上行", value: "上行" },
|
||||
{ label: "下行", value: "下行" },
|
||||
{ label: "上行折返", value: "上行折返" },
|
||||
{ label: "下行折返", value: "下行折返" },
|
||||
],
|
||||
options: props.direction,
|
||||
fieldProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
@ -352,10 +153,7 @@ const searchList: any = computed(() => [
|
||||
fieldProps: {
|
||||
allowClear: true,
|
||||
},
|
||||
options: [
|
||||
{ label: "正常", value: "01" },
|
||||
{ label: "异常", value: "02" },
|
||||
],
|
||||
options: props.guoyuStatus,
|
||||
},
|
||||
|
||||
{
|
||||
@ -374,43 +172,6 @@ const searchList: any = computed(() => [
|
||||
},
|
||||
]);
|
||||
// --- 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();
|
||||
};
|
||||
@ -421,52 +182,46 @@ const onSearchFinish = (values: any) => {
|
||||
// 模拟获取 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);
|
||||
emit("searchFinish", values);
|
||||
};
|
||||
|
||||
const onValuesChange = (changedValues: any, allValues: any) => {
|
||||
// 同步更新本地 searchData,以便其他逻辑使用
|
||||
console.log(changedValues, allValues);
|
||||
searchData.value = { ...searchData.value, ...allValues };
|
||||
|
||||
if (changedValues.strdt) {
|
||||
// 如果需要在时间改变时做额外处理
|
||||
console.log("Time changed:", changedValues.strdt);
|
||||
// 同步更新本地 searchData,以便其他逻辑使用
|
||||
if (changedValues.rstcd || changedValues.baseId) {
|
||||
shuJuTianBaoStore.getFpssOption(
|
||||
allValues.baseId == "all" ? "" : allValues.baseId,
|
||||
allValues.rstcd
|
||||
);
|
||||
const formInstance = basicSearchRef.value?.formData;
|
||||
formInstance.stcd = null;
|
||||
}
|
||||
};
|
||||
|
||||
// const handleReset = (form: any) => {
|
||||
// // 重置表单
|
||||
// if (form) {
|
||||
// form.resetFields();
|
||||
// }
|
||||
|
||||
// // 重置后重新设置默认值并触发搜索
|
||||
// nextTick(() => {
|
||||
// if (form) {
|
||||
// form.setFieldsValue(initSearchData);
|
||||
// }
|
||||
// // 触发初始搜索
|
||||
// emit("searchFinish", initSearchData, "两河口出库水温站");
|
||||
// });
|
||||
// };
|
||||
const handleReset = () => {
|
||||
localTypeDate.value = null;
|
||||
emit("reset", searchData.value);
|
||||
};
|
||||
watch(
|
||||
() => initSearchData.typeDate,
|
||||
() => initSearchData.ftp,
|
||||
(newVal) => {
|
||||
localTypeDate.value = newVal || [];
|
||||
localTypeDate.value = newVal || "";
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
// --- Lifecycle ---
|
||||
onMounted(() => {
|
||||
// 初始请求
|
||||
emit("searchFinish", initSearchData, "两河口出库水温站");
|
||||
emit("searchFinish", initSearchData);
|
||||
|
||||
shuJuTianBaoStore.getFpssOption("", "");
|
||||
});
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user