1.优化地区csv加载机制 2.修复数据库监控无法访问问题 3.增加了登录状态保存 4.针对session存储机制做适配 5.文件扫描机制优化,并行扫描速度更快
This commit is contained in:
+26
-3
@@ -22,6 +22,8 @@ import java.nio.channels.FileChannel;
|
|||||||
import java.nio.file.*;
|
import java.nio.file.*;
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
import static java.nio.file.StandardWatchEventKinds.*;
|
import static java.nio.file.StandardWatchEventKinds.*;
|
||||||
|
|
||||||
@@ -50,6 +52,9 @@ public class FileWatchServiceManager {
|
|||||||
private String rootReceiveDir;
|
private String rootReceiveDir;
|
||||||
private volatile boolean running = false;
|
private volatile boolean running = false;
|
||||||
|
|
||||||
|
/** 文件处理线程池,用于并发 MD5 计算 + 入库 */
|
||||||
|
private final ExecutorService fileProcessExecutor = Executors.newFixedThreadPool(4);
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
try {
|
try {
|
||||||
@@ -72,6 +77,7 @@ public class FileWatchServiceManager {
|
|||||||
log.warn("WatchService 关闭异常", e);
|
log.warn("WatchService 关闭异常", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fileProcessExecutor.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 确保文件扫描目录结构存在(receive/display/archive 及各部门子目录) */
|
/** 确保文件扫描目录结构存在(receive/display/archive 及各部门子目录) */
|
||||||
@@ -188,7 +194,8 @@ public class FileWatchServiceManager {
|
|||||||
/** 扫描部门目录下的所有文件 */
|
/** 扫描部门目录下的所有文件 */
|
||||||
private void scanDirectory(Path deptDir, Long deptId) {
|
private void scanDirectory(Path deptDir, Long deptId) {
|
||||||
try (var fileStream = Files.list(deptDir)) {
|
try (var fileStream = Files.list(deptDir)) {
|
||||||
fileStream.filter(Files::isRegularFile).forEach(file -> processFile(file, deptId));
|
fileStream.filter(Files::isRegularFile).forEach(file ->
|
||||||
|
fileProcessExecutor.submit(() -> processFile(file, deptId)));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("扫描目录失败: {}", deptDir, e);
|
log.error("扫描目录失败: {}", deptDir, e);
|
||||||
}
|
}
|
||||||
@@ -212,7 +219,7 @@ public class FileWatchServiceManager {
|
|||||||
|
|
||||||
Long deptId = deptDirMap.get(watchDir.toString());
|
Long deptId = deptDirMap.get(watchDir.toString());
|
||||||
// deptDirMap 中没有的是根目录,deptId 为 null
|
// deptDirMap 中没有的是根目录,deptId 为 null
|
||||||
processFile(fullPath, deptId);
|
fileProcessExecutor.submit(() -> processFile(fullPath, deptId));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!key.reset()) {
|
if (!key.reset()) {
|
||||||
@@ -227,8 +234,24 @@ public class FileWatchServiceManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 等待文件写入完成并可读 */
|
/**
|
||||||
|
* 等待文件写入完成并可读。
|
||||||
|
* 先快速路径尝试获取共享锁(已有文件通常立即可用),
|
||||||
|
* 若被占用再进入大小稳定 + 等待循环。
|
||||||
|
*/
|
||||||
private boolean waitForFileReady(Path filePath) {
|
private boolean waitForFileReady(Path filePath) {
|
||||||
|
if (!Files.exists(filePath)) return false;
|
||||||
|
|
||||||
|
// 快速路径:已写完的文件可立即获得共享锁,直接返回
|
||||||
|
try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "rw");
|
||||||
|
FileChannel ch = raf.getChannel()) {
|
||||||
|
java.nio.channels.FileLock lock = ch.lock(0, Long.MAX_VALUE, true);
|
||||||
|
lock.release();
|
||||||
|
return true;
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
// 文件正在被写入,进入等待逻辑
|
||||||
|
}
|
||||||
|
|
||||||
long lastSize = -1;
|
long lastSize = -1;
|
||||||
int stableCount = 0;
|
int stableCount = 0;
|
||||||
int maxRetries = 20;
|
int maxRetries = 20;
|
||||||
|
|||||||
+1
-1
@@ -39,7 +39,7 @@ import java.util.Map;
|
|||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("station/weatherstation")
|
@RequestMapping("station/weatherstation")
|
||||||
@Tag(name = "站点")
|
@Tag(name = "气象站点")
|
||||||
public class WeatherStationController {
|
public class WeatherStationController {
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
|
|||||||
@@ -33,19 +33,11 @@ spring:
|
|||||||
test-on-borrow: false
|
test-on-borrow: false
|
||||||
test-on-return: false
|
test-on-return: false
|
||||||
stat-view-servlet:
|
stat-view-servlet:
|
||||||
|
allow: localhost
|
||||||
enabled: true
|
enabled: true
|
||||||
url-pattern: /druid/*
|
url-pattern: /druid/*
|
||||||
# login-username: admin
|
# login-username: admin
|
||||||
# login-password: admin
|
# login-password: admin
|
||||||
#达梦数据库,需要注释掉,其他数据库可以打开
|
|
||||||
# filter:
|
|
||||||
# stat:
|
|
||||||
# log-slow-sql: true
|
|
||||||
# slow-sql-millis: 1000
|
|
||||||
# merge-sql: false
|
|
||||||
# wall:
|
|
||||||
# config:
|
|
||||||
# multi-statement-allow: true
|
|
||||||
|
|
||||||
# 是否开启redis缓存 true开启 false关闭
|
# 是否开启redis缓存 true开启 false关闭
|
||||||
project-options:
|
project-options:
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ spring:
|
|||||||
test-on-borrow: false
|
test-on-borrow: false
|
||||||
test-on-return: false
|
test-on-return: false
|
||||||
stat-view-servlet:
|
stat-view-servlet:
|
||||||
|
allow: localhost
|
||||||
enabled: true
|
enabled: true
|
||||||
url-pattern: /druid/*
|
url-pattern: /druid/*
|
||||||
#login-username: admin
|
#login-username: admin
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ spring:
|
|||||||
test-on-borrow: false
|
test-on-borrow: false
|
||||||
test-on-return: false
|
test-on-return: false
|
||||||
stat-view-servlet:
|
stat-view-servlet:
|
||||||
|
allow: 0.0.0.0
|
||||||
enabled: true
|
enabled: true
|
||||||
url-pattern: /druid/*
|
url-pattern: /druid/*
|
||||||
# login-username: admin
|
# login-username: admin
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
VITE_APP_API=http://192.168.2.151:48080/system-admin
|
VITE_APP_API=/system-admin
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* 地区数据模块级单例缓存。
|
||||||
|
* 首次调用时 fetch /region.csv 并构建 idMap 和 parentMap,
|
||||||
|
* 后续调用直接返回缓存,SPA 生命周期内只加载一次。
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface RegionItem {
|
||||||
|
id: number;
|
||||||
|
parentId: number;
|
||||||
|
level: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RegionCache {
|
||||||
|
/** id → RegionItem,用于表格列的 id→name 转换 */
|
||||||
|
idMap: Map<number, RegionItem>;
|
||||||
|
/** parentId → children[],用于省/市/区下拉联动 O(1) 查找 */
|
||||||
|
parentMap: Map<number, RegionItem[]>;
|
||||||
|
/** 所有地区平铺数组 */
|
||||||
|
list: RegionItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
let _cache: RegionCache | null = null;
|
||||||
|
|
||||||
|
function parseCSV(text: string): RegionItem[] {
|
||||||
|
const lines = text.split("\n").filter((l) => l.trim());
|
||||||
|
const headers = lines[0].split(",").map((h) => h.trim());
|
||||||
|
return lines.slice(1).map((line) => {
|
||||||
|
const cols = line.split(",").map((c) => c.trim());
|
||||||
|
const obj: any = {};
|
||||||
|
headers.forEach((h, i) => {
|
||||||
|
obj[h] = h === "name" ? cols[i] : Number(cols[i]);
|
||||||
|
});
|
||||||
|
return obj as RegionItem;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function useRegionCache(): Promise<RegionCache> {
|
||||||
|
if (_cache) return _cache;
|
||||||
|
|
||||||
|
const res = await fetch("/region.csv");
|
||||||
|
if (!res.ok) throw new Error("地区数据加载失败");
|
||||||
|
const text = await res.text();
|
||||||
|
const list = parseCSV(text);
|
||||||
|
|
||||||
|
const idMap = new Map<number, RegionItem>();
|
||||||
|
const parentMap = new Map<number, RegionItem[]>();
|
||||||
|
|
||||||
|
for (const item of list) {
|
||||||
|
idMap.set(item.id, item);
|
||||||
|
if (!parentMap.has(item.parentId)) {
|
||||||
|
parentMap.set(item.parentId, []);
|
||||||
|
}
|
||||||
|
parentMap.get(item.parentId)!.push(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
_cache = { idMap, parentMap, list };
|
||||||
|
return _cache;
|
||||||
|
}
|
||||||
@@ -91,6 +91,14 @@ export function useWeatherChart(
|
|||||||
async function renderChart() {
|
async function renderChart() {
|
||||||
if (!chartRef.value) return;
|
if (!chartRef.value) return;
|
||||||
|
|
||||||
|
// 确保 DOM 已渲染且有尺寸,避免 ECharts "invalid dom" 报错
|
||||||
|
const { width, height } = chartRef.value.getBoundingClientRect();
|
||||||
|
if (width === 0 || height === 0) {
|
||||||
|
// DOM 未就绪,延迟重试
|
||||||
|
scheduleRender();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 动态按需加载 ECharts
|
// 动态按需加载 ECharts
|
||||||
const echarts = await import("echarts");
|
const echarts = await import("echarts");
|
||||||
const mod = (echarts as any).default || echarts;
|
const mod = (echarts as any).default || echarts;
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export const useAppStore = defineStore("useAppStore", {
|
|||||||
},
|
},
|
||||||
//退出
|
//退出
|
||||||
logout() {
|
logout() {
|
||||||
removeCache(CacheToken, true);
|
removeCache(CacheToken);
|
||||||
this.updateState({
|
this.updateState({
|
||||||
appIsLogin: false,
|
appIsLogin: false,
|
||||||
permissions: [],
|
permissions: [],
|
||||||
|
|||||||
@@ -67,5 +67,5 @@ export const removeCache = (key: string, isSessionStorage?: boolean): void => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getToken = (): string => {
|
export const getToken = (): string => {
|
||||||
return getCache(CacheToken, { isSessionStorage: true }, {})["token"];
|
return getCache(CacheToken, {}, {})["token"];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ const onLogin = () => {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
state.loading = false;
|
state.loading = false;
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
setCache(CacheToken, res.data, true);
|
setCache(CacheToken, res.data);
|
||||||
ElMessage.success("登录成功");
|
ElMessage.success("登录成功");
|
||||||
router.push("/");
|
router.push("/");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, onMounted, computed } from 'vue';
|
import { ref, onMounted, computed } from 'vue';
|
||||||
|
import { useRegionCache } from '@/composables/useRegionCache';
|
||||||
|
|
||||||
const rawList = ref<any[]>([]);
|
const rawList = ref<any[]>([]);
|
||||||
const treeData = ref<any[]>([]);
|
const treeData = ref<any[]>([]);
|
||||||
@@ -35,61 +36,32 @@ const loadingStatus = ref('正在解析数据...');
|
|||||||
|
|
||||||
const rawListCount = computed(() => rawList.value.length);
|
const rawListCount = computed(() => rawList.value.length);
|
||||||
|
|
||||||
// 优化后的构建树函数
|
// 从 parentMap 递归构建树
|
||||||
const buildTreeMap = (list: any[]) => {
|
const buildTreeFromCache = (parentMap: Map<number, any[]>, pId: number): any[] => {
|
||||||
const map = new Map<number, any[]>();
|
const children = parentMap.get(pId) || [];
|
||||||
|
return children.map((child) => {
|
||||||
// 构建 parentId -> children 的 Map
|
const node = { ...child };
|
||||||
list.forEach(item => {
|
const sub = buildTreeFromCache(parentMap, child.id);
|
||||||
const pid = item.parentid;
|
if (sub.length > 0) node.children = sub;
|
||||||
if (!map.has(pid)) map.set(pid, []);
|
return node;
|
||||||
map.get(pid)!.push({ ...item });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 递归构建树
|
|
||||||
const createTree = (pId: number): any[] => {
|
|
||||||
const children = map.get(pId) || [];
|
|
||||||
children.forEach(child => {
|
|
||||||
const sub = createTree(child.id);
|
|
||||||
if (sub.length > 0) child.children = sub;
|
|
||||||
});
|
|
||||||
return children;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 自动探测根节点
|
|
||||||
const rootParentId = Math.min(...list.map(r => r.parentid));
|
|
||||||
return createTree(rootParentId);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const initRegionTree = async () => {
|
const initRegionTree = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/region.csv");
|
const cache = await useRegionCache();
|
||||||
if (!res.ok) throw new Error("无法读取数据,请检查文件");
|
rawList.value = cache.list;
|
||||||
|
|
||||||
const text = await res.text();
|
// 自动探测根节点(最小 parentId)
|
||||||
const lines = text.split("\n").map(l => l.trim()).filter(l => l);
|
const rootParentId = Math.min(...cache.list.map((r) => r.parentId));
|
||||||
if (lines.length < 2) throw new Error("内容为空或格式错误");
|
const tree = buildTreeFromCache(cache.parentMap, rootParentId);
|
||||||
|
|
||||||
const headers = lines[0].split(",").map(h => h.trim().toLowerCase());
|
|
||||||
|
|
||||||
rawList.value = lines.slice(1).map(line => {
|
|
||||||
const cols = line.split(",").map(c => c.trim());
|
|
||||||
const obj: any = {};
|
|
||||||
headers.forEach((h, i) => {
|
|
||||||
obj[h] = h === 'name' ? cols[i] : Number(cols[i]);
|
|
||||||
});
|
|
||||||
return obj;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 构建树
|
|
||||||
const tree = buildTreeMap(rawList.value);
|
|
||||||
if (tree.length === 0) {
|
if (tree.length === 0) {
|
||||||
loadingStatus.value = '构建失败:根节点下无数据';
|
loadingStatus.value = '构建失败:根节点下无数据';
|
||||||
} else {
|
} else {
|
||||||
treeData.value = tree;
|
treeData.value = tree;
|
||||||
loadingStatus.value = '加载完成';
|
loadingStatus.value = '加载完成';
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
loadingStatus.value = `错误: ${error.message}`;
|
loadingStatus.value = `错误: ${error.message}`;
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|||||||
@@ -21,12 +21,7 @@
|
|||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="测站级别" prop="stationLevel">
|
<el-form-item label="测站级别" prop="stationLevel">
|
||||||
<el-select v-model="dataForm.stationLevel" placeholder="选择级别">
|
<el-select v-model="dataForm.stationLevel" placeholder="选择级别">
|
||||||
<el-option label="国家基准气候站(11)" :value="11"/>
|
<el-option v-for="item in stationLevelOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
<el-option label="国家基本气象站(12)" :value="12"/>
|
|
||||||
<el-option label="国家一般气象站(13)" :value="13"/>
|
|
||||||
<el-option label="区域气象站(14)" :value="14"/>
|
|
||||||
<el-option label="其他气象站(15)" :value="15"/>
|
|
||||||
<el-option label="国家站(16)" :value="16"/>
|
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</el-col>
|
||||||
@@ -135,11 +130,15 @@
|
|||||||
import { reactive, ref, computed, nextTick } from "vue";
|
import { reactive, ref, computed, nextTick } from "vue";
|
||||||
import baseService from "@/service/baseService";
|
import baseService from "@/service/baseService";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
|
import { useAppStore } from "@/store";
|
||||||
|
import { getDictDataList } from "@/utils/utils";
|
||||||
|
|
||||||
// 接收父页面传来的 CSV 原始数组和 Map 索引
|
const store = useAppStore();
|
||||||
|
|
||||||
|
// 接收父页面传来的地区数据
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
regionsData: any[]
|
parentMap: Map<number, any[]>
|
||||||
regionMap: Map<number, any>
|
deptData: any[]
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits(["refreshDataList"]);
|
const emit = defineEmits(["refreshDataList"]);
|
||||||
@@ -170,14 +169,19 @@ const rules = {
|
|||||||
stationLevel: [{ required: true, message: "请选择测站级别", trigger: "change" }]
|
stationLevel: [{ required: true, message: "请选择测站级别", trigger: "change" }]
|
||||||
};
|
};
|
||||||
|
|
||||||
// ----------------- 下拉框联动逻辑 -----------------
|
// ----------------- 字典数据 ----------------
|
||||||
|
|
||||||
// 1. 获取省级列表 (根据你说的:parentId 为 1 是省级)
|
const stationLevelOptions = computed(() =>
|
||||||
const provinceOptions = computed(() => {
|
getDictDataList(store.state.dicts, "station_level").map((item: any) => ({
|
||||||
const list = props.regionsData.filter(r => Number(r.parentId) === 1);
|
label: `${item.dictLabel}(${item.dictValue})`,
|
||||||
console.log("省级加载检查,匹配数量:", list.length);
|
value: Number(item.dictValue)
|
||||||
return list;
|
}))
|
||||||
});
|
);
|
||||||
|
|
||||||
|
// ----------------- 下拉框联动逻辑(O(1) 查找) -----------------
|
||||||
|
|
||||||
|
// 省级:parentId = 1
|
||||||
|
const provinceOptions = computed(() => props.parentMap.get(1) || []);
|
||||||
|
|
||||||
// 市、区列表:动态变化
|
// 市、区列表:动态变化
|
||||||
const cityOptions = ref<any[]>([]);
|
const cityOptions = ref<any[]>([]);
|
||||||
@@ -189,9 +193,8 @@ const onProvinceChange = (val: string) => {
|
|||||||
dataForm.countyName = "";
|
dataForm.countyName = "";
|
||||||
cityOptions.value = [];
|
cityOptions.value = [];
|
||||||
countyOptions.value = [];
|
countyOptions.value = [];
|
||||||
|
|
||||||
if (val) {
|
if (val) {
|
||||||
cityOptions.value = props.regionsData.filter(r => Number(r.parentId) === Number(val));
|
cityOptions.value = props.parentMap.get(Number(val)) || [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -199,18 +202,11 @@ const onProvinceChange = (val: string) => {
|
|||||||
const onCityChange = (val: string) => {
|
const onCityChange = (val: string) => {
|
||||||
dataForm.countyName = "";
|
dataForm.countyName = "";
|
||||||
countyOptions.value = [];
|
countyOptions.value = [];
|
||||||
|
|
||||||
if (val) {
|
if (val) {
|
||||||
countyOptions.value = props.regionsData.filter(r => Number(r.parentId) === Number(val));
|
countyOptions.value = props.parentMap.get(Number(val)) || [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function getDeptList() {
|
|
||||||
return baseService.get("/sys/dept/list").then((res) => {
|
|
||||||
deptList.value = res.data || [];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDeptClick(data: any) {
|
function onDeptClick(data: any) {
|
||||||
dataForm.deptId = data.id;
|
dataForm.deptId = data.id;
|
||||||
}
|
}
|
||||||
@@ -220,9 +216,11 @@ function onDeptClick(data: any) {
|
|||||||
const init = (id?: number) => {
|
const init = (id?: number) => {
|
||||||
visible.value = true;
|
visible.value = true;
|
||||||
|
|
||||||
// 重置表单和选项
|
// 重置选项
|
||||||
cityOptions.value = [];
|
cityOptions.value = [];
|
||||||
countyOptions.value = [];
|
countyOptions.value = [];
|
||||||
|
// 复用父页面的部门数据,无需重复请求
|
||||||
|
deptList.value = props.deptData || [];
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (dataFormRef.value) dataFormRef.value.resetFields();
|
if (dataFormRef.value) dataFormRef.value.resetFields();
|
||||||
@@ -234,13 +232,10 @@ const init = (id?: number) => {
|
|||||||
townName: "",
|
townName: "",
|
||||||
deptId: ""
|
deptId: ""
|
||||||
});
|
});
|
||||||
|
|
||||||
getDeptList().then(() => {
|
|
||||||
if (id) {
|
if (id) {
|
||||||
getInfo(id);
|
getInfo(id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 编辑回显
|
// 编辑回显
|
||||||
@@ -251,12 +246,12 @@ const getInfo = async (id: number) => {
|
|||||||
// 1. 基础数据赋值
|
// 1. 基础数据赋值
|
||||||
Object.assign(dataForm, data);
|
Object.assign(dataForm, data);
|
||||||
|
|
||||||
// 2. 关键:手动触发联动列表加载,否则 Select 只有 ID 没有数据源,无法显示名字
|
// 2. 手动加载省市区联动列表数据源
|
||||||
if (data.provinceName) {
|
if (data.provinceName) {
|
||||||
cityOptions.value = props.regionsData.filter(r => Number(r.parentId) === Number(data.provinceName));
|
cityOptions.value = props.parentMap.get(Number(data.provinceName)) || [];
|
||||||
}
|
}
|
||||||
if (data.cityName) {
|
if (data.cityName) {
|
||||||
countyOptions.value = props.regionsData.filter(r => Number(r.parentId) === Number(data.cityName));
|
countyOptions.value = props.parentMap.get(Number(data.cityName)) || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 回显选择的管理部门
|
// 3. 回显选择的管理部门
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
<el-pagination :current-page="state.page" :page-sizes="[10, 20, 50, 100]" :page-size="state.limit" :total="state.total" layout="total, sizes, prev, pager, next, jumper" @size-change="state.pageSizeChangeHandle" @current-change="state.pageCurrentChangeHandle"> </el-pagination>
|
<el-pagination :current-page="state.page" :page-sizes="[10, 20, 50, 100]" :page-size="state.limit" :total="state.total" layout="total, sizes, prev, pager, next, jumper" @size-change="state.pageSizeChangeHandle" @current-change="state.pageCurrentChangeHandle"> </el-pagination>
|
||||||
|
|
||||||
<!-- 弹窗, 新增 / 修改 -->
|
<!-- 弹窗, 新增 / 修改 -->
|
||||||
<add-or-update ref="addOrUpdateRef" :regions-data="regionsData" :region-map="regionMap" @refreshDataList="state.getDataList"> </add-or-update>
|
<add-or-update ref="addOrUpdateRef" :parent-map="parentMap" :dept-data="deptData" @refreshDataList="state.getDataList"> </add-or-update>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -68,6 +68,7 @@ import useView from "@/hooks/useView";
|
|||||||
import { reactive, ref, toRefs, onMounted } from "vue";
|
import { reactive, ref, toRefs, onMounted } from "vue";
|
||||||
import AddOrUpdate from "./weatherstation-add-or-update.vue";
|
import AddOrUpdate from "./weatherstation-add-or-update.vue";
|
||||||
import baseService from "@/service/baseService";
|
import baseService from "@/service/baseService";
|
||||||
|
import { useRegionCache } from "@/composables/useRegionCache";
|
||||||
|
|
||||||
// 表格 state
|
// 表格 state
|
||||||
const view = reactive({
|
const view = reactive({
|
||||||
@@ -97,22 +98,25 @@ const addOrUpdateHandle = (id?: number) => {
|
|||||||
//
|
//
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// ---------------- CSV 数据加载 ----------------
|
// ---------------- 地区数据(模块级缓存) ----------------
|
||||||
const regionsData = ref<any[]>([]);
|
const parentMap = reactive(new Map<number, any[]>());
|
||||||
const regionMap = reactive(new Map<number, any>());
|
const regionMap = reactive(new Map<number, any>());
|
||||||
|
|
||||||
// ---------------- 部门数据加载 ----------------
|
// ---------------- 部门数据 ----------------
|
||||||
const deptMap = reactive(new Map<number, string>());
|
const deptMap = reactive(new Map<number, string>());
|
||||||
|
const deptData = ref<any[]>([]);
|
||||||
|
|
||||||
async function loadDepts() {
|
async function loadDepts() {
|
||||||
try {
|
try {
|
||||||
const res = await baseService.get("/sys/dept/list");
|
const res = await baseService.get("/sys/dept/list");
|
||||||
|
deptData.value = res.data || [];
|
||||||
const flat = (list: any[]) => {
|
const flat = (list: any[]) => {
|
||||||
list.forEach((d) => {
|
list.forEach((d: any) => {
|
||||||
deptMap.set(Number(d.id), d.name);
|
deptMap.set(Number(d.id), d.name);
|
||||||
if (d.children) flat(d.children);
|
if (d.children) flat(d.children);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
flat(res.data || []);
|
flat(deptData.value);
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
@@ -120,26 +124,8 @@ async function loadDepts() {
|
|||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
loadDepts();
|
loadDepts();
|
||||||
const res = await fetch("/region.csv");
|
const cache = await useRegionCache();
|
||||||
const text = await res.text();
|
cache.idMap.forEach((item, id) => regionMap.set(id, item));
|
||||||
const parseCSV = (csvText: string) => {
|
cache.parentMap.forEach((children, pid) => parentMap.set(pid, children));
|
||||||
const lines = csvText.split("\n").filter((l) => l.trim());
|
|
||||||
const headers = lines[0].split(",");
|
|
||||||
return lines.slice(1).map((line) => {
|
|
||||||
const cols = line.split(",");
|
|
||||||
const obj: any = {};
|
|
||||||
headers.forEach((h, i) => (obj[h.trim()] = cols[i].trim()));
|
|
||||||
return obj;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const data = parseCSV(text);
|
|
||||||
data.forEach((r) => {
|
|
||||||
const id = Number(r.id);
|
|
||||||
const parentId = Number(r.parentId);
|
|
||||||
const level = Number(r.level);
|
|
||||||
const name = r.name;
|
|
||||||
regionMap.set(id, { id, parentId, level, name });
|
|
||||||
regionsData.value.push({ id, parentId, level, name });
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -74,9 +74,8 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import app from "@/constants/app";
|
import app from "@/constants/app";
|
||||||
import { CacheToken } from "@/constants/cacheKey";
|
import { getToken } from "@/utils/cache";
|
||||||
import baseService from "@/service/baseService";
|
import baseService from "@/service/baseService";
|
||||||
import { getCache } from "@/utils/cache";
|
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { computed, onMounted, onUnmounted, reactive, ref } from "vue";
|
import { computed, onMounted, onUnmounted, reactive, ref } from "vue";
|
||||||
@@ -134,7 +133,7 @@ const loadBlobUrl = (fileId: string): Promise<string> => {
|
|||||||
|
|
||||||
const base = (app.api || "").replace(/\/$/, "");
|
const base = (app.api || "").replace(/\/$/, "");
|
||||||
const url = `${base}/filescan/file/display/${fileId}`;
|
const url = `${base}/filescan/file/display/${fileId}`;
|
||||||
const token = getCache(CacheToken, { isSessionStorage: true }, {})?.token;
|
const token = getToken();
|
||||||
const headers: Record<string, string> = {};
|
const headers: Record<string, string> = {};
|
||||||
if (token) headers.token = token;
|
if (token) headers.token = token;
|
||||||
|
|
||||||
|
|||||||
@@ -75,9 +75,9 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import app from "@/constants/app";
|
import app from "@/constants/app";
|
||||||
import { CacheToken } from "@/constants/cacheKey";
|
|
||||||
import baseService from "@/service/baseService";
|
import baseService from "@/service/baseService";
|
||||||
import { getCache } from "@/utils/cache";
|
import { getToken } from "@/utils/cache";
|
||||||
import { copyToClipboard } from "@/utils/utils";
|
import { copyToClipboard } from "@/utils/utils";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
@@ -137,7 +137,7 @@ const loadBlobUrl = (fileId: string): Promise<string> => {
|
|||||||
|
|
||||||
const base = (app.api || "").replace(/\/$/, "");
|
const base = (app.api || "").replace(/\/$/, "");
|
||||||
const url = `${base}/filescan/file/display/${fileId}`;
|
const url = `${base}/filescan/file/display/${fileId}`;
|
||||||
const token = getCache(CacheToken, { isSessionStorage: true }, {})?.token;
|
const token = getToken();
|
||||||
const headers: Record<string, string> = {};
|
const headers: Record<string, string> = {};
|
||||||
if (token) headers.token = token;
|
if (token) headers.token = token;
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,13 @@ export default (config: UserConfig): UserConfigExport => {
|
|||||||
open: false, // 自动启动浏览器
|
open: false, // 自动启动浏览器
|
||||||
host: "0.0.0.0", // localhost
|
host: "0.0.0.0", // localhost
|
||||||
port: 8001, // 端口号
|
port: 8001, // 端口号
|
||||||
hmr: { overlay: false }
|
hmr: { overlay: false },
|
||||||
|
proxy: {
|
||||||
|
"/system-admin": {
|
||||||
|
target: "http://192.168.2.151:48080",
|
||||||
|
changeOrigin: true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user