添加数据填报以及地图整理

This commit is contained in:
扈兆增 2026-04-20 16:57:54 +08:00
parent 6d1b71d783
commit 6119407300
32 changed files with 1382691 additions and 354 deletions

View File

@ -21,14 +21,19 @@
"better-scroll": "^2.4.2", "better-scroll": "^2.4.2",
"dayjs": "^1.11.20", "dayjs": "^1.11.20",
"default-passive-events": "^2.0.0", "default-passive-events": "^2.0.0",
"dom-to-image": "^2.6.0",
"echarts": "^5.2.2", "echarts": "^5.2.2",
"element-plus": "^2.2.27", "element-plus": "^2.2.27",
"esri-leaflet": "^3.0.19", "esri-leaflet": "^3.0.19",
"js-base64": "^3.7.5", "js-base64": "^3.7.5",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",
"jsencrypt": "^3.3.2", "jsencrypt": "^3.3.2",
"jszip": "^3.10.1",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
"lodash": "^4.18.1",
"moment": "^2.30.1",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"ol": "^10.8.0",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"path-to-regexp": "^6.2.0", "path-to-regexp": "^6.2.0",
"pinia": "^2.0.12", "pinia": "^2.0.12",
@ -39,7 +44,8 @@
"vue-router": "^4.1.6", "vue-router": "^4.1.6",
"vuedraggable": "^2.24.3", "vuedraggable": "^2.24.3",
"wujie": "^1.0.24", "wujie": "^1.0.24",
"wujie-vue3": "^1.0.24" "wujie-vue3": "^1.0.24",
"xlsx": "^0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "^16.2.3", "@commitlint/cli": "^16.2.3",

View File

@ -1,12 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { ElConfigProvider } from 'element-plus'; import { ElConfigProvider } from 'element-plus';
import { useAppStore,usetTheme } from '@/store/modules/app'; import { useAppStore,usetTheme } from '@/store/modules/app';
import locale from "ant-design-vue/es/locale/zh_CN";
const appStore = useAppStore(); const appStore = useAppStore();
</script> </script>
<template> <template>
<el-config-provider :locale="appStore.locale" :size="appStore.size"> <el-config-provider :locale="appStore.locale" :size="appStore.size">
<a-config-provider :theme="usetTheme"> <a-config-provider :theme="usetTheme" :locale="locale">
<router-view /> <router-view />
</a-config-provider> </a-config-provider>
</el-config-provider> </el-config-provider>

1134498
frontend/src/assets/geoJson.json Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 303 KiB

View File

@ -1,44 +1,60 @@
<template> <template>
<div class="baselayer-switcher"> <div class="baselayer-switcher" :style="{ right: drawerOpen ? '480px' : '12px' }">
<div <div
class="switcher-item" class="switcher-item"
v-for="item in data" v-for="item in layers"
:key="item.name" :key="item.key"
:class="{ active: item.name === activeLayer }" :class="{ active: item.key === activeKey }"
@click="activeLayer = item.name" @click="handleSwitch(item.key)"
> >
<img :src="item.img" alt="" /> <img :src="item.img" alt="" />
<div class="label">{{ item.name }}</div> <div class="label">{{ item.name }}</div>
</div> </div>
<div class="nineSectionsImg"> <div class="nineSectionsImg">
<img :src="nineSectionsImg" alt="" /> <img :src="nineSectionsImg[activeKey]" alt="" />
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch, onMounted } from "vue"; import { ref, watch, onMounted } from "vue";
import mapShiliangtu from "@/assets/images/map-shiliangtu.png"; import { useUiStore } from "@/store/modules/ui";
import mapDixingtu from "@/assets/images/map-dixingtu.png"; import shiliangImg from "@/assets/images/map-shiliangtu.png";
import mapYingxiangtu from "@/assets/images/map-yingxiangtu.png"; import dixingImg from "@/assets/images/map-dixingtu.png";
import nineSectionsShiliang from "@/assets/images/nineSections-shiliang.png"; import yingxiangImg from "@/assets/images/map-yingxiangtu.png";
import nineSectionsDixing from "@/assets/images/nineSections-dixing.png"; import nineSectionsShiliangImg from "@/assets/images/nineSections-shiliang.png";
import nineSectionsYingxiang from "@/assets/images/nineSections-yingxiang.png"; import nineSectionsDixingImg from "@/assets/images/nineSections-dixing.png";
const data = ref([ import nineSectionsYingxiangImg from "@/assets/images/nineSections-yingxiang.png";
{ name: "矢量", img: mapShiliangtu }, const props = defineProps({
{ name: "地形", img: mapDixingtu }, map: {
{ name: "影像", img: mapYingxiangtu }, type: Object,
]); default: () => {},
const nineSectionsImg = ref(nineSectionsShiliang); },
const nineSectionsData = ref([ })
{ name: "矢量", img: nineSectionsShiliang }, const uiStore = useUiStore();
{ name: "地形", img: nineSectionsDixing }, const drawerOpen = ref(uiStore.drawerOpen);
{ name: "影像", img: nineSectionsYingxiang },
]);
const activeLayer = ref("矢量");
watch(activeLayer, (val) => { // store drawerOpen
nineSectionsImg.value = watch(
nineSectionsData.value.find((item) => item.name === val)?.img || ""; () => uiStore.drawerOpen,
(newVal) => {
drawerOpen.value = newVal;
}
);
const layers = [
{ key: "s_province_boundaries", name: "矢量", img: shiliangImg },
{ key: "BASEMAP-white", name: "地形", img: dixingImg },
{ key: "BASEMAP-img", name: "影像", img: yingxiangImg },
];
const nineSectionsImg:any = {'s_province_boundaries':nineSectionsShiliangImg,'BASEMAP-white':nineSectionsDixingImg,'BASEMAP-img':nineSectionsYingxiangImg,}
const activeKey = ref(layers[0].key);
const handleSwitch = (key: string) => {
activeKey.value = key;
props.map.baseLayerSwitcher(key);
}
watch(activeKey, (val) => {
// nineSectionsImg.value =
// nineSectionsData.value.find((item) => item.name === val)?.img || "";
}); });
</script> </script>

View File

@ -0,0 +1,275 @@
<template>
<div class="basic-search-container">
<a-form
ref="formRef"
:model="formData"
:rules="rules"
layout="inline"
class="basic-search-form"
@finish="handleFinish"
@values-change="handleValuesChange"
>
<div class="form-content-wrapper">
<!-- 1. 搜索项 + 查询/重置按钮 (栅格布局) -->
<a-row :gutter="[16, 0]" class="search-row" wrap>
<a-col v-for="item in validSearchList" :key="item.name">
<a-form-item
:label="item.label"
:name="item.name"
style="width: 100%; margin-bottom: 0"
>
<!-- 普通日期选择器 -->
<a-date-picker
v-if="item.type === 'DataPicker'"
v-model:value="formData[item.name]"
:picker="item.picker"
:format="item.fieldProps?.format"
:value-format="item.fieldProps?.valueFormat"
:disabled-date="item.fieldProps?.disabledDate"
:show-time="item.fieldProps?.showTime"
:allow-clear="item.fieldProps?.allowClear"
:presets="item.presets"
style="width: 100%"
/>
<!-- 日期范围选择器 -->
<a-range-picker
v-else-if="item.type === 'RangePicker'"
v-model:value="formData[item.name]"
:picker="item.picker"
:format="item.fieldProps?.format"
:value-format="item.fieldProps?.valueFormat"
:disabled-date="item.fieldProps?.disabledDate"
:show-time="item.fieldProps?.showTime"
:allow-clear="item.fieldProps?.allowClear"
:presets="item.presets"
style="width: 100%"
/>
<!-- 普通输入框 -->
<a-input
v-else-if="!item.type || item.type === 'Input'"
v-model:value="formData[item.name]"
:placeholder="item.placeholder || '请输入'"
:allow-clear="item.fieldProps?.allowClear"
:style="{ width: item.width ? item.width + 'px' : '200px' }"
/>
<!-- 电站下拉框 -->
<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"
>
{{ opt.label }}
</a-select-option>
</a-select>
<a-select
:value="formData.stcd?.hbrvcd"
placeholder="请选择河流"
@change="hbrvcdChange"
style="width: 135px"
>
<a-select-option
v-for="opt in item.options"
:key="opt.value"
:value="opt.value"
>
{{ 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"
>
{{ opt.label }}
</a-select-option>
</a-select>
</a-form-item-rest>
</div>
<!-- 下拉选择 -->
<a-select
v-else-if="item.type === 'Select'"
v-model:value="formData[item.name]"
:placeholder="item.placeholder || '请选择'"
:allow-clear="item.fieldProps?.allowClear"
:style="{ width: item.width ? item.width + 'px' : '200px' }"
>
<a-select-option
v-for="opt in item.options"
:key="opt.value"
:value="opt.value"
>
{{ opt.label }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<!-- 2. 查询/重置按钮列 (固定占据一部分宽度例如 6) -->
<a-col class="custom-action-col">
<a-space>
<a-button type="primary" html-type="submit" v-if="showSearch">
查询
</a-button>
<a-button v-if="showReset" @click="handleReset"> 重置 </a-button>
<slot name="actions" :form="formRef" :values="formData">
<!-- 默认无内容 -->
</slot>
</a-space>
</a-col>
</a-row>
</div>
</a-form>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, reactive, watch, onMounted } from "vue";
import type { FormInstance } from "ant-design-vue";
// --- ---
export interface SearchItem {
type?: "Input" | "Select" | "DataPicker" | string;
name: string;
label: string;
picker?: "year" | "month" | "date" | "week";
fieldProps?: any;
placeholder?: string;
span?: number;
xlSpan?: number;
width?: number;
presets?: any[];
options?: { label: string; value: any }[];
component?: any;
}
interface Props {
searchList: SearchItem[];
initialValues?: any;
showSearch?: boolean;
showReset?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
initialValues: () => ({}),
showSearch: true,
showReset: true,
});
const emit = defineEmits<{
(e: "finish", values: any): void;
(e: "valuesChange", changedValues: any, allValues: any): void;
(e: "reset"): void;
}>();
const formRef = ref<FormInstance>();
const formData = reactive<any>({});
const rules = reactive<Record<string, any>>({});
// 2. false/null/undefined
const validSearchList = computed(() => {
return props.searchList.filter(item => item);
});
// --- ---
const initForm = () => {
const initial = JSON.parse(JSON.stringify(props.initialValues || {}));
// 1. formData
Object.keys(formData).forEach((key) => delete formData[key]);
// 2.
Object.assign(formData, initial);
// 3. searchList false/null/undefined
validSearchList.value.forEach((item) => {
if (item.fieldProps?.required) {
rules[item.name] = [
{ required: true, message: `${item.label}不能为空`, trigger: "blur" },
];
}
});
};
const dataDimensionDataChange = (value: any) => {
formData.stcd.dataDimensionData = value;
};
const hbrvcdChange = (value: any) => {
formData.stcd.hbrvcd = value;
};
const stcdIdChange = (value: any) => {
formData.stcd.stcdId = value;
};
onMounted(() => {
initForm();
});
watch(
() => props.initialValues,
(newVal) => {
if (newVal && formRef.value) {
formRef.value.setFieldsValue(newVal);
Object.assign(formData, newVal);
}
},
{ deep: true }
);
// --- ---
const handleFinish = (values: any) => {
emit("finish", values);
};
const handleValuesChange = (changedValues: any, allValues: any) => {
emit("valuesChange", changedValues, allValues);
};
const handleReset = () => {
if (formRef.value) {
formRef.value.resetFields();
emit("reset");
}
};
defineExpose({
form: formRef,
reset: handleReset,
submit: () => formRef.value?.submit(),
});
</script>
<style scoped lang="scss">
.basic-search-container {
width: 100%;
margin-bottom: 10px;
}
:deep(.ant-form-item) {
margin-bottom: 24px;
margin-inline-end: 0px;
}
/* 关键代码:让自定义按钮列自动占据剩余空间并靠右 */
.custom-action-col {
margin-left: auto;
display: flex;
align-items: flex-start;
justify-content: flex-start;
/* 防止在小屏幕下挤压变形 */
white-space: nowrap;
}
</style>

View File

@ -51,6 +51,7 @@ const handleToggle = () => {
height: 100%; height: 100%;
position: relative; position: relative;
box-sizing: border-box; box-sizing: border-box;
pointer-events: all;
.drawerController1 { .drawerController1 {
width: 18px; width: 18px;

View File

@ -38,11 +38,11 @@
<a-date-picker v-model:value="datetimeValue" show-time <a-date-picker v-model:value="datetimeValue" show-time
:format="datetimePicker.format !== null ? datetimePicker.format : undefined" :format="datetimePicker.format !== null ? datetimePicker.format : undefined"
:picker="datetimePicker.picker" placeholder="请选择时间" style="width: 180px" :picker="datetimePicker.picker" placeholder="请选择时间" style="width: 180px"
@change="handleDateTimeChange" :locale="locale" /> @change="handleDateTimeChange" />
<!-- 修改为 locale 变量 --> <!-- 修改为 locale 变量 -->
</div> </div>
<div v-if="scopeDate.show"> <div v-if="scopeDate.show">
<a-range-picker v-model:value="scopeDateValue" :locale="locale" :picker="scopeDate.picker" <a-range-picker v-model:value="scopeDateValue" :picker="scopeDate.picker"
:format="'YYYY-MM-DD'" :range-separator="' 至 '" style="width: 200px" /> :format="'YYYY-MM-DD'" :range-separator="' 至 '" style="width: 200px" />
</div> </div>
</div> </div>
@ -60,14 +60,8 @@ import {
InfoCircleOutlined InfoCircleOutlined
} from '@ant-design/icons-vue'; } from '@ant-design/icons-vue';
import type { SelectProps } from 'ant-design-vue'; import type { SelectProps } from 'ant-design-vue';
//
import zhCN from 'ant-design-vue/es/locale/zh_CN';
// dayjs // dayjs
import dayjs, { Dayjs } from 'dayjs'; import dayjs, { Dayjs } from 'dayjs';
import 'dayjs/locale/zh-cn'; //
dayjs.locale('zh-cn'); //
console.log(dayjs().format('MMMM'));
const locale = ref(zhCN);
// //
interface PromptConfig { interface PromptConfig {

View File

@ -0,0 +1,367 @@
// import { Session } from '@zebras/qgc-share/service/Session'
import domtoimage from 'dom-to-image'
import { offset2, drawDotImg2, drawDotImg1, offset1, drawDotImg3, offset3, drawDotImg5, offset5 } from '@/utils/GisUrlList'
/**
*
* @param {Number} height
*/
const A = 40487.57
const B = 0.00007096758
const C = 91610.74
const D = -40467.74
/**
*
* @param data
* @returns nameEn为下标的图例数据
*/
export const legendData2Obj = (data: any[]) => {
const _tempData = {}
const f = (_data) => {
_data.forEach((item) => {
// childrenList有值表示这是一个分组
if (item?.childrenList && item.childrenList?.length > 0) {
f(item.childrenList)
} else {
if (item.nameEn.includes('_测试')) {
item.nameEn = item.nameEn.split('_测试')[0]
}
_tempData[item.nameEn] = item
}
})
}
f(data)
return _tempData
}
/**
* item中
* @param item - item
* @param index - item的索引
* @param labelType -
*/
export const appendOffsetPropties = (item: any, index: any, labelType: number = 2) => {
let drawDotImg = null
let offset = null
if (labelType == 2) {
drawDotImg = drawDotImg1
offset = offset1
addItemProperty(item, index, drawDotImg, offset)
} else if (labelType == 3) {
drawDotImg = drawDotImg2
offset = offset2
addItemProperty(item, index, drawDotImg, offset)
} else if (labelType == 4) {
drawDotImg = drawDotImg3
offset = offset3
addItemProperty2(item, index, drawDotImg, offset)
} else if (labelType == 5) {
drawDotImg = drawDotImg5
offset = offset5
addItemProperty2(item, index, drawDotImg, offset)
}
}
const addItemProperty2 = (item: any, index: any, drawDotImg: any, offset: any) => {
if (!drawDotImg || !offset) return
item.icon_image = index % 2 !== 0 ? drawDotImg[item.anchoPointState]?.left || '' : drawDotImg[item.anchoPointState]?.right || ''
item.text_anchor = index % 2 !== 0 ? 'right' : 'left'
item.text_offset = offset[item.icon_image]?.text_offset ?? [-10, -1.8]
item.text_offset2 = offset[item.icon_image]?.text_offset2 ?? [-10, -1.8]
item.icon_offset = [offset[item.icon_image]?.icon_x ?? -200, offset[item.icon_image]?.icon_y ?? -50]
item.icon_offset2 = offset[item.icon_image]?.icon_offset2 ?? [-200, -50]
item.billboard_offset = [offset[item.icon_image]?.billboard_x ?? -80, offset[item.icon_image]?.billboard_y ?? -15]
item.label_offset = offset[item.icon_image]?.labelOffset ?? [100, 41]
}
export const addItemProperty = (item: any, index: any, drawDotImg: any, offset: any) => {
if (!drawDotImg || !offset) return
item.icon_image = index % 2 !== 0 ? drawDotImg[item.anchoPointState]?.left || '' : drawDotImg[item.anchoPointState]?.right || ''
item.text_offset = [offset[item.icon_image]?.text_x ?? -10, offset[item.icon_image]?.text_y ?? -1.8]
item.icon_offset = [offset[item.icon_image]?.icon_x ?? -200, offset[item.icon_image]?.icon_y ?? -50]
item.billboard_offset = [offset[item.icon_image]?.billboard_x ?? -80, offset[item.icon_image]?.billboard_y ?? -15]
item.label_offset = offset[item.icon_image]?.labelOffset ?? [100, 41]
}
/**
* popName属性
* @param item - popName的item
*/
export const setPopName = (item: any) => {
if (item.sttp === 'ENG') {
item.popName = item.ennm || item.titleName;
} else if (item.sttp === 'ylfb') {
if (item?.ftp?.length > 20) {
item.popName = item.ftp.slice(0, 20) + '...';
} else {
item.popName = item?.ftp;
}
} else if (item.sttp === 'WE_FISH') {
item.popName = item.fishList?.[0]?.fishName + `(${item.fishList?.[0]?.ptypeName})`
item.popName1 = item.total + "尾"
} else {
item.popName = item.stnm || item.titleName;
}
}
/**
* key
* @param data -
* @returns key数组
*/
export const getCheckedLayerConfigs = (data: any[]): any[] => {
const rs: any[] = []
const f = (arr: any[] = []) => {
let count = 0
arr.forEach((item: any) => {
if (item?.children && item.children.length > 0) {
const childrenCount = f(item.children)
if (childrenCount) {
rs.push(item.key)
}
} else if (item.checked) {
count++
rs.push(item.key) // 对应图例的layerCode
}
})
return count
}
f(data)
return rs
}
// type mapType = "" | "pointMap" | "gisLayer"
/**
*
* @param data -
* @returns
*/
export const layerConfig2Flat = (data: any): any[] => {
const rs: any[] = []
const f = (arr: any[] = []) => {
arr.forEach((item: any) => {
const { type } = item
if (type) {
if (type == 'GISMap') {
if (item?.children && item.children.length > 0) {
f(item.children)
} else {
rs.push(item)
}
} else if (type == 'pointMap') {
if (item.url || item.title === '国家水文站' || item.title === '自建水文站') {
rs.push(item)
} else if (item.children.length > 0) {
f(item.children)
}
}
} else if (item.children.length > 0) {
f(item.children)
}
})
}
f(data)
return rs
}
/**
*
* @param config -
* @returns
*/
export const getMapConfig = (config) => {
// const mapBaseUrls = MemoryCache.get('mapBaseUrls') || {}
// const r = { ...config }
// const { urlType } = r
// const baseUrlObj = mapBaseUrls[urlType as string]
// if (baseUrlObj) {
// r.url = baseUrlObj.url + r.url // 'http://localhost:8088'
// r.url_3d = baseUrlObj.url + r.url_3d
// // r.url = baseUrlObj.url + r.url //baseUrlObj.url
// // r.url_3d = baseUrlObj.url + r.url_3d
// if (r.geojson_url) {
// r.geojson_url = baseUrlObj.url + r.geojson_url + `&token=${Session.getToken()?.accessToken}`
// }
// }
// return r
}
/**
*
*/
export const resetMapElPos = () => {
const legend = document.querySelector('#qgc-legendtl') as HTMLElement // 图例
const filter = document.querySelector('#map-filter-container') as HTMLElement // 全局表单
const controller = document.querySelector('#map-controller') as HTMLElement // 地图工具栏
const baselayer = document.querySelector('#map-baselayer') as HTMLElement // 底图模式切换
if (legend) {
legend.style.left = '0'
legend.style.bottom = '0'
}
if (controller) {
controller.style.right = '480px'
controller.style.bottom = '114px'
}
if (baselayer) {
baselayer.style.right = '480px'
baselayer.style.bottom = '20px'
}
}
/**
*
* @param data -
* @param position -
* @returns
*/
const getListByPosition = (data, position: string) => data?.data?.filter((el) => el.position === position && el.code)
/**
*
* @param layoutType -
* @param data -
* @param offset -
*/
export const setMapLegendPos = (layoutType: string, data, offset: number = 456) => {
const menuStateString = localStorage.getItem('menuState'); //处理澜沧江左侧菜单状态
const menuState = menuStateString !== null ? JSON.parse(menuStateString) : true;
const _theme = localStorage.getItem("ly-theme") || window.__lyConfigs?.theme
const leftEle = document.querySelector('#page-layout-left') as HTMLElement
const rightEle = document.querySelector('#page-layout-right') as HTMLElement
const bottomEle = document.querySelector('#page-layout-bottom') as HTMLElement
const legend = document.querySelector('#qgc-legendtl') as HTMLElement // 图例
const filter = document.querySelector('#map-filter-container') as HTMLElement // 全局表单
const compassControl = document.querySelector('#map-compassControl') as HTMLElement // 全局表单
const controller = document.querySelector('#map-controller') as HTMLElement // 地图工具栏
const monitor = document.querySelector('#map-monitor') as HTMLElement // 地图工具栏
const baselayer = document.querySelector('#map-baselayer') as HTMLElement // 底图模式切换
const vd = document.querySelector('#vd_operate') as HTMLElement // 底部视频
const left = ['layout1', 'layout2', 'layout3', 'layout4', 'layout6', 'layout7', 'layout8', 'layout9', 'layout10', 'layout11', 'layout14', 'layout15', 'layout16', 'layout17'] // 左侧布局
const right = ['layout1', 'layout2', 'layout3', 'layout4', 'layout5', 'layout6', 'layout8', 'layout10', 'layout11', 'layout15', 'layout16', 'layout17'] // 右侧布局
const bottom1 = ['layout1', 'layout6', 'layout8', 'layout9', 'layout10', 'layout16'] // 三行底部布局
const bottom2 = ['layout2', 'layout15'] // 四行底部布局
const w = `${offset}px`
const l = `${_theme === 'ly-8' ? menuState ? 643 : 510 : offset}px`
let b = `0px`
const le = ['layout17', 'layout10'].includes(layoutType) ? 0 : 1
const leftList = getListByPosition(data, 'left')
const rightList = getListByPosition(data, 'right')
const bottomList = getListByPosition(data, 'bottom')
let bottom = '0'
if (_theme === 'ly-8') {
if (window.__mapMode === '3D') {
b = `200px`
} else {
b = `${menuState ? 200 : 50}px`
}
}
if (bottomList?.length > 0) {
if (bottom1.includes(layoutType)) {
bottom = 'calc((100% - 16px) / 3 + 8px)'
}
if (bottom2.includes(layoutType)) {
bottom = 'calc((100% - 24px) / 4 + 8px)'
}
} else {
// 没有底部布局时,底部高度为
bottom = '28px'
}
const rle = ['layout6'].includes(layoutType) || bottom != '28px' ? 0 : 1
const leftHide = leftEle?.classList?.contains('hide')
const rightHide = rightEle?.classList?.contains('hide')
const bottomHide = bottomEle?.classList?.contains('hide')
if (legend) {
legend.style.left = !leftHide && left.includes(layoutType) && leftList?.length > le ? l : b
legend.style.bottom = bottomHide ? '0' : bottomList?.length > 0 ? bottom : '12px'
}
if (filter) {
if (layoutType === 'layout10') {
filter.style.left = !leftHide && left.includes(layoutType) && leftList?.length > 1 ? l : b
} else {
filter.style.left = !leftHide && left.includes(layoutType) && leftList?.length > 0 && layoutType !== 'layout17' ? l : b
}
}
if (compassControl) {
if (layoutType === 'layout10') {
compassControl.style.left = !leftHide && left.includes(layoutType) && leftList?.length > 1 ? l : b
} else {
compassControl.style.left = !leftHide && left.includes(layoutType) && leftList?.length > 0 && layoutType !== 'layout17' ? l : b
}
}
if (controller) {
controller.style.right = !rightHide && right.includes(layoutType) && rightList?.length > rle ? w : '0'
controller.style.bottom = bottomHide ? '0' : bottom
}
if (monitor) {
monitor.style.right = !rightHide && right.includes(layoutType) && rightList?.length > rle ? w : '0'
// monitor.style.bottom = bottomHide ? '0' : bottom
}
if (baselayer) {
baselayer.style.right = !rightHide && right.includes(layoutType) && rightList?.length > rle ? `calc(${w} + 60px)` : '60px'
baselayer.style.bottom = bottomHide ? '0' : bottom
}
}
export const altitudeToZoom = (height: number) => {
const lv = Math.round(D + (A - D) / (1 + Math.pow(Number(height) / C, B))) + 1
return lv > -1 ? lv : 0
}
/**
*
* @param {Number} zoom
*/
export const zoomToAltitude = (zoom: number) => {
return Math.round(C * Math.pow((A - D) / (zoom - D) - 1, 1 / B))
}
export const mapOutPut = (imageUrl: string) => {
const canvas = document.createElement('canvas')
const downloadElement = document.createElement('a')
const mapElem = document.getElementById('mapContainer')
if (mapElem == null) {
return
}
const context = canvas.getContext('2d')!
canvas.width = mapElem.offsetWidth
canvas.height = mapElem.offsetHeight
const image = new Image()
image.src = imageUrl
image.onload = () => {
context.drawImage(image, 0, 0)
const elem: any = document.getElementById('qgc-legendtl')
const l = elem?.style?.left === '' ? 0 : parseInt(elem?.style?.left)
if (elem) {
domtoimage
.toPng(elem, {
quality: 1.0,
width: elem.offsetWidth + (l + 24),
height: mapElem.offsetHeight - 20
})
.then((legendUrl: string) => {
const image = new Image()
image.src = legendUrl
image.width = elem.offsetWidth
image.onload = () => {
context.drawImage(image, 0, 0)
downloadElement.href = canvas.toDataURL('image/png')
downloadElement.download = '地图截图'
downloadElement.click()
}
})
.catch((e: any) => {
console.log('e', e)
})
} else {
downloadElement.href = canvas.toDataURL('image/png')
downloadElement.download = 'download'
downloadElement.click()
}
}
}

View File

@ -1,11 +1,21 @@
import type { layer, MapInterface } from "./map.d"; import type { layer, MapInterface } from "./map.d";
import { MapLeaflet } from "./map.leaflet"; import { MapLeaflet } from "./map.leaflet";
import { MapOl } from "./map.ol";
interface MapClassInterface extends MapInterface { interface MapClassInterface extends MapInterface {
layers: Map<string, layer>; layers: Map<string, layer>;
view: any; view: any;
} }
//描点参数
export type MDOptions = {
isAllowOverlap?: boolean; // 是否允许重叠
labelType?: number; // 标签类型
labelminZoom?: number; // 标签最小缩放级别
labelAltitude?: object; // 标签海拔
isRemove?: boolean; // 是否移除标签
};
export const mapServerBaseUrl = localStorage.getItem("gisurl") || "http://210.72.227.199:18084/"; export const mapServerBaseUrl = localStorage.getItem("gisurl") || "http://210.72.227.199:18084/";
export class MapClass implements MapClassInterface { export class MapClass implements MapClassInterface {
@ -14,16 +24,90 @@ export class MapClass implements MapClassInterface {
private static instance: MapClass; private static instance: MapClass;
private service: MapInterface; private service: MapInterface;
constructor() {
this.layers = new Map();
this.view = null;
// this.service = new MapLeaflet();
this.service = new MapOl();
}
static getInstance(): MapClass { static getInstance(): MapClass {
if (!this.instance) { if (!this.instance) {
this.instance = new MapClass(); this.instance = new MapClass();
} }
return this.instance; return this.instance;
} }
// 地图初始化
constructor() { init(container: HTMLElement, rectangle?: any): Promise<any> {
this.layers = new Map(); return this.service.init(container, rectangle).then((map) => {
this.view = null; this.view = map;
this.service = new MapLeaflet(); return map;
});
} }
// 基地面板控制
jdPanelControlShowAndHidden(baseid: String, isAll: boolean): void {
this.service.jdPanelControlShowAndHidden(baseid, isAll)
}
mdLayerShowOrHidden(layerType: String, key?: String, baseid: String, checked: boolean, isAll: boolean): void {
this.service.mdLayerShowOrHidden(layerType, key, baseid, checked, isAll)
}
// 添加基础数据图层
addBaseDataLayer(layer: any): void {
return this.service.addBaseDataLayer(layer)
}
// 基础图层显示影隐藏方法
controlBaseLayerTreeShowAndHidden(layerType: String, key: String, checked: boolean) {
this.service.controlBaseLayerTreeShowAndHidden(layerType, key, checked)
}
// 图层树控制描点数据显示隐藏方法
mdLayerTreeShowOrHidden(layerType: String, checked?: boolean) {
this.service.mdLayerTreeShowOrHidden(layerType, checked)
}
// 初始化加载描点数据
addInitDataLayer = (pointData: any[], layerType: any, mdoptions?: MDOptions, legendArray?: any) => {
return this.service.addInitDataLayer(pointData, layerType, mdoptions, legendArray)
}
//切换底图
baseLayerSwitcher(key: string): void {
this.service.baseLayerSwitcher(key)
}
// 添加梯级流域图
addTertiarybasinLayer(layer: layer, fillcolor: any, outlineColor: any, datas: any): void {
this.service.addTertiarybasinLayer(layer, fillcolor, outlineColor, datas)
}
// 移除梯级流域图
removeTertiarybasinLayer(layer: layer): void {
this.service.removeTertiarybasinLayer?.(layer)
}
// 缩放
zoomToggle(type: 'out' | 'in') {
this.service.zoomToggle(type)
}
/**
*
*/
lengthCalculate(): void {
this.service.lengthCalculate()
}
/**
*
*/
areCalculate(): void {
this.service.areCalculate()
}
/**
*
*/
removeQueryLayer(): void {
this.service.removeQueryLayer()
}
// 地图打印
mapOutPut() {
this.service.mapOutPut()
}
// 销毁地图
destroy(): void {
this.service.destroy()
}
} }

View File

@ -1,3 +1,5 @@
import { MDOptions } from './map.class'
export type layerType = export type layerType =
| "markers" | "markers"
| "tiledMap" | "tiledMap"
@ -19,214 +21,171 @@ export type layerOption = {
}; };
export interface layer { export interface layer {
id: string; id: string
key: string; key: string
_layer?: any; _layer?: any
type?: layerType; type?: layerType
url?: string; url?: string
urlThd?: string; url_3d?: string
label?: string; geojson_url: string
thumbnail?: string; label?: string
visible?: boolean; thumbnail?: string
option?: layerOption; visible?: boolean
tempobj?: any; option?: layerOption
opacity?: any; tempobj?: any
layers?: any
rasteropacity?: any
imgUrl?: any
minZoom?: any
maxZoom?: any
minHeight?: number
maxHeight?: number
/** layer 类型 */
layerType?: string
matrixIds_index?: string[]
tileMatrixSetID?: string
} }
export interface MapInterface { export interface MapInterface {
getRainColor: any; /**
getRainColor1: any; *
removeContentmarkers: any; * @param container DOM容器
removeGanliumarkers: any; * @return any
addarcgisLayerWithNotLine: any */
/** init(container: HTMLElement, rectangle?: any, center?: any, altitude?: number, bearing?: number): Promise<any>
*
* @param container DOM容器
* @return any
*/
init(container: React.ReactNode, rectangle?: any, center?: any): Promise<any>;
/** /**
* *
*/ * @param pointData
addLayer(layer: layer): any; * @param layerType
*/
addInitDataLayer(pointData: any[], layerType: any, mdoptions: MDOptions): void
/** /**
* canvasMarker *
*/ * @param layer
addCanvasMarker( */
data: Array<any>, addBaseDataLayer(layer: any): void
legend?: Array<any>,
option?: { skipRemoveHtml?: boolean },
flag?: any
): any;
invalidateSize(): void; /**
*
* @param layer
*/
baseLayerSwitcher(key: string): void
getRain(data: any): void; /**
/** * 2D或者3D视图
* * @param type '2D' | '3D'
*/ */
addZoomEvent(func: Function, type?: string): void; switchView(type: '2D' | '3D'): any | null
/** /**
* *
* @param func * out
*/ * in
addMouseMoveEvent(func: Function): void; */
zoomToggle(type: 'out' | 'in'): void
/** /**
* *
* @param func */
*/ mapOutPut(): void
addMouseClickEvent(func: Function): void;
/** //飞行到指定的点
* fitBounds(bbox,bearing):void
* @param data flyTopanto(positon,zoom,stcd):void
*/ /**
fitBounds(data: any): void; *
* @param HH3DUrlArray
*/
addQxsyLayer(HH3DUrlArray: siteItem): void
/** /**
* *
*/ * @param HH3DUrlArray
getLayers(): Map<string, layer> | null; */
removeQxsyLayer(HH3DUrlArray: siteItem): void
/** /**
* *
*/ * @param HH3DUrlArray
getMapZoom(): number; */
qxsyToPosition(HH3DUrlArray: siteItem): void
/** /**
* *
*/ * @param HH3DUrlArray
zoomEnlarge(): number; */
qxsyClipBoundary(HH3DUrlArray: siteItem): void
/** /**
* *
*/ * @param HH3DUrlArray
zoomNarrow(): number; */
removeQxsyClipBoundary(HH3DUrlArray: siteItem): void
/** /**
* *
*/ * @param baseid
getMapZoomOrScale(): { scale?: number; zoom?: number }; * @param isAll
*/
jdPanelControlShowAndHidden(baseid: String, isAll: boolean): void
/**
*
* @param layerType
* @param key
* @param checked
* @param isAll
*/
mdLayerShowOrHidden(layerType: String, key?: String, baseid: String, checked: boolean, isAll: boolean): void
/** /**
* *
* @param layers * @param layerType
*/ * @param checked
showLayer(id: string): void; */
controlBaseLayerTreeShowAndHidden(layerType: String, key: String, checked: boolean): void
/** /**
* *
* @param layers * @param layerType
*/ * @param checked
hideLayer(id: string): void; */
mdLayerTreeShowOrHidden(layerType: String, checked?: boolean): void
/** /**
* *
* @param position */
*/ lengthCalculate(): void
setCenter(position: { lat: number; lng: number }, zoom?: any): void; /**
*
*/
areCalculate(): void
/** /**
* *
*/ */
removeAllLayer(): void; removeQueryLayer(): void
/**
*
* @param layer
* @param fillcolor
*/
addTertiarybasinLayer(layer: layer, fillcolor: any, outlineColor: any, datas: any): void
/** /**
* *
*/ * @param layer
removeAllLabel(): void; */
removeTertiarybasinLayer(layer: layer): void
/**
*
*/
destroy(): void
/** setVisibleDistanceOfLable(): void
*
* @param layer
*/
removeLayer(layer: layer): void;
/** recoverVisibleDistanceOfLable(): viod
*
*/
reRenderLayer(
key: string,
options?: { opacity?: number; data: Array<any>; zIndex?: number }
): Promise<any>;
/** changeMaskStyle(options?: any)
*
*/
removeMouseMoveEvent(): void;
/**
*
*/
removeEvent(type: string, func: Function): void;
/**
*
*/
removeFeatureLayer(): void;
/**
*
*/
removedataSource(item: any): void;
/**
*
*/
removeMouseClickEvent(): void;
/**
*
* out
* in
*/
zoomToggle(type: "out" | "in"): void;
/**
* arcgis图层
*/
addarcgisLayer(item: any, data?: any): void;
/**
* label
*/
addmapLabel(item: any, data: any, zoom: any): void;
addmapLine(item: any, data: any): void;
/**
* arcgis图层
*/
removearcgisLayer(): void;
/**
* label
*/
removearcgisLabel(): void;
removeLayermarkers(): void;
removeRainLayer(): void;
removeGroupLayer(): void;
removeMapLayer(): void;
/**
*
*/
handelstartDrawLine(): void;
/**
*
*/
handelstartDrawPolygon(): void;
/**
*
*/
handelclearLayer(): void;
} }

View File

@ -2,19 +2,14 @@
<a-tooltip placement="left" trigger="click" color="transparent"> <a-tooltip placement="left" trigger="click" color="transparent">
<template #title> <template #title>
<div class="calculate"> <div class="calculate">
<a-tooltip title="测距" placement="top"> <a-tooltip
<div class="map-controller-item"> v-for="(item, index) in tools"
<i class="icon iconfont icon-ranging"></i> :key="item.title"
</div> :title="item.title"
</a-tooltip> placement="top"
<a-tooltip title="测面积" placement="top"> >
<div class="map-controller-item"> <div class="map-controller-item" :class="{'active': active === index}" @click="calculateClick(index)">
<i class="icon iconfont icon-measure"></i> <i :class="`icon iconfont icon-${item.icon}`"></i>
</div>
</a-tooltip>
<a-tooltip title="清除" placement="top">
<div class="map-controller-item">
<i class="icon iconfont icon-clear"></i>
</div> </div>
</a-tooltip> </a-tooltip>
</div> </div>
@ -26,8 +21,34 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from "vue";
const calculateClick = () => { const props = defineProps<{
console.log("点击了测量工具"); map: any;
}>();
const map = props.map;
const active = ref(-1);
const tools = ref([
{ title: "测距", icon: "ranging" },
{ title: "测面积", icon: "measure" },
{ title: "清除", icon: "clear" },
]);
const calculateClick = (index: number) => {
console.log("点击了测量工具", index);
active.value = index;
switch (index) {
case 0:
map.lengthCalculate();
break;
case 1:
map.areCalculate();
break;
case 2:
active.value = -1;
map.removeQueryLayer();
break;
default:
break;
}
}; };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@ -57,5 +78,8 @@ const calculateClick = () => {
cursor: pointer; cursor: pointer;
padding-right: 10px; padding-right: 10px;
} }
.active {
color: #005292 !important;
}
} }
</style> </style>

View File

@ -8,7 +8,6 @@
<template #title> <template #title>
<div class="layers-tree"> <div class="layers-tree">
<div class="layers-tree-title">图层管理</div> <div class="layers-tree-title">图层管理</div>
<div class="layers-tree-content"> <div class="layers-tree-content">
<a-Tree <a-Tree
checkable checkable
@ -84,7 +83,7 @@ const onCheck = (v: any, e: any) => {
const getCheckedKeys = () => { const getCheckedKeys = () => {
const name = route.path.split("/")[2]; const name = route.path.split("/")[2];
if (name === "shuiDianKaiFaZhuangKuang") { if (name === "shuiDianKaiFaZhuangKuang") {
checkedKeys.value = ["customBaseLayer", "eng", "eng_point"]; checkedKeys.value = ["customBaseLayer","powerBaseStation", "eng", "eng_point"];
} }
mapStore.updateLayerData(checkedKeys.value); mapStore.updateLayerData(checkedKeys.value);
}; };

View File

@ -30,6 +30,35 @@ export const hasPerm: Directive = {
} }
}; };
/**
*
* @param perms ['sjtb:import-zip']
* @returns boolean
*/
export function checkPerm(perms: string[]): boolean {
const userStore = useUserStoreHook();
// 1. 超级管理员拥有所有权限
if (userStore.roles.includes('超级管理员')) {
return true;
}
// 2. 校验普通权限
// 只要用户拥有的权限列表中,包含传入列表中的任意一个,即返回 true
// 注意:原指令逻辑是 requiredPerms.includes(perm),即用户拥有的 perm 必须在要求列表中
// 通常业务逻辑是:要求的权限列表中的某一个在用户拥有的权限列表中即可
// 这里沿用你原指令的逻辑requiredPerms (传入值) 包含 userPerms (用户拥有的)
if (!perms || perms.length === 0) {
return false;
}
const hasPermission = userStore.perms?.some(userPerm => {
return perms.includes(userPerm);
});
return !!hasPermission;
}
/** /**
* *
*/ */

View File

@ -39,6 +39,6 @@ const routeKey = computed(() => router.path + Math.random());
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 100%; height: 100%;
z-index: 2000; z-index: 900;
} }
</style> </style>

View File

@ -39,30 +39,9 @@ function logout() {
}); });
}); });
} }
const badgeval = ref(0);
const isbadge = ref(true);
var source = new EventSource(url + `/sse/connect/` + getToken());
onMounted(() => { onMounted(() => {
if ("EventSource" in window) {
source.onmessage = function (e) {
if (e.data > 0) {
badgeval.value = e.data;
isbadge.value = false;
} else {
isbadge.value = true;
}
};
source.onopen = function (e) {};
source.onerror = function (e: any) {
if (e.readyState == EventSource.CLOSED) {
} else {
}
};
} else {
}
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
source.close();
}); });
</script> </script>
@ -74,7 +53,7 @@ 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 fontSize-16">{{ t("login.title") }}</h1></a <h1 class="text-blank font-bold text-[16px]">{{ t("login.title") }}</h1></a
> >
</transition> </transition>
<Sidebar /> <Sidebar />

View File

@ -33,9 +33,6 @@ const handleSubTabChange = (key: string) => {
}; };
onBeforeMount(() => { onBeforeMount(() => {
console.log(route)
console.log(route)
console.log(permissionStore.routes)
permissionStore.routes.map((item: any) => { permissionStore.routes.map((item: any) => {
if (item.meta?.hidden) { if (item.meta?.hidden) {
return ""; return "";

View File

@ -10,6 +10,9 @@ import '@/permission';
import Antd from 'ant-design-vue' import Antd from 'ant-design-vue'
import 'ant-design-vue/dist/reset.css' // Ant Design 全局样式重置 import 'ant-design-vue/dist/reset.css' // Ant Design 全局样式重置
import dayjs from 'dayjs'; // ant 中文语言
import 'dayjs/locale/zh-cn';
// 引入svg注册脚本 // 引入svg注册脚本
import 'virtual:svg-icons-register'; import 'virtual:svg-icons-register';
@ -31,6 +34,7 @@ for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component) app.component(key, component)
} }
dayjs.locale('zh-cn');
// 全局方法 // 全局方法
import { getDictionaries } from '@/api/dict'; import { getDictionaries } from '@/api/dict';
app.config.globalProperties.$getDictionaries = getDictionaries; app.config.globalProperties.$getDictionaries = getDictionaries;

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { onMounted, ref } from "vue";
import { useJidiSelectEventStore } from "@/store/modules/jidiSelectEvent"; import { useJidiSelectEventStore } from "@/store/modules/jidiSelectEvent";
const loading = ref(false); const loading = ref(false);
const isOpen = ref(true); const isOpen = ref(true);
@ -9,6 +9,9 @@ const jidiDataNum = ref(9);
const itemClick = (index: any) => { const itemClick = (index: any) => {
useJidiSelectEventStore().updataJidiData(index); useJidiSelectEventStore().updataJidiData(index);
}; };
onMounted(() => {
JidiSelectEventStore.jidiData[0].selected = true;
});
</script> </script>
<template> <template>
@ -33,7 +36,7 @@ const itemClick = (index: any) => {
@click="itemClick(index)" @click="itemClick(index)"
> >
<i class="icon iconfont icon-hydroPower"></i> <i class="icon iconfont icon-hydroPower"></i>
<span style="margin-left: 10px">{{ i.name }}</span> <span style="margin-left: 10px">{{ i.wbsName }}</span>
</div> </div>
</div> </div>
</a-spin> </a-spin>

View File

@ -25,7 +25,7 @@ export enum SizeType {
} }
export const usetTheme = { export const usetTheme = {
token: { token: {
colorPrimary: '#1890ff', colorPrimary: '#2f6b98',
borderRadius: 2, borderRadius: 2,
}, },
}; };

View File

@ -3,62 +3,712 @@ import { ref } from 'vue';
export const useJidiSelectEventStore = defineStore('jidiSelectEvent', () => { export const useJidiSelectEventStore = defineStore('jidiSelectEvent', () => {
const jidiData = ref([ const jidiData = ref([
{ {
id: 1, "_tls": {},
name: '当前全部', "id": null,
selected: true "recordUser": null,
}, "recordTime": null,
{ "modifyTime": null,
id: 2, "displayRecordUser": null,
name: '水电基地2', "isolateType": null,
selected: false "wbsType": null,
}, "wbsCode": "all",
{ "wbsName": "当前全部",
id: 3, "wbsSname": null,
name: '水电基地3', "wbsNameEn": null,
selected: false "wbsSnameEn": null,
}, "enable": null,
{ "description": null,
id: 4, "parentId": null,
name: '水电基地4', "parentCode": null,
selected: false "treeLevel": null,
}, "hasChildren": null,
{ "objId": null,
id: 5, "topWbsType": null,
name: '水电基地5', "fullPath": null,
selected: false "internal": null,
}, "lgtd": null,
{ "lttd": null,
id: 6, "area": null,
name: '水电基地6', "perimeter": null,
selected: false "synopsis": null,
}, "introduce": null,
{ "logo": null,
id: 7, "inffile": null,
name: '水电基地7', "orderIndex": null,
selected: false "filterContent": null,
}, "departmentId": null,
{ "systemId": null,
id: 8, "platformId": null,
name: '水电基地8', "reachWwqtg": null,
selected: false "maxElev": null,
}, "minElev": null,
{ "datTp": null,
id: 9, "rvAg": null,
name: '水电基地9', "ifInnRv": null,
selected: false "showControl": null,
}, "stcd": null,
{ "displayDepartment": null
id: 10, },
name: '水电基地10', {
selected: false "_tls": {},
}, "id": null,
{ "recordUser": null,
id: 11, "recordTime": null,
name: '水电基地11', "modifyTime": null,
selected: false "displayRecordUser": null,
} "isolateType": null,
]); "wbsType": null,
"wbsCode": "01",
"wbsName": "金沙江干流",
"wbsSname": null,
"wbsNameEn": null,
"wbsSnameEn": null,
"enable": null,
"description": null,
"parentId": null,
"parentCode": null,
"treeLevel": null,
"hasChildren": null,
"objId": null,
"topWbsType": null,
"fullPath": null,
"internal": null,
"lgtd": null,
"lttd": null,
"area": null,
"perimeter": null,
"synopsis": null,
"introduce": null,
"logo": null,
"inffile": null,
"orderIndex": null,
"filterContent": null,
"departmentId": null,
"systemId": null,
"platformId": null,
"reachWwqtg": null,
"maxElev": null,
"minElev": null,
"datTp": null,
"rvAg": null,
"ifInnRv": null,
"showControl": null,
"stcd": null,
"displayDepartment": null
},
{
"_tls": {},
"id": null,
"recordUser": null,
"recordTime": null,
"modifyTime": null,
"displayRecordUser": null,
"isolateType": null,
"wbsType": null,
"wbsCode": "02",
"wbsName": "雅砻江干流",
"wbsSname": null,
"wbsNameEn": null,
"wbsSnameEn": null,
"enable": null,
"description": null,
"parentId": null,
"parentCode": null,
"treeLevel": null,
"hasChildren": null,
"objId": null,
"topWbsType": null,
"fullPath": null,
"internal": null,
"lgtd": null,
"lttd": null,
"area": null,
"perimeter": null,
"synopsis": null,
"introduce": null,
"logo": null,
"inffile": null,
"orderIndex": null,
"filterContent": null,
"departmentId": null,
"systemId": null,
"platformId": null,
"reachWwqtg": null,
"maxElev": null,
"minElev": null,
"datTp": null,
"rvAg": null,
"ifInnRv": null,
"showControl": null,
"stcd": null,
"displayDepartment": null
},
{
"_tls": {},
"id": null,
"recordUser": null,
"recordTime": null,
"modifyTime": null,
"displayRecordUser": null,
"isolateType": null,
"wbsType": null,
"wbsCode": "03",
"wbsName": "大渡河干流",
"wbsSname": null,
"wbsNameEn": null,
"wbsSnameEn": null,
"enable": null,
"description": null,
"parentId": null,
"parentCode": null,
"treeLevel": null,
"hasChildren": null,
"objId": null,
"topWbsType": null,
"fullPath": null,
"internal": null,
"lgtd": null,
"lttd": null,
"area": null,
"perimeter": null,
"synopsis": null,
"introduce": null,
"logo": null,
"inffile": null,
"orderIndex": null,
"filterContent": null,
"departmentId": null,
"systemId": null,
"platformId": null,
"reachWwqtg": null,
"maxElev": null,
"minElev": null,
"datTp": null,
"rvAg": null,
"ifInnRv": null,
"showControl": null,
"stcd": null,
"displayDepartment": null
},
{
"_tls": {},
"id": null,
"recordUser": null,
"recordTime": null,
"modifyTime": null,
"displayRecordUser": null,
"isolateType": null,
"wbsType": null,
"wbsCode": "04",
"wbsName": "乌江干流",
"wbsSname": null,
"wbsNameEn": null,
"wbsSnameEn": null,
"enable": null,
"description": null,
"parentId": null,
"parentCode": null,
"treeLevel": null,
"hasChildren": null,
"objId": null,
"topWbsType": null,
"fullPath": null,
"internal": null,
"lgtd": null,
"lttd": null,
"area": null,
"perimeter": null,
"synopsis": null,
"introduce": null,
"logo": null,
"inffile": null,
"orderIndex": null,
"filterContent": null,
"departmentId": null,
"systemId": null,
"platformId": null,
"reachWwqtg": null,
"maxElev": null,
"minElev": null,
"datTp": null,
"rvAg": null,
"ifInnRv": null,
"showControl": null,
"stcd": null,
"displayDepartment": null
},
{
"_tls": {},
"id": null,
"recordUser": null,
"recordTime": null,
"modifyTime": null,
"displayRecordUser": null,
"isolateType": null,
"wbsType": null,
"wbsCode": "05",
"wbsName": "长江上游干流",
"wbsSname": null,
"wbsNameEn": null,
"wbsSnameEn": null,
"enable": null,
"description": null,
"parentId": null,
"parentCode": null,
"treeLevel": null,
"hasChildren": null,
"objId": null,
"topWbsType": null,
"fullPath": null,
"internal": null,
"lgtd": null,
"lttd": null,
"area": null,
"perimeter": null,
"synopsis": null,
"introduce": null,
"logo": null,
"inffile": null,
"orderIndex": null,
"filterContent": null,
"departmentId": null,
"systemId": null,
"platformId": null,
"reachWwqtg": null,
"maxElev": null,
"minElev": null,
"datTp": null,
"rvAg": null,
"ifInnRv": null,
"showControl": null,
"stcd": null,
"displayDepartment": null
},
{
"_tls": {},
"id": null,
"recordUser": null,
"recordTime": null,
"modifyTime": null,
"displayRecordUser": null,
"isolateType": null,
"wbsType": null,
"wbsCode": "10",
"wbsName": "湘西",
"wbsSname": null,
"wbsNameEn": null,
"wbsSnameEn": null,
"enable": null,
"description": null,
"parentId": null,
"parentCode": null,
"treeLevel": null,
"hasChildren": null,
"objId": null,
"topWbsType": null,
"fullPath": null,
"internal": null,
"lgtd": null,
"lttd": null,
"area": null,
"perimeter": null,
"synopsis": null,
"introduce": null,
"logo": null,
"inffile": null,
"orderIndex": null,
"filterContent": null,
"departmentId": null,
"systemId": null,
"platformId": null,
"reachWwqtg": null,
"maxElev": null,
"minElev": null,
"datTp": null,
"rvAg": null,
"ifInnRv": null,
"showControl": null,
"stcd": null,
"displayDepartment": null
},
{
"_tls": {},
"id": null,
"recordUser": null,
"recordTime": null,
"modifyTime": null,
"displayRecordUser": null,
"isolateType": null,
"wbsType": null,
"wbsCode": "08",
"wbsName": "黄河上游干流",
"wbsSname": null,
"wbsNameEn": null,
"wbsSnameEn": null,
"enable": null,
"description": null,
"parentId": null,
"parentCode": null,
"treeLevel": null,
"hasChildren": null,
"objId": null,
"topWbsType": null,
"fullPath": null,
"internal": null,
"lgtd": null,
"lttd": null,
"area": null,
"perimeter": null,
"synopsis": null,
"introduce": null,
"logo": null,
"inffile": null,
"orderIndex": null,
"filterContent": null,
"departmentId": null,
"systemId": null,
"platformId": null,
"reachWwqtg": null,
"maxElev": null,
"minElev": null,
"datTp": null,
"rvAg": null,
"ifInnRv": null,
"showControl": null,
"stcd": null,
"displayDepartment": null
},
{
"_tls": {},
"id": null,
"recordUser": null,
"recordTime": null,
"modifyTime": null,
"displayRecordUser": null,
"isolateType": null,
"wbsType": null,
"wbsCode": "09",
"wbsName": "黄河中游干流",
"wbsSname": null,
"wbsNameEn": null,
"wbsSnameEn": null,
"enable": null,
"description": null,
"parentId": null,
"parentCode": null,
"treeLevel": null,
"hasChildren": null,
"objId": null,
"topWbsType": null,
"fullPath": null,
"internal": null,
"lgtd": null,
"lttd": null,
"area": null,
"perimeter": null,
"synopsis": null,
"introduce": null,
"logo": null,
"inffile": null,
"orderIndex": null,
"filterContent": null,
"departmentId": null,
"systemId": null,
"platformId": null,
"reachWwqtg": null,
"maxElev": null,
"minElev": null,
"datTp": null,
"rvAg": null,
"ifInnRv": null,
"showControl": null,
"stcd": null,
"displayDepartment": null
},
{
"_tls": {},
"id": null,
"recordUser": null,
"recordTime": null,
"modifyTime": null,
"displayRecordUser": null,
"isolateType": null,
"wbsType": null,
"wbsCode": "06",
"wbsName": "南盘江-红水河",
"wbsSname": null,
"wbsNameEn": null,
"wbsSnameEn": null,
"enable": null,
"description": null,
"parentId": null,
"parentCode": null,
"treeLevel": null,
"hasChildren": null,
"objId": null,
"topWbsType": null,
"fullPath": null,
"internal": null,
"lgtd": null,
"lttd": null,
"area": null,
"perimeter": null,
"synopsis": null,
"introduce": null,
"logo": null,
"inffile": null,
"orderIndex": null,
"filterContent": null,
"departmentId": null,
"systemId": null,
"platformId": null,
"reachWwqtg": null,
"maxElev": null,
"minElev": null,
"datTp": null,
"rvAg": null,
"ifInnRv": null,
"showControl": null,
"stcd": null,
"displayDepartment": null
},
{
"_tls": {},
"id": null,
"recordUser": null,
"recordTime": null,
"modifyTime": null,
"displayRecordUser": null,
"isolateType": null,
"wbsType": null,
"wbsCode": "12",
"wbsName": "东北",
"wbsSname": null,
"wbsNameEn": null,
"wbsSnameEn": null,
"enable": null,
"description": null,
"parentId": null,
"parentCode": null,
"treeLevel": null,
"hasChildren": null,
"objId": null,
"topWbsType": null,
"fullPath": null,
"internal": null,
"lgtd": null,
"lttd": null,
"area": null,
"perimeter": null,
"synopsis": null,
"introduce": null,
"logo": null,
"inffile": null,
"orderIndex": null,
"filterContent": null,
"departmentId": null,
"systemId": null,
"platformId": null,
"reachWwqtg": null,
"maxElev": null,
"minElev": null,
"datTp": null,
"rvAg": null,
"ifInnRv": null,
"showControl": null,
"stcd": null,
"displayDepartment": null
},
{
"_tls": {},
"id": null,
"recordUser": null,
"recordTime": null,
"modifyTime": null,
"displayRecordUser": null,
"isolateType": null,
"wbsType": null,
"wbsCode": "07",
"wbsName": "澜沧江干流",
"wbsSname": null,
"wbsNameEn": null,
"wbsSnameEn": null,
"enable": null,
"description": null,
"parentId": null,
"parentCode": null,
"treeLevel": null,
"hasChildren": null,
"objId": null,
"topWbsType": null,
"fullPath": null,
"internal": null,
"lgtd": null,
"lttd": null,
"area": null,
"perimeter": null,
"synopsis": null,
"introduce": null,
"logo": null,
"inffile": null,
"orderIndex": null,
"filterContent": null,
"departmentId": null,
"systemId": null,
"platformId": null,
"reachWwqtg": null,
"maxElev": null,
"minElev": null,
"datTp": null,
"rvAg": null,
"ifInnRv": null,
"showControl": null,
"stcd": null,
"displayDepartment": null
},
{
"_tls": {},
"id": null,
"recordUser": null,
"recordTime": null,
"modifyTime": null,
"displayRecordUser": null,
"isolateType": null,
"wbsType": null,
"wbsCode": "13",
"wbsName": "怒江干流",
"wbsSname": null,
"wbsNameEn": null,
"wbsSnameEn": null,
"enable": null,
"description": null,
"parentId": null,
"parentCode": null,
"treeLevel": null,
"hasChildren": null,
"objId": null,
"topWbsType": null,
"fullPath": null,
"internal": null,
"lgtd": null,
"lttd": null,
"area": null,
"perimeter": null,
"synopsis": null,
"introduce": null,
"logo": null,
"inffile": null,
"orderIndex": null,
"filterContent": null,
"departmentId": null,
"systemId": null,
"platformId": null,
"reachWwqtg": null,
"maxElev": null,
"minElev": null,
"datTp": null,
"rvAg": null,
"ifInnRv": null,
"showControl": null,
"stcd": null,
"displayDepartment": null
},
{
"_tls": {},
"id": null,
"recordUser": null,
"recordTime": null,
"modifyTime": null,
"displayRecordUser": null,
"isolateType": null,
"wbsType": null,
"wbsCode": "11",
"wbsName": "闽浙赣",
"wbsSname": null,
"wbsNameEn": null,
"wbsSnameEn": null,
"enable": null,
"description": null,
"parentId": null,
"parentCode": null,
"treeLevel": null,
"hasChildren": null,
"objId": null,
"topWbsType": null,
"fullPath": null,
"internal": null,
"lgtd": null,
"lttd": null,
"area": null,
"perimeter": null,
"synopsis": null,
"introduce": null,
"logo": null,
"inffile": null,
"orderIndex": null,
"filterContent": null,
"departmentId": null,
"systemId": null,
"platformId": null,
"reachWwqtg": null,
"maxElev": null,
"minElev": null,
"datTp": null,
"rvAg": null,
"ifInnRv": null,
"showControl": null,
"stcd": null,
"displayDepartment": null
},
{
"_tls": {},
"id": null,
"recordUser": null,
"recordTime": null,
"modifyTime": null,
"displayRecordUser": null,
"isolateType": null,
"wbsType": null,
"wbsCode": "other",
"wbsName": "其他",
"wbsSname": null,
"wbsNameEn": null,
"wbsSnameEn": null,
"enable": null,
"description": null,
"parentId": null,
"parentCode": null,
"treeLevel": null,
"hasChildren": null,
"objId": null,
"topWbsType": null,
"fullPath": null,
"internal": null,
"lgtd": null,
"lttd": null,
"area": null,
"perimeter": null,
"synopsis": null,
"introduce": null,
"logo": null,
"inffile": null,
"orderIndex": null,
"filterContent": null,
"departmentId": null,
"systemId": null,
"platformId": null,
"reachWwqtg": null,
"maxElev": null,
"minElev": null,
"datTp": null,
"rvAg": null,
"ifInnRv": null,
"showControl": null,
"stcd": null,
"displayDepartment": null
}
]);
const selectedItem = ref(jidiData.value[0]); const selectedItem = ref(jidiData.value[0]);
const updataJidiData = (index: any) => { const updataJidiData = (index: any) => {
selectedItem.value = jidiData.value[index]; selectedItem.value = jidiData.value[index];

View File

@ -0,0 +1,40 @@
.ant-model {
.ant-modal-content {
border-radius: 2px;
}
.ant-modal-header {
padding: 16px 24px;
color: rgba(0, 0, 0, 0.85);
border-bottom: 1px solid #f0f0f0;
border-radius: 2px 2px 0 0;
background-color: #005293;
}
}
:where(.css-dev-only-do-not-override-ekaqbe).ant-modal {
.ant-modal-content {
border-radius: 2px;
padding: 0 !important;
.ant-modal-close-x {
color: #ffffff !important;
}
.ant-modal-body {
padding: 16px 24px !important;
padding-bottom: 0 !important;
}
.ant-modal-footer {
margin-top: 0 !important;
padding: 16px 24px !important;
padding-top: 0 !important;
}
}
.ant-modal-header {
padding: 16px 24px;
border-bottom: 1px solid #f0f0f0;
border-radius: 2px 2px 0 0;
background-color: #005293;
margin-bottom: 0;
.ant-modal-title {
color: #ffffff;
}
}
}

View File

@ -1,4 +1,5 @@
@use 'src/styles/element-plus' as element-plus; @use 'src/styles/element-plus' as element-plus;
@use 'src/styles/ant-design-vue' as permission;
@use './sidebar' as sidebar; @use './sidebar' as sidebar;
@use './tailwind' as tailwind; @use './tailwind' as tailwind;
@import './iconfont/iconfont.css'; @import './iconfont/iconfont.css';
@ -40,8 +41,8 @@ body::-webkit-scrollbar {
-webkit-box-shadow: inset 0 0 6px #ddd; -webkit-box-shadow: inset 0 0 6px #ddd;
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
border-radius: 10px; border-radius: 10px;
width: 10px; width: 10px;
} }
// main-container global css // main-container global css
.app-container { .app-container {
@ -95,13 +96,3 @@ svg {
height: 98%; height: 98%;
position: relative; position: relative;
} }
//滚动条统一样式
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-thumb {
border-radius: 4px;
background: #bbb;
-webkit-box-shadow: inset 0 0 6px #ddd;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,315 @@
import dayjs, { Dayjs } from 'dayjs';
import isoWeek from 'dayjs/plugin/isoWeek'; // 如果需要严格的 ISO 周计算
import quarterOfYear from 'dayjs/plugin/quarterOfYear'; // 用于季度操作
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore';
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
// 启用插件
dayjs.extend(quarterOfYear);
dayjs.extend(isoWeek);
dayjs.extend(isSameOrBefore);
dayjs.extend(isSameOrAfter);
// 设置全局语言为中文(可选,根据项目需求)
import 'dayjs/locale/zh-cn';
dayjs.locale('zh-cn');
namespace Options {
//开发方式
export const developmentMode = [
{ value: '1', label: '堤坝式' },
{ value: '2', label: '引水式' },
{ value: '3', label: '混合式' }
];
// 建设状态
export const bldsttList = [
{ value: '1', label: '在建' },
{ value: '2', label: '已建' },
{ value: '0', label: '规划' }
];
export const developmentMode2 = [
{ value: 'RRU', label: '上水库' },
{ value: 'RRL', label: '下水库' },
{ value: 'GN', label: '机组' },
{ value: 'GT', label: '闸门' }
];
//电站类型
export const powerStationType = [
{
value: '',
label: '所有'
},
{
value: '1',
label: '已接入'
},
{
value: '0',
label: '未接入'
}
];
//工程类型
export const projectType = [
{
value: '',
label: '所有'
},
{
value: '1',
label: '常规水电'
},
{
value: '2',
label: '抽水蓄能'
}
];
//调节性能
export const performance = [
{
value: '1',
label: '多年'
},
{
value: '2',
label: '年'
},
{
value: '3',
label: '不完全年'
},
{
value: '4',
label: '径流式'
},
{
value: '5',
label: '季'
},
{
value: '6',
label: '月'
},
{
value: '7',
label: '周'
},
{
value: '8',
label: '日'
},
{
value: '9',
label: '无'
}
];
//流域
export const type = [
{ value: 'district', label: '行政区' },
{ value: 'basin', label: '流域' },
{ value: 'company', label: '公司' }
];
export const type2 = [{ value: 'district', label: '行政区' }];
export const evaluateType = [
{
value: '1',
label: '水能效益'
},
{
value: '2',
label: '水量效益'
},
{
value: '3',
label: '水位运行评价'
},
{
value: '4',
label: '防洪效益'
},
{
value: '5',
label: '生态效益'
}
];
}
namespace DateSetting {
//获取当前时间
function getStartTime(): Dayjs {
return dayjs().startOf('day').startOf('hour');
}
//获取当前时间
function getEndTime(): Dayjs {
return dayjs().endOf('day');
}
function getStartYear(): Dayjs {
return dayjs().startOf('year');
}
function getEarlyDays(): Array<Dayjs> {
return [dayjs().startOf('month'), dayjs().startOf('month').add(9, 'day')];
}
function getMidmonth(): Array<Dayjs> {
return [
dayjs().startOf('month').add(10, 'day'),
dayjs().startOf('month').add(19, 'day')
];
}
function getLastTenDays(): Array<Dayjs> {
return [dayjs().startOf('month').add(20, 'day'), dayjs().endOf('month')];
}
export const RangeButton: any = {
days: [
{
label: '今天',
value: [dayjs().startOf('day'), dayjs().endOf('day')]
},
{
label: '昨天',
value: [
dayjs().subtract(1, 'day').startOf('day'),
dayjs().subtract(1, 'day').endOf('day')
]
},
{
label: '本周',
// 注意:如果项目配置了 isoWeek 插件且希望周一为起始,可使用 startOf('isoWeek')
value: [dayjs().startOf('week'), dayjs().endOf('week')]
},
{
label: '上周',
value: [
dayjs().subtract(1, 'week').startOf('week'),
dayjs().subtract(1, 'week').endOf('week')
]
},
{
label: '本月',
value: [dayjs().startOf('month'), dayjs().endOf('month')]
},
{
label: '上月',
value: [
dayjs().subtract(1, 'month').startOf('month'),
dayjs().subtract(1, 'month').endOf('month')
]
},
{
label: '上半年',
value: [
dayjs().startOf('year'),
dayjs()
.startOf('year')
.add(6, 'month')
.subtract(1, 'day')
.endOf('day')
]
},
{
label: '本年',
value: [dayjs().startOf('year'), dayjs().endOf('year')]
}
],
tenDays: [
{
label: '上旬',
value: getEarlyDays()
},
{
label: '中旬',
value: getMidmonth()
},
{
label: '下旬',
value: getLastTenDays()
},
],
month: [
{
label: '最近一个月',
value: [getStartTime().subtract(1, 'month'), getStartTime()],
},
{
label: '最近三个月',
value: [getStartTime().subtract(3, 'month'), getStartTime()],
},
{
label: '最近半年',
value: [getStartTime().subtract(6, 'month'), getStartTime()],
},
{
label: '最近一年',
value: [getStartTime().subtract(1, 'year'), getStartTime()],
},
],
season: [
{
label: '第一季度',
value: [getStartYear(), getStartYear().add(2, 'month').endOf('month')],
},
{
label: '第二季度',
value: [
getStartYear().add(3, 'month'),
getStartYear().add(5, 'month').endOf('month')
],
},
{
label: '第三季度',
value: [
getStartYear().add(6, 'month'),
getStartYear().add(8, 'month').endOf('month')
],
},
{
label: '第四季度',
value: [
getStartYear().add(9, 'month'),
getStartYear().add(11, 'month').endOf('month')
],
},
],
year: [
{
label: '最近一年',
value: [getStartTime().subtract(1, 'year'), getStartTime()],
},
{
label: '近三年',
value: [getStartTime().subtract(3, 'year'), getStartTime()],
},
{
label: '最近五年',
value: [getStartTime().subtract(5, 'year'), getStartTime()],
},
{
label: '最近十年',
value: [getStartTime().subtract(10, 'year'), getStartTime()]
},
],
future: [
{
label: '未来一周',
value: [getStartTime(), getStartTime().add(7, 'day')],
},
{
label: '未来一个月',
value: [getStartTime(), getStartTime().add(1, 'month')],
},
{
label: '未来三个月',
value: [getStartTime(), getStartTime().add(3, 'month')]
},
],
};
}
export { Options, DateSetting };

View File

@ -1,14 +1,507 @@
<!-- SidePanelItem.vue -->
<template> <template>
<div> <div class="guoYuSheShiShuJuTianBao-page">
过鱼设施数据填报 <!-- 搜索区域组件具体 props 需根据实际子组件调整 -->
</div> <GuoYuSheShiShuJuTianBaoSearch
:import-btn="importBtn"
:save-btn="saveBtn"
:handle-add="handleAdd"
@search-finish="handleSearchFinish"
/>
<!-- 主表格 -->
<a-table
size="small"
:loading="loading"
:row-selection="rowSelection"
:data-source="tableData"
:columns="columns"
:pagination="paginationConfig"
:scroll="{ x: '100%' }"
row-key="key"
>
<!-- 自定义插槽渲染可根据需要扩展这里主要依赖 columns 中的 render 函数逻辑但在 Vue中通常使用 slot h 函数 -->
<!-- 注意Antdv columns render 支持返回 VNode 或字符串 -->
</a-table>
<!-- 导入预览 Modal -->
<a-modal
title="导入数据预览"
ok-text="提交导入"
cancel-text="取消"
:width="1500"
:open="visible"
:confirm-loading="fileLoading"
@cancel="handleModalCancel"
@ok="handleModalOk"
>
<a-table
size="small"
:loading="fileLoading"
:data-source="fileTableData"
:columns="modalColumns"
:pagination="false"
: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="视频预览"
:open="videoPreviewVisible"
:footer="null"
width="800px"
@cancel="closeVideoPreview"
>
<video v-if="currentVideoUrl" controls autoplay style="width: 100%" :src="currentVideoUrl">
您的浏览器不支持视频播放
</video>
</a-modal>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted } from 'vue'; import { ref, reactive, computed, onMounted } from 'vue'
// import { message, Modal } from 'ant-design-vue' // 使 ant-design-vue
onMounted(() => { }); import JSZip from 'jszip'
import * as XLSX from 'xlsx'
import GuoYuSheShiShuJuTianBaoSearch from './guoYuSheShiShuJuTianBaoSearch.vue'
import EditModal from './guoYuSheShiShuJuTianBaoForm.vue'
// 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
}
// --- ---
const baseColumnsConfig: ColumnConfig[] = [
{ dataIndex: 'stcd', key: 'stcd', title: '水电基地', width: 100 },
{ dataIndex: 'title', key: 'title', title: '电站名称', width: 120 },
{ dataIndex: 'office', key: 'office', title: '过鱼设施名称', width: 150 },
{ dataIndex: 'regionName', key: 'regionName', title: '过鱼时间', width: 120 },
{ dataIndex: 'location', key: 'location', title: '鱼种类', width: 120 },
{ dataIndex: 'location11', key: 'location', title: '是否鱼苗', width: 120 },
{ dataIndex: 'DIRECTION', key: 'DIRECTION', title: '游向', width: 120 },
{ dataIndex: 'level1', key: 'level1', title: '过鱼数量(尾)', width: 160 },
{ dataIndex: 'level2', key: 'level2', title: '体长', width: 120 },
{ dataIndex: 'level3', key: 'level3', title: '平均体重', width: 120 },
{ dataIndex: 'level4', key: 'level4', title: '水温', width: 120 },
{ dataIndex: 'level5', key: 'level5', title: '图片', width: 100 },
{ dataIndex: 'level6', key: 'level6', title: '视频', width: 100 },
{ dataIndex: 'status', key: 'status', title: '状态', width: 100 }
]
// --- ---
const searchData = ref<any>(null)
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 loading = ref(false)
const fileLoading = ref(false)
// Key ()
const editingKey = ref<string | number>('')
const total = ref(0)
const page = ref(1)
const size = ref(10)
// --- ---
// 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',
fixed: 'right',
width: 120,
align: 'center',
customRender: ({ record, index }: any) => {
// Vue slot #bodyCell
// template <template #bodyCell="{ column, record, index }">
return '操作列'
}
}
]
})
// 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 rowSelection = {
onChange: (selectedRowKeys: string[], selectedRows: any[]) => {
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
},
getCheckboxProps: (record: any) => ({
disabled: record.name === 'Disabled User', // Column configuration not to be checked
name: record.name,
}),
};
// --- ---
const handleAdd = () => {
currentRecord.value = null
editModalVisible.value = true
}
const handleEdit = (record: any) => {
currentRecord.value = { ...record }
editModalVisible.value = true
}
const handleDeleteMain = (index: number) => {
Modal.confirm({
title: '确定删除这条数据吗?',
onOk: () => {
tableData.value = tableData.value.filter((_, i) => i !== index)
message.success('删除成功')
}
})
}
const editModalCancel = () => {
editModalVisible.value = false
}
const handleEditSubmit = async (values: FormData) => {
submitLoading.value = true
//
setTimeout(() => {
if (currentRecord.value) {
//
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 }
}
message.success('编辑成功')
} else {
//
const newRecord = { ...values, key: Date.now() }
tableData.value = [newRecord, ...tableData.value]
message.success('新增成功')
}
submitLoading.value = false
editModalVisible.value = false
}, 500)
}
const getData = async (searchDataParam?: any, label?: string) => {
loading.value = true
// TODO: API
console.log('Fetching data with:', searchDataParam)
setTimeout(() => {
loading.value = false
}, 500)
}
const parseExcelFile = async (fileName: string, arrayBuffer: ArrayBuffer) => {
try {
const workbook = XLSX.read(arrayBuffer, { type: 'array' })
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 = (e: any, label: string) => {
const newSearchData = { ...searchData.value, ...e }
searchData.value = newSearchData
getData(newSearchData, label)
}
const closeVideoPreview = () => {
videoPreviewVisible.value = false
currentVideoUrl.value = ''
}
//
const paginationConfig = computed(() => ({
size: 'small' as const,
total: total.value,
showTotal: (total: number) => `${total}`,
showQuickJumper: true,
pageSize: size.value,
current: page.value,
onChange: (p: number, ps: number) => {
page.value = p
size.value = ps
//
// getData(searchData.value)
}
}))
// --- ---
onMounted(() => {
//
// getData()
})
</script> </script>
<style lang="scss" scoped></style> <style lang="scss" scoped>
.guoYuSheShiShuJuTianBao-page {
width: 100%;
height: 100%;
background-color: #ffffff;
padding: 20px;
}
</style>

View File

@ -0,0 +1,276 @@
<template>
<a-modal
:title="isEdit ? '编辑数据' : '新增数据'"
:opne="visible"
:confirm-loading="loading"
width="800px"
destroy-on-close
@cancel="handleCancel"
@ok="handleOk"
>
<a-form
ref="formRef"
:model="formData"
:rules="rules"
layout="vertical"
name="edit_form"
>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="流域" name="stcd">
<a-input v-model:value="formData.stcd" placeholder="请输入流域" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="电站名称" name="title">
<a-input v-model:value="formData.title" placeholder="请输入电站名称" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="过鱼设施名称" name="office">
<a-input v-model:value="formData.office" placeholder="请输入过鱼设施名称" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="过鱼时间" name="regionName">
<a-date-picker
v-model:value="formData.regionName"
style="width: 100%"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
placeholder="选择日期"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="鱼种类" name="location">
<a-input v-model:value="formData.location" placeholder="请输入鱼种类" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="游向" name="DIRECTION">
<a-select v-model:value="formData.DIRECTION" placeholder="请选择游向" allow-clear>
<a-select-option value="上行">上行</a-select-option>
<a-select-option value="下行">下行</a-select-option>
<a-select-option value="其他">其他</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="level1">
<a-input-number
v-model:value="formData.level1"
style="width: 100%"
placeholder="数量"
:min="0"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="体长" name="level2">
<a-input-number
v-model:value="formData.level2"
style="width: 100%"
placeholder="体长"
:min="0"
/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="平均体重" name="level3">
<a-input-number
v-model:value="formData.level3"
style="width: 100%"
placeholder="平均体重"
:min="0"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="水温" name="level4">
<a-input-number
v-model:value="formData.level4"
style="width: 100%"
placeholder="水温"
:min="0"
/>
</a-form-item>
</a-col>
</a-row>
<a-form-item
label="图片路径"
name="level5"
extra="请输入图片文件名或路径images/photo.jpg"
>
<a-input v-model:value="formData.level5" placeholder="图片路径" />
</a-form-item>
<a-form-item
label="视频路径"
name="level6"
extra="请输入视频文件名或路径videos/video.mp4"
>
<a-input v-model:value="formData.level6" placeholder="视频路径" />
</a-form-item>
</a-form>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, reactive, watch, computed } from 'vue'
import { message } from 'ant-design-vue'
import type { Rule } from 'ant-design-vue/es/form'
import dayjs from 'dayjs' // Antdv dayjs
// Props
interface Props {
visible: boolean
initialValues?: any | null
loading?: boolean
}
const props = withDefaults(defineProps<Props>(), {
visible: false,
initialValues: null,
loading: false
})
// Emits
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'cancel'): void
(e: 'ok', values: any): void
}>()
//
const formRef = ref()
//
const formData = reactive({
stcd: undefined,
title: undefined,
office: undefined,
regionName: undefined,
location: undefined,
DIRECTION: undefined,
level1: undefined,
level2: undefined,
level3: undefined,
level4: undefined,
level5: undefined,
level6: undefined,
status: '正常'
})
//
const rules: Record<string, Rule[]> = {
stcd: [{ required: true, message: '请输入流域', trigger: 'blur' }],
title: [{ required: true, message: '请输入电站名称', trigger: 'blur' }],
office: [{ required: true, message: '请输入过鱼设施名称', trigger: 'blur' }],
regionName: [{ required: true, message: '请选择过鱼时间', trigger: 'change' }]
}
//
const isEdit = computed(() => !!props.initialValues)
// visible initialValues
watch(
() => [props.visible, props.initialValues],
([newVisible, newValues]) => {
if (newVisible) {
if (newValues) {
//
// regionName dayjs 便 DatePicker
// a-date-picker 使 value-format="YYYY-MM-DD" dayjs
Object.keys(formData).forEach((key) => {
if (key === 'regionName') {
// 'YYYY-MM-DD'
formData[key] = newValues[key] ? dayjs(newValues[key]) : undefined
} else {
formData[key] = newValues[key]
}
})
} else {
//
resetForm()
}
}
},
{ immediate: true }
)
//
const resetForm = () => {
if (formRef.value) {
formRef.value.resetFields()
}
// reactive
formData.stcd = undefined
formData.title = undefined
formData.office = undefined
formData.regionName = undefined
formData.location = undefined
formData.DIRECTION = undefined
formData.level1 = undefined
formData.level2 = undefined
formData.level3 = undefined
formData.level4 = undefined
formData.level5 = undefined
formData.level6 = undefined
formData.status = '正常'
}
//
const handleCancel = () => {
emit('update:visible', false)
emit('cancel')
resetForm()
}
//
const handleOk = async () => {
try {
//
await formRef.value.validate()
//
const submitValues = { ...formData }
// dayjs
if (submitValues.regionName && dayjs.isDayjs(submitValues.regionName)) {
submitValues.regionName = submitValues.regionName.format('YYYY-MM-DD')
}
// (InputNumber number)
//
;['level1', 'level2', 'level3', 'level4'].forEach((key) => {
if (submitValues[key] !== undefined && submitValues[key] !== null) {
submitValues[key] = Number(submitValues[key])
}
})
emit('ok', submitValues)
} catch (error) {
console.error('Validate Failed:', error)
message.error('请检查表单填写是否正确')
}
}
</script>
<style scoped>
/* 如有需要,添加局部样式 */
</style>

View File

@ -0,0 +1,255 @@
<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 #actions="{ form }">
<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-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>
</template>
</BasicSearch>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted, nextTick } from "vue";
import { message } from "ant-design-vue";
import { PlusOutlined, SaveOutlined, CheckSquareOutlined } 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";
// --- Props & Emits ---
interface Props {
importBtn: (file: File) => void;
saveBtn: () => void;
handleAdd: () => void;
}
const props = defineProps<Props>();
const emit = defineEmits<{
(e: "searchFinish", values: any, label: string): void;
}>();
// --- State ---
const fileInputRef = ref<HTMLInputElement>();
// initSearchData
const initSearchData = {
dmStcd: "008660306300000079", // ID
stcd: {
dataDimensionData: "all",
dataDimensionType: "hyBase",
hbrvcd: "",
stcdId: "",
},
typeDate: "",
RangePickerList: [
dayjs().startOf("month").format("YYYY-MM-DD"),
dayjs().endOf("day").format("YYYY-MM-DD"),
],
};
const searchData = ref<any>({ ...initSearchData });
// --- Search List Configuration ---
// STCDSTRDTENDDTDIRECTION012
const searchList: any = computed(() => [
{
type: "waterStation",
name: "stcd",
label: "选择水电站",
fieldProps: {
allowClear: true,
},
options: [],
},
{
type: "Select",
name: "strdt",
label: "过鱼设施编码",
fieldProps: {
allowClear: true,
},
options: [],
},
{
type: "Select",
name: "DIRECTION",
label: "游向",
width: 120,
options: [
{ label: "上行", value: "0" },
{ label: "下行", value: "1" },
{ label: "上下行", value: "2" },
],
fieldProps: {
allowClear: true,
},
},
{
type: "Input",
name: "typeDate",
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: "RangePickerList",
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.RangePickerList) {
params.startDate = values.RangePickerList[0].format("YYYY-MM-DD");
params.endDate = values.RangePickerList[1].format("YYYY-MM-DD");
}
emit("searchFinish", values, label);
};
const onValuesChange = (changedValues: any, allValues: any) => {
// searchData便使
searchData.value = { ...searchData.value, ...allValues };
if (changedValues.RangePickerList) {
//
console.log("Time changed:", changedValues.RangePickerList);
}
};
const handleReset = (form: any) => {
//
if (form) {
form.resetFields();
}
//
nextTick(() => {
if (form) {
form.setFieldsValue(initSearchData);
}
//
emit("searchFinish", initSearchData, "两河口出库水温站");
});
};
// --- Lifecycle ---
onMounted(() => {
//
emit("searchFinish", initSearchData, "两河口出库水温站");
});
</script>
<style scoped></style>

View File

@ -25,13 +25,20 @@ export default ({ mode }: ConfigEnv): UserConfig => {
proxy: { proxy: {
[env.VITE_APP_BASE_API]: { [env.VITE_APP_BASE_API]: {
// 线上API地址 // 线上API地址
//target: 'http://192.168.1.20:8090/', // target: 'http://10.84.121.4:8093/',
// 本地API地址 // 本地API地址
target: 'http://localhost:8093', target: 'http://localhost: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), '')
}, },
'/geoserver': {
// target: 'https://211.99.26.225:18085', // 地图-线上API地址
target: 'http://172.16.31.112:18084', // 地图-本地API地址
changeOrigin: true,
secure: false,
rewrite: path => path.replace(new RegExp('^/geoserver'), '/geoserver')
},
'/process': { '/process': {
target: 'http://localhost:5174', target: 'http://localhost:5174',
changeOrigin: true changeOrigin: true