打包不成功修改严格模式

This commit is contained in:
扈兆增 2026-04-22 17:53:20 +08:00
parent 7d95fd9b27
commit d8f743ef4e
66 changed files with 60845 additions and 7328 deletions

View File

@ -3,6 +3,6 @@
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
NODE_ENV='development'
VITE_APP_TITLE = '公司开发平台框架'
VITE_APP_TITLE = '水电水利建设项目全过程环境管理信息平台'
VITE_APP_PORT = 3000
VITE_APP_BASE_API = '/dev-api'

View File

@ -1,6 +1,6 @@
## 生产环境
NODE_ENV='production'
VITE_APP_TITLE = 'NewFrameWork2023-WEB'
VITE_APP_TITLE = 'qgc-buji-web'
VITE_APP_PORT = 3000
VITE_APP_BASE_API = '/prod-api'

View File

@ -1,6 +1,6 @@
## 模拟环境
NODE_ENV='staging'
VITE_APP_TITLE = 'NewFrameWork2023-WEB'
VITE_APP_TITLE = 'qgc-buji-web'
VITE_APP_PORT = 3000
VITE_APP_BASE_API = '/prod--api'

View File

@ -4,9 +4,9 @@
<meta charset="UTF-8" />
<link rel="icon" href="/logo.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="NewFrameWork2023-WEB" />
<meta name="keywords" content="NewFrameWork2023-WEB" />
<title>公司开发平台框架</title>
<meta name="description" content="qgc-buji-web" />
<meta name="keywords" content="qgc-buji-web" />
<title>水电水利建设项目全过程环境管理信息平台</title>
</head>
<body>
<div id="app"></div>

View File

@ -1,9 +1,9 @@
{
"name": "NewFrameWork2023-WEB",
"name": "qgc-buji-web",
"version": "1.2.0",
"scripts": {
"dev": "vite serve --mode development",
"build:prod": "vue-tsc --noEmit && vite build --mode production",
"build": "vue-tsc --noEmit && vite build --mode production",
"build:mvn": "vite build --mode production",
"serve": "vite preview",
"lint": "eslint src/**/*.{ts,js,vue} --fix",
@ -16,7 +16,7 @@
"@vueuse/core": "^9.1.1",
"@wangeditor/editor": "^5.0.0",
"@wangeditor/editor-for-vue": "^5.1.10",
"ant-design-vue": "^4.2.6",
"ant-design-vue": "latest",
"axios": "^1.2.0",
"better-scroll": "^2.4.2",
"dayjs": "^1.11.20",
@ -50,6 +50,8 @@
"devDependencies": {
"@commitlint/cli": "^16.2.3",
"@commitlint/config-conventional": "^16.2.1",
"@types/dom-to-image": "^2.6.7",
"@types/lodash": "^4.17.24",
"@types/node": "^16.11.7",
"@types/nprogress": "^0.2.0",
"@types/path-browserify": "^1.0.0",
@ -67,10 +69,10 @@
"prettier": "^2.6.2",
"sass": "^1.53.0",
"tailwindcss": "^3.2.4",
"typescript": "^4.7.4",
"typescript": "latest",
"vite": "^4.0.3",
"vite-plugin-svg-icons": "^2.0.1",
"vue-tsc": "^0.35.0"
"vue-tsc": "latest"
},
"repository": "https://gitee.com/youlaiorg/vue3-element-admin.git",
"author": "有来开源组织",

File diff suppressed because it is too large Load Diff

View File

@ -80,7 +80,7 @@ export function listDepartments(queryParams?: DeptQuery): AxiosPromise<Dept[]> {
/**
*
*/
export function listDeptOptions(): AxiosPromise<OptionType[]> {
export function listDeptOptions(): AxiosPromise<Dept[]> {
return request({
url: '/api/v1/dept/options',
method: 'get'

View File

@ -170,7 +170,7 @@ export function deleteDictTypes(ids: string) {
*
* @param typeCode
*/
export function getDictionaries(typeCode: string): AxiosPromise<OptionType[]> {
export function getDictionaries(typeCode: string): AxiosPromise<any[]> {
return request({
url: '/api/v1/dict/types/' + typeCode + '/items',
method: 'get'

View File

@ -7,6 +7,10 @@ export interface DictQuery extends PageQuery {
*/
name?: string;
}
interface PageResult<T> {
total: number;
list: T[];
}
/**
*
@ -34,6 +38,10 @@ export interface DictTypeForm {
status: number;
remark: string;
}
interface PageQuery {
pageIndex: number;
pageSize: number;
}
/**
*

View File

@ -0,0 +1,34 @@
import request from '@/utils/request';
// 分页查询过鱼数据
export function getFishDraftPage(data:any) {
return request({
url: '/data/fishDraft/page',
method: 'post',
data
});
}
//新增目录
export function addFishDraft(queryParams:any) {
return request({
url: '/data/fishDraft/add',
method: 'post',
data: queryParams
});
}
//修改目录
export function editFishDraft(queryParams:any) {
return request({
url: '/data/fishDraft/update',
method: 'post',
data: queryParams
});
}
//删除
export function delFishDraft(data:any) {
return request({
url: '/data/fishDraft/batchDelete',
method: 'post',
data
});
}

View File

@ -91,7 +91,7 @@ export function listMenus(queryParams: MenuQuery): AxiosPromise<Menu[]> {
/**
*
*/
export function listMenuOptions(): AxiosPromise<OptionType[]> {
export function listMenuOptions(): AxiosPromise<Menu[]> {
return request({
url: '/api/v1/menus/options',
method: 'get'

View File

@ -108,7 +108,7 @@ export function postOrgscope (queryParams:any){
*/
export function listRoleOptions(
queryParams?: RoleQuery
): AxiosPromise<OptionType[]> {
): AxiosPromise<any[]> {
return request({
url: '/api/v1/roles/options',
method: 'get',

View File

@ -1,10 +1,17 @@
/**
*
*/
interface PageQuery {
pageIndex: number;
pageSize: number;
}
export interface RoleQuery extends PageQuery {
keywords?: string;
}
interface PageResult<T> {
total: number;
list: T[];
}
/**
*
*/

View File

@ -9,10 +9,14 @@ export interface UserInfo {
roles: string[];
perms: string[];
}
/**
*
*/
export interface PageQuery {
keywords: string;
status: number;
deptId: number;
}
export interface UserQuery extends PageQuery {
keywords: string;
status: number;
@ -36,10 +40,14 @@ export interface UserType {
createTime: string;
}
interface PageResult<T> {
total: number;
list: T[];
}
/**
*
*/
export type UserPageResult = PageResult<UserType[]>;
export type UserPageResult = PageResult<any[]>;
/**
*

View File

@ -16,7 +16,7 @@
</div>
</template>
<script setup lang="ts">
import { ref, watch, onMounted } from "vue";
import { ref, watch } from "vue";
import { useUiStore } from "@/store/modules/ui";
import shiliangImg from "@/assets/images/map-shiliangtu.png";
import dixingImg from "@/assets/images/map-dixingtu.png";
@ -52,10 +52,6 @@ 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>
<style lang="scss" scoped>

View File

@ -18,9 +18,17 @@
: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"
/>
<!-- 普通日期选择器 -->
<a-date-picker
v-if="item.type === 'DataPicker'"
v-else-if="item.type === 'DataPicker'"
v-model:value="formData[item.name]"
:picker="item.picker"
:format="item.fieldProps?.format"
@ -70,20 +78,6 @@
{{ 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="请选择电站"
@ -117,6 +111,21 @@
{{ opt.label }}
</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' }"
>
<a-radio
v-for="opt in item.options"
:key="opt.value"
:value="opt.value"
>
{{ opt.label }}
</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
@ -141,7 +150,6 @@
<script lang="ts" setup>
import { ref, computed, reactive, watch, onMounted } from "vue";
import type { FormInstance } from "ant-design-vue";
// --- ---
export interface SearchItem {
@ -178,7 +186,7 @@ const emit = defineEmits<{
(e: "reset"): void;
}>();
const formRef = ref<FormInstance>();
const formRef = ref<any>();
const formData = reactive<any>({});
const rules = reactive<Record<string, any>>({});
@ -209,9 +217,9 @@ const initForm = () => {
const dataDimensionDataChange = (value: any) => {
formData.stcd.dataDimensionData = value;
};
const hbrvcdChange = (value: any) => {
formData.stcd.hbrvcd = value;
};
// const hbrvcdChange = (value: any) => {
// formData.stcd.hbrvcd = value;
// };
const stcdIdChange = (value: any) => {
formData.stcd.stcdId = value;
};

View File

@ -0,0 +1,186 @@
<template>
<a-table
size="small"
:loading="loading"
:row-selection="enableRowSelection ? rowSelection : undefined"
:data-source="tableData"
:columns="columns"
:pagination="paginationConfig"
:scroll="{ x: '100%' }"
:row-key="rowKey"
@change="handleTableChange"
>
<!-- 透传插槽支持自定义列内容 -->
<template v-for="slot in Object.keys($slots)" #[slot]="scope" :key="slot">
<slot :name="slot" v-bind="scope"></slot>
</template>
</a-table>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, watch } from "vue";
// --- Types ---
interface Props {
columns: any[];
//
listUrl: (params: any) => Promise<any>;
//
enableRowSelection?: boolean;
// Key Table
rowKey?: string;
// /
searchParams?: Record<string, any>;
//
defaultPageSize?: number;
}
const props = withDefaults(defineProps<Props>(), {
enableRowSelection: false,
rowKey: "id",
searchParams: () => ({}),
defaultPageSize: 20,
});
const emit = defineEmits<{
//
(e: "data-loaded", params: any, data: any): void;
//
(e: "selection-change", selectedRowKeys: string[], selectedRows: any[]): void;
}>();
// --- State ---
const loading = ref(false);
const tableData = ref<any[]>([]);
const total = ref(0);
const page = ref(1);
const size = ref(props.defaultPageSize);
const selectedRowKeys = ref<string[]>([]);
const selectedRows = ref<any[]>([]);
// --- Row Selection ---
const rowSelection = computed(() => ({
selectedRowKeys: selectedRowKeys.value,
onChange: (keys: string[], rows: any[]) => {
selectedRowKeys.value = keys;
selectedRows.value = rows;
emit("selection-change", keys, rows);
},
getCheckboxProps: (record: any) => ({
disabled: record.disabled, //
}),
}));
// --- Pagination Config ---
const paginationConfig = computed(() => ({
total: total.value,
current: page.value,
pageSize: size.value,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total: number) => `${total}`,
pageSizeOptions: ["20", "50", "100"],
}));
// --- Methods ---
/**
* 获取列表数据
* @param extraParams 额外的临时参数可选
*/
const getList = async (extraParams?: Record<string, any>) => {
loading.value = true;
try {
//
const params = {
...props.searchParams,
...extraParams,
skip: page.value,
take: size.value,
filter: {}
// skip/take
// skip: (page.value - 1) * size.value,
// take: size.value,
};
const res = await props.listUrl(params);
// { data: { records: [], total: 0 } }
//
const records = res?.data?.records || res?.data?.list || res?.data || [];
const totalCount = res?.data?.total || res?.total || 0;
tableData.value = records;
total.value = totalCount;
//
emit("data-loaded", params, { records, total: totalCount });
} catch (error) {
console.error("Fetch table data error:", error);
tableData.value = [];
total.value = 0;
} finally {
loading.value = false;
}
};
/**
* 处理分页排序筛选变化
*/
const handleTableChange = (pag: any) => {
page.value = pag.current;
size.value = pag.pageSize;
getList();
};
/**
* 重置表格状态回到第一页清空选中
*/
const reset = () => {
page.value = 1;
size.value = props.defaultPageSize;
selectedRowKeys.value = [];
selectedRows.value = [];
getList();
};
/**
* 刷新当前页
*/
const refresh = () => {
getList();
};
// --- Expose Methods to Parent ---
defineExpose({
getList,
reset,
refresh,
//
getSelected: () => ({
keys: selectedRowKeys.value,
rows: selectedRows.value,
}),
});
// --- Watchers ---
//
watch(
() => props.searchParams,
() => {
page.value = 1; //
getList();
},
{ deep: true }
);
// --- Lifecycle ---
onMounted(() => {
getList();
});
</script>
<style scoped>
/* 如有必要,添加少量样式 */
</style>

View File

@ -22,7 +22,7 @@
</template>
<script setup lang="ts">
import { ref, PropType, defineEmits, computed ,onMounted} from 'vue'
import { ref, PropType, computed ,onMounted} from 'vue'
import { ElMessage } from 'element-plus'
import { useAppStore } from '@/store/modules/app';
const appStore = useAppStore();

View File

@ -1,79 +0,0 @@
<template>
<div style="border: 1px solid #ccc">
<!-- 工具栏 -->
<Toolbar
:editor="editorRef"
:defaultConfig="toolbarConfig"
style="border-bottom: 1px solid #ccc"
:mode="mode"
/>
<!-- 编辑器 -->
<Editor
:defaultConfig="editorConfig"
v-model="defaultHtml"
@onChange="handleChange"
style="height: 500px; overflow-y: hidden"
:mode="mode"
@onCreated="handleCreated"
/>
</div>
</template>
<script setup lang="ts">
import { onBeforeUnmount, shallowRef, reactive, toRefs } from 'vue';
import { Editor, Toolbar } from '@wangeditor/editor-for-vue';
// API
import { uploadFileApi } from '@/api/file';
const props = defineProps({
modelValue: {
type: [String],
default: ''
}
});
const emit = defineEmits(['update:modelValue']);
// shallowRef
const editorRef = shallowRef();
const state = reactive({
toolbarConfig: {},
editorConfig: {
placeholder: '请输入内容...',
MENU_CONF: {
uploadImage: {
//
async customUpload(file: any, insertFn: any) {
uploadFileApi(file).then(response => {
const url = response.data.url;
insertFn(url);
});
}
}
}
},
defaultHtml: props.modelValue,
mode: 'default'
});
const { toolbarConfig, editorConfig, defaultHtml, mode } = toRefs(state);
const handleCreated = (editor: any) => {
editorRef.value = editor; // editor
};
function handleChange(editor: any) {
emit('update:modelValue', editor.getHtml());
}
//
onBeforeUnmount(() => {
const editor = editorRef.value;
if (editor == null) return;
editor.destroy();
});
</script>
<style src="@wangeditor/editor/dist/css/style.css"></style>

View File

@ -44,11 +44,10 @@
</template>
<script setup lang="ts">
import { ref, watch, computed } from 'vue';
import { ref } from 'vue';
//
const dataLoading = ref(false);
const data = ref<Record<string, number>>({ one: 0, two: 0, three: 0 });

View File

@ -0,0 +1,271 @@
<template>
<a-select
:style="{ width: width }"
:value="modelValue"
@change="handleChange"
placeholder="请选择鱼名称"
mode="multiple"
show-search
:filter-option="filterOption"
class="custom-fish-select"
:dropdownMatchSelectWidth="false"
:getPopupContainer="(triggerNode: HTMLElement) => triggerNode.parentNode"
@dropdownVisibleChange="handleDropdownVisibleChange"
>
<!-- 自定义 Tag 显示名称 -->
<template #tagRender="{ value: tagId, onClose }">
<a-tag
closable
@close="onClose"
style="margin-right: 3px; max-width: 120px"
>
{{ getFishNameById(tagId) }}
</a-tag>
</template>
<template #dropdownRender>
<div class="custom-dropdown-container">
<!-- 左侧可滚动的选项列表 -->
<div class="dropdown-left-list">
<div
v-for="opt in filteredOptions"
:key="opt.id"
class="dropdown-item"
:class="{
'is-active': Array.isArray(modelValue) && modelValue.includes(opt.id),
'is-hovered': opt.id === hoveredId,
}"
@click.stop="handleSelectOption(opt)"
@mouseenter="hoveredId = opt.id"
>
<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">
无匹配数据
</div>
</div>
<!-- 中间分割线 -->
<div class="dropdown-divider"></div>
<!-- 右侧固定显示的别名/详情 -->
<div class="dropdown-right-detail">
<div v-if="currentDetailData" class="detail-content">
<div class="detail-title">{{ currentDetailData.name }}</div>
<div class="detail-alias" :title="currentDetailData.alias">
{{ currentDetailData.alias || "暂无别名" }}
</div>
</div>
<div v-else class="detail-placeholder">请选择或悬停查看</div>
</div>
</div>
</template>
</a-select>
</template>
<script lang="ts" setup>
import { ref, computed } from "vue";
// --- Props & Emits ---
interface Props {
modelValue: string[]; // ID
options: any[];
width: string;
}
const props = defineProps<Props>();
const emit = defineEmits<{
(e: "update:modelValue", value: string[]): void;
}>();
// --- State ---
// API
const options = ref<any>(props.options || []);
const hoveredId = ref<string | null>(null);
// --- Computed ---
const filteredOptions = computed(() => {
return options.value;
});
const currentDetailData = computed(() => {
if (hoveredId.value) {
return options.value.find((item: any) => item.id === hoveredId.value);
}
return null;
});
// --- Methods ---
const handleDropdownVisibleChange = (open: boolean) => {
if (!open) {
hoveredId.value = null;
}
};
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 lowerInput = input.toLowerCase();
const nameMatch = targetOpt.name?.toLowerCase().includes(lowerInput);
const aliasMatch = targetOpt.alias?.toLowerCase().includes(lowerInput);
return nameMatch || aliasMatch;
};
const handleSelectOption = (opt: any) => {
let newValues: string[] = Array.isArray(props.modelValue) ? [...props.modelValue] : [];
const index = newValues.indexOf(opt.id);
if (index > -1) {
newValues.splice(index, 1);
} else {
newValues.push(opt.id);
}
//
emit("update:modelValue", newValues);
};
const handleChange = (val: any) => {
// a-select tag
emit("update:modelValue", val);
};
const getFishNameById = (id: string) => {
if (!id) return "";
const fish = options.value.find((item: any) => item.id === id);
return fish ? fish.name : id;
};
</script>
<style lang="scss" scoped>
.custom-fish-select {
:deep(.ant-select-dropdown) {
padding: 0 !important;
min-width: 400px !important;
width: auto !important;
}
}
.custom-dropdown-container {
display: flex;
background: #fff;
border-radius: 4px;
overflow: hidden;
height: 300px; /* 固定高度 */
}
.dropdown-left-list {
width: 150px;
height: 100%;
overflow-y: auto;
border-right: 1px solid #f0f0f0;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 3px;
}
}
.dropdown-item {
padding: 8px 12px;
cursor: pointer;
font-size: 14px;
color: #333;
transition: background-color 0.3s;
display: flex;
justify-content: space-between;
align-items: center;
&:hover {
background-color: #f5f5f5;
}
&.is-active {
background-color: #e6f7ff;
color: #1890ff;
font-weight: 500;
}
&.is-hovered {
background-color: #fafafa;
}
.item-name {
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
}
.check-icon {
color: #1890ff;
font-weight: bold;
margin-left: 8px;
}
}
.dropdown-divider {
width: 1px;
background-color: #e8e8e8;
height: 100%;
}
.dropdown-right-detail {
width: 250px;
height: 100%;
padding: 16px;
box-sizing: border-box;
background-color: #fff;
overflow-y: auto;
display: flex;
flex-direction: column;
position: relative;
}
.detail-content {
display: flex;
flex-direction: column;
gap: 8px;
}
.detail-title {
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 4px;
}
.detail-alias {
font-size: 14px;
color: #666;
line-height: 1.5;
word-break: break-all;
}
.detail-placeholder {
color: #999;
font-size: 14px;
text-align: center;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.empty-tip {
padding: 10px;
color: #999;
text-align: center;
font-size: 12px;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -1,115 +0,0 @@
type EventCallback = (...args: any[]) => void;
export class EventEmitter {
private events: Map<string, EventCallback[]> = new Map();
on(event: string, callback: EventCallback): this {
if (!this.events.has(event)) {
this.events.set(event, []);
}
this.events.get(event)!.push(callback);
return this;
}
off(event: string, callback?: EventCallback): this {
if (!this.events.has(event)) return this;
if (callback) {
const callbacks = this.events.get(event)!;
const index = callbacks.indexOf(callback);
if (index > -1) {
callbacks.splice(index, 1);
}
} else {
this.events.delete(event);
}
return this;
}
emit(event: string, ...args: any[]): this {
if (this.events.has(event)) {
this.events.get(event)!.forEach(callback => {
try {
callback(...args);
} catch (error) {
console.error(`Event ${event} handler error:`, error);
}
});
}
return this;
}
once(event: string, callback: EventCallback): this {
const onceCallback: EventCallback = (...args: any[]) => {
callback(...args);
this.off(event, onceCallback);
};
return this.on(event, onceCallback);
}
removeAllListeners(): this {
this.events.clear();
return this;
}
listenerCount(event: string): number {
return this.events.get(event)?.length || 0;
}
}type EventCallback = (...args: any[]) => void;
export class EventEmitter {
private events: Map<string, EventCallback[]> = new Map();
on(event: string, callback: EventCallback): this {
if (!this.events.has(event)) {
this.events.set(event, []);
}
this.events.get(event)!.push(callback);
return this;
}
off(event: string, callback?: EventCallback): this {
if (!this.events.has(event)) return this;
if (callback) {
const callbacks = this.events.get(event)!;
const index = callbacks.indexOf(callback);
if (index > -1) {
callbacks.splice(index, 1);
}
} else {
this.events.delete(event);
}
return this;
}
emit(event: string, ...args: any[]): this {
if (this.events.has(event)) {
this.events.get(event)!.forEach(callback => {
try {
callback(...args);
} catch (error) {
console.error(`Event ${event} handler error:`, error);
}
});
}
return this;
}
once(event: string, callback: EventCallback): this {
const onceCallback: EventCallback = (...args: any[]) => {
callback(...args);
this.off(event, onceCallback);
};
return this.on(event, onceCallback);
}
removeAllListeners(): this {
this.events.clear();
return this;
}
listenerCount(event: string): number {
return this.events.get(event)?.length || 0;
}
}

View File

@ -1,7 +1,15 @@
// 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'
declare global {
interface Window {
__lyConfigs?: {
theme?: string;
[key: string]: any; // 根据实际配置结构补充具体字段,或使用索引签名兼容其他属性
};
__mapMode?: string; // 建议同时声明代码中用到的其他全局变量
}
}
/**
*
* @param {Number} height
@ -17,8 +25,8 @@ const D = -40467.74
* @returns nameEn为下标的图例数据
*/
export const legendData2Obj = (data: any[]) => {
const _tempData = {}
const f = (_data) => {
const _tempData: any = {}
const f = (_data: any[]) => {
_data.forEach((item) => {
// childrenList有值表示这是一个分组
if (item?.childrenList && item.childrenList?.length > 0) {
@ -171,7 +179,7 @@ export const layerConfig2Flat = (data: any): any[] => {
* @param config -
* @returns
*/
export const getMapConfig = (config) => {
export const getMapConfig = () => {
// const mapBaseUrls = MemoryCache.get('mapBaseUrls') || {}
// const r = { ...config }
// const { urlType } = r
@ -193,7 +201,7 @@ export const getMapConfig = (config) => {
*/
export const resetMapElPos = () => {
const legend = document.querySelector('#qgc-legendtl') as HTMLElement // 图例
const filter = document.querySelector('#map-filter-container') 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 // 底图模式切换
@ -217,7 +225,7 @@ export const resetMapElPos = () => {
* @param position -
* @returns
*/
const getListByPosition = (data, position: string) => data?.data?.filter((el) => el.position === position && el.code)
const getListByPosition = (data: any, position: string) => data?.data?.filter((el: any) => el.position === position && el.code)
/**
*
@ -225,7 +233,7 @@ const getListByPosition = (data, position: string) => data?.data?.filter((el) =>
* @param data -
* @param offset -
*/
export const setMapLegendPos = (layoutType: string, data, offset: number = 456) => {
export const setMapLegendPos = (layoutType: string, data: any, 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
@ -238,7 +246,7 @@ export const setMapLegendPos = (layoutType: string, data, offset: number = 456)
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 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'] // 三行底部布局

View File

@ -1,5 +1,5 @@
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 {
@ -47,8 +47,9 @@ export class MapClass implements MapClassInterface {
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)
mdLayerShowOrHidden(): void {
// layerType: String, key?: String, baseid: String, checked: boolean, isAll: boolean
// this.service.mdLayerShowOrHidden(layerType, key, baseid, checked, isAll)
}
// 添加基础数据图层
addBaseDataLayer(layer: any): void {
@ -63,8 +64,8 @@ export class MapClass implements MapClassInterface {
this.service.mdLayerTreeShowOrHidden(layerType, checked)
}
// 初始化加载描点数据
addInitDataLayer = (pointData: any[], layerType: any, mdoptions?: MDOptions, legendArray?: any) => {
return this.service.addInitDataLayer(pointData, layerType, mdoptions, legendArray)
addInitDataLayer = (pointData: any[], layerType: any, mdoptions?: any) => {
return this.service.addInitDataLayer(pointData, layerType, mdoptions)
}
//切换底图
baseLayerSwitcher(key: string): void {
@ -110,4 +111,19 @@ export class MapClass implements MapClassInterface {
this.service.destroy()
}
// 切换地图视图
switchView(): void {
// this.service.switchView()
}
// 飞行到指定的点
fitBounds(): void {
// this.service.fitBounds(bounds)
}
// 飞行到指定的点
flyTopanto(): void {
// this.service.flyTopanto(point)
}
}

View File

@ -8,7 +8,8 @@ export type layerType =
| "arcgisFeature"
| "dynamicMapLayer"
| "arcgisMap"
| "label";
| "label"
| "vector";
export type layerOption = {
opacity?: number;
data?: Array<any>;
@ -44,6 +45,7 @@ export interface layer {
layerType?: string
matrixIds_index?: string[]
tileMatrixSetID?: string
urlType: string
}
export interface MapInterface {
@ -94,35 +96,35 @@ export interface MapInterface {
//飞行到指定的点
fitBounds(bbox,bearing):void
flyTopanto(positon,zoom,stcd):void
/**
*
* @param HH3DUrlArray
*/
addQxsyLayer(HH3DUrlArray: siteItem): void
// /**
// * 加载倾斜摄影数据
// * @param HH3DUrlArray
// */
// addQxsyLayer(HH3DUrlArray: siteItem): void
/**
*
* @param HH3DUrlArray
*/
removeQxsyLayer(HH3DUrlArray: siteItem): void
// /**
// * 移除倾斜摄影数据
// * @param HH3DUrlArray
// */
// removeQxsyLayer(HH3DUrlArray: siteItem): void
/**
*
* @param HH3DUrlArray
*/
qxsyToPosition(HH3DUrlArray: siteItem): void
// /**
// * 倾斜摄影定位
// * @param HH3DUrlArray
// */
// qxsyToPosition(HH3DUrlArray: siteItem): void
/**
*
* @param HH3DUrlArray
*/
qxsyClipBoundary(HH3DUrlArray: siteItem): void
// /**
// * 倾斜摄影裁剪
// * @param HH3DUrlArray
// */
// qxsyClipBoundary(HH3DUrlArray: siteItem): void
/**
*
* @param HH3DUrlArray
*/
removeQxsyClipBoundary(HH3DUrlArray: siteItem): void
// /**
// * 移除倾斜摄影裁剪
// * @param HH3DUrlArray
// */
// removeQxsyClipBoundary(HH3DUrlArray: siteItem): void
/**
*
@ -137,8 +139,8 @@ export interface MapInterface {
* @param checked
* @param isAll
*/
mdLayerShowOrHidden(layerType: String, key?: String, baseid: String, checked: boolean, isAll: boolean): void
mdLayerShowOrHidden(): void
// layerType: String, key?: String, baseid: String, checked: boolean, isAll: boolean
/**
*
* @param layerType
@ -182,10 +184,4 @@ export interface MapInterface {
*
*/
destroy(): void
setVisibleDistanceOfLable(): void
recoverVisibleDistanceOfLable(): viod
changeMaskStyle(options?: any)
}

View File

@ -1,373 +1,440 @@
import { layer, MapInterface } from "./map";
import * as L from "leaflet";
import * as esriLeaflet from "esri-leaflet";
import { mapServerBaseUrl } from "./map.class";
// @ts-ignore
import axios from "axios";
// import "@/components/thematicMap/leaflet/leaflet.inflatable-markers-group.js"
// import { MapInterface } from './map';
// import * as L from 'leaflet';
// import * as esriLeaflet from 'esri-leaflet';
// import '@/utils/leaflet/leaflet-tilelayer-wmts-src.js';
// import { mapServerBaseUrl } from './map.class';
// import '@/utils/leaflet/leaflet.inflatable-markers-group.js';
// import {MDOptions} from './map.class';
// import { getIconPath } from "@/utils/index";
// // @ts-ignore
// import axios from 'axios';
// // import "@/components/thematicMap/leaflet/leaflet.inflatable-markers-group.js"
const tiledMapGroup = L.layerGroup();
const chartMapGroup = L.layerGroup();
const overlayGroup = L.layerGroup();
const CENTER_positionCN: any = [37.072654, 86.171125]; // [26.072654, 107.171125]; //中心纬经度 中国
// const tiledMapGroup = L.layerGroup();
// const chartMapGroup = L.layerGroup();
// const overlayGroup = L.layerGroup();
// const CENTER_positionCN: any = [38, 114.17112499999996]; //中心纬经度 中国
// const basinCenter = {
// DA_HHGLSX: [103.343357, 35.931812],
// FA_CJGLSX: [111.001911, 30.821327]
// };
// let boundCavansLayer: any = null;
// let ganliulist: any = [];
// export class MapLeaflet implements MapInterface {
// map: any = null;
// htmlMakerLayer: any = [];
// defaultScale = 10;
// minimumZoom = 7;
// setDrawPlug: any = null;
// layermarkers: any = [];
// rainlayerslist: any = [];
// private layerRegistry: Map<string, any> = new Map(); // ✅ 新增:存储 key -> layer 实例
let boundCavansLayer: any = null;
let ganliulist:any = []
export class MapLeaflet implements MapInterface {
map: any = null;
htmlMakerLayer: any = [];
minimumZoom = 7;
// private currentBaseLayerKey: string | null = null; // ✅ 新增:记录当前激活的底图 Key
temperatureMapObj: any = [];
// temperatureMapObj: any = [];
// //地图初始化
// init(container: HTMLElement, rectangle?: any): Promise<any> {
// try {
// console.log('init初始化container', container);
// var corner1 = L.latLng(55.35715491537772, 140.7821677051657);
// var corner2 = L.latLng(0.975580441812298, 67.56008229483018);
// var bounds = L.latLngBounds(corner2, corner1);
// const map = L.map(container as any, {
// preferCanvas: true,
// zoom: 4.5,
// minZoom: 4.23,
// maxZoom: 22, // 【修改点1】增大最大缩放级别允许用户继续滚轮放大
// maxNativeZoom: 12,
// zoomSnap: 0.1, // 【修改点2】让缩放更平滑不强制对齐整数
// zoomDelta: 0.5,
// center: CENTER_positionCN,
// attributionControl: false,
// zoomControl: false,
// trackResize: true,
// maxBounds: bounds,
// maxBoundsViscosity: 1.0,
// wheelPxPerZoomLevel: 180,
// });
// // ✅ 新增:可视化显示 maxBounds 范围
// // L.rectangle(bounds, {color: "#ff7800", weight: 3, opacity: 0.8, fillOpacity: 0.1})
// // .addTo(map)
// // .bindPopup("这是 maxBounds 的范围");
// this.map = map;
// this.map.on('zoomend', (e: any) => {
// console.log('当前缩放级别', e.target.getZoom());
// });
// return Promise.resolve(map);
// } catch (e) {
// console.log('测试', e);
// return Promise.reject({});
// }
// }
// addBaseDataLayer(layer: any): any {
// // The WMTS URL
// console.log(layer);
// switch (layer.type) {
// case 'wmts':
// if (layer.url) {
// console.log('https://211.99.26.225:18085' + layer.url);
// if (layer) {
// var ignLayer = L.tileLayer
// .wmts('https://211.99.26.225:18085/geoserver/gwc/service/wmts', {
// tileMatrixSet: 'EPSG:3857_qgc_qsj_arcgistiles_l13',
// tileSize: 256, //切片大小
// maxZoom: 13,
// noWrap: true,
// opacity: 0.99,
// minZoom: 4,
// styles:{
// abc:123
// },
// layer: 'qgc_qsj_arcgistiles_l13'
// })
// .addTo(this.map)
// .bringToBack();
// const registryKey = layer.key || layer.title;
// if (registryKey) {
// this.layerRegistry.set(registryKey, ignLayer);
// }
// layer._layer = ignLayer;
// layer._layer.layerGroup = overlayGroup;
// }
// }
// return layer;
addLayer(layer: any): any {
// The WMTS URL
console.warn(layer)
switch (layer.type) {
case "tiledMap":
if (layer.url) {
let setzIndex = 1;
if (layer.zIndex && layer.zIndex != "" && layer.zIndex != "0") {
setzIndex = Number(layer.zIndex);
}
if (layer.title !== '干流河流') {
layer._layer = esriLeaflet
.dynamicMapLayer({
url: layer.url,
opacity: layer.opacity,
zIndex: 999999999999
})
?.addTo(this.map)
.bringToBack()
} else {
axios.get('http://210.72.227.199:18084/geoserver/cite/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=cite:glhl_epsg4326&maxFeatures=50&outputFormat=application%2Fjson')
.then((res) => {
res.data?.features.map((item: any, index: number) => {
const a = L.geoJSON(item, {
style: {
},
}).addTo(this.map);
ganliulist.push(a)
});
})
}
}
return layer;
break
case "geoserver-wmts":
if (layer.url) {
if (layer.title == "地图瓦片图层") {
console.log('地图瓦片')
//增加遮罩
axios.get('/zfile/qgcbuji/overlayLand3.json')
.then((res) => {
var landLayer = L.geoJson(res.data, {
style: {
opacity: 0,
//填充透明度
fillOpacity: 1,
//填充颜色
fillColor: '#ececec'
}
})?.addTo(overlayGroup).bringToBack();
})
axios.get('/zfile/qgcbuji/overlayOcean.json')
.then((res) => {
var oceanLayer = L.geoJson(res.data, {
style: {
opacity: 0,
//填充透明度
fillOpacity: 1,
//填充颜色
fillColor: '#8eccf1'
}
})?.addTo(overlayGroup).bringToFront();
})
overlayGroup?.addTo(this.map);
}
var optionobj = JSON.parse(layer.options);
if (optionobj) {
var ignLayer = L.tileLayer
.wmts(layer.url, {
tileMatrixSet: optionobj.tileMatrixSet, //"EPSG:3857_qgc_arcgistiles_l13_google",
tileSize: 256, //切片大小
maxZoom: 13,
noWrap: true,
opacity: 1.01,
minZoom: 4,
layer: optionobj.layer, //"qgc_arcgistiles_l13_google",
})
.addTo(this.map)
.bringToBack();
layer._layer = ignLayer;
layer._layer.layerGroup = overlayGroup;
}
}
return layer;
case "arcgisMap":
if (layer.url) {
if (this.map) {
const mapLayer = esriLeaflet
.tiledMapLayer({
url: layer.url,
format: "image/png",
maxNativeZoom: 17,
})
.addTo(this.map)
.bringToBack();
layer._layer = mapLayer;
}
}
return layer;
case "tiledMapQuery":
if (layer.url && layer.id) {
if (layer.title == "岛屿") {
layer.layers = "cite:nhqddt_epsg3857";
}
layer._layer = L.tileLayer
.wms(layer.url, {
id: layer.id,
format: "image/png",
transparent: true,
zIndex: 9999,
layers: layer.layers,
// maxZoom:10,
})
.addTo(this.map);
// .bringToBack();
return layer;
}
return layer;
case "label":
if (layer.url) {
const tileLayer: any = L.tileLayer(layer.url, {
subdomains: ["0", "1", "2", "3", "4", "5", "6", "7"],
crossOrigin: "anonymous",
});
const boundary = (providerName: any) => {
// return BoundaryCanvas(tileLayer, {});
};
let boundaryLayers;
if (layer.title == "行政标注") {
boundaryLayers = boundary(tileLayer._leaflet_id)
.addTo(this.map)
.bringToBack()
.setZIndex(10);
} else {
boundaryLayers = boundary(tileLayer._leaflet_id).addTo(this.map);
}
tileLayer.boundLayer = boundaryLayers;
layer._layer = boundaryLayers;
return layer;
}
break;
// case 'markers':
// this.getRain(layer);
// break;
// default: {
// return;
// }
// }
// }
case "markers":
this.getRain(layer);
break;
default: {
return;
}
}
}
// baseLayerSwitcher(key: string) {
// if (!this.map) return;
// console.log('切换底图 key:', key);
/**
* leaflet加载wmts在非整数缩放时瓦片间会有缝隙
*/
inittiles() {
var originalInitTile = L.GridLayer.prototype._initTile;
L.GridLayer.include({
_initTile: function (tile: any) {
originalInitTile.call(this, tile);
// // 1. 如果当前已有底图且不是同一个,先移除
// if (this.currentBaseLayerKey && this.currentBaseLayerKey !== key) {
// const oldLayer = this.layerRegistry.get(this.currentBaseLayerKey);
// if (oldLayer && this.map.hasLayer(oldLayer)) {
// this.map.removeLayer(oldLayer);
// console.log(`已移除旧底图: ${this.currentBaseLayerKey}`);
// }
// }
var tileSize = this.getTileSize();
// // 2. 检查新底图是否已存在 registry 中
// let newLayer = this.layerRegistry.get(key);
tile.style.width = tileSize.x + 2 + "px";
tile.style.height = tileSize.y + 2 + "px";
},
});
}
init(container: React.ReactNode, rectangle?: any): Promise<any> {
try {
console.log("init初始化container", container);
this.inittiles();
// var corner1 = L.latLng(0.712, -10.227),
// corner2 = L.latLng(70.774, 204.125),
// // 3. 如果不存在,则创建并添加到地图和 registry
// if (!newLayer) {
// newLayer = this.createBaseLayerByKey(key);
// if (newLayer) {
// this.layerRegistry.set(key, newLayer);
// newLayer.addTo(this.map);
// // 确保底图在最底层
// newLayer.bringToBack();
// }
// } else {
// // 4. 如果已存在,直接添加(如果未添加)
// if (!this.map.hasLayer(newLayer)) {
// newLayer.addTo(this.map);
// newLayer.bringToBack();
// }
// }
// var corner1 = L.latLng(-50.712, -10.227),
// corner2 = L.latLng(100.774, 204.125),
// var corner1 = L.latLng(20.712, 50.227),
// corner2 = L.latLng(51.774, 160.125),
var corner1 = L.latLng(20.712, 0),
corner2 = L.latLng(51.774, 179.125),
bounds = L.latLngBounds(corner1, corner2);
const map = L.map(container as any, {
preferCanvas: true,
minZoom: 4.4,
center: CENTER_positionCN,
zoom: 4.4,
maxZoom: 18,
zoomControl: false,
maxBounds: bounds,
zoomSnap: 0,
crs: L.CRS.EPSG3857,
});
this.map = map;
// // 5. 更新当前激活的 Key
// if (newLayer) {
// this.currentBaseLayerKey = key;
// }
// }
//遮罩
var pNW = { lat: 85.0, lng: -90.0 };
var pNE = { lat: 85.0, lng: 270.0 };
var pSE = { lat: -45.0, lng: 270.0 };
var pSW = { lat: -45.0, lng: -90.0 };
var pArray = [];
pArray.push(pNW);
pArray.push(pSW);
pArray.push(pSE);
pArray.push(pNE);
pArray.push(pNW);
// /**
// * 根据 Key 创建具体的 Leaflet 图层实例
// */
// createBaseLayerByKey(key: string): L.Layer | null {
// console.log(key)
// const tdtToken = 'e90d56e5a09d1767899ad45846b0cefd' //企业版密钥勿换e650f138c4481cca888cd13094bb9026
// const mapType = 'w' //c:天地图经纬度底图;w:天地图墨卡托底图
// const URL_TerTDT = `https://t0.tianditu.gov.cn/ter_${mapType}/wmts?tk=${tdtToken}` //terMap
// switch (key) {
// case 's_province_boundaries':
// // 假设这是之前的 WMTS 矢量图层
// // 注意:原代码中硬编码了 URL 和 layer 名称,这里需要确保与实际服务对应
// return L.tileLayer.wmts('https://211.99.26.225:18085/geoserver/gwc/service/wmts', {
// tileMatrixSet: 'EPSG:3857_qgc_qsj_arcgistiles_l13',
// tileSize: 256,
// // maxZoom: 13,
// noWrap: true,
// opacity: 1,
// minZoom: 4,
// zIndex: 1,
// layer: 'qgc_qsj_arcgistiles_l13', // 请确认此 layer 名称是否对应“矢量”
// format: 'image/png'
// });
let mapURL =
mapServerBaseUrl + "arcgis/rest/services/zh_boundary/MapServer/0/query";
esriLeaflet
.query({
url: mapURL,
})
// .where(mapWhere)
.run((error: any, result: any, response: any) => {
if (response) {
// let test = arcgisToGeoJSON(response);
// let chinaList = test.features[0].geometry.coordinates;
// var boundsCounty = [
// [0.0, -20.0],
// [276.0, -20.0],
// [276.0, 75.0],
// [0.0, 75.0],
// [0.0, -20.0],
// ];
// console.log("chinaList", chinaList);
// case 'BASEMAP-white':
// // 地形图示例 (使用 OpenStreetMap 或其他地形服务作为占位,请替换为实际地址)
// return L.tileLayer(`${URL_TerTDT}&SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ter&STYLE=default&TILEMATRIXSET=${mapType}&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&FORMAT=tiles`, {
// // maxZoom: 17,
// format: "image/png",
// zIndex: 12,
// attribution: 'Map data: &copy; OpenStreetMap contributors, SRTM | Map style: &copy; OpenTopoMap (CC-BY-SA)'
// });
// var geojsonRelt: any = {
// type: "Feature",
// properties: {},
// geometry: {
// type: "MultiPolygon",
// coordinates: [[boundsCounty, ...chinaList]],
// },
// };
// case 'BASEMAP-img':
// // 影像图示例 (使用 Esri World Imagery 作为占位,请替换为实际地址)
// return L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
// attribution: 'Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
// });
// var chinaCavansLayer = L.geoJSON(geojsonRelt, {
// style: {
// color: "#8EA5BA",
// weight: 1,
// fillColor: "#ffffff",
// fillOpacity: 1,
// },
// });
//chinaCavansLayer.addTo(this.map).bringToBack().setZIndex(0);
}
});
// default:
// console.warn(`未知的底图 Key: ${key}`);
// return null;
// }
// }
let params = {
startTime: "2022-01-01 00:00:00",
};
// controlBaseLayerTreeShowAndHidden(layerType: String, key: String, checked: boolean) {
// // 优先使用 key 查找,如果没有 key 则尝试用 layerType
// // console.log(this.getAllLayers())
// console.log(key)
// console.log(this.layerRegistry)
// const registryKey:any = key || layerType;
// const layerInstance = this.layerRegistry.get(registryKey);
// if (layerInstance) {
// if (checked) {
// // 显示:如果不在地图上,则添加
// if (!this.map.hasLayer(layerInstance)) {
// layerInstance.addTo(this.map);
// }
// } else {
// // 隐藏:如果在地图上,则移除
// if (this.map.hasLayer(layerInstance)) {
// this.map.removeLayer(layerInstance);
// }
// }
// } else {
// console.warn(`未找到标识为 ${registryKey} 的图层实例`);
// }
// }
// /**
// * 添加初始化数据图层
// * @param pointLayerObj 图层配置对象 (包含 data, key, checked 等属性)
// * @param layerType 图层类型/Key (如果 pointLayerObj 中有 key则优先使用 obj.key)
// * @param mdoptions 描点选项配置
// * @param legendArray 图例映射数据 (用于根据 anchoPointState 匹配图标)
// */
// addInitDataLayer = (pointLayerObj: any, layerType?: any, mdoptions?: MDOptions, legendArray?: any) => {
// // 1. 参数校验与数据提取
// if (!this.map || !pointLayerObj) {
// return;
// }
L.control
.scale({ maxWidth: 150, metric: true, imperial: false })
.addTo(this.map);
this.map.on("zoomend", (e: any) => {
var scale = document.getElementsByClassName(
"leaflet-control-scale-line"
)[0].innerHTML;
var num = parseInt(scale);
// let dataArray: any[] = [];
// let targetLayerKey: string = layerType;
if (scale.includes("km")) {
console.log("比例尺:1:" + num * 1000 + "m");
} else {
console.log("比例尺1:" + num + "m");
}
console.log("scale", scale);
// if (Array.isArray(pointLayerObj)) {
// dataArray = pointLayerObj;
// } else {
// dataArray = pointLayerObj.data || [];
// targetLayerKey = pointLayerObj.key || layerType;
// }
console.log("当前缩放级别", e.target.getZoom());
});
console.log("rectangle", rectangle);
if (rectangle) {
if (rectangle[0] && rectangle[0][0] == 90) {
//this.map.setView([43.719771, 126.687641,], 9);
} else {
map.fitBounds(rectangle);
}
}
return Promise.resolve(map);
} catch (e) {
console.log("测试", e);
return Promise.reject({});
}
}
// if (!targetLayerKey) {
// console.warn('缺少图层 Key无法加载描点');
// return;
// }
// if (dataArray.length === 0) {
// const existingGroup = this.layerRegistry.get(targetLayerKey);
// if (existingGroup && existingGroup instanceof L.LayerGroup) {
// existingGroup.clearLayers();
// }
// return;
// }
removeLayer(layer: layer | any): void {
switch (layer.type) {
case "geoJson":
break;
default:
if (layer._layer) {
if (layer._layer.layerGroup) {
layer._layer.layerGroup.clearLayers();
}
this.map?.off("zoom", layer._layer.func);
this.map && this.map.removeLayer(layer._layer);
} else if (layer) {
if (layer.layerGroup) {
layer.layerGroup.clearLayers();
}
this.map?.off("zoom", layer.func);
this.map && this.map.removeLayer(layer);
let mapvalue = tiledMapGroup.getLayers();
// console.log(`开始加载图层 [${targetLayerKey}] 的描点,数量: ${dataArray.length}`);
for (let i = 0; i < mapvalue.length; i++) {
if (mapvalue[i].options.id == layer.id) {
tiledMapGroup.removeLayer(mapvalue[i]);
}
}
}
break;
}
}
// // 2. 获取或创建该图层的 LayerGroup
// let layerGroup = this.layerRegistry.get(targetLayerKey);
addboundCavansLayer = (list: any, bgStyle?: any) => {
if (boundCavansLayer && this.map.hasLayer(boundCavansLayer)) {
this.map.removeLayer(boundCavansLayer);
}
var boundsCounty = [
[0.0, -20.0],
[176.0, -20.0],
[176.0, 75.0],
[0.0, 75.0],
[0.0, -20.0],
];
var geojsonRelt: any = {
type: "Feature",
properties: {},
geometry: {
type: "MultiPolygon",
coordinates: [[boundsCounty, ...list]],
},
};
var boundCavansLayerRelt = L.geoJSON(
geojsonRelt,
bgStyle
? { ...bgStyle }
: {
color: "transparent",
fillColor: "#0a2b4b",
fillOpacity: 0.9,
}
);
boundCavansLayer = boundCavansLayerRelt;
console.warn(boundCavansLayerRelt.addTo)
boundCavansLayerRelt.addTo(this.map).bringToFront().setZIndex(99999999999);
};
// const shouldClear = mdoptions?.isRemove !== false;
}
// if (!layerGroup || !(layerGroup instanceof L.LayerGroup)) {
// if (layerGroup && this.map.hasLayer(layerGroup)) {
// this.map.removeLayer(layerGroup);
// }
// layerGroup = L.layerGroup();
// this.layerRegistry.set(targetLayerKey, layerGroup);
// layerGroup.addTo(this.map);
// } else {
// if (shouldClear) {
// layerGroup.clearLayers();
// }
// }
// // 3. 遍历数据生成 Marker
// dataArray.forEach((item: any) => {
// const { lgtd, lttd, stcd, stnm, iconCode, anchoPointState, _id, titleName, ennm } = item;
// if (lgtd == null || lttd == null) {
// return;
// }
// // 4. 确定图标样式和文字
// let iconUrl = '';
// let iconSize = [15, 15];
// // 获取图标 URL
// if (iconCode) {
// iconUrl = getIconPath(iconCode);
// } else if (anchoPointState && legendArray) {
// const legendItem = legendArray[anchoPointState];
// if (legendItem && legendItem.icon) {
// iconUrl = getIconPath(legendItem.icon);
// if (legendItem.width && legendItem.height) {
// iconSize = [legendItem.width, legendItem.height];
// }
// }
// }
// // 如果没有找到图标,使用默认或跳过
// if (!iconUrl) {
// // 可以选择使用默认图标或者继续
// iconUrl = getIconPath('default'); // 假设有一个默认图标
// if(!iconUrl) return;
// }
// // 5. 创建带文字的 DivIcon
// // 显示的文字优先使用 titleName其次 ennm最后 stnm
// const labelText = titleName || ennm || stnm || '';
// // 构建 HTML 结构:一个容器包含图片和文字
// // 注意:这里使用内联样式简单演示,建议在实际项目中提取到 CSS 类中
// const iconHtml = `
// <div style="position: relative; display: flex; flex-direction: column; align-items: center; width: max-content;">
// <div style="
// font: 10px sans-serif;
// font-weight: bold;
// color: #fff;
// white-space: nowrap;
// margin-bottom: 2px;
// -webkit-text-stroke: 2px #2e2d2d;
// paint-order: stroke fill; /* 确保描边在填充外部,防止文字变细 */
// ">
// ${labelText}
// </div>
// <img src="${iconUrl}" style="width: ${iconSize[0]}px; height: ${iconSize[1]}px; display: block;" />
// </div>`;
// const customIcon = L.divIcon({
// html: iconHtml,
// className: '', // 重要:设置为空字符串以避免 Leaflet 默认样式干扰
// iconSize: [iconSize[0], iconSize[1] + 20], // 高度增加以容纳文字 (假设文字高约20px)
// iconAnchor: [iconSize[0] / 2, iconSize[1] + 10], // 锚点设在图片底部中心
// popupAnchor: [0, -(iconSize[1] + 20)] // 弹窗锚点设在整体顶部
// });
// // 6. 创建 Marker
// const marker = L.marker([lttd, lgtd], {
// icon: customIcon,
// // @ts-ignore
// options: {
// ...item,
// layerKey: targetLayerKey,
// originalEvent: item
// }
// });
// // 7. 绑定点击事件 (如果需要)
// marker.on('click', (e: any) => {
// // 触发全局事件,例如:
// // GlobalEvents.get('map_dataLayer_click').set(item);
// console.log('Marker clicked:', item);
// });
// // 8. 将 Marker 添加到 LayerGroup
// layerGroup.addLayer(marker);
// });
// console.log(`图层 [${targetLayerKey}] 描点加载完成`);
// }
// getRain(data: any) {
// if (data?.geojson) {
// this.removeRainLayer();
// this.removeLayermarkers();
// const renderColor = (item: any, colRules: any) => {
// let color = 'rgba(0,0,0,0)';
// colRules.map((rule: any, index: number) => {
// if (
// item.properties.hvalue >= rule?.sv &&
// item.properties.hvalue < rule?.ev
// ) {
// color = rule.colors;
// }
// if (index === colRules.length - 1) {
// if (item.properties.hvalue >= rule?.ev) {
// color = rule.colors;
// }
// }
// });
// return color;
// };
// data.geojson.features.map((item: any, index: number) => {
// let pushlist = L.geoJSON(item, {
// style: {
// color: renderColor(item, data.colRules),
// weight: 2,
// opacity: 0.8,
// fillOpacity: 0.7
// // fillColor: "#1D91C0",
// }
// }).addTo(this.map);
// this.layermarkers.push(pushlist);
// });
// }
// }
// removeLayermarkers = () => {
// // console.log('layermarkers',layermarkers);
// try {
// if (this.layermarkers.length > 0) {
// if (this.map.hasLayer(this.layermarkers)) {
// this.map.removeLayer(this.layermarkers);
// } else {
// console.log('移除图层失败,应已经移除');
// }
// }
// // removeAllGeojson(map);
// } catch (e) {
// // message.info('移除图层失败')
// console.log('移除图层失败,是否应移除');
// }
// };
// removeRainLayer(): void {
// console.log('test!删除Layer');
// const _this = this;
// if (this.rainlayerslist.length > 0) {
// this.rainlayerslist.map((item: any) => {
// _this.map.removeLayer(item);
// });
// this.rainlayerslist = [];
// }
// } // 缩放
// zoomToggle(type: 'out' | 'in') {
// if (this.map) {
// if (type === 'out') {
// this.map && this.map.zoomOut();
// } else {
// this.map && this.map.zoomIn();
// }
// }
// }
// }

View File

@ -12,11 +12,11 @@ import Stroke from 'ol/style/Stroke';
import Icon from 'ol/style/Icon';
import Text from 'ol/style/Text';
import LayerGroup from 'ol/layer/Group';
import OSM from 'ol/source/OSM';
// import LayerGroup from 'ol/layer/Group';
// import OSM from 'ol/source/OSM';
import WMTS from 'ol/source/WMTS';
import WMTSTileGrid from 'ol/tilegrid/WMTS';
import { get as getProjection, fromLonLat, toLonLat } from 'ol/proj';
import { get as getProjection, fromLonLat } from 'ol/proj';
import {
defaults as defaultInteractions,
Draw,
@ -25,13 +25,11 @@ import {
import { getTopLeft, getWidth } from 'ol/extent';
import MouseWheelZoom from 'ol/interaction/MouseWheelZoom';
import { servers } from './mapurlManage';
import geoJsonData from '@/assets/geoJson.json';
import geoJsonData1 from '@/assets/geoJson1.json';
import { XYZ } from 'ol/source';
import Feature from 'ol/Feature';
import Point from 'ol/geom/Point'; // ✅ 新增导入
import LineString from 'ol/geom/LineString';
import Polygon from 'ol/geom/Polygon';
// import Polygon from 'ol/geom/Polygon';
import Overlay from 'ol/Overlay';
import {
getLength as getSphericalLength,
@ -58,20 +56,41 @@ export class MapOl implements MapInterface {
private layerRegistry: Map<string, any> = new Map();
// ✅ 新增:记录当前正在显示的“切换 Key”用于判断是否需要重新加载或只是切换显隐
private baseLayerConfig: TileLayer | null = null;
private hydropBaseConfig: TileLayer | null = null;
// private hydropBaseConfig: TileLayer | null = null;
private REGISTRY_KEY = 'customBaseLayer';
// ✅ 新增:量算相关属性
private drawInteraction: any | null = null;
private drawInteraction: any = null;
private measureLayer: VectorLayer | null = null;
private measureSource: VectorSource | null = null;
// ✅ 新增:存储点图层的 Registrykey 为 layerTypevalue 为 VectorLayer
private pointLayerRegistry: Map<string, VectorLayer> = new Map();
geoJsonData: any = null;
geoJsonData1: any = null;
private BASEID: string = '01';
// private BASEID: string = '01';
constructor() {
this.loadGeoJsonData();
this.loadGeoJsonData1();
}
private async loadGeoJsonData(): Promise<void> {
try {
const response = await fetch('/data/geoJson.json')
this.geoJsonData = await response.json()
} catch (error) {
console.error('配置加载失败:', error)
}
}
private async loadGeoJsonData1(): Promise<void> {
try {
const response = await fetch('/data/geoJson1.json')
this.geoJsonData1 = await response.json()
} catch (error) {
console.error('配置加载失败:', error)
}
}
//地图初始化
init(container: HTMLElement, rectangle?: any): Promise<any> {
init(container: HTMLElement): Promise<any> {
try {
console.log('OL init初始化container', container);
@ -222,10 +241,8 @@ export class MapOl implements MapInterface {
const {
lgtd,
lttd,
stcd,
stnm,
iconCode,
anchoPointState,
titleName,
ennm
} = item;
@ -287,20 +304,20 @@ export class MapOl implements MapInterface {
// ✅ 1. 获取当前地图分辨率
// 注意如果样式函数在地图未完全初始化时调用view 可能为 null
const currentResolution = this.view ? this.view.getResolution() : 1000;
// const currentResolution = this.view ? this.view.getResolution() : 1000;
// ✅ 2. 定义基准分辨率和基准缩放比例
// INITIAL_ZOOM (4.5) 对应的分辨率大约是 3000-4000 左右 (取决于投影)
// 这里我们用一个经验公式:分辨率越小(zoom越大)scale 越大
// 假设在初始分辨率下 scale 为 0.7
const baseResolution = 3000; // 这是一个估算值,你可以根据实际效果微调
const baseScale = 0.7;
// const baseResolution = 3000; // 这是一个估算值,你可以根据实际效果微调
// const baseScale = 0.7;
// 计算动态缩放比例:
// 如果 currentResolution 变小 (放大地图)ratio 变大 -> icon 变大
// 限制最大和最小缩放,防止过大或过小
// let dynamicScale = baseScale * (baseResolution / (currentResolution || 1));
const currentZoom = this.view ? this.view.getZoom() : 4.5;
const currentZoom:any = this.view ? this.view.getZoom() : 4.5;
let dynamicScale = 0.7 + (currentZoom - 4.5) * 0.08;
// 限制范围:最小 0.5,最大 3.0 (可根据需求调整)
@ -420,11 +437,12 @@ export class MapOl implements MapInterface {
this.map.addLayer(tileLayer);
} else if (layer.type === 'vector') {
if (layer.key === 'hydropBase') {
this.hydropBaseConfig = layer;
// this.hydropBaseConfig = layer;
}
console.log(this.geoJsonData1)
// ✅ 1. 创建矢量源,关键是要配置投影转换
const vectorSource = new VectorSource({
features: new GeoJSON().readFeatures(geoJsonData1, {
features: new GeoJSON().readFeatures(this.geoJsonData1, {
dataProjection: 'EPSG:4326',
featureProjection: 'EPSG:3857' // 确保转换到地图使用的投影
})
@ -473,95 +491,95 @@ export class MapOl implements MapInterface {
* @param layer TileLayer
* @param clipGeometry Polygon MultiPolygonEPSG:3857
*/
private addClipToRasterLayer(layer: TileLayer<any>, clipGeometry: Geometry): void {
if (!layer || !clipGeometry) return;
// private addClipToRasterLayer(layer: TileLayer<any>, clipGeometry: Geometry): void {
// if (!layer || !clipGeometry) return;
// 处理几何类型,统一为多边形数组
let polygons: Polygon[] = [];
const type = clipGeometry.getType();
// // 处理几何类型,统一为多边形数组
// let polygons: Polygon[] = [];
// const type = clipGeometry.getType();
if (type === 'Polygon') {
polygons = [clipGeometry as Polygon];
} else if (type === 'MultiPolygon') {
const multiPolygon = clipGeometry as MultiPolygon;
const coords = multiPolygon.getCoordinates();
polygons = coords.map(coord => new Polygon(coord));
} else {
console.error('不支持的几何类型:', type);
return;
}
// if (type === 'Polygon') {
// polygons = [clipGeometry as Polygon];
// } else if (type === 'MultiPolygon') {
// const multiPolygon = clipGeometry as MultiPolygon;
// const coords = multiPolygon.getCoordinates();
// polygons = coords.map(coord => new Polygon(coord));
// } else {
// console.error('不支持的几何类型:', type);
// return;
// }
// 预存储所有外环坐标
const allRings: number[][][] = [];
for (const polygon of polygons) {
const coords = polygon.getCoordinates();
if (coords && coords[0] && coords[0].length > 0) {
allRings.push(coords[0]);
}
}
// // 预存储所有外环坐标
// const allRings: number[][][] = [];
// for (const polygon of polygons) {
// const coords = polygon.getCoordinates();
// if (coords && coords[0] && coords[0].length > 0) {
// allRings.push(coords[0]);
// }
// }
// 移除旧事件
layer.removeEventListener('prerender');
layer.removeEventListener('postrender');
// // 移除旧事件
// layer.removeEventListener('prerender');
// layer.removeEventListener('postrender');
// 渲染前:设置裁切区域
layer.on('prerender', (event) => {
const context = event.context;
const frameState = event.frameState;
// // 渲染前:设置裁切区域
// layer.on('prerender', (event) => {
// const context = event.context;
// const frameState = event.frameState;
if (!context || !frameState) return;
// if (!context || !frameState) return;
// 获取坐标转像素函数
const toPixel = frameState.coordinateToPixel;
if (!toPixel) return;
// // 获取坐标转像素函数
// const toPixel = frameState.coordinateToPixel;
// if (!toPixel) return;
context.save();
context.beginPath();
// context.save();
// context.beginPath();
let hasPath = false;
// let hasPath = false;
for (const ring of allRings) {
if (!ring || ring.length === 0) continue;
// for (const ring of allRings) {
// if (!ring || ring.length === 0) continue;
for (let i = 0; i < ring.length; i++) {
const coord = ring[i];
const pixel = toPixel(coord);
// for (let i = 0; i < ring.length; i++) {
// const coord = ring[i];
// const pixel = toPixel(coord);
if (!pixel || pixel.length < 2) continue;
// if (!pixel || pixel.length < 2) continue;
if (i === 0) {
context.moveTo(pixel[0], pixel[1]);
} else {
context.lineTo(pixel[0], pixel[1]);
}
hasPath = true;
}
context.closePath();
}
// if (i === 0) {
// context.moveTo(pixel[0], pixel[1]);
// } else {
// context.lineTo(pixel[0], pixel[1]);
// }
// hasPath = true;
// }
// context.closePath();
// }
if (hasPath) {
context.clip();
} else {
context.restore();
}
});
// if (hasPath) {
// context.clip();
// } else {
// context.restore();
// }
// });
// 渲染后:恢复状态
layer.on('postrender', (event) => {
if (event.context) {
event.context.restore();
}
});
}
// // 渲染后:恢复状态
// layer.on('postrender', (event) => {
// if (event.context) {
// event.context.restore();
// }
// });
// }
/**
*
* @param regionId ID ( "hebei", "13", "01" Key )
* @param isAll (true: ; false: regionId )
*/
jdPanelControlShowAndHidden(regionId: string, isAll: boolean): void {
this.BASEID = regionId;
console.log(this.layerRegistry);
console.log(this.layerRegistry.keys());
jdPanelControlShowAndHidden(_regionId: string): void {
// this.BASEID = regionId;
// console.log(this.layerRegistry);
// console.log(this.layerRegistry.keys());
// this.addBaseDataLayer(this.hydropBaseConfig);
// this.controlBaseLayerTreeShowAndHidden(this.REGISTRY_KEY,this.REGISTRY_KEY,false)
}
@ -607,7 +625,7 @@ private addClipToRasterLayer(layer: TileLayer<any>, clipGeometry: Geometry): voi
}
// 图层树控制描点数据显示隐藏方法
mdLayerTreeShowOrHidden(layerType: String, checked?: boolean): void {
const layerInstance = this.pointLayerRegistry.get(layerType as string);
const layerInstance: any = this.pointLayerRegistry.get(layerType as string);
layerInstance.setVisible(checked);
// this.service.mdLayerTreeShowOrHidden(layerType, checked)
}
@ -707,7 +725,7 @@ private addClipToRasterLayer(layer: TileLayer<any>, clipGeometry: Geometry): voi
// 3. 创建矢量源 (VectorSource) 并直接加载数据
const vectorSource = new VectorSource({
features: new GeoJSON().readFeatures(geoJsonData, {
features: new GeoJSON().readFeatures(this.geoJsonData, {
featureProjection: 'EPSG:3857' // 确保转换到地图使用的投影
})
});
@ -901,7 +919,7 @@ private addClipToRasterLayer(layer: TileLayer<any>, clipGeometry: Geometry): voi
// 3. 创建绘制交互
this.drawInteraction = new Draw({
source: this.measureSource,
source: this.measureSource!,
type: geomType,
style: drawStyle
});
@ -1188,6 +1206,11 @@ private addClipToRasterLayer(layer: TileLayer<any>, clipGeometry: Geometry): voi
const areaSqKm = areaSqMeters / 1000000;
return '面积:' + areaSqKm.toFixed(3) + 'km²';
}
mdLayerShowOrHidden(): void { }
switchView(): void { }
fitBounds(): void { }
flyTopanto(): void { }
/**
*
*/
@ -1199,7 +1222,7 @@ private addClipToRasterLayer(layer: TileLayer<any>, clipGeometry: Geometry): voi
// 2. 清除所有通过 layerRegistry 管理的图层
if (this.layerRegistry) {
this.layerRegistry.forEach((layer, key) => {
this.layerRegistry.forEach((layer) => {
if (this.map && this.map.getLayers().getArray().includes(layer)) {
this.map.removeLayer(layer);
}
@ -1209,7 +1232,7 @@ private addClipToRasterLayer(layer: TileLayer<any>, clipGeometry: Geometry): voi
// ✅ 3. 清除点图层 Registry
if (this.pointLayerRegistry) {
this.pointLayerRegistry.forEach((layer, key) => {
this.pointLayerRegistry.forEach((layer) => {
if (this.map && this.map.getLayers().getArray().includes(layer)) {
this.map.removeLayer(layer);
}
@ -1221,7 +1244,7 @@ private addClipToRasterLayer(layer: TileLayer<any>, clipGeometry: Geometry): voi
if (this.map) {
this.map.getInteractions().clear();
this.map.getOverlays().clear();
this.map.setTarget(null);
this.map.setTarget(undefined);
this.map.dispose();
this.map = null;
}

View File

@ -38,7 +38,7 @@ const URL_TerTDT = `https://t0.tianditu.gov.cn/ter_${mapType}/wmts?tk=${tdtToken
// const URL_TerLabelTDT = `http://t0.tianditu.gov.cn/cta_${mapType}/wmts?tk=${tdtToken}` //terLabel
// const URL_VecTDT = `http://t0.tianditu.com/vec_${mapType}/wmts?tk=${tdtToken}` //vecMap
// const URL_VecLableTDT = `http://t0.tianditu.com/cva_${mapType}/wmts?tk=${tdtToken}` //vecLabel
const URL_ImgTDT = `http://t0.tianditu.com/img_${mapType}/wmts?tk=${tdtToken}` //imgMap
// const URL_ImgTDT = `http://t0.tianditu.com/img_${mapType}/wmts?tk=${tdtToken}` //imgMap
// const URL_ImgLableTDT = `http://t0.tianditu.com/cia_${mapType}/wmts?tk=${tdtToken}`
// const znyMapUrl = "http://10.219.26.6:8050/" //中南院的测试地图环境

View File

@ -33,7 +33,7 @@ const mapStore: any = useMapStore();
const layerConfigs = computed(() => mapStore.layerData);
//
const checkedKeys = ref([]);
const checkedKeys: any = ref([]);
const onCheck = (v: any, e: any) => {
//
const fData = layerConfigs.value?.filter((e: any) => e.key === "facilities")?.[0];

View File

@ -7,7 +7,7 @@
v-for="child in item.children"
:key="child.key"
>
<component v-if="child.component" :is="child.component" />
<component v-if="child.component" :is="child.component" :map="map" />
<div v-else class="map-controller-item" @click="handleControllerClick(child)">
<i class="icon iconfont" :class="'icon-' + child.icon"></i>
</div>
@ -20,6 +20,11 @@ import { ref, watch, computed } from "vue";
import { useUiStore } from "@/store/modules/ui";
import Calculate from "./Calculate.vue";
import LayerController from "./LayerController.vue";
const props = defineProps<{
map: any;
onClick:(key: any) => void
}>();
const map = props.map;
// 使 Pinia store
const uiStore = useUiStore();
@ -126,27 +131,48 @@ const controllers: any = computed(() => [
]);
//
const toggleFullScreen = () => {
isFullScreen.value = !isFullScreen.value;
//
if (isFullScreen.value) {
document.documentElement.requestFullscreen();
} else {
document.exitFullscreen();
}
};
// const toggleFullScreen = () => {
// isFullScreen.value = !isFullScreen.value;
// //
// if (isFullScreen.value) {
// document.documentElement.requestFullscreen();
// } else {
// document.exitFullscreen();
// }
// };
//
const handleControllerClick = (item: any) => {
switch (item.key) {
case "fullScreen":
toggleFullScreen();
isFullScreen.value = !isFullScreen.value;
map.jdPanelControlShowAndHidden('01',false)
// map.mdLayerTreeShowOrHidden('fp_point',!isFullScreen.value);
// map.controlBaseLayerTreeShowAndHidden('customBaseLayer','customBaseLayer',!isFullScreen.value);
// map.controlBaseLayerTreeShowAndHidden('lcj_bhq','lcj_bhq',!isFullScreen.value);
// toggleFullScreen();
break;
case "zoomIn":
map.zoomToggle('in');
break;
case "zoomOut":
map.zoomToggle('out');
break;
case "dim":
mapType.value = mapType.value === "2D" ? "3D" : "2D";
break;
case "rightDrawer":
// 使
// GlobalEvents.get('rightDrawerState').set(!drawerOpen);
break;
//
case "screenShot":
map.mapOutPut()
break;
case "TJ":
props.onClick(4)
// map.addTertiarybasinLayer(servers.Tertiarybasin, '#4DFFDD', '#92A0A5')
break;
default:
console.log(`点击了控制器: ${item.name}`);
break;

View File

@ -31,7 +31,7 @@
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { ref } from "vue";
const siteRangePicker = [
{ label: "0-10", value: "0-10" },
@ -47,7 +47,6 @@ const formModel = ref({
const rules = ref({
siteRangePicker: [{ required: true, message: "请选择装机容量" }],
});
const formRef = ref<any>(null);
</script>
<style lang="scss" scoped>

View File

@ -36,7 +36,7 @@
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, computed, watch } from "vue";
import { onMounted, ref, watch } from "vue";
import { useMapStore } from "@/store/modules/map";
import LegendItem from "@/components/mapLegend/LegendItem.vue";
const mapStore: any = useMapStore();

View File

@ -1,8 +1,7 @@
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from "vue";
import { onMounted, onBeforeUnmount } from "vue";
import { useRoute, useRouter } from "vue-router";
import { ElMessageBox } from "element-plus";
import { getToken } from "@/utils/auth";
import { UserOutlined, LogoutOutlined } from "@ant-design/icons-vue";
//
import { useI18n } from "vue-i18n";
@ -13,10 +12,9 @@ import Sidebar from "./Sidebar/index.vue";
import { useTagsViewStore } from "@/store/modules/tagsView";
import { useUserStore } from "@/store/modules/user";
import Cookies from "js-cookie";
import { storeToRefs } from "pinia";
import {getPath,removePath } from '@/utils/auth';
const url = import.meta.env.VITE_APP_BASE_API;
// const url = import.meta.env.VITE_APP_BASE_API;
const username = Cookies.get("username");
const tagsViewStore = useTagsViewStore();

View File

@ -13,7 +13,7 @@ import 'ant-design-vue/dist/reset.css' // Ant Design 全局样式重置
import dayjs from 'dayjs'; // ant 中文语言
import 'dayjs/locale/zh-cn';
// 引入svg注册脚本
// @ts-ignore
import 'virtual:svg-icons-register';
// 国际化

View File

@ -2,7 +2,7 @@
<template>
<SidePanelItem title="设施类型及接入情况">
<div class="card-container">
<div v-for="(item, index) in dataJson" class="facility-card">
<div v-for="(item) in dataJson" class="facility-card">
<div class="img_icon">
<i class="icon iconfont" :class="item?.icon" />
</div>
@ -91,21 +91,15 @@ const dataJson = ref<DataString[]>([
}
]);
const clickList = ref<DataString | null>(null);
const res = ref({
bldstt: '',
hydrodtin: ''
});
//
const handleCardClick = (item: DataString) => {
clickList.value = item;
// dialog.current?.open() -
res.value = {
bldstt: '',
hydrodtin: ''
};
};
// const handleCardClick = (item: DataString) => {
// clickList.value = item;
// // dialog.current?.open() -
// res.value = {
// bldstt: '',
// hydrodtin: ''
// };
// };
//
onMounted(() => {

View File

@ -7,7 +7,7 @@
<div class="legend-container">
<div class="legend-items">
<div
v-for="(item, index) in currentLegendItems"
v-for="(item) in currentLegendItems"
:key="item.name"
class="legend-item"
:class="{ 'inactive': legendInactiveSet.has(item.name) }"

View File

@ -11,7 +11,7 @@
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
import { ref, onMounted, onBeforeUnmount } from 'vue'
import * as echarts from 'echarts'
import SidePanelItem from '@/components/SidePanelItem/index.vue';
const data = [
@ -23,18 +23,18 @@ const data = [
const chartRef = ref<HTMLElement | null>(null)
let chartInstance: echarts.ECharts | null = null
const transUnit = (value: number | null, code: string, type: string) => {
const transUnit = (value: number | null) => {
if (value === null) return null
return value
}
const getUnitConfigByCode = (code: string, type: string) => {
const getUnitConfigByCode = (_code: string, _type: string) => {
return {
unit: '℃'
}
}
const getColorByCodeAndType = (code: string[], typeKey: string[]) => {
const getColorByCodeAndType = (_code: string[], _typeKey: string[]) => {
return ['#4b79ab', '#78c300']
}
@ -47,8 +47,8 @@ const getChartOption = () => {
data.forEach((item: any) => {
xData.push(`${item.monthInt}`)
actualData.push(item.actualTemp === null ? null : transUnit(item.actualTemp, 'Other', 'ACTUALTEMP'))
naturalData.push(item.naturalTemp === null ? null : transUnit(item.naturalTemp, 'Other', 'NATURALTEMP'))
actualData.push(item.actualTemp === null ? null : transUnit(item.actualTemp))
naturalData.push(item.naturalTemp === null ? null : transUnit(item.naturalTemp))
})
const code = ['Other']
@ -65,10 +65,10 @@ const getChartOption = () => {
color: '#ffffff',
fontSize: 14
},
formatter: function(params) {
formatter: function(params: any) {
if (!params || params.length === 0) return '';
let result = `<div style="font-weight: bold; margin-bottom: 8px;">${params[0].axisValue}</div>`;
params.forEach(item => {
params.forEach((item: any) => {
result += `<div style="display: flex; align-items: center; margin: 4px 0;">`;
result += `<span style="display: inline-block; width: 10px; height: 10px; border-radius: 50%; background-color: ${item.color}; margin-right: 8px;"></span>`;
result += `<span>${item.seriesName} ${item.value}${unit}</span>`;

View File

@ -188,7 +188,7 @@ const tableData = ref([
]);
//
const customRow = (record: any, index: number) => {
const customRow = (_record: any, index: number) => {
return {
style: {
backgroundColor: index % 2 === 1 ? '#fafafa' : '#ffffff'

View File

@ -12,7 +12,7 @@ const permissionStore = usePermissionStoreHook();
const whiteList = ['/login', '/login-sjtb']; //login
// 查找第一个可用路由
function findFirstAvailableRoute(routes: RouteRecordRaw[]): string | undefined {
function findFirstAvailableRoute(routes: any[]): string | undefined {
for (const route of routes) {
if (route.meta?.hidden) continue;

View File

@ -1,5 +1,4 @@
import {
setSidebarStatus,
getSize,
setSize,
setLanguage

View File

@ -2,7 +2,7 @@ import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useJidiSelectEventStore = defineStore('jidiSelectEvent', () => {
const jidiData = ref([
const jidiData:any = ref([
{
"_tls": {},
"id": null,

File diff suppressed because it is too large Load Diff

View File

@ -19,7 +19,6 @@
}
.ant-modal-body {
padding: 16px 24px !important;
padding-bottom: 0 !important;
}
.ant-modal-footer {
margin-top: 0 !important;
@ -38,3 +37,6 @@
}
}
}
:where(.css-dev-only-do-not-override-ekaqbe).ant-btn >span {
display: inline-flex;
}

View File

@ -142,11 +142,6 @@ namespace DateSetting {
return dayjs().startOf('day').startOf('hour');
}
//获取当前时间
function getEndTime(): Dayjs {
return dayjs().endOf('day');
}
function getStartYear(): Dayjs {
return dayjs().startOf('year');
}

View File

@ -65,7 +65,7 @@ export function parseTime(time :any, cFormat :any) {
}
date = new Date(time)
}
const formatObj = {
const formatObj:any = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),

View File

@ -0,0 +1,99 @@
L.TileLayer.WMTS = L.TileLayer.extend({
defaultWmtsParams: {
service: "WMTS",
request: "GetTile",
version: "1.0.0",
layer: "",
style: "",
tilematrixset: "",
format: "image/jpeg",
},
initialize: function (url, options) {
// (String, Object)
this._url = url;
var lOptions = {};
var cOptions = Object.keys(options);
cOptions.forEach((element) => {
lOptions[element.toLowerCase()] = options[element];
});
var wmtsParams = L.extend({}, this.defaultWmtsParams);
var tileSize = lOptions.tileSize || this.options.tileSize;
if (lOptions.detectRetina && L.Browser.retina) {
wmtsParams.width = wmtsParams.height = tileSize * 2;
} else {
wmtsParams.width = wmtsParams.height = tileSize;
}
for (var i in lOptions) {
// all keys that are in defaultWmtsParams options go to WMTS params
if (wmtsParams.hasOwnProperty(i) && i != "matrixIds") {
wmtsParams[i] = lOptions[i];
}
}
this.wmtsParams = wmtsParams;
this.matrixIds = options.matrixIds || this.getDefaultMatrix();
L.setOptions(this, options);
},
onAdd: function (map) {
this._crs = this.options.crs || map.options.crs;
L.TileLayer.prototype.onAdd.call(this, map);
},
getTileUrl: function (coords) {
// (Point, Number) -> String
var tileSize = this.options.tileSize;
var nwPoint = coords.multiplyBy(tileSize);
nwPoint.x += 1;
nwPoint.y -= 1;
var sePoint = nwPoint.add(new L.Point(tileSize, tileSize));
var zoom = this._tileZoom;
var nw = this._crs.project(this._map.unproject(nwPoint, zoom));
var se = this._crs.project(this._map.unproject(sePoint, zoom));
var tilewidth = se.x - nw.x;
var ident = this.matrixIds[zoom].identifier;
var tilematrix = this.wmtsParams.tilematrixset + ":" + ident;
var X0 = this.matrixIds[zoom].topLeftCorner.lng;
var Y0 = this.matrixIds[zoom].topLeftCorner.lat;
var tilecol = Math.floor((nw.x - X0) / tilewidth);
var tilerow = -Math.floor((nw.y - Y0) / tilewidth);
var url = L.Util.template(this._url, { s: this._getSubdomain(coords) });
return (
url +
L.Util.getParamString(this.wmtsParams, url) +
"&tilematrix=" +
tilematrix +
"&tilerow=" +
tilerow +
"&tilecol=" +
tilecol
);
},
setParams: function (params, noRedraw) {
L.extend(this.wmtsParams, params);
if (!noRedraw) {
this.redraw();
}
return this;
},
getDefaultMatrix: function () {
/**
* the matrix3857 represents the projection
* for in the IGN WMTS for the google coordinates.
*/
var matrixIds3857 = new Array(22);
for (var i = 0; i < 22; i++) {
matrixIds3857[i] = {
identifier: "" + i,
topLeftCorner: new L.LatLng(20037508.3428, -20037508.3428),
};
}
return matrixIds3857;
},
});
L.tileLayer.wmts = function (url, options) {
return new L.TileLayer.WMTS(url, options);
};

View File

@ -0,0 +1,877 @@
(function (factory, window) {
if (typeof window !== 'undefined' && window.L) {
factory(window.L);
}
}(function leafletInflatableMarkersGroupFactory(L) {
/**
* The Z-index offset to apply to inflated markers to make them show on top
* of deflated markers
*/
const INFLATED_MARKERS_ZINDEX_OFFSET = 10000;
/**
* The Z-index offset to apply to deflated markers to make them show on top
* of all markers when the special 'show hidden markers' action is used
*/
const DEFLATED_MARKERS_ZINDEX_OFFSET = 20000;
/**
* A two-state marker that can be added to a InflatableMarkerGroup
*
* An inflatable marker can be either inflated (it's displayed normally)
* or deflated (it's displayed as a smaller different icon to declutter the
* map).
*
* This class is not normally used by end-users. Normal markers should be
* added to L.InflatableMarkerGroup instead and the group will manage its
* own L.InflatableMarker-s.
*
* To handle the two inflated/deflated states and the associated changes in
* shape and size, instances of this class are also their own icons.
* Toggling between the inflated and deflated state boils down to toggle the
* display of the base marker's icon (shown when inflated) and this class'
* icon (shown when deflated).
* @extends L.Marker
*/
const InflatableMarker = L.InflatableMarker = L.Marker.extend({
/**
* The options applicable to the marker, same as the Icon options but
* an explicit size in mandatory
* @public
* @type {L.IconOptions}
*/
options: L.Icon.prototype.options,
/**
* Constructs the marker.
* @constructs L.InflatableMarker
* @public
* @param {L.LatLng} latlng - The position of the marker on the map
* @param {L.InflatableMarkerGroup} group - The group this marker belongs to,
* marker collisions are only computed inside a single group
* @param {L.Layer} baseMarker - The inflated version of the marker, added to the
* group via addLayer(...)
*/
initialize: function (latlng, group, baseMarker) {
L.Util.setOptions(this, baseMarker.options);
this.options.pane = group.options.pane;
// hijack the icon drawing process
this.options._inflatedIcon = this.options.icon;
this.options.icon = this;
/**
* The underlying marker added to the group, used as the inflated
* version of the current inflatable marker
* @public
* @type {L.Marker}
*/
this.baseMarker = baseMarker;
/**
* Where the marker is displayed
* @private
* @type {L.LatLng}
*/
this._latlng = latlng;
/**
* The clearance box around the marker in the order (north, east, south, west)
* @type {[number, number, number, number]}
* @private
*/
this._borders = [null, null, null, null];
/**
* The L.InflatableMarkerGroup this marker belongs to
* @private
* @type {L.InflatableMarkersGroup}
*/
this._group = group;
/**
* The set of all markers which collision with the current marker if
* they're both inflated
* @private
* @type {Set<L.InflatableMarker>}
*/
this._obstructiveMarkers = new Set();
/**
* Whether the marker is currently inflated
* @private
* @type {boolean}
*/
this._inflated = false;
/**
* Whether the marker's icon needs to be redrawn, typically after a
* change from inflated to deflated or vice-versa
* @private
* @type {boolean}
*/
this._iconNeedsUpdate = true;
/**
* The original Z-index attributed to this marker
* @private
* @type {integer}
*/
this._savedZIndexOffset = this.options.zIndexOffset;
this.addEventParent(this.baseMarker);
this.on("contextmenu", this.toggle, this);
},
_computeBorders: function(map, margin) {
const pos = map.latLngToContainerPoint(this._latlng);
const halfSize = L.point(this.options._inflatedIcon.options.iconSize).divideBy(2);
const br = pos.add(halfSize).add(margin);
const ul = pos.subtract(halfSize).subtract(margin);
this._borders = [ul.y, br.x, br.y, ul.x];
},
/**
* Gets the size of the marker when inflated.
* @public
* @return {L.Point} The inflated size of the marker
*/
getInflatedSize: function () {
const iconOptions = this.options._inflatedIcon.options;
if (!(iconOptions.iconSize instanceof L.Point))
iconOptions.iconSize = L.point(iconOptions.iconSize);
return iconOptions.iconSize;
},
/**
* Returns the icon to be displayed on the map, depending on its
* inflated/deflated state.
* @public
* @return {HTMLElement} The icon to draw
*/
createIcon: function () {
this._iconObj = this._inflated ?
this.options._inflatedIcon :
this._group.options.iconCreateFunction(this);
return this._iconObj.createIcon();
},
/**
* Returns the shadow to be displayed on the map, alongside with the
* icon.
* @public
* @todo This is not implemented for now and returns null (no shadow)
* @return {HTMLElement} The shadow to add to the icon
*/
createShadow: function () {
return null;
},
/**
* Forces the icon to be redrawn, for example if its inflated/deflated
* state has been modified externally.
* @public
*/
redraw: function () {
// Wierd but convenient; remember that InflatableMarker-s are their
// own icons, this is why this works.
this.setIcon(this);
},
/**
* Sets another marker as conflicting/collisioning with the current one.
* @private
* @param {L.InflatableMarker} otherMarker - Another marker in the group
*/
_addObstructiveMarker: function (otherMarker) {
this._obstructiveMarkers.add(otherMarker);
otherMarker._obstructiveMarkers.add(this);
},
/**
* Sets another marker as NOT conflicting/collisioning with the current
* one (for example, if it's getting removed altogether or reduced in
* size).
* @private
* @param {L.InflatableMarker} otherMarker - Another marker in the group
*/
_removeObstructiveMarker: function (otherMarker) {
this._obstructiveMarkers.delete(otherMarker);
otherMarker._obstructiveMarkers.delete(this);
},
/**
* Removes all markers from the conflicting/collisioning set of the
* current one.
* @private
*/
_clearObstructiveMarkers: function() {
for (const other of this._obstructiveMarkers) {
this._removeObstructiveMarker(other);
}
},
/**
* Returns the set of all markers conflicting/collisioning to the
* current one.
* @private
* @return {Set<L.InflatableMarker>} The conflicting/collisioning
* markers.
*/
_getObstructiveMarkers: function() {
return this._obstructiveMarkers;
},
/**
* Puts a marker on top, this is used to ensure for instance that
* inflated markers are shown on top by default
* @private
* @param {int} offset - The offset by which to increase the Z-index of
* the marker
*/
_bringToFront: function(offset=INFLATED_MARKERS_ZINDEX_OFFSET) {
this.setZIndexOffset(offset);
},
/**
* Puts a marker back where it was after it has been brought onto top.
* @private
*/
_bringBackFromFront: function() {
this.setZIndexOffset(this._savedZIndexOffset);
},
/**
* Switches the marker to the inflated state (or no-op if it was already
* inflated).
* @private
*/
_inflate: function() {
if (this._inflated)
return;
this._bringToFront();
this._inflated = true;
this._iconNeedsUpdate = true;
},
/**
* Switches the marker to the deflated state (or no-op if it was already
* inflated).
* @private
*/
_deflate: function() {
if (!this._inflated)
return;
if (this._group._inflatedMarkersAbove)
this._bringBackFromFront();
else
this._bringToFront(DEFLATED_MARKERS_ZINDEX_OFFSET);
this._inflated = false;
this._iconNeedsUpdate = true;
},
/**
* Toggles the inflated/deflated state of the marker
* @public
*/
toggle: function() {
if (this._inflated) {
this._deflate();
} else {
this._inflate();
for (const other of this._obstructiveMarkers) {
other._deflate();
}
}
// call daddy to refresh everybody
this._group._refreshIcons();
},
/**
* Tells whether the marker is currently inflated
* @public
* @return {boolean} Whether the marker is inflated
*/
isInflated: function() {
return this._inflated;
},
});
/**
* A group of inflatable markers that can be added onto a leaflet map.
*
* This class should be used as a regular L.FeatureGroup but for now, users
* should stick to L.Marker objects. Other features are not explicitly
* supported (but have never been tested so who knows?). In any case, this
* class handled its own L.InflatableMarker, so end-users should not
* construct them manually but instead add the normal markers to this group.
*
* The markers group make sure that two conflicting markers (i.e. markers
* that would collision if they are both inflated) are never inflated at the
* same time. This makes the map much more readable while keeping the
* markers at their place on the map, as long as the deflated icon of each
* marker is carefully chosen not to clutter the map.
* @extends L.FeatureGroup
*/
const InflatableMarkersGroup = L.InflatableMarkersGroup = L.FeatureGroup.extend({
/**
* How to configure the markers group, additionally from the
* L.FeatureGroup options
* @public
*/
options: {
/**
* The margin that must be kept clear around an inflated marker.
*
* Any marker closer to the current marker than this clearance will
* be marked as collisioning. The first element is the horizontal
* margin, the second one the vertical margin. It's set by default
* to [2, 2] (pixels). You can set it to [0, 0] or even to a
* negative value to tolerate some amount of overlapping between
* inflated markers.
*/
obstructionSize: L.point(2, 2),
/**
* The function that will be used to create deflated markers' icons.
*/
iconCreateFunction: null,
/**
* The map pane this group has to be added to, by default the
* markers pane
*/
groupPane: L.Marker.prototype.options.pane,
},
/**
* Constructs an InflatableMarkersGroup
* @constructs {L.InflatableMarkersGroup}
* @param {L.InflatableMarkersGroup.options} options - The configuration options
*/
initialize: function (options) {
L.Util.setOptions(this, options);
if (!this.options.obstructionSize instanceof L.Point)
this.options.obstructionSize = L.point(this.options.obstructionSize);
/**
* The underlying layer group used to handle the map layer adding and
* removal operations
* @private
* @type {L.FeatureGroup}
*/
this._featureGroup = L.featureGroup();
/**
* A associative array between base Leaflet layers (the one added to
* this group) and the corresponding inflatable markers this group
* constructs.
*
* Note: this attribute is a Map but a Javascript one (i.e. an
* associative array), not a Leaflet Map!
* @private
* @type {Map<L.Layer, L.InflatableMarker>}
*/
this._markers = new Map();
/**
* The boundaries of this layer group
* @private
* @type {L.LatLngBounds}
*/
this._bounds = null;
/**
* Whether this group has already been initialized and added to a map,
* setting it to false will force recompute all the InflatableMarker-s
* collisions.
* @private
* @type {boolean}
*/
this._alreadyDisplayed = false;
/**
* Whether the inflated markers should be displayed on top of deflated
* markers (the default) or the opposite (to make masked deflated
* markers prominent).
* @type {boolean}
*/
this._inflatedMarkersAbove = true;
/**
* Other InflatableMarkersGroup that could be added to the same map and
* whose member markers may collision with the current group.
* @type {Set<L.InflatableMarkersGroup>}
*/
this._otherGroups = new Set();
this._featureGroup.addEventParent(this);
},
/**
* The events we handle.
*
* The class reacts to zooming/dezooming to inflate as many markers as
* possible with the new zoom without causing collisions.
* @public
* @inheritdoc
*/
getEvents: function() {
return {
'zoomend': this._zoomend,
};
},
/**
* Whether two markers collision when both are inflated
*
* @param {L.Point} distance - The distance between the center of both
* markers
* @param {L.InflatableMarker} marker1 - The first marker
* @param {L.InflatableMarker} marker2 - The second marker
* @return {boolean} Whether the markers are closer to each other than
* the obstruction size
*/
_mayObstruct: function(distance, marker1, marker2) {
const marker1HalfSize = marker1.getInflatedSize().divideBy(2);
const marker2HalfSize = marker2.getInflatedSize().divideBy(2);
return Math.abs(distance.x) <= marker1HalfSize.x + marker2HalfSize.x + this.options.obstructionSize.x &&
Math.abs(distance.y) <= marker1HalfSize.y + marker2HalfSize.y + this.options.obstructionSize.y
},
/**
* Add a marker to the group and compute the collisions with already
* existing markers
* @param {L.Marker} layer - A marker to add (technically, it should be
* any layer, but we kind of break the Liskov principle to make things
* simpler for now...)
* @public
* @inheritdoc
*/
addLayer: function (layer) {
if (this.hasLayer(layer)) {
return this;
}
const marker = new InflatableMarker(layer._latlng, this, layer);
let inhibited = false;
if (this._map) {
const target = this._map.latLngToContainerPoint(layer._latlng);
for (const [l, m] of this._markers) {
const other = this._map.latLngToContainerPoint(l._latlng);
if (this._mayObstruct(target.subtract(other), marker, m)) {
marker._addObstructiveMarker(m);
inhibited = true;
}
}
}
this._markers.set(layer, marker);
this._featureGroup.addLayer(marker);
if (!inhibited) {
marker._inflate();
marker.redraw();
}
if (!this._inflatedMarkersAbove && !marker._inflated) {
marker._bringToFront(DEFLATED_MARKERS_ZINDEX_OFFSET);
}
if (this._bounds == null) {
this._bounds = L.latLngBounds(layer._latlng, layer._latlng);
} else {
this._bounds.extend(layer._latlng);
}
},
/**
* Remove a marker from the group and recompute the collisions of
* previously collisioning markers
* @param {L.Layer} layer - The layer to remove
* @public
* @inheritdoc
*/
removeLayer: function (layer) {
if (this._markers.has(layer)) {
const marker = this._markers.get(layer);
marker._clearObstructiveMarkers();
this._featureGroup.removeLayer(marker);
this._markers.delete(layer);
}
},
/**
* Remove all layers from this group and reset it completely
* @public
* @inheritdoc
*/
clearLayers: function () {
this._markers.clear();
this._featureGroup.clearLayers();
this._alreadyDisplayed = false; // reset the layer as if it had never
// been displayed
this._bounds = null;
},
/**
* Returns the bounds of this group on the map
* @return L.LatLngBounds
* @public
* @inheritdoc
*/
getBounds: function () {
return this._bounds;
},
/**
* Whether a marker has been added to the group
* @param {L.Marker} layer - The marker looked for
* @return {boolean} whether the layer belongs to this group
* @public
* @inheritdoc
*/
hasLayer: function(layer) {
return this._markers.has(layer);
},
/**
* Recompute all collision sets for all markers
* @private
*/
_recomputeObstructions: async function() {
for (const [l,m] of this._markers) {
m._clearObstructiveMarkers();
}
const waitFor = delay => new Promise(resolve => setTimeout(resolve, delay));
const iterator = this._markers.entries();
let result = iterator.next();
const allMarkers = [...this._iterateOnOwnAndOtherGroupsMarkers()];
allMarkers.forEach(m => m[1]._computeBorders(this._map, this.options.obstructionSize));
allMarkers.sort((m1, m2) => {
return m1[1]._borders[0] - m2[1]._borders[0];
});
const process = L.bind(async function () {
const start = new Date();
while (!result.done) {
const currentDate = new Date();
if (currentDate - start > 200) {
break;
}
const marker = result.value[1];
const posNorth = this._bisectNorth(allMarkers, marker._borders[0]);
const posSouth = this._bisectSouth(allMarkers, marker._borders[2]);
const sortedByWest = allMarkers.slice(posNorth, posSouth)
.sort((m1, m2) => {
return m1[1]._borders[3] - m2[1]._borders[3];
});
const posWest = this._bisectWest(sortedByWest, marker._borders[3]);
const posEast = this._bisectEast(sortedByWest, marker._borders[1]);
sortedByWest.slice(posWest, posEast).forEach(m => {
if (marker !== m[1])
marker._addObstructiveMarker(m[1]);
})
result = iterator.next();
}
if (!result.done) {
process();
return waitFor(50).then(process);
} else {
return true;
}
}, this);
return process();
},
_iterateOnOwnAndOtherGroupsMarkers: function* () {
let iteratorOnGroups = this._otherGroups.values();
let iteratorOnMarkers = this._markers.entries();
let result = iteratorOnMarkers.next();
while (!result.done) {
yield result.value;
result = iteratorOnMarkers.next();
}
let gr = iteratorOnGroups.next();
while (!gr.done) {
iteratorOnMarkers = gr.value._markers.entries();
result = iteratorOnMarkers.next();
while (!result.done) {
yield result.value;
result = iteratorOnMarkers.next();
}
gr = iteratorOnGroups.next();
}
},
/**
* React to this group being added onto a map
*
* Computation of the collision sets is delayed until this method is
* called.
* @param {L.Map} map - The leaflet map
* @public
* @inheritdoc
*/
onAdd: function (map) {
this._map = map;
this._recomputeObstructions().then(() => {
this._featureGroup.addTo(map);
if (!this._alreadyDisplayed) {
this._alreadyDisplayed = true;
this.inflateAsManyAsPossible(true);
}
});
},
/**
* React to this group being removed from a map
*
* The collision sets are not cleared at this point in case someone
* wants to have a look at them, but they will be recomputed if the
* group is added again onto a map anyway.
* @param {L.Map} map - The leaflet map
* @public
* @inheritdoc
*/
onRemove: function (map) {
this._featureGroup.removeFrom(map);
this._map = null;
},
/**
* Redraw all icons that are marked for update (after zooming in for
* instance)
* @private
* @inheritdoc
*/
_refreshIcons: function() {
for (const [baseMarker,marker] of this._iterateOnOwnAndOtherGroupsMarkers()) {
if (marker._iconNeedsUpdate)
marker.redraw();
marker._iconNeedsUpdate = false;
}
},
/**
* Inflate as many deflated markers as possible without causing
* collisions
*
* We don't actually go clever and guarantee that we display the
* theoretical maximum number of markers. We just go through them in
* order and avoid inflating a marker if it would collision with already
* inflated markers.
*
* @param {boolean} reset - Start by deflating all markers before
* inflating as many as possible
* @public
*/
inflateAsManyAsPossible: function(reset = false) {
const inhibited = new Set();
if (!reset) {
// add all the markers that could obstruct the already inflated
// markers
for (const [layer, marker] of this._markers) {
if (marker.inflated) {
for (const other of marker._obstructiveMarkers) {
inhibited.add(other);
}
}
}
}
for (const [layer, marker] of this._markers) {
if (!inhibited.has(marker)) {
marker._inflate();
for (const other of marker._obstructiveMarkers) {
inhibited.add(other);
other._deflate();
}
}
}
this._refreshIcons();
},
/**
* Deflate all markers
* @public
*/
deflateAll: function() {
for (const [layer, marker] of this._markers) {
marker._deflate();
}
},
/**
* React to zoom/dezoom by recomputing the collision between markers and
* inflate as many as possible
* @private
*/
_zoomend: function() {
this._recomputeObstructions().then(() =>
this.inflateAsManyAsPossible()
);
},
/**
* Toggles between showing the inflating markers on top of all markers
* (the normal state) and showing the deflating markers (so that we can
* locate deflated markers previously hidden).
* @public
*/
toggleInflatedMarkersAbove() {
if (this._inflatedMarkersAbove) {
for (const [layer, marker] of this._markers) {
if (!marker._inflated) {
marker._bringToFront(DEFLATED_MARKERS_ZINDEX_OFFSET);
}
}
this._inflatedMarkersAbove = false;
} else {
for (const [layer, marker] of this._markers) {
if (!marker._inflated) {
marker._bringBackFromFront();
}
}
this._inflatedMarkersAbove = true;
}
},
makeAwareOfOtherGroup(other) {
this._otherGroups.add(other);
other._otherGroups.add(this);
if (this._map) {
this._recomputeObstructions().then(() =>
this.inflateAsManyAsPossible()
);
}
},
removeOtherGroup(other) {
this._otherGroups.delete(other);
other._otherGroups.delete(this);
if (this._map) {
this._recomputeObstructions().then(() =>
this.inflateAsManyAsPossible()
);
}
},
/**
* Bisect a map of markers to find the last one before the west boundary in parameter
* The array has to be sorted.
* @param {[L.Marker, L.InflatableMarker][]} markers An array of pairs [Marker, InflatableMarker]
* @param {number} x The minimum X boundary
* @private
*/
_bisectWest(markers, x) {
if (!markers.length)
return markers.length;
let left = 0;
let right = markers.length - 1;
let pos = Math.trunc((left + right) / 2);
while (pos > left) {
if (x > markers[pos][1]._borders[1]) {
left = pos;
} else {
right = pos;
}
pos = Math.trunc((left + right) / 2);
}
return markers.length > left && x > markers[left][1]._borders[1] ? left + 1 : left;
},
/**
* Bisect a map of markers to find the first one past the east boundary in parameter
* The array has to be sorted.
* @param {[L.Marker, L.InflatableMarker][]} markers An array of pairs [Marker, InflatableMarker]
* @param {number} x The minimum X boundary
* @private
*/
_bisectEast(markers, x) {
if (!markers.length)
return -1;
let left = 0;
let right = markers.length - 1;
let pos = Math.ceil((left + right) / 2);
while (pos < right) {
if (x > markers[pos][1]._borders[3]) {
left = pos;
} else {
right = pos;
}
pos = Math.ceil((left + right) / 2);
}
return markers.length > right && x < markers[right][1]._borders[3] ? right : right - 1;
},
/**
* Bisect a map of markers to find the first one past the east boundary in parameter
* The array has to be sorted.
* @param {[L.Marker, L.InflatableMarker][]} markers An array of pairs [Marker, InflatableMarker]
* @param {number} y The minimum Y boundary
* @private
*/
_bisectNorth(markers, y) {
if (!markers.length)
return markers.length;
let left = 0;
let right = markers.length - 1;
let pos = Math.trunc((left + right) / 2);
while (pos > left) {
if (y > markers[pos][1]._borders[2]) {
left = pos;
} else {
right = pos;
}
pos = Math.trunc((left + right) / 2);
}
return y > markers[left][1]._borders[2] ? left : left + 1;
},
/**
* Bisect a map of markers to find the first one past the east boundary in parameter
* The array has to be sorted.
* @param {[L.Marker, L.InflatableMarker][]} markers An array of pairs [Marker, InflatableMarker]
* @param {number} y The minimum Y boundary
* @private
*/
_bisectSouth(markers, y) {
if (!markers.length)
return -1;
let left = 0;
let right = markers.length - 1;
let pos = Math.ceil((left + right) / 2);
while (pos < right) {
if (y > markers[pos][1]._borders[0]) {
left = pos;
} else {
right = pos;
}
pos = Math.ceil((left + right) / 2);
}
return markers.length > right && y < markers[right][1]._borders[0] ? right : right - 1;
},
});
/**
* Constructs an inflatable markers group
* @constructs {L.InflatableMarkersGroup}
* @param {L.InflatableMarkersGroup.options} options - the configuration
* @return {L.InflatableMarkersGroup} The newly constructed group
*/
L.inflatableMarkersGroup = function (options) {
return new L.InflatableMarkersGroup(options);
};
}, window));

View File

@ -21,9 +21,6 @@ export const localStorage = {
}
};
export function setSidebarStatus(sidebarStatus: string) {
localStorage.set(SidebarStatusKey, sidebarStatus);
}
// 布局大小
const SizeKey = 'size';

View File

@ -1,4 +1,4 @@
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
import axios from 'axios';
import { message, Modal } from 'ant-design-vue';
import { getToken } from '@/utils/auth';
import { useUserStoreHook } from '@/store/modules/user';
@ -12,7 +12,7 @@ const service = axios.create({
// 请求拦截器
service.interceptors.request.use(
(config: AxiosRequestConfig) => {
(config: any) => {
if (!config.headers) {
throw new Error(
`Expected 'config' and 'config.headers' not to be undefined`
@ -31,10 +31,8 @@ service.interceptors.request.use(
// 响应拦截器
service.interceptors.response.use(
(response: AxiosResponse) => {
(response: any) => {
const { status, msg } = response;
// console.log(msg)
// console.log(response);
if (status === 200) {
if (response.data.code == 401) {
message.error(response.data.msg||'请求失败');
@ -62,7 +60,7 @@ service.interceptors.response.use(
title: "提示",
content: "当前页面已失效,请重新登录",
okText: "确定",
cancelButtonProps: { style: { display: "none" } },
cancelButtonProps: { disabled: false },
onOk: () => {
localStorage.clear();
window.location.href = '/';

View File

@ -4,22 +4,22 @@ import JidiSelectorMod from "@/modules/jidiSelectorMod.vue";
import RightDrawer from "@/components/RightDrawer/index.vue";
import jidiInfoMod from "@/modules/jidiInfoMod/index.vue";
import shuidianhuangjingjieruMod from "@/modules/shuidianhuangjingjieruMod/index.vue";
import { getQgcStaticData } from "@/api/ecoFlow";
// import { getQgcStaticData } from "@/api/ecoFlow";
onMounted(() => {
const params = {
filter: {
logic: "and",
filters: [
{ field: "dtin", operator: "eq", dataType: "string", value: "1" },
{ field: "type", operator: "eq", dataType: "string", value: "hour" },
{ field: "tm", operator: "gte", dataType: "date", value: "2026-03-02 00:00:00" },
{ field: "tm", operator: "lte", dataType: "date", value: "2026-04-02 23:59:59" },
],
},
};
getQgcStaticData(params).then((res) => {
console.log(res);
});
// const params = {
// filter: {
// logic: "and",
// filters: [
// { field: "dtin", operator: "eq", dataType: "string", value: "1" },
// { field: "type", operator: "eq", dataType: "string", value: "hour" },
// { field: "tm", operator: "gte", dataType: "date", value: "2026-03-02 00:00:00" },
// { field: "tm", operator: "lte", dataType: "date", value: "2026-04-02 23:59:59" },
// ],
// },
// };
// getQgcStaticData(params).then((res) => {
// console.log(res);
// });
});
</script>

View File

@ -240,7 +240,7 @@
</template>
<script setup lang="ts">
import { onMounted, reactive, ref, toRefs, watch, nextTick } from "vue";
import { onMounted, reactive, ref, toRefs, watch } from "vue";
import loginImg from "@/assets/images/logo.png";
import { UserOutlined, LockOutlined, MobileOutlined } from "@ant-design/icons-vue";
import { getCaptcha } from "@/api/auth";
@ -322,25 +322,27 @@ const forgotPasswordRules = ref({
],
});
const { loginData, loginRules, loading, passwordType, capslockTooltipDisabled } = toRefs(
const { loginData, loginRules, loading,
// passwordType, capslockTooltipDisabled
} = toRefs(
state
);
function checkCapslock(e: any) {
const { key } = e;
state.capslockTooltipDisabled = key && key.length === 1 && key >= "A" && key <= "Z";
}
// function checkCapslock(e: any) {
// const { key } = e;
// state.capslockTooltipDisabled = key && key.length === 1 && key >= "A" && key <= "Z";
// }
function showPwd() {
if (passwordType.value === "password") {
passwordType.value = "";
} else {
passwordType.value = "password";
}
nextTick(() => {
passwordRef.value.focus();
});
}
// function showPwd() {
// if (passwordType.value === "password") {
// passwordType.value = "";
// } else {
// passwordType.value = "password";
// }
// nextTick(() => {
// passwordRef.value.focus();
// });
// }
/**
* 登录
@ -432,9 +434,9 @@ const startCountdown = () => {
};
//
const showForgotPasswordPage = () => {
showForgotPassword.value = true;
};
// const showForgotPasswordPage = () => {
// showForgotPassword.value = true;
// };
//
const backToLogin = () => {
@ -448,45 +450,45 @@ const backToLogin = () => {
};
//
const sendSms = async () => {
//
if (!loginData.value.username) {
message.error("请输入手机号");
return;
}
// const sendSms = async () => {
// //
// if (!loginData.value.username) {
// message.error("");
// return;
// }
//
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(loginData.value.username)) {
message.error("请输入正确的手机号");
return;
}
// //
// const phoneRegex = /^1[3-9]\d{9}$/;
// if (!phoneRegex.test(loginData.value.username)) {
// message.error("");
// return;
// }
//
if (smsCountdown.value > 0) {
return;
}
// //
// if (smsCountdown.value > 0) {
// return;
// }
loading.value = true;
smsButtonDisabled.value = true;
// loading.value = true;
// smsButtonDisabled.value = true;
try {
//
// await axios.post('/sms/send', { phone: loginForm.value.username })
// try {
// //
// // await axios.post('/sms/send', { phone: loginForm.value.username })
//
message.success("验证码发送成功");
// //
// message.success("");
//
startCountdown();
} catch (error) {
console.error("发送验证码失败", error);
message.error("验证码发送失败,请重试");
smsButtonDisabled.value = false;
} finally {
loading.value = false;
}
};
// //
// startCountdown();
// } catch (error) {
// console.error("", error);
// message.error("");
// smsButtonDisabled.value = false;
// } finally {
// loading.value = false;
// }
// };
//
const sendForgotPasswordSms = async () => {
//

View File

@ -241,7 +241,7 @@
<script setup lang="ts">
import { setPath } from '@/utils/auth';
import { onMounted, reactive, ref, toRefs, watch, nextTick } from "vue";
import { onMounted, reactive, ref, toRefs, watch } from "vue";
import loginImg from "@/assets/images/logo.png";
import { UserOutlined, LockOutlined, MobileOutlined } from "@ant-design/icons-vue";
import { getCaptcha } from "@/api/auth";
@ -322,25 +322,27 @@ const forgotPasswordRules = ref({
],
});
const { loginData, loginRules, loading, passwordType, capslockTooltipDisabled } = toRefs(
const { loginData, loginRules, loading,
// passwordType, capslockTooltipDisabled
} = toRefs(
state
);
function checkCapslock(e: any) {
const { key } = e;
state.capslockTooltipDisabled = key && key.length === 1 && key >= "A" && key <= "Z";
}
// function checkCapslock(e: any) {
// const { key } = e;
// state.capslockTooltipDisabled = key && key.length === 1 && key >= "A" && key <= "Z";
// }
function showPwd() {
if (passwordType.value === "password") {
passwordType.value = "";
} else {
passwordType.value = "password";
}
nextTick(() => {
passwordRef.value.focus();
});
}
// function showPwd() {
// if (passwordType.value === "password") {
// passwordType.value = "";
// } else {
// passwordType.value = "password";
// }
// nextTick(() => {
// passwordRef.value.focus();
// });
// }
/**
* 登录

View File

@ -5,23 +5,33 @@
:import-btn="importBtn"
:save-btn="saveBtn"
:handle-add="handleAdd"
:batchData="batchData"
:batchDel="batchDel"
@search-finish="handleSearchFinish"
/>
<!-- 主表格 -->
<a-table
size="small"
:loading="loading"
:row-selection="rowSelection"
:data-source="tableData"
<BasicTable
ref="tableRef"
:columns="columns"
:pagination="paginationConfig"
:scroll="{ x: '100%' }"
row-key="key"
:list-url="getFishDraftPage"
:search-params="{}"
:enable-row-selection="true"
@selection-change="handleSelectionChange"
>
<!-- 自定义插槽渲染可根据需要扩展这里主要依赖 columns 中的 render 函数逻辑但在 Vue中通常使用 slot h 函数 -->
<!-- 注意Antdv columns render 支持返回 VNode 或字符串 -->
</a-table>
<!-- 使用 bodyCell 插槽自定义单元格渲染 -->
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'action' || column.dataIndex === 'action'">
<div class="flex">
<a-button type="link" size="small" @click="handleEdit(record)">编辑</a-button>
<a-button type="link" danger size="small" @click="handleDelete([record.id])"
>删除</a-button
>
</div>
</template>
</template>
</BasicTable>
<!-- <BasicTable :columns="columns" :listUrl="getFishDraftPage" /> -->
<!-- 导入预览 Modal -->
<a-modal
@ -29,7 +39,7 @@
ok-text="提交导入"
cancel-text="取消"
:width="1500"
:open="visible"
v-model:open="visible"
:confirm-loading="fileLoading"
@cancel="handleModalCancel"
@ok="handleModalOk"
@ -39,7 +49,6 @@
:loading="fileLoading"
:data-source="fileTableData"
:columns="modalColumns"
:pagination="false"
:scroll="{ y: 500, x: '100%' }"
row-key="index"
>
@ -50,7 +59,7 @@
<!-- 新增/编辑 Modal (对应 React EditModal) -->
<!-- 假设已创建对应的 Vue 组件 GuoYuSheShiShuJuTianBaoForm -->
<EditModal
v-model:visible="editModalVisible"
v-model:visible="editModalVisible"
:initial-values="currentRecord"
:loading="submitLoading"
@cancel="editModalCancel"
@ -60,12 +69,18 @@
<!-- 视频预览 Modal -->
<a-modal
title="视频预览"
:open="videoPreviewVisible"
v-model:open="videoPreviewVisible"
:footer="null"
width="800px"
@cancel="closeVideoPreview"
>
<video v-if="currentVideoUrl" controls autoplay style="width: 100%" :src="currentVideoUrl">
<video
v-if="currentVideoUrl"
controls
autoplay
style="width: 100%"
:src="currentVideoUrl"
>
您的浏览器不支持视频播放
</video>
</a-modal>
@ -73,97 +88,120 @@
</template>
<script lang="ts" setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { message, Modal } from 'ant-design-vue' // 使 ant-design-vue
import JSZip from 'jszip'
import * as XLSX from 'xlsx'
import GuoYuSheShiShuJuTianBaoSearch from './guoYuSheShiShuJuTianBaoSearch.vue'
import EditModal from './guoYuSheShiShuJuTianBaoForm.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";
import {
getFishDraftPage,
addFishDraft,
editFishDraft,
delFishDraft,
} from "@/api/guoYuSheShiShuJuTianBao";
import dayjs from "dayjs";
import { Tag } from 'ant-design-vue'; // Tag
// import { FileImageOutlined, VideoCameraOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons-vue'
// --- ---
interface FormData {
[key: string]: any
[key: string]: any;
}
interface ColumnConfig {
dataIndex: string
key: string
title: string
width?: number
dataIndex: string;
key: string;
title: string;
width?: number;
customRender?: (text: any, record: any) => any;
}
const tableRef = ref<any>(null);
// --- ---
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 }
]
{ dataIndex: "engName", key: "engName", title: "水电基地", width: 100 },
{ dataIndex: "baseName", key: "baseName", title: "电站名称", width: 120 },
{ dataIndex: "fpname", key: "fpname", title: "过鱼设施名称", width: 150 },
{ dataIndex: "strdt", key: "strdt", title: "过鱼时间", width: 150 },
{ dataIndex: "ftp", key: "ftp", title: "鱼种类", width: 120 },
{
dataIndex: "isfs",
key: "isfs",
title: "是否鱼苗",
width: 74,
customRender: ({ text }: any) => {
const isYes = text === 1 || text === '1';
return h(
Tag,
{
color: isYes ? 'success' : 'error', // Antdv Tag
style: { margin: 0 } // margin使
},
() => isYes ? '是' : '否'
);
},
},
{ dataIndex: "direction", key: "direction", title: "游向", width: 80 },
{ dataIndex: "fcnt", key: "fcnt", title: "过鱼数量(尾)", width: 120 },
{ dataIndex: "fsz", key: "fsz", title: "体长(cm)", width: 110 },
{ dataIndex: "fwet", key: "fwet", title: "平均体重(g)", width: 110 },
{ dataIndex: "wt", key: "wt", title: "水温(℃)", width: 80 },
{ dataIndex: "picpth", key: "level5", title: "图片", width: 100 },
{ dataIndex: "vdpth", key: "level6", title: "视频", width: 100 },
{ dataIndex: "tm", key: "tm", title: "填报时间", width: 150 },
{ dataIndex: "status", key: "status", title: "状态", width: 100 },
];
// --- ---
const searchData = ref<any>(null)
const visible = ref(false) // Modal
const visible = ref(false); // Modal
//
const editModalVisible = ref(false)
const currentRecord = ref<FormData | null>(null)
const submitLoading = ref(false)
const editModalVisible = ref(false);
const currentRecord = ref<FormData | null>(null);
const submitLoading = ref(false);
//
const videoPreviewVisible = ref(false)
const currentVideoUrl = ref<string>('')
const videoPreviewVisible = ref(false);
const currentVideoUrl = ref<string>("");
//
const tableData = ref<any[]>([])
const fileTableData = ref<any[]>([])
const tableData = ref<any[]>([]);
const fileTableData = ref<any[]>([]);
const batchData = ref<any[]>([]);
const loading = ref(false)
const fileLoading = ref(false)
const fileLoading = ref(false);
// Key ()
const editingKey = ref<string | number>('')
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 ''
}
}
// 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' ? '查看图片' : '播放视频'
}
}
// const createMediaRender = (type: "image" | "video") => {
// return (text: string) => {
// if (!text) return "-";
// // 使 h
// // click columns 使 slots h
// // UI Antdv customRender
// return type === "image" ? "" : "";
// };
// };
// --- Columns ---
@ -171,337 +209,338 @@ const createMediaRender = (type: 'image' | 'video') => {
const columns = computed(() => {
return [
...baseColumnsConfig.map((col) => {
if (col.dataIndex === 'level5') {
if (col.dataIndex === "level5") {
return {
...col,
customRender: ({ text }: any) => {
if(!text) return '-'
// Icon
return `<span style="color:#52c41a; cursor:pointer">查看图片</span>`
}
}
if (!text) return "-";
// Icon
return `<span style="color:#52c41a; cursor:pointer">查看图片</span>`;
},
};
}
if (col.dataIndex === 'level6') {
if (col.dataIndex === "level6") {
return {
...col,
customRender: ({ text }: any) => {
if(!text) return '-'
return `<span style="color:#1890ff; cursor:pointer">播放视频</span>`
}
}
if (!text) return "-";
return `<span style="color:#1890ff; cursor:pointer">播放视频</span>`;
},
};
}
return { ...col, visible: true }
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 '操作列'
}
}
]
})
title: "操作",
key: "action",
dataIndex: "action",
fixed: "right",
width: 100,
align: "center",
},
];
});
// Columns ()
const modalColumns = computed(() => {
const isEditing = (record: any, index: number) => index === editingKey.value
const isEditing = (_record: any, index: number) => index === editingKey.value;
const save = async (index: number) => {
// fileTableData input change
editingKey.value = ''
message.success('行数据已更新')
}
// 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('行数据已删除')
}
// 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)
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编辑中'
//
if (col.dataIndex === "level5" || col.dataIndex === "level6") {
if (editing) {
// Input VNode slot h
return "Input编辑中";
}
return col.dataIndex === "level5" ? "查看图片" : "播放视频";
}
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' }
})
})
//
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
}
currentRecord.value = null;
editModalVisible.value = true;
};
const handleEdit = (record: any) => {
currentRecord.value = { ...record }
editModalVisible.value = true
}
currentRecord.value = { ...record };
editModalVisible.value = true;
};
const handleDeleteMain = (index: number) => {
//
const handleDelete = (ids: any[]) => {
console.log(ids)
Modal.confirm({
title: '确定删除这条数据吗?',
onOk: () => {
tableData.value = tableData.value.filter((_, i) => i !== index)
message.success('删除成功')
}
})
}
title: "是否确认删除选中数据吗?",
onOk: async () => {
let res: any = await delFishDraft(ids);
if (res && res?.code == 0) {
message.success("删除成功");
tableRef.value?.getList();
}
},
});
};
//
const batchDel = () => {
handleDelete(batchData.value);
};
//
const handleSelectionChange = (keys: any) => {
batchData.value = keys;
};
const editModalCancel = () => {
editModalVisible.value = false
}
editModalVisible.value = false;
};
const handleEditSubmit = async (values: FormData) => {
submitLoading.value = true
submitLoading.value = true;
console.log(values);
//
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)
}
// setTimeout(() => {
if (currentRecord.value) {
//
const getData = async (searchDataParam?: any, label?: string) => {
loading.value = true
// TODO: API
console.log('Fetching data with:', searchDataParam)
setTimeout(() => {
loading.value = false
}, 500)
}
let res: any = await editFishDraft({
...values
});
if (res && res?.code == 0) {
message.success("编辑成功");
editModalVisible.value = false;
tableRef.value?.getList();
}
submitLoading.value = false;
// const newData = tableData.value.map((item) => {
// // ID
// if (JSON.stringify(item) === JSON.stringify(currentRecord.value)) {
// // ID
// return { ...item, ...values };
// }
// return item;
// });
// // key
// const targetIndex = tableData.value.findIndex(
// (item) => item.key === currentRecord.value?.key
// );
// if (targetIndex > -1) {
// tableData.value[targetIndex] = { ...tableData.value[targetIndex], ...values };
// }
} else {
//
let res: any = await addFishDraft({
...values,
tm: dayjs().startOf("day").format("YYYY-MM-DD HH:mm:ss"),
stcd: 1,
});
if (res && res?.code == 0) {
message.success("新增成功");
editModalVisible.value = false;
tableRef.value?.getList();
}
submitLoading.value = false;
}
// }, 500);
};
const parseExcelFile = async (fileName: string, arrayBuffer: ArrayBuffer) => {
try {
const workbook = XLSX.read(arrayBuffer, { type: 'array' })
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
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 []
console.error(`解析文件 ${fileName} 失败:`, error);
message.error(`文件 ${fileName} 解析失败`);
return [];
}
}
};
const handleModalOk = () => {
tableData.value = [...fileTableData.value]
visible.value = false
message.success('数据已导入至列表')
}
tableData.value = [...fileTableData.value];
visible.value = false;
message.success("数据已导入至列表");
};
const handleModalCancel = () => {
visible.value = false
editingKey.value = ''
}
visible.value = false;
editingKey.value = "";
};
const importBtn = async (file: File) => {
fileLoading.value = true
editingKey.value = ''
const hideMessage = message.loading('正在解析压缩包...', 0)
fileLoading.value = true;
editingKey.value = "";
const hideMessage = message.loading("正在解析压缩包...", 0);
try {
const zip = await JSZip.loadAsync(file)
const zipPathMap: Record<string, string> = {}
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('/')
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 subPath = pathParts.slice(i).join("/");
if (subPath) zipPathMap[subPath.toLowerCase()] = relativePath;
}
}
})
});
const fileNames = Object.keys(zip.files)
const fileNames = Object.keys(zip.files);
if (fileNames.length === 0) {
hideMessage()
message.warning('压缩包为空')
fileLoading.value = false
return
hideMessage();
message.warning("压缩包为空");
fileLoading.value = false;
return;
}
let allExcelData: any[] = []
let allExcelData: any[] = [];
for (const fileName of fileNames) {
const zipEntry = zip.files[fileName]
if (zipEntry.dir) continue
if (!fileName.match(/\.(xls|xlsx)$/i)) continue
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 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 = {}
const newObj: any = {};
for (const excelKey in item) {
if (!Object.prototype.hasOwnProperty.call(item, excelKey)) continue
const value = item[excelKey]
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)
)
const matchedCol = baseColumnsConfig.find(
(col) => excelKey.includes(col.title) || col.title.includes(excelKey)
);
if (matchedCol) {
let finalValue = value
let finalValue = value;
//
if ((matchedCol.dataIndex === 'level5' || matchedCol.dataIndex === 'level6') && value && typeof value === 'string') {
const trimPath = value.trim().replace(/\\/g, '/')
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]
const searchKey = trimPath.toLowerCase();
const realPath = zipPathMap[searchKey];
if (realPath) {
try {
const zipFile = zip.file(realPath)
const zipFile = zip.file(realPath);
if (zipFile) {
const blob = await zipFile.async('blob')
finalValue = URL.createObjectURL(blob)
const blob = await zipFile.async("blob");
finalValue = URL.createObjectURL(blob);
}
} catch (e) {
console.error(`Failed to extract blob for: ${realPath}`, e)
console.error(`Failed to extract blob for: ${realPath}`, e);
}
}
}
}
newObj[matchedCol.dataIndex] = finalValue
newObj[matchedCol.dataIndex] = finalValue;
}
}
return newObj
return newObj;
})
)
allExcelData = [...allExcelData, ...transformedData]
);
allExcelData = [...allExcelData, ...transformedData];
} catch (err) {
console.error(`读取文件 ${fileName} 失败`, err)
console.error(`读取文件 ${fileName} 失败`, err);
}
}
fileTableData.value = allExcelData
visible.value = true
hideMessage()
message.success(`解析完成,共获取 ${allExcelData.length} 条数据`)
fileTableData.value = allExcelData;
visible.value = true;
hideMessage();
message.success(`解析完成,共获取 ${allExcelData.length} 条数据`);
} catch (error) {
hideMessage()
console.error('ZIP 解析失败:', error)
message.error('文件格式错误或解析失败')
hideMessage();
console.error("ZIP 解析失败:", error);
message.error("文件格式错误或解析失败");
} finally {
fileLoading.value = false
fileLoading.value = false;
}
}
};
const saveBtn = async () => {
// TODO:
console.log('Save button clicked')
}
console.log("Save button clicked");
};
const handleSearchFinish = (e: any, label: string) => {
const newSearchData = { ...searchData.value, ...e }
searchData.value = newSearchData
getData(newSearchData, label)
}
const handleSearchFinish = (values: any) => {
console.log(values);
// const newSearchData = { ...searchData.value, ...e };
// searchData.value = newSearchData;
// getData(newSearchData, label);
};
const closeVideoPreview = () => {
videoPreviewVisible.value = false
currentVideoUrl.value = ''
}
//
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)
}
}))
videoPreviewVisible.value = false;
currentVideoUrl.value = "";
};
// --- ---
onMounted(() => {
//
// getData()
})
onMounted(() => {});
</script>
<style lang="scss" scoped>
.guoYuSheShiShuJuTianBao-page {
width: 100%;
height: 100%;
background-color: #ffffff;
padding: 20px;
width: 100%;
height: 100%;
background-color: #ffffff;
padding: 20px;
}
</style>

View File

@ -1,10 +1,10 @@
<template>
<a-modal
:title="isEdit ? '编辑数据' : '新增数据'"
:opne="visible"
v-model:open="modalVisible"
:confirm-loading="loading"
width="800px"
destroy-on-close
:destroy-on-close="true"
@cancel="handleCancel"
@ok="handleOk"
>
@ -12,35 +12,35 @@
ref="formRef"
:model="formData"
:rules="rules"
layout="vertical"
name="edit_form"
:labelCol="{ span: 7 }"
>
<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 label="水电基地" name="engName">
<a-input v-model:value="formData.engName" 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 label="电站名称" name="baseName">
<a-input v-model:value="formData.baseName" 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 label="过鱼设施名称" name="fpname">
<a-input v-model:value="formData.fpname" placeholder="请输入过鱼设施名称" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="过鱼时间" name="regionName">
<a-form-item label="过鱼时间" name="strdt">
<a-date-picker
v-model:value="formData.regionName"
v-model:value="formData.strdt"
style="width: 100%"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="选择日期"
/>
</a-form-item>
@ -49,226 +49,372 @@
<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 label="鱼种类" name="ftp">
<a-input v-model:value="formData.ftp" 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 label="是否鱼苗" name="isfs">
<a-radio-group v-model:value="formData.isfs">
<a-radio :value="1"></a-radio>
<a-radio :value="0"></a-radio>
</a-radio-group>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="过鱼数量(尾)" name="level1">
<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-option value="下行折返">下行折返</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="过鱼数量(尾)" name="fcnt">
<a-input-number
v-model:value="formData.level1"
v-model:value="formData.fcnt"
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
label="体长(cm)"
:validate-status="bodyLengthError ? 'error' : ''"
:help="bodyLengthError"
>
<div class="flex">
<a-input-number
v-model:value="formData.bodyLengthMin"
style="width: 50%"
placeholder="请输入"
:min="0"
@change="validateBodyLength"
/>
<span class="px-[10px]">~</span>
<a-input-number
v-model:value="formData.bodyLengthMax"
style="width: 50%"
placeholder="请输入"
:min="0"
@change="validateBodyLength"
/>
</div>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="水温" name="level4">
<a-form-item
label="平均体重(g)"
:validate-status="weightError ? 'error' : ''"
:help="weightError"
>
<div class="flex">
<a-input-number
v-model:value="formData.weightMin"
style="width: 50%"
placeholder="请输入"
:min="0"
@change="validateWeight"
/>
<span class="px-[10px]">~</span>
<a-input-number
v-model:value="formData.weightMax"
style="width: 50%"
placeholder="请输入"
:min="0"
@change="validateWeight"
/>
</div>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="水温(℃)" name="wt">
<a-input-number
v-model:value="formData.level4"
v-model:value="formData.wt"
style="width: 100%"
placeholder="水温"
:min="0"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="图片" name="picpth">
<!-- <a-input v-model:value="formData.picpth" placeholder="图片路径" /> -->
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="视频" name="vdpth">
<!-- <a-input v-model:value="formData.vdpth" placeholder="视频路径" /> -->
</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
import { ref, reactive, watch, computed } from "vue";
import { message } from "ant-design-vue";
import type { Rule } from "ant-design-vue/es/form";
// Props
interface Props {
visible: boolean
initialValues?: any | null
loading?: boolean
visible: boolean;
initialValues?: any | null;
loading?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
visible: false,
initialValues: null,
loading: false
})
loading: false,
});
// --- v-model ---
const modalVisible = computed({
get: () => props.visible,
set: (val) => emit("update:visible", val),
});
// Emits
const emit = defineEmits<{
(e: 'update:visible', value: boolean): void
(e: 'cancel'): void
(e: 'ok', values: any): void
}>()
(e: "update:visible", value: boolean): void;
(e: "cancel"): void;
(e: "ok", values: any): void;
}>();
//
const formRef = ref()
const formRef = ref();
// 1.
const bodyLengthError = ref<string>("");
const weightError = ref<string>("");
//
const formData = reactive({
const defaultFormData = reactive({
id: undefined,
engName: undefined,
baseName: undefined,
fpname: undefined,
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: '正常'
})
strdt: undefined,
ftp: undefined,
isfs: 0,
direction: undefined,
fcnt: undefined,
fsz: undefined,
fwet: undefined,
wt: undefined,
picpth: undefined,
vdpth: undefined,
bodyLengthMin: undefined,
bodyLengthMax: undefined,
weightMin: undefined,
weightMax: undefined,
});
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[]> = {
stcd: [{ required: true, message: '请输入流域', trigger: 'blur' }],
title: [{ required: true, message: '请输入电站名称', trigger: 'blur' }],
office: [{ required: true, message: '请输入过鱼设施名称', trigger: 'blur' }],
regionName: [{ required: true, message: '请选择过鱼时间', trigger: 'change' }]
}
// engName: [{ required: true, message: "", trigger: "blur" }],
// baseName: [{ required: true, message: "", trigger: "blur" }],
// fpname: [{ required: true, message: "", trigger: "blur" }],
// 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" }],
};
//
const isEdit = computed(() => !!props.initialValues)
const isEdit = computed(() => !!props.initialValues);
//
const validateBodyLength = () => {
const min = formData.bodyLengthMin;
const max = formData.bodyLengthMax;
// 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()
//
bodyLengthError.value = "";
//
if (min !== undefined && min !== null && max !== undefined && max !== null) {
if (Number(min) >= Number(max)) {
bodyLengthError.value = "最小体长必须小于最大体长";
// handleOk
return false;
}
}
return true;
};
const validateWeight = () => {
const min = formData.weightMin;
const max = formData.weightMax;
//
weightError.value = "";
if (min !== undefined && min !== null && max !== undefined && max !== null) {
if (Number(min) >= Number(max)) {
weightError.value = "最小体重必须小于最大体重";
return false;
}
}
return true;
};
// 1.
const initForm = () => {
if (props.initialValues) {
// --- ---
const values = props.initialValues;
//
if (values.fwet) {
const weights = values.fwet.split("~");
formData.weightMin = weights[0];
formData.weightMax = weights[1];
} else {
formData.weightMin = undefined;
formData.weightMax = undefined;
}
if (values.fsz) {
const sizes = values.fsz.split("~");
formData.bodyLengthMin = sizes[0];
formData.bodyLengthMax = sizes[1];
} else {
formData.bodyLengthMin = undefined;
formData.bodyLengthMax = undefined;
}
//
Object.keys(formData).forEach((key) => {
//
if (
key === "weightMin" ||
key === "weightMax" ||
key === "bodyLengthMin" ||
key === "bodyLengthMax"
) {
return;
}
// initialValues
if (values.hasOwnProperty(key)) {
formData[key] = values[key];
}
});
} else {
// --- ---
resetForm();
}
};
// 2. watch visible
watch(
() => props.visible,
(newVisible) => {
if (newVisible) {
//
initForm();
} else {
//
// initialValues
}
},
{ immediate: true }
)
{ immediate: false } // immediate false
);
// 3. watch
//
// watch(
// () => [props.visible, props.initialValues],
// ...
// );
//
const resetForm = () => {
if (formRef.value) {
formRef.value.resetFields()
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 = '正常'
}
Object.assign(formData, defaultFormData);
//
bodyLengthError.value = '';
weightError.value = '';
};
//
const handleCancel = () => {
emit('update:visible', false)
emit('cancel')
resetForm()
}
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')
//
const isBodyLenValid = validateBodyLength();
const isWeightValid = validateWeight();
console.log(isBodyLenValid, isWeightValid);
if (!isBodyLenValid || !isWeightValid) {
message.error("请检查体长或体重填写是否正确");
return;
}
//
await formRef.value.validate();
//
const submitValues = {
...formData,
fwet: formData.weightMin + "~" + formData.weightMax,
fsz: formData.bodyLengthMin + "~" + formData.bodyLengthMax,
};
// (InputNumber number)
//
;['level1', 'level2', 'level3', 'level4'].forEach((key) => {
if (submitValues[key] !== undefined && submitValues[key] !== null) {
submitValues[key] = Number(submitValues[key])
}
})
emit('ok', submitValues)
emit("ok", submitValues);
} catch (error) {
console.error('Validate Failed:', error)
message.error('请检查表单填写是否正确')
console.error("Validate Failed:", error);
message.error("请检查表单填写是否正确");
}
}
};
</script>
<style scoped>

View File

@ -16,18 +16,28 @@
@finish="onSearchFinish"
@values-change="onValuesChange"
>
<template #typeDate="{ onChange }">
<fishSearch
v-model="localTypeDate"
width="280px"
:options="options"
@update:modelValue="onChange"
/>
</template>
<!-- 自定义重置及操作按钮区域 -->
<template #actions="{ form }">
<template #actions>
<a-tooltip title="新增">
<a-button @click="props.handleAdd">
新增
</a-button>
<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-button v-hasPerm="['sjtb:import-zip']" @click="triggerFileInput">
导入zip
</a-button>
</a-tooltip>
<a-button @click="props.batchDel" :disabled="batchData.length === 0">
批量删除
</a-button>
<a-tooltip title="提交数据">
<a-button @click="props.saveBtn">
<template #icon><SaveOutlined /></template>
@ -41,24 +51,39 @@
批量审批
</a-button>
</a-tooltip>
<a-tooltip placement="leftBottom">
<template #title>
<div>1.</div>
</template>
<a-button>
<template #icon><QuestionOutlined /></template>
</a-button>
</a-tooltip>
</template>
</BasicSearch>
</div>
</template>
<script lang="ts" setup>
import { ref, computed, onMounted, nextTick } from "vue";
import { ref, computed, onMounted, watch } from "vue";
import { message } from "ant-design-vue";
import { PlusOutlined, SaveOutlined, CheckSquareOutlined } from "@ant-design/icons-vue";
import {
SaveOutlined,
CheckSquareOutlined,
QuestionOutlined,
} from "@ant-design/icons-vue";
import dayjs from "dayjs";
import BasicSearch from "@/components/BasicSearch/index.vue"; //
import { DateSetting } from "@/utils/enumeration";
import { checkPerm, } from "@/directive/permission";
import { checkPerm } from "@/directive/permission";
import fishSearch from "@/components/fishSearch/index.vue";
// --- Props & Emits ---
interface Props {
importBtn: (file: File) => void;
batchDel: () => void;
saveBtn: () => void;
batchData: any[];
handleAdd: () => void;
}
@ -67,9 +92,194 @@ const props = defineProps<Props>();
const emit = defineEmits<{
(e: "searchFinish", values: any, label: string): void;
}>();
const localTypeDate = ref<string[]>([]);
// --- State ---
const fileInputRef = ref<HTMLInputElement>();
const options = ref<any>([
{
_tls: {},
id: "00DDF2A72147B2115384F64DDFE26A5E",
recordUser: null,
recordTime: null,
modifyTime: null,
displayRecordUser: null,
departmentId: null,
displayDepartment: null,
index: 1,
name: "异唇裂腹鱼",
code: null,
nameEn: null,
alias: null,
description: null,
logo: null,
introduce: null,
inffile: null,
genus: null,
family: null,
species: null,
fsz: null,
type: 1,
typeName: "淡水",
rare: null,
specOrigin: null,
specOriginName: null,
ptype: null,
ptypeName: null,
rvcd: "null",
rvcdName: "",
zyFishId: "00DDF2A72147B2115384F64DDFE26A5E",
habitMigrat: null,
feedingHabit: null,
spawnCharact: null,
spawnMonth: null,
food: null,
timeFeed: null,
orignDate: null,
pretemp: null,
flowRate: null,
depth: null,
botmMater: null,
wqtq: null,
habitat: null,
situation: null,
resourceType: null,
shapedesc: null,
protectlvl: null,
habitation: null,
fid: null,
enable: null,
internal: null,
orderIndex: null,
filterContent: null,
platformId: null,
isTempStorage: null,
},
{
_tls: {},
id: "0249006974f34c288d6cb4df54e3b19d",
recordUser: null,
recordTime: null,
modifyTime: null,
displayRecordUser: null,
departmentId: null,
displayDepartment: null,
index: 2,
name: "匙吻鲟",
code: null,
nameEn: "Polyodon spathula",
alias: "美国匙吻鲟、鸭嘴鲟",
description:
"匙吻鲟(Polyodonspathula)亦称匙吻猫鱼(spoonbillcat)。产于北美洲的原始鱼,为桨吻鲟(paddlefish)的一种。属鲟形目、匙吻鲟科是北美洲的一种名贵大型淡水经济鱼类。匙吻鲟的显著特点是吻呈扁平桨状特别长。鱼的体表光滑无鳞背部黑蓝灰色有一些斑点在其间体侧有点状赭色腹部白色。个体大这种大型淡水鱼可以长到220厘米重达90公斤以上。",
logo: "20240527221754634033127655455265",
introduce: null,
inffile:
"20240527221811830658320352201158,20240527221805865127213075311524,20240527221822527347221377607671,20240527221828072460253583084314,20240527221800311481326028334838,20240527221817630761245563388673",
genus: "匙吻鲟属",
family: "匙吻鲟科",
species: "匙吻鲟",
fsz: "85~220",
type: 1,
typeName: "淡水",
rare: null,
specOrigin: 2,
specOriginName: "外来鱼类",
ptype: 4,
ptypeName: "易危",
rvcd: "SJLY148",
rvcdName: "大渡河",
zyFishId: "0249006974f34c288d6cb4df54e3b19d",
habitMigrat: "繁殖洄游",
feedingHabit: "肉食性",
spawnCharact: "粘性卵类型",
spawnMonth: "4-5",
food: "主要以浮游动物,也以甲壳类和双壳类生物为食",
timeFeed: "夜间觅食",
orignDate:
"匙吻鲟在美国密西西比河流域的22个洲均有发现。包括密苏里河到蒙大拿州俄亥俄河和它的主要支流流域。雄鱼在79龄达到性成熟雌鱼晚一年相对怀卵量约为每克体重3.5粒。匙吻鲟多在45月繁殖适宜水温为1618℃繁殖期会游到江河上游产卵受精卵灰黑色直径22.5毫米有黏性往往粘在砾石上孵化孵化期67天。",
pretemp: "037℃",
flowRate: "0.3m/s",
depth: "2~2.5",
botmMater: "泥质",
wqtq: "适宜的pH范围为6.58对溶解氧要求较高应在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 Gunther1873是鲤科马口鱼属的一种生活的溪流中的小型鱼类。体长稍侧扁腹部圆。头稍尖头长大于体高。吻钝吻长远比其宽为大。口特大下颌前端突起两侧面各有一凹陷恰与上颌突出部分吻合。下咽齿3行。鳞圆形背鳍条27无硬刺。臀鳍条38-10。背部黑灰色体侧下半部及腹面银白色喉部、口唇及各鳍橙黄背鳍上有黑色的小斑点眼上部有一红色斑点体两侧具有浅蓝色的垂直条纹。生殖季节时雄鱼体色更为鲜艳。",
logo: "20240527192500111683624865306342",
introduce: null,
inffile:
"20240527192505300717052825727341,20240527192533035616525871580354,20240527192510217883087850433201,20240527192516128514164206355182,20240527192522835236402141341053,20240527192527583177528213025212",
genus: "马口鱼属",
family: "鲤科",
species: "南方马口鱼",
fsz: "720",
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:
"产卵期在68月份。第一年生长较迅速可达711厘米。1龄鱼即有繁殖能力系小型鱼类。",
pretemp: "030℃",
flowRate: "0.3m/s",
depth: "1~1.5",
botmMater: "砂砾底质",
wqtq: "pH在7.27.8之内,凉爽清洁、溶氧丰富的水质",
habitat: null,
situation: null,
resourceType: null,
shapedesc:
"背鳍条37臀鳍条39侧线鳞4547下咽齿3行1·4·54·4·1。鳃耙外侧10脊椎骨35。体长为体高的3.14.3倍为头长的3.53.9倍为尾柄长的4.75.2倍为尾柄高的10.211.3倍。头长为吻长的2.73.2倍为眼径的5.06.2倍为眼间距的3.13.3倍。体延长侧扁。吻长其长略大于宽口大端位口裂向上倾斜下颌后端延长达眼前缘其前缘凸起两侧凹陷恰与上颌前端和两侧嵌合。眼中等大位于头侧上方。鳃耙短小而稀疏。下咽齿圆柱性顶端尖而长。侧线完全前端弯向体侧腹方后端向上延至尾柄正中。背鳍无硬刺其起点至吻端稍大于至尾鳍基部的距离胸鳍不达腹鳍其末端可达胸、腹鳍间距的3/5处。腹鳍外缘略钝圆起点约与背鳍不分支鳍条相对。鳔2视后室约为前室的2倍腹腔膜银白色。体背部灰黑色腹部银白色体侧有浅蓝色的垂直条纹胸鳍、腹鳍和臀鳍为橙黄色。雄鱼在生殖期出现婚装头部、吻部和臀部有显眼的珠星臀鳍的第14根分支鳍条特别延长全身具有很鲜艳的婚姻色。",
protectlvl: null,
habitation: "流水型;冷水性;中上水层",
fid: null,
enable: null,
internal: null,
orderIndex: null,
filterContent: null,
platformId: null,
isTempStorage: null,
},
]);
// initSearchData
const initSearchData = {
@ -80,8 +290,9 @@ const initSearchData = {
hbrvcd: "",
stcdId: "",
},
typeDate: "",
RangePickerList: [
mway: "1",
typeDate: [],
strdt: [
dayjs().startOf("month").format("YYYY-MM-DD"),
dayjs().endOf("day").format("YYYY-MM-DD"),
],
@ -94,8 +305,8 @@ const searchData = ref<any>({ ...initSearchData });
const searchList: any = computed(() => [
{
type: "waterStation",
name: "stcd",
label: "选择水电站",
name: "engName",
label: "水电基地",
fieldProps: {
allowClear: true,
},
@ -103,8 +314,8 @@ const searchList: any = computed(() => [
},
{
type: "Select",
name: "strdt",
label: "过鱼设施编码",
name: "fpname",
label: "过鱼设施",
fieldProps: {
allowClear: true,
},
@ -112,27 +323,28 @@ const searchList: any = computed(() => [
},
{
type: "Select",
name: "DIRECTION",
name: "direction",
label: "游向",
width: 120,
options: [
{ label: "上行", value: "0" },
{ label: "下行", value: "1" },
{ label: "上下行", value: "2" },
{ label: "上行", value: "上行" },
{ label: "下行", value: "下行" },
{ label: "上行折返", value: "上行折返" },
{ label: "下行折返", value: "下行折返" },
],
fieldProps: {
allowClear: true,
},
},
{
type: "Input",
name: "typeDate",
label: "鱼名称",
type: "custom",
name: "ftp",
label: "鱼种类",
fieldProps: {
allowClear: true,
},
},
checkPerm(['sjtb:edit-ztcx']) && {
checkPerm(["sjtb:edit-ztcx"]) && {
width: 120,
type: "Select",
name: "status",
@ -149,8 +361,8 @@ const searchList: any = computed(() => [
{
span: 12,
type: "RangePicker",
name: "RangePickerList",
label: "时间",
name: "strdt",
label: "过鱼时间",
picker: "date",
fieldProps: {
format: "YYYY-MM-DD",
@ -210,11 +422,11 @@ const onSearchFinish = (values: any) => {
// 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");
}
// 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);
};
@ -223,33 +435,38 @@ const onValuesChange = (changedValues: any, allValues: any) => {
// searchData便使
searchData.value = { ...searchData.value, ...allValues };
if (changedValues.RangePickerList) {
if (changedValues.strdt) {
//
console.log("Time changed:", changedValues.RangePickerList);
console.log("Time changed:", changedValues.strdt);
}
};
const handleReset = (form: any) => {
//
if (form) {
form.resetFields();
}
//
nextTick(() => {
if (form) {
form.setFieldsValue(initSearchData);
}
//
emit("searchFinish", initSearchData, "两河口出库水温站");
});
};
// const handleReset = (form: any) => {
// //
// if (form) {
// form.resetFields();
// }
// //
// nextTick(() => {
// if (form) {
// form.setFieldsValue(initSearchData);
// }
// //
// emit("searchFinish", initSearchData, "");
// });
// };
watch(
() => initSearchData.typeDate,
(newVal) => {
localTypeDate.value = newVal || [];
},
{ immediate: true }
);
// --- Lifecycle ---
onMounted(() => {
//
emit("searchFinish", initSearchData, "两河口出库水温站");
});
</script>
<style scoped></style>
<style lang="scss"></style>

View File

@ -87,7 +87,7 @@ function getTree() {
});
getData();
})
.catch((error:any)=>{
.catch(()=>{
treeloading.value = false
})
}
@ -135,7 +135,7 @@ function switchChange(row: any) {
id: row.id,
};
deptIsVaild(params).then((res) => {
deptIsVaild(params).then(() => {
getData();
ElMessage({
type: "success",
@ -169,7 +169,7 @@ function editisrepetition(formEl: any) {
formEl.validate((valid: any) => {
if (valid) {
if (causeInfo.value.id) {
updataTreelist(causeInfo.value).then((res) => {
updataTreelist(causeInfo.value).then(() => {
getTree();
ElMessage({
message: "修改成功",
@ -177,7 +177,7 @@ function editisrepetition(formEl: any) {
});
});
} else {
addTreelist(causeInfo.value).then((res) => {
addTreelist(causeInfo.value).then(() => {
getTree();
ElMessage({
message: "新建成功",
@ -194,11 +194,11 @@ function confirmClick(formEl: any) {
formEl.validate((valid: any) => {
if (valid) {
if (info.value.id) {
reviseDepartment(info.value).then((item) => {
reviseDepartment(info.value).then(() => {
getData();
});
} else {
addTreelist(info.value).then((item) => {
addTreelist(info.value).then(() => {
getData();
});
}
@ -231,7 +231,7 @@ function remove(data: any) {
let params = {
id: data.id,
};
delTreelist(params).then((res) => {
delTreelist(params).then(() => {
getTree();
ElMessage({
message: "删除成功",

View File

@ -127,7 +127,7 @@ function getTree() {
if (res.data.length == 0) treeId.value = ''
init();
})
.catch((error)=>{
.catch(()=>{
treeloading.value = false
})
}
@ -229,7 +229,7 @@ function treenodeDrop(before: any, after: any) {
});
}
const allowDrop = (draggingNode: any, dropNode: any, type: any) => {
const allowDrop:any = (draggingNode: any, dropNode: any, type: any) => {
//
if (type === 'inner' || Number(dropNode.data.pid) === 0) return
if (draggingNode.nextSibling === undefined) {
@ -263,7 +263,7 @@ function init() {
querystr.value.size = result.data.size;
querystr.value.current = result.data.current
tableloading.value = false;
}).catch((err: any) => {
}).catch(() => {
tableloading.value = false;
});
}
@ -490,7 +490,7 @@ const total = ref()
<p style="color:#f56c6c;font-size: 12px;width: 70%;margin: auto; margin-top: -15px; padding: 5px 0px;">
如果添加项为子项则需输入父项编码</p>
<el-form-item label="备注">
<el-input v-model="dictInfoItem.custom1" rows="5" type="textarea" style="width:100%"
<el-input v-model="dictInfoItem.custom1" :rows="5" type="textarea" style="width:100%"
placeholder="请输入备注"></el-input>
</el-form-item>
</el-form>

View File

@ -58,7 +58,7 @@ function init() {
pcode.value = result.data.size
queryParams.value.current = result.data.current
loading.value = false;
}).catch((err: any) => {
}).catch(() => {
loading.value = false;
});
}

View File

@ -24,7 +24,7 @@ function gettableData() {
listRolePages(params).then((result) => {
tableData.value = result;
loading.value = false;
}).catch((err) => {
}).catch(() => {
loading.value = false;
});
}
@ -106,7 +106,7 @@ function confirmClick(formEl: any) {
level: info.value.level,
description: info.value.description,
};
addDept(params).then((res) => {
addDept(params).then(() => {
gettableData();
dialogVisible.value = false;
});
@ -117,7 +117,7 @@ function confirmClick(formEl: any) {
description: info.value.description,
id: info.value.id,
};
renewDept(params).then((res) => {
renewDept(params).then(() => {
gettableData();
dialogVisible.value = false;
});
@ -150,7 +150,7 @@ function editrole(row: any) {
}
//
const businessVisible = ref(false);
function businessclick(row: any) {
function businessclick() {
// businessVisible.value = true;
ElMessageBox.confirm("此模块允许用户进行定制。", "提示信息", {
confirmButtonText: "确定",
@ -209,7 +209,7 @@ function organizesubmit() {
id: roleIda.value,
orgscope: allid.value.toString()
}
postOrgscope(params).then((res) => {
postOrgscope(params).then(() => {
ElMessage({
type: "success",
message: "组织范围修改成功",
@ -279,7 +279,7 @@ function assignment(row: any) {
//
function currentChecked(nodeObj: any, SelectedObj: any) {
function currentChecked(_nodeObj: any, SelectedObj: any) {
Passparameter.value = SelectedObj.checkedKeys.concat(SelectedObj.halfCheckedKeys)
}
// --
@ -288,7 +288,7 @@ function accesssubmit() {
id: rowid.value,
menuIds: Passparameter.value.toString()
}
setMenuById(parans).then((res) => {
setMenuById(parans).then(() => {
accessVisible.value = false
gettableData()
ElMessage({
@ -395,7 +395,7 @@ onMounted(() => {
style="display: flex;display: -webkit-flex; justify-content: space-around;-webkit-justify-content: space-around; ">
<img v-hasPerm="['update:role']" src="@/assets/MenuIcon/lbcz_xg.png" alt="" title="修改"
@click="editrole(scope.row)" style="cursor: pointer; ">
<img src="@/assets/MenuIcon/lbcz_zyw.png" alt="" title="业务范围" @click="businessclick(scope.row)"
<img src="@/assets/MenuIcon/lbcz_zyw.png" alt="" title="业务范围" @click="businessclick"
style="cursor: pointer;">
<img src="@/assets/MenuIcon/u343.png" alt="" title="组织范围" @click="organizeclick(scope.row)"
style="cursor: pointer; ">

View File

@ -58,7 +58,7 @@ function getTree() {
});
getdata()
})
.catch((error: any) => {
.catch(() => {
treeloading.value = false
})
@ -146,7 +146,7 @@ function switchChange(row: any) {
status: row.status,
id: row.id,
};
DataStatus(params).then((res) => {
DataStatus(params).then(() => {
getdata();
ElMessage({
type: "success",
@ -200,7 +200,7 @@ function confirmClick(formEl: any) {
orgid: orgId.value,
};
const roleids = String(info.value.roleinfo)
updataUser(params, roleids).then((res) => {
updataUser(params, roleids).then(() => {
getdata();
dialogVisible.value = false;
@ -533,7 +533,7 @@ function handleFishCheckChange(checkedNode: any, checkedInfo: any) {
}
// 使resultIds,
// saveFishPermissions(resultIds);
getFishTableData(resultIds)
getFishTableData()
tableids.value = resultIds
}
//
@ -560,7 +560,7 @@ const fishTableData = ref([
const fishDialog = ref(false)
const fishTableSelection = ref([])
//
function getFishTableData(ids: any[]) {
function getFishTableData() {
fishDialog.value = true
fishDialog.value = false;
//

View File

@ -70,7 +70,7 @@ const moderules = ref(
],
},
)
function confirmPassword(rule: any, value: any, callback: any) {
function confirmPassword(_rule: any, value: any, callback: any) {
if(value == null) {
callback('请输入新密码');
} else {
@ -155,7 +155,7 @@ function savePass(formEl: any){
id: info.value.id,
password: encrypt(pass.value.newPassword)
}
updatePassword(params).then((res) => {
updatePassword(params).then(() => {
userStore.logout().then(() => {
tagsViewStore.delAllViews();
}).then(() => {

View File

@ -5,8 +5,8 @@
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"moduleResolution": "bundler",
"strict": false, //
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
@ -17,14 +17,15 @@
"paths": {
"@/*": ["src/*"]
},
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
"allowSyntheticDefaultImports": true /* */,
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
"types": ["element-plus/global"],
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"types": ["vite/client"],
"ignoreDeprecations": "6.0",
"typeRoots": [
"./node_modules/@types/",
"./types"
] /* './node_modules/@types'. */
]
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"exclude": ["node_modules", "dist", "**/*.js"]

View File

@ -27,7 +27,9 @@ export default ({ mode }: ConfigEnv): UserConfig => {
// 线上API地址
// target: 'http://10.84.121.4:8093/',
// 本地API地址
target: 'http://localhost:8093',
// target: 'http://localhost:8093',
// 汤伟
target: 'http://10.84.121.21:8093',
changeOrigin: true,
rewrite: path =>
path.replace(new RegExp('^' + env.VITE_APP_BASE_API), '')
@ -45,6 +47,26 @@ export default ({ mode }: ConfigEnv): UserConfig => {
}
}
},
build: {
// 1. 增加 chunk 大小警告限制(避免过多小文件)
chunkSizeWarningLimit: 1000,
// 2. 优化 Rollup 配置
rollupOptions: {
output: {
// 手动分块,将大型库单独打包,减少单个 chunk 的复杂度
manualChunks(id) {
if (id.includes('node_modules')) {
// 将 element-plus, lodash 等大型库单独拆分
if (id.includes('element-plus')) return 'element-plus';
if (id.includes('lodash')) return 'lodash';
// 其他 node_modules 归为 vendor
return 'vendor';
}
}
}
}
},
resolve: {
// Vite路径别名配置
alias: {