Commit c293da23e24a47595fbbbe88e9c7e29486eb5987

Authored by 刘淇
0 parents

新园林init

.gitignore 0 → 100644
  1 +++ a/.gitignore
  1 +# 依赖文件夹
  2 +node_modules/
  3 +# 编译产物
  4 +unpackage/
  5 +# 缓存文件
  6 +.DS_Store
  7 +*.log
  8 +# IDE配置
  9 +.idea/
  10 +.vscode/
  11 +.hbuilderx/
  12 +# 环境变量(若有)
  13 +.env
  14 +# 微信开发者工具缓存
  15 +miniprogram_npm/
0 \ No newline at end of file 16 \ No newline at end of file
App.vue 0 → 100644
  1 +++ a/App.vue
  1 +<script setup>
  2 +// 错误:import { onLaunch } from 'vue';
  3 +// 正确:从 uni-app 导入应用生命周期钩子
  4 +import { onLaunch } from '@dcloudio/uni-app';
  5 +
  6 +onLaunch(() => {
  7 + console.log('App Launch');
  8 +});
  9 +</script>
  10 +
  11 +<style lang="scss">
  12 +/* 注意要写在第一行,注意不能引入至uni.scss,同时给style标签加入lang="scss"属性 */
  13 +@import "@/uni_modules/uview-plus/index.scss";
  14 +
  15 +/* 你的全局样式 */
  16 +page {
  17 + background-color: #f8f8f8;
  18 + height: 100%;
  19 +}
  20 +</style>
0 \ No newline at end of file 21 \ No newline at end of file
api/common.js 0 → 100644
  1 +++ a/api/common.js
api/index.js 0 → 100644
  1 +++ a/api/index.js
api/upload.js 0 → 100644
  1 +++ a/api/upload.js
api/user.js 0 → 100644
  1 +++ a/api/user.js
  1 +// api/user.js
  2 +import { post, get } from '@/common/utils/request';
  3 +
  4 +/**
  5 + * 登录接口
  6 + * @param {Object} params {mobile, password, code}
  7 + * @returns {Promise}
  8 + */
  9 +export const login = (params) => {
  10 + return post('/admin-api/system/auth/login', params);
  11 +};
  12 +
  13 +/**
  14 + * 获取用户信息
  15 + * @returns {Promise}
  16 + */
  17 +export const getUserInfo = () => {
  18 + return get('/admin-api/system/auth/get-permission-info');
  19 +};
  20 +
  21 +/**
  22 + * 退出登录
  23 + * @returns {Promise}
  24 + */
  25 +export const logout = () => {
  26 + return post('/admin-api/system/auth/logout');
  27 +};
  28 +
  29 +/**
  30 + * 模块列表用这个
  31 + * @returns {Promise}
  32 + */
  33 +export const moduleList = () => {
  34 + return get('/app-api/member/app-module/list');
  35 +};
api/workbench.js 0 → 100644
  1 +++ a/api/workbench.js
  1 +// api/workbench.js - 补全所有导出方法
  2 +import { get } from '@/common/utils/request';
  3 +
  4 +/**
  5 + * 获取日常管理菜单(适配登录用户权限)
  6 + * @returns {Promise}
  7 + */
  8 +export const getDailyManageMenu = () => {
  9 + return get('/workbench/dailyManage');
  10 +};
  11 +
  12 +/**
  13 + * 获取问题管理菜单(适配登录用户权限)
  14 + * @returns {Promise}
  15 + */
  16 +export const getProblemManageMenu = () => {
  17 + return get('/workbench/problemManage');
  18 +};
  19 +
  20 +/**
  21 + * 获取数据管理菜单(固定菜单,也可改为接口获取)
  22 + * @returns {Promise}
  23 + */
  24 +export const getDataManageMenu = () => {
  25 + return get('/workbench/dataManage');
  26 +};
  27 +
  28 +/**
  29 + * 获取行道树档案列表(分页)
  30 + * @param {Object} params {pageNo, pageSize}
  31 + * @returns {Promise}
  32 + */
  33 +export const getTreeArchiveList = (params) => {
  34 + return get('/workbench/treeArchive', params);
  35 +};
0 \ No newline at end of file 36 \ No newline at end of file
common/config/env.js 0 → 100644
  1 +++ a/common/config/env.js
  1 +// 环境配置 - 确保无语法错误
  2 +const env = {
  3 + // 开发环境
  4 + development: {
  5 + baseUrl: 'https://test.jichengshanshui.com.cn:28302',
  6 + uploadUrl: 'https://test.jichengshanshui.com.cn:28302',
  7 + // baseApi:'admin-api',
  8 + // appApi:'app-api'
  9 + },
  10 + // 生产环境
  11 + production: {
  12 + baseUrl: 'https://api.jcss.com',
  13 + uploadUrl: 'https://upload.jcss.com/upload',
  14 + // baseApi:'admin-api',
  15 + // appApi:'app-api'
  16 + }
  17 +};
  18 +
  19 +// 获取当前环境
  20 +const getEnv = () => {
  21 +
  22 + const currentEnv = 'development';
  23 +
  24 + return currentEnv;
  25 +};
  26 +
  27 +// 导出当前环境配置(关键:补全分号,避免语法错误)
  28 +const currentEnvConfig = env[getEnv()];
  29 +export default currentEnvConfig;
0 \ No newline at end of file 30 \ No newline at end of file
common/config/global.js 0 → 100644
  1 +++ a/common/config/global.js
  1 +import env from './env';
  2 +
  3 +export default {
  4 + api: {
  5 + baseUrl: env.baseUrl,
  6 + uploadUrl: env.uploadUrl,
  7 + // baseApi: env.baseApi || 'admin-api', // 默认前缀:admin-api
  8 + // appApi: env.appApi || 'app-api', // 特殊前缀:app-api
  9 + timeout: 10000,
  10 + tokenKey: 'jcss_token'
  11 + },
  12 + router: {
  13 + loginPath: '/pages/login/index',
  14 + tabBarList: [
  15 + { path: '/pages/index/index', name: '首页' },
  16 + { path: '/pages/workbench/index', name: '工作台' },
  17 + { path: '/pages/mine/index', name: '我的' }
  18 + ]
  19 + },
  20 + cache: {
  21 + userInfoKey: 'jcss_user_info',
  22 + tokenKey: 'jcss_token',
  23 + expireTimeKey: 'jcss_token_expire',
  24 + userIdKey:'jcss_user_id',
  25 + moduleListKey:'jcss_module_list',
  26 +
  27 + },
  28 + appName: 'JCSS管理系统',
  29 + tokenExpireTime: 7 * 24 * 60 * 60 * 1000
  30 +};
0 \ No newline at end of file 31 \ No newline at end of file
common/mixins/authMixin.js 0 → 100644
  1 +++ a/common/mixins/authMixin.js
common/utils/cache.js 0 → 100644
  1 +++ a/common/utils/cache.js
  1 +export default {
  2 + set(key, value, expire = 0) {
  3 + const data = {
  4 + value,
  5 + expire: expire ? Date.now() + expire : 0
  6 + };
  7 + uni.setStorageSync(key, JSON.stringify(data));
  8 + },
  9 +
  10 + get(key) {
  11 + try {
  12 + const data = JSON.parse(uni.getStorageSync(key));
  13 + if (data.expire && data.expire < Date.now()) {
  14 + this.remove(key);
  15 + return null;
  16 + }
  17 + return data.value;
  18 + } catch (e) {
  19 + return null;
  20 + }
  21 + },
  22 +
  23 + remove(key) {
  24 + uni.removeStorageSync(key);
  25 + },
  26 +
  27 + clear() {
  28 + uni.clearStorageSync();
  29 + }
  30 +};
0 \ No newline at end of file 31 \ No newline at end of file
common/utils/common.js 0 → 100644
  1 +++ a/common/utils/common.js
common/utils/debounce.js 0 → 100644
  1 +++ a/common/utils/debounce.js
  1 +export function debounce(fn, delay = 500) {
  2 + let timer = null;
  3 + return function (...args) {
  4 + if (timer) clearTimeout(timer);
  5 + timer = setTimeout(() => fn.apply(this, args), delay);
  6 + };
  7 +}
  8 +
  9 +export function throttle(fn, interval = 500) {
  10 + let lastTime = 0;
  11 + return function (...args) {
  12 + const now = Date.now();
  13 + if (now - lastTime >= interval) {
  14 + fn.apply(this, args);
  15 + lastTime = now;
  16 + }
  17 + };
  18 +}
0 \ No newline at end of file 19 \ No newline at end of file
common/utils/request.js 0 → 100644
  1 +++ a/common/utils/request.js
  1 +import globalConfig from '@/common/config/global';
  2 +import cache from '@/common/utils/cache';
  3 +import { useUserStore } from '@/pinia/user';
  4 +
  5 +const request = (options) => {
  6 + const defaultOptions = {
  7 + url: '',
  8 + method: 'GET',
  9 + data: {},
  10 + header: { 'Content-Type': 'application/json' },
  11 + timeout: globalConfig.api.timeout
  12 + };
  13 +
  14 + const opts = { ...defaultOptions, ...options };
  15 + let token = '';
  16 + try {
  17 + const userStore = useUserStore();
  18 + token = userStore.token || cache.get(globalConfig.cache.tokenKey);
  19 + } catch (err) {
  20 + token = cache.get(globalConfig.cache.tokenKey);
  21 + }
  22 +
  23 + if (token) {
  24 + opts.header['Authorization'] = `Bearer ${token}`;
  25 + }
  26 +
  27 + opts.url = globalConfig.api.baseUrl + opts.url;
  28 +
  29 + return new Promise((resolve, reject) => {
  30 + uni.request({
  31 + ...opts,
  32 + success: (res) => {
  33 + if (res.statusCode === 200) {
  34 + const { code, data, msg } = res.data;
  35 + if (code === 0) {
  36 + resolve(data);
  37 + } else if (code === 401) {
  38 + const userStore = useUserStore();
  39 + userStore.logout();
  40 + uni.showToast({ title: msg || '登录过期', icon: 'none' });
  41 + reject(res.data);
  42 + } else {
  43 + uni.showToast({ title: msg || '请求失败', icon: 'none' });
  44 + reject(res.data);
  45 + }
  46 + } else {
  47 + uni.showToast({ title: `错误:${res.statusCode}`, icon: 'none' });
  48 + reject(res);
  49 + }
  50 + },
  51 + fail: (err) => {
  52 + uni.showToast({ title: '网络失败', icon: 'none' });
  53 + reject(err);
  54 + }
  55 + });
  56 + });
  57 +};
  58 +
  59 +export const get = (url, data = {}, options = {}) => request({ url, method: 'GET', data, ...options });
  60 +export const post = (url, data = {}, options = {}) => request({ url, method: 'POST', data, ...options });
  61 +export const put = (url, data = {}, options = {}) => request({ url, method: 'PUT', data, ...options });
  62 +export const del = (url, data = {}, options = {}) => request({ url, method: 'DELETE', data, ...options });
  63 +
  64 +export default request;
0 \ No newline at end of file 65 \ No newline at end of file
common/utils/upload.js 0 → 100644
  1 +++ a/common/utils/upload.js
  1 +import globalConfig from '@/common/config/global';
  2 +import cache from '@/common/utils/cache';
  3 +
  4 +export const uploadImages = (files, options = {}) => {
  5 + const opts = { count: 9, sizeType: ['original', 'compressed'], sourceType: ['album', 'camera'], showLoading: true, ...options };
  6 + const token = cache.get(globalConfig.cache.tokenKey);
  7 +
  8 + if (!token) {
  9 + uni.showToast({ title: '请先登录', icon: 'none' });
  10 + return Promise.reject('未登录');
  11 + }
  12 +
  13 + if (files.length > opts.count) {
  14 + uni.showToast({ title: `最多上传${opts.count}张`, icon: 'none' });
  15 + return Promise.reject('超出数量');
  16 + }
  17 +
  18 + if (opts.showLoading) uni.showLoading({ title: '上传中...' });
  19 +
  20 + const uploadPromises = files.map(filePath => {
  21 + return new Promise((resolve, reject) => {
  22 + uni.uploadFile({
  23 + url: globalConfig.api.uploadUrl,
  24 + filePath,
  25 + name: 'file',
  26 + header: { 'Authorization': `Bearer ${token}` },
  27 + formData: opts.formData || {},
  28 + success: (res) => {
  29 + const data = JSON.parse(res.data);
  30 + if (data.code === 200) resolve(data.data);
  31 + else {
  32 + uni.showToast({ title: data.msg || '上传失败', icon: 'none' });
  33 + reject(data);
  34 + }
  35 + },
  36 + fail: (err) => {
  37 + uni.showToast({ title: '上传失败', icon: 'none' });
  38 + reject(err);
  39 + }
  40 + });
  41 + });
  42 + });
  43 +
  44 + return Promise.all(uploadPromises)
  45 + .then(results => {
  46 + if (opts.showLoading) uni.hideLoading();
  47 + return results;
  48 + })
  49 + .catch(err => {
  50 + if (opts.showLoading) uni.hideLoading();
  51 + return Promise.reject(err);
  52 + });
  53 +};
  54 +
  55 +export const chooseAndUploadImages = (options = {}) => {
  56 + const opts = { count: 9, sizeType: ['original', 'compressed'], sourceType: ['album', 'camera'], ...options };
  57 + return new Promise((resolve, reject) => {
  58 + uni.chooseImage({
  59 + count: opts.count,
  60 + sizeType: opts.sizeType,
  61 + sourceType: opts.sourceType,
  62 + success: (res) => {
  63 + uploadImages(res.tempFilePaths, opts).then(resolve).catch(reject);
  64 + },
  65 + fail: reject
  66 + });
  67 + });
  68 +};
0 \ No newline at end of file 69 \ No newline at end of file
common/utils/validate.js 0 → 100644
  1 +++ a/common/utils/validate.js
components/upload-image/upload-image.vue 0 → 100644
  1 +++ a/components/upload-image/upload-image.vue
  1 +<template>
  2 + <view class="upload-image">
  3 + <view class="image-list">
  4 + <view class="image-item" v-for="(item, index) in imageList" :key="index">
  5 + <image class="image" :src="item.url || item" mode="aspectFill"></image>
  6 + <view class="delete-btn" @click="deleteImage(index)">
  7 + <u-icon name="close" color="#fff"></u-icon>
  8 + </view>
  9 + </view>
  10 + <view class="upload-btn" v-if="imageList.length < maxCount" @click="chooseImage">
  11 + <u-icon name="plus" size="40" color="#999"></u-icon>
  12 + <view class="upload-text">上传图片</view>
  13 + </view>
  14 + </view>
  15 + <view class="tips" v-if="tips">{{ tips }}</view>
  16 + </view>
  17 +</template>
  18 +
  19 +<script setup>
  20 +import { ref, defineProps, defineEmits, watch } from 'vue';
  21 +import { chooseAndUploadImages } from '@/common/utils/upload';
  22 +
  23 +const props = defineProps({
  24 + maxCount: { type: Number, default: 9 },
  25 + modelValue: { type: Array, default: () => [] },
  26 + tips: { type: String, default: '最多上传9张图片' },
  27 + showTips: { type: Boolean, default: true }
  28 +});
  29 +
  30 +const emit = defineEmits(['update:modelValue', 'change']);
  31 +const imageList = ref([...props.modelValue]);
  32 +
  33 +watch(() => props.modelValue, (newVal) => imageList.value = [...newVal], { deep: true });
  34 +
  35 +const chooseImage = async () => {
  36 + try {
  37 + const res = await chooseAndUploadImages({ count: props.maxCount - imageList.value.length });
  38 + imageList.value = [...imageList.value, ...res];
  39 + emit('update:modelValue', imageList.value);
  40 + emit('change', imageList.value);
  41 + } catch (err) {
  42 + console.error('上传失败:', err);
  43 + }
  44 +};
  45 +
  46 +const deleteImage = (index) => {
  47 + imageList.value.splice(index, 1);
  48 + emit('update:modelValue', imageList.value);
  49 + emit('change', imageList.value);
  50 +};
  51 +</script>
  52 +
  53 +<style scoped>
  54 +.upload-image { width: 100%; }
  55 +.image-list { display: flex; flex-wrap: wrap; gap: 10rpx; }
  56 +.image-item { position: relative; width: 200rpx; height: 200rpx; border-radius: 8rpx; overflow: hidden; }
  57 +.image { width: 100%; height: 100%; }
  58 +.delete-btn { position: absolute; top: 0; right: 0; width: 40rpx; height: 40rpx; background: rgba(0,0,0,0.5); border-radius: 50%; display: flex; align-items: center; justify-content: center; }
  59 +.upload-btn { width: 200rpx; height: 200rpx; border: 1rpx dashed #ddd; border-radius: 8rpx; display: flex; flex-direction: column; align-items: center; justify-content: center; background: #f9f9f9; }
  60 +.upload-text { font-size: 24rpx; color: #999; margin-top: 10rpx; }
  61 +.tips { font-size: 24rpx; color: #999; margin-top: 10rpx; }
  62 +</style>
0 \ No newline at end of file 63 \ No newline at end of file
index.html 0 → 100644
  1 +++ a/index.html
  1 +<!DOCTYPE html>
  2 +<html lang="en">
  3 + <head>
  4 + <meta charset="UTF-8" />
  5 + <script>
  6 + var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
  7 + CSS.supports('top: constant(a)'))
  8 + document.write(
  9 + '<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
  10 + (coverSupport ? ', viewport-fit=cover' : '') + '" />')
  11 + </script>
  12 + <title></title>
  13 + <!--preload-links-->
  14 + <!--app-context-->
  15 + </head>
  16 + <body>
  17 + <div id="app"><!--app-html--></div>
  18 + <script type="module" src="/main.js"></script>
  19 + </body>
  20 +</html>
main.js 0 → 100644
  1 +++ a/main.js
  1 +// 1. 先导入必要依赖(注意顺序)
  2 +import App from './App'
  3 +import uviewPlus from '@/uni_modules/uview-plus'
  4 +// 导入 Pinia 实例(你的 stores/index.js 导出的 pinia)
  5 +import pinia from '@/pinia/index'
  6 +
  7 +// #ifdef VUE3
  8 +import { createSSRApp } from 'vue'
  9 +
  10 +export function createApp() {
  11 + // 2. 创建 Vue 实例
  12 + const app = createSSRApp(App)
  13 +
  14 + // 3. 注册 uviewPlus(保持原有逻辑)
  15 + app.use(uviewPlus)
  16 +
  17 + // 4. 注册 Pinia(核心:在 app 挂载前注册)
  18 + app.use(pinia)
  19 +
  20 + // 5. 返回 app + pinia(可选,便于调试)
  21 + return {
  22 + app,
  23 + pinia
  24 + }
  25 +}
  26 +// #endif
0 \ No newline at end of file 27 \ No newline at end of file
manifest.json 0 → 100644
  1 +++ a/manifest.json
  1 +{
  2 + "name" : "jcss-miniprogram",
  3 + "appid" : "__UNI__0CFF50D",
  4 + "description" : "",
  5 + "versionName" : "1.0.0",
  6 + "versionCode" : "100",
  7 + "transformPx" : false,
  8 + /* 5+App特有相关 */
  9 + "app-plus" : {
  10 + "usingComponents" : true,
  11 + "nvueStyleCompiler" : "uni-app",
  12 + "compilerVersion" : 3,
  13 + "splashscreen" : {
  14 + "alwaysShowBeforeRender" : true,
  15 + "waiting" : true,
  16 + "autoclose" : true,
  17 + "delay" : 0
  18 + },
  19 + /* 模块配置 */
  20 + "modules" : {},
  21 + /* 应用发布信息 */
  22 + "distribute" : {
  23 + /* android打包配置 */
  24 + "android" : {
  25 + "permissions" : [
  26 + "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
  27 + "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
  28 + "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
  29 + "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
  30 + "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
  31 + "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
  32 + "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
  33 + "<uses-permission android:name=\"android.permission.CAMERA\"/>",
  34 + "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
  35 + "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
  36 + "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
  37 + "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
  38 + "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
  39 + "<uses-feature android:name=\"android.hardware.camera\"/>",
  40 + "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
  41 + ]
  42 + },
  43 + /* ios打包配置 */
  44 + "ios" : {},
  45 + /* SDK配置 */
  46 + "sdkConfigs" : {}
  47 + }
  48 + },
  49 + /* 快应用特有相关 */
  50 + "quickapp" : {},
  51 + /* 小程序特有相关 */
  52 + "mp-weixin" : {
  53 + "appid" : "wxcb4cd34066b97d82",
  54 + "setting" : {
  55 + "urlCheck" : false
  56 + },
  57 + "usingComponents" : true,
  58 + "mergeVirtualHostAttributes" : true
  59 + },
  60 + "mp-alipay" : {
  61 + "usingComponents" : true
  62 + },
  63 + "mp-baidu" : {
  64 + "usingComponents" : true
  65 + },
  66 + "mp-toutiao" : {
  67 + "usingComponents" : true,
  68 + "mergeVirtualHostAttributes" : true
  69 + },
  70 + "uniStatistics" : {
  71 + "enable" : false
  72 + },
  73 + "vueVersion" : "3"
  74 +}
package-lock.json 0 → 100644
  1 +++ a/package-lock.json
  1 +{
  2 + "name": "jcss-miniprogram",
  3 + "lockfileVersion": 3,
  4 + "requires": true,
  5 + "packages": {
  6 + "": {
  7 + "dependencies": {
  8 + "clipboard": "^2.0.11",
  9 + "dayjs": "^1.11.19",
  10 + "pinia-plugin-persistedstate": "^4.7.1",
  11 + "vue": "^3.5.25"
  12 + },
  13 + "devDependencies": {
  14 + "rollup-plugin-visualizer": "^6.0.5"
  15 + }
  16 + },
  17 + "node_modules/@babel/helper-string-parser": {
  18 + "version": "7.27.1",
  19 + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
  20 + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
  21 + "license": "MIT",
  22 + "engines": {
  23 + "node": ">=6.9.0"
  24 + }
  25 + },
  26 + "node_modules/@babel/helper-validator-identifier": {
  27 + "version": "7.28.5",
  28 + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
  29 + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
  30 + "license": "MIT",
  31 + "engines": {
  32 + "node": ">=6.9.0"
  33 + }
  34 + },
  35 + "node_modules/@babel/parser": {
  36 + "version": "7.28.5",
  37 + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.5.tgz",
  38 + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
  39 + "license": "MIT",
  40 + "dependencies": {
  41 + "@babel/types": "^7.28.5"
  42 + },
  43 + "bin": {
  44 + "parser": "bin/babel-parser.js"
  45 + },
  46 + "engines": {
  47 + "node": ">=6.0.0"
  48 + }
  49 + },
  50 + "node_modules/@babel/types": {
  51 + "version": "7.28.5",
  52 + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.5.tgz",
  53 + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
  54 + "license": "MIT",
  55 + "dependencies": {
  56 + "@babel/helper-string-parser": "^7.27.1",
  57 + "@babel/helper-validator-identifier": "^7.28.5"
  58 + },
  59 + "engines": {
  60 + "node": ">=6.9.0"
  61 + }
  62 + },
  63 + "node_modules/@jridgewell/sourcemap-codec": {
  64 + "version": "1.5.5",
  65 + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
  66 + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
  67 + "license": "MIT"
  68 + },
  69 + "node_modules/@vue/compiler-core": {
  70 + "version": "3.5.25",
  71 + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.25.tgz",
  72 + "integrity": "sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==",
  73 + "license": "MIT",
  74 + "dependencies": {
  75 + "@babel/parser": "^7.28.5",
  76 + "@vue/shared": "3.5.25",
  77 + "entities": "^4.5.0",
  78 + "estree-walker": "^2.0.2",
  79 + "source-map-js": "^1.2.1"
  80 + }
  81 + },
  82 + "node_modules/@vue/compiler-dom": {
  83 + "version": "3.5.25",
  84 + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.25.tgz",
  85 + "integrity": "sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==",
  86 + "license": "MIT",
  87 + "dependencies": {
  88 + "@vue/compiler-core": "3.5.25",
  89 + "@vue/shared": "3.5.25"
  90 + }
  91 + },
  92 + "node_modules/@vue/compiler-sfc": {
  93 + "version": "3.5.25",
  94 + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.25.tgz",
  95 + "integrity": "sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==",
  96 + "license": "MIT",
  97 + "dependencies": {
  98 + "@babel/parser": "^7.28.5",
  99 + "@vue/compiler-core": "3.5.25",
  100 + "@vue/compiler-dom": "3.5.25",
  101 + "@vue/compiler-ssr": "3.5.25",
  102 + "@vue/shared": "3.5.25",
  103 + "estree-walker": "^2.0.2",
  104 + "magic-string": "^0.30.21",
  105 + "postcss": "^8.5.6",
  106 + "source-map-js": "^1.2.1"
  107 + }
  108 + },
  109 + "node_modules/@vue/compiler-ssr": {
  110 + "version": "3.5.25",
  111 + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.25.tgz",
  112 + "integrity": "sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==",
  113 + "license": "MIT",
  114 + "dependencies": {
  115 + "@vue/compiler-dom": "3.5.25",
  116 + "@vue/shared": "3.5.25"
  117 + }
  118 + },
  119 + "node_modules/@vue/reactivity": {
  120 + "version": "3.5.25",
  121 + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.25.tgz",
  122 + "integrity": "sha512-5xfAypCQepv4Jog1U4zn8cZIcbKKFka3AgWHEFQeK65OW+Ys4XybP6z2kKgws4YB43KGpqp5D/K3go2UPPunLA==",
  123 + "license": "MIT",
  124 + "dependencies": {
  125 + "@vue/shared": "3.5.25"
  126 + }
  127 + },
  128 + "node_modules/@vue/runtime-core": {
  129 + "version": "3.5.25",
  130 + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.25.tgz",
  131 + "integrity": "sha512-Z751v203YWwYzy460bzsYQISDfPjHTl+6Zzwo/a3CsAf+0ccEjQ8c+0CdX1WsumRTHeywvyUFtW6KvNukT/smA==",
  132 + "license": "MIT",
  133 + "dependencies": {
  134 + "@vue/reactivity": "3.5.25",
  135 + "@vue/shared": "3.5.25"
  136 + }
  137 + },
  138 + "node_modules/@vue/runtime-dom": {
  139 + "version": "3.5.25",
  140 + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.25.tgz",
  141 + "integrity": "sha512-a4WrkYFbb19i9pjkz38zJBg8wa/rboNERq3+hRRb0dHiJh13c+6kAbgqCPfMaJ2gg4weWD3APZswASOfmKwamA==",
  142 + "license": "MIT",
  143 + "dependencies": {
  144 + "@vue/reactivity": "3.5.25",
  145 + "@vue/runtime-core": "3.5.25",
  146 + "@vue/shared": "3.5.25",
  147 + "csstype": "^3.1.3"
  148 + }
  149 + },
  150 + "node_modules/@vue/server-renderer": {
  151 + "version": "3.5.25",
  152 + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.25.tgz",
  153 + "integrity": "sha512-UJaXR54vMG61i8XNIzTSf2Q7MOqZHpp8+x3XLGtE3+fL+nQd+k7O5+X3D/uWrnQXOdMw5VPih+Uremcw+u1woQ==",
  154 + "license": "MIT",
  155 + "dependencies": {
  156 + "@vue/compiler-ssr": "3.5.25",
  157 + "@vue/shared": "3.5.25"
  158 + },
  159 + "peerDependencies": {
  160 + "vue": "3.5.25"
  161 + }
  162 + },
  163 + "node_modules/@vue/shared": {
  164 + "version": "3.5.25",
  165 + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.25.tgz",
  166 + "integrity": "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==",
  167 + "license": "MIT"
  168 + },
  169 + "node_modules/ansi-regex": {
  170 + "version": "5.0.1",
  171 + "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
  172 + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
  173 + "dev": true,
  174 + "license": "MIT",
  175 + "engines": {
  176 + "node": ">=8"
  177 + }
  178 + },
  179 + "node_modules/ansi-styles": {
  180 + "version": "4.3.0",
  181 + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
  182 + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
  183 + "dev": true,
  184 + "license": "MIT",
  185 + "dependencies": {
  186 + "color-convert": "^2.0.1"
  187 + },
  188 + "engines": {
  189 + "node": ">=8"
  190 + },
  191 + "funding": {
  192 + "url": "https://github.com/chalk/ansi-styles?sponsor=1"
  193 + }
  194 + },
  195 + "node_modules/clipboard": {
  196 + "version": "2.0.11",
  197 + "resolved": "https://registry.npmmirror.com/clipboard/-/clipboard-2.0.11.tgz",
  198 + "integrity": "sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==",
  199 + "license": "MIT",
  200 + "dependencies": {
  201 + "good-listener": "^1.2.2",
  202 + "select": "^1.1.2",
  203 + "tiny-emitter": "^2.0.0"
  204 + }
  205 + },
  206 + "node_modules/cliui": {
  207 + "version": "8.0.1",
  208 + "resolved": "https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz",
  209 + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
  210 + "dev": true,
  211 + "license": "ISC",
  212 + "dependencies": {
  213 + "string-width": "^4.2.0",
  214 + "strip-ansi": "^6.0.1",
  215 + "wrap-ansi": "^7.0.0"
  216 + },
  217 + "engines": {
  218 + "node": ">=12"
  219 + }
  220 + },
  221 + "node_modules/color-convert": {
  222 + "version": "2.0.1",
  223 + "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
  224 + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
  225 + "dev": true,
  226 + "license": "MIT",
  227 + "dependencies": {
  228 + "color-name": "~1.1.4"
  229 + },
  230 + "engines": {
  231 + "node": ">=7.0.0"
  232 + }
  233 + },
  234 + "node_modules/color-name": {
  235 + "version": "1.1.4",
  236 + "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
  237 + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
  238 + "dev": true,
  239 + "license": "MIT"
  240 + },
  241 + "node_modules/csstype": {
  242 + "version": "3.2.3",
  243 + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
  244 + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
  245 + "license": "MIT"
  246 + },
  247 + "node_modules/dayjs": {
  248 + "version": "1.11.19",
  249 + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.19.tgz",
  250 + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==",
  251 + "license": "MIT"
  252 + },
  253 + "node_modules/define-lazy-prop": {
  254 + "version": "2.0.0",
  255 + "resolved": "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
  256 + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
  257 + "dev": true,
  258 + "license": "MIT",
  259 + "engines": {
  260 + "node": ">=8"
  261 + }
  262 + },
  263 + "node_modules/defu": {
  264 + "version": "6.1.4",
  265 + "resolved": "https://registry.npmmirror.com/defu/-/defu-6.1.4.tgz",
  266 + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
  267 + "license": "MIT"
  268 + },
  269 + "node_modules/delegate": {
  270 + "version": "3.2.0",
  271 + "resolved": "https://registry.npmmirror.com/delegate/-/delegate-3.2.0.tgz",
  272 + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==",
  273 + "license": "MIT"
  274 + },
  275 + "node_modules/emoji-regex": {
  276 + "version": "8.0.0",
  277 + "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
  278 + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
  279 + "dev": true,
  280 + "license": "MIT"
  281 + },
  282 + "node_modules/entities": {
  283 + "version": "4.5.0",
  284 + "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
  285 + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
  286 + "license": "BSD-2-Clause",
  287 + "engines": {
  288 + "node": ">=0.12"
  289 + },
  290 + "funding": {
  291 + "url": "https://github.com/fb55/entities?sponsor=1"
  292 + }
  293 + },
  294 + "node_modules/escalade": {
  295 + "version": "3.2.0",
  296 + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz",
  297 + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
  298 + "dev": true,
  299 + "license": "MIT",
  300 + "engines": {
  301 + "node": ">=6"
  302 + }
  303 + },
  304 + "node_modules/estree-walker": {
  305 + "version": "2.0.2",
  306 + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
  307 + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
  308 + "license": "MIT"
  309 + },
  310 + "node_modules/get-caller-file": {
  311 + "version": "2.0.5",
  312 + "resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz",
  313 + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
  314 + "dev": true,
  315 + "license": "ISC",
  316 + "engines": {
  317 + "node": "6.* || 8.* || >= 10.*"
  318 + }
  319 + },
  320 + "node_modules/good-listener": {
  321 + "version": "1.2.2",
  322 + "resolved": "https://registry.npmmirror.com/good-listener/-/good-listener-1.2.2.tgz",
  323 + "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==",
  324 + "license": "MIT",
  325 + "dependencies": {
  326 + "delegate": "^3.1.2"
  327 + }
  328 + },
  329 + "node_modules/is-docker": {
  330 + "version": "2.2.1",
  331 + "resolved": "https://registry.npmmirror.com/is-docker/-/is-docker-2.2.1.tgz",
  332 + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
  333 + "dev": true,
  334 + "license": "MIT",
  335 + "bin": {
  336 + "is-docker": "cli.js"
  337 + },
  338 + "engines": {
  339 + "node": ">=8"
  340 + },
  341 + "funding": {
  342 + "url": "https://github.com/sponsors/sindresorhus"
  343 + }
  344 + },
  345 + "node_modules/is-fullwidth-code-point": {
  346 + "version": "3.0.0",
  347 + "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
  348 + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
  349 + "dev": true,
  350 + "license": "MIT",
  351 + "engines": {
  352 + "node": ">=8"
  353 + }
  354 + },
  355 + "node_modules/is-wsl": {
  356 + "version": "2.2.0",
  357 + "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-2.2.0.tgz",
  358 + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
  359 + "dev": true,
  360 + "license": "MIT",
  361 + "dependencies": {
  362 + "is-docker": "^2.0.0"
  363 + },
  364 + "engines": {
  365 + "node": ">=8"
  366 + }
  367 + },
  368 + "node_modules/magic-string": {
  369 + "version": "0.30.21",
  370 + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz",
  371 + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
  372 + "license": "MIT",
  373 + "dependencies": {
  374 + "@jridgewell/sourcemap-codec": "^1.5.5"
  375 + }
  376 + },
  377 + "node_modules/nanoid": {
  378 + "version": "3.3.11",
  379 + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
  380 + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
  381 + "funding": [
  382 + {
  383 + "type": "github",
  384 + "url": "https://github.com/sponsors/ai"
  385 + }
  386 + ],
  387 + "license": "MIT",
  388 + "bin": {
  389 + "nanoid": "bin/nanoid.cjs"
  390 + },
  391 + "engines": {
  392 + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
  393 + }
  394 + },
  395 + "node_modules/open": {
  396 + "version": "8.4.2",
  397 + "resolved": "https://registry.npmmirror.com/open/-/open-8.4.2.tgz",
  398 + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
  399 + "dev": true,
  400 + "license": "MIT",
  401 + "dependencies": {
  402 + "define-lazy-prop": "^2.0.0",
  403 + "is-docker": "^2.1.1",
  404 + "is-wsl": "^2.2.0"
  405 + },
  406 + "engines": {
  407 + "node": ">=12"
  408 + },
  409 + "funding": {
  410 + "url": "https://github.com/sponsors/sindresorhus"
  411 + }
  412 + },
  413 + "node_modules/picocolors": {
  414 + "version": "1.1.1",
  415 + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
  416 + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
  417 + "license": "ISC"
  418 + },
  419 + "node_modules/picomatch": {
  420 + "version": "4.0.3",
  421 + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz",
  422 + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
  423 + "dev": true,
  424 + "license": "MIT",
  425 + "engines": {
  426 + "node": ">=12"
  427 + },
  428 + "funding": {
  429 + "url": "https://github.com/sponsors/jonschlinkert"
  430 + }
  431 + },
  432 + "node_modules/pinia-plugin-persistedstate": {
  433 + "version": "4.7.1",
  434 + "resolved": "https://registry.npmmirror.com/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-4.7.1.tgz",
  435 + "integrity": "sha512-WHOqh2esDlR3eAaknPbqXrkkj0D24h8shrDPqysgCFR6ghqP/fpFfJmMPJp0gETHsvrh9YNNg6dQfo2OEtDnIQ==",
  436 + "license": "MIT",
  437 + "dependencies": {
  438 + "defu": "^6.1.4"
  439 + },
  440 + "peerDependencies": {
  441 + "@nuxt/kit": ">=3.0.0",
  442 + "@pinia/nuxt": ">=0.10.0",
  443 + "pinia": ">=3.0.0"
  444 + },
  445 + "peerDependenciesMeta": {
  446 + "@nuxt/kit": {
  447 + "optional": true
  448 + },
  449 + "@pinia/nuxt": {
  450 + "optional": true
  451 + },
  452 + "pinia": {
  453 + "optional": true
  454 + }
  455 + }
  456 + },
  457 + "node_modules/postcss": {
  458 + "version": "8.5.6",
  459 + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz",
  460 + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
  461 + "funding": [
  462 + {
  463 + "type": "opencollective",
  464 + "url": "https://opencollective.com/postcss/"
  465 + },
  466 + {
  467 + "type": "tidelift",
  468 + "url": "https://tidelift.com/funding/github/npm/postcss"
  469 + },
  470 + {
  471 + "type": "github",
  472 + "url": "https://github.com/sponsors/ai"
  473 + }
  474 + ],
  475 + "license": "MIT",
  476 + "dependencies": {
  477 + "nanoid": "^3.3.11",
  478 + "picocolors": "^1.1.1",
  479 + "source-map-js": "^1.2.1"
  480 + },
  481 + "engines": {
  482 + "node": "^10 || ^12 || >=14"
  483 + }
  484 + },
  485 + "node_modules/require-directory": {
  486 + "version": "2.1.1",
  487 + "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz",
  488 + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
  489 + "dev": true,
  490 + "license": "MIT",
  491 + "engines": {
  492 + "node": ">=0.10.0"
  493 + }
  494 + },
  495 + "node_modules/rollup-plugin-visualizer": {
  496 + "version": "6.0.5",
  497 + "resolved": "https://registry.npmmirror.com/rollup-plugin-visualizer/-/rollup-plugin-visualizer-6.0.5.tgz",
  498 + "integrity": "sha512-9+HlNgKCVbJDs8tVtjQ43US12eqaiHyyiLMdBwQ7vSZPiHMysGNo2E88TAp1si5wx8NAoYriI2A5kuKfIakmJg==",
  499 + "dev": true,
  500 + "license": "MIT",
  501 + "dependencies": {
  502 + "open": "^8.0.0",
  503 + "picomatch": "^4.0.2",
  504 + "source-map": "^0.7.4",
  505 + "yargs": "^17.5.1"
  506 + },
  507 + "bin": {
  508 + "rollup-plugin-visualizer": "dist/bin/cli.js"
  509 + },
  510 + "engines": {
  511 + "node": ">=18"
  512 + },
  513 + "peerDependencies": {
  514 + "rolldown": "1.x || ^1.0.0-beta",
  515 + "rollup": "2.x || 3.x || 4.x"
  516 + },
  517 + "peerDependenciesMeta": {
  518 + "rolldown": {
  519 + "optional": true
  520 + },
  521 + "rollup": {
  522 + "optional": true
  523 + }
  524 + }
  525 + },
  526 + "node_modules/select": {
  527 + "version": "1.1.2",
  528 + "resolved": "https://registry.npmmirror.com/select/-/select-1.1.2.tgz",
  529 + "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==",
  530 + "license": "MIT"
  531 + },
  532 + "node_modules/source-map": {
  533 + "version": "0.7.6",
  534 + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.7.6.tgz",
  535 + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
  536 + "dev": true,
  537 + "license": "BSD-3-Clause",
  538 + "engines": {
  539 + "node": ">= 12"
  540 + }
  541 + },
  542 + "node_modules/source-map-js": {
  543 + "version": "1.2.1",
  544 + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
  545 + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
  546 + "license": "BSD-3-Clause",
  547 + "engines": {
  548 + "node": ">=0.10.0"
  549 + }
  550 + },
  551 + "node_modules/string-width": {
  552 + "version": "4.2.3",
  553 + "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
  554 + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
  555 + "dev": true,
  556 + "license": "MIT",
  557 + "dependencies": {
  558 + "emoji-regex": "^8.0.0",
  559 + "is-fullwidth-code-point": "^3.0.0",
  560 + "strip-ansi": "^6.0.1"
  561 + },
  562 + "engines": {
  563 + "node": ">=8"
  564 + }
  565 + },
  566 + "node_modules/strip-ansi": {
  567 + "version": "6.0.1",
  568 + "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
  569 + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
  570 + "dev": true,
  571 + "license": "MIT",
  572 + "dependencies": {
  573 + "ansi-regex": "^5.0.1"
  574 + },
  575 + "engines": {
  576 + "node": ">=8"
  577 + }
  578 + },
  579 + "node_modules/tiny-emitter": {
  580 + "version": "2.1.0",
  581 + "resolved": "https://registry.npmmirror.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz",
  582 + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==",
  583 + "license": "MIT"
  584 + },
  585 + "node_modules/vue": {
  586 + "version": "3.5.25",
  587 + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.25.tgz",
  588 + "integrity": "sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==",
  589 + "license": "MIT",
  590 + "dependencies": {
  591 + "@vue/compiler-dom": "3.5.25",
  592 + "@vue/compiler-sfc": "3.5.25",
  593 + "@vue/runtime-dom": "3.5.25",
  594 + "@vue/server-renderer": "3.5.25",
  595 + "@vue/shared": "3.5.25"
  596 + },
  597 + "peerDependencies": {
  598 + "typescript": "*"
  599 + },
  600 + "peerDependenciesMeta": {
  601 + "typescript": {
  602 + "optional": true
  603 + }
  604 + }
  605 + },
  606 + "node_modules/wrap-ansi": {
  607 + "version": "7.0.0",
  608 + "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
  609 + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
  610 + "dev": true,
  611 + "license": "MIT",
  612 + "dependencies": {
  613 + "ansi-styles": "^4.0.0",
  614 + "string-width": "^4.1.0",
  615 + "strip-ansi": "^6.0.0"
  616 + },
  617 + "engines": {
  618 + "node": ">=10"
  619 + },
  620 + "funding": {
  621 + "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
  622 + }
  623 + },
  624 + "node_modules/y18n": {
  625 + "version": "5.0.8",
  626 + "resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz",
  627 + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
  628 + "dev": true,
  629 + "license": "ISC",
  630 + "engines": {
  631 + "node": ">=10"
  632 + }
  633 + },
  634 + "node_modules/yargs": {
  635 + "version": "17.7.2",
  636 + "resolved": "https://registry.npmmirror.com/yargs/-/yargs-17.7.2.tgz",
  637 + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
  638 + "dev": true,
  639 + "license": "MIT",
  640 + "dependencies": {
  641 + "cliui": "^8.0.1",
  642 + "escalade": "^3.1.1",
  643 + "get-caller-file": "^2.0.5",
  644 + "require-directory": "^2.1.1",
  645 + "string-width": "^4.2.3",
  646 + "y18n": "^5.0.5",
  647 + "yargs-parser": "^21.1.1"
  648 + },
  649 + "engines": {
  650 + "node": ">=12"
  651 + }
  652 + },
  653 + "node_modules/yargs-parser": {
  654 + "version": "21.1.1",
  655 + "resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz",
  656 + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
  657 + "dev": true,
  658 + "license": "ISC",
  659 + "engines": {
  660 + "node": ">=12"
  661 + }
  662 + }
  663 + }
  664 +}
package.json 0 → 100644
  1 +++ a/package.json
  1 +{
  2 + "dependencies": {
  3 + "clipboard": "^2.0.11",
  4 + "dayjs": "^1.11.19",
  5 + "pinia-plugin-persistedstate": "^4.7.1",
  6 + "vue": "^3.5.25"
  7 + },
  8 + "devDependencies": {
  9 + "rollup-plugin-visualizer": "^6.0.5"
  10 + }
  11 +}
pages-sub/daily/12345-order/index.vue 0 → 100644
  1 +++ a/pages-sub/daily/12345-order/index.vue
  1 +<script setup lang="ts">
  2 +
  3 +</script>
  4 +
  5 +<template>
  6 +
  7 +</template>
  8 +
  9 +<style scoped>
  10 +
  11 +</style>
0 \ No newline at end of file 12 \ No newline at end of file
pages-sub/daily/maintain-manage/index.vue 0 → 100644
  1 +++ a/pages-sub/daily/maintain-manage/index.vue
  1 +<script setup lang="ts">
  2 +
  3 +</script>
  4 +
  5 +<template>
  6 +
  7 +</template>
  8 +
  9 +<style scoped>
  10 +
  11 +</style>
0 \ No newline at end of file 12 \ No newline at end of file
pages-sub/daily/patrol-plan/index.vue 0 → 100644
  1 +++ a/pages-sub/daily/patrol-plan/index.vue
  1 +<template>
  2 + <view class="page-container">
  3 + <!-- 顶部固定区域:Tabs + 搜索框 -->
  4 + <view class="top-fixed">
  5 + <!-- uView Tabs 标签 -->
  6 + <u-tabs
  7 + v-model="activeTab"
  8 + :list="tabList"
  9 + :is-scroll="false"
  10 + :activeStyle="{
  11 + color: '#3c9cff',
  12 + fontWeight: 'bold',
  13 + transform: 'scale(1.05)'
  14 + }"
  15 + :inactiveStyle="{
  16 + color: '#606266',
  17 + transform: 'scale(1)'
  18 + }"
  19 + font-size="28rpx"
  20 + @change="handleTabChange"
  21 + ></u-tabs>
  22 +
  23 + <!-- 道路名称搜索框 -->
  24 + <u-search
  25 + v-model="searchValue"
  26 + placeholder="请输入道路名称"
  27 + bg-color="#f5f5f5"
  28 + border-radius="8rpx"
  29 + :clearabled="true"
  30 + @search="handleSearch"
  31 + @clear="handleSearchClear"
  32 + maxlength="50"
  33 + style="margin: 10rpx 20rpx 0"
  34 + ></u-search>
  35 + </view>
  36 +
  37 + <!-- z-paging 分页列表 -->
  38 + <z-paging
  39 + v-model="planList"
  40 + :page-size="pageSize"
  41 + :total="total"
  42 + @query="loadData"
  43 + :scroll-top-reset-type="1"
  44 + :top="160"
  45 + bg-color="#f8f8f8"
  46 + >
  47 + <!-- 计划卡片列表 -->
  48 + <view class="card-list">
  49 + <view class="plan-card" v-for="(item, index) in planList" :key="index">
  50 + <view class="card-content">
  51 + <!-- 道路名称 -->
  52 + <view class="row-item">
  53 + <text class="label">道路名称:</text>
  54 + <text class="value">{{ item.roadName }}</text>
  55 + </view>
  56 + <!-- 所属街道 -->
  57 + <view class="row-item">
  58 + <text class="label">所属街道:</text>
  59 + <text class="value">{{ item.street }}</text>
  60 + </view>
  61 + <!-- 养护级别 + 计划明细按钮 -->
  62 + <view class="row-item flex-between">
  63 + <view>
  64 + <text class="label">养护级别:</text>
  65 + <text class="value">{{ item.maintainLevel }}</text>
  66 + </view>
  67 + <text class="detail-btn" @click="gotoDetail(item)">计划明细</text>
  68 + </view>
  69 + <!-- 计划类型 -->
  70 + <view class="row-item">
  71 + <text class="label">计划类型:</text>
  72 + <text class="value">{{ item.planType }}</text>
  73 + </view>
  74 + <!-- 计划时间 -->
  75 + <view class="row-item">
  76 + <text class="label">计划时间:</text>
  77 + <text class="value">{{ item.planTime }}</text>
  78 + </view>
  79 + </view>
  80 + </view>
  81 + </view>
  82 +
  83 + <!-- 空数据占位 -->
  84 + <template #empty>
  85 + <view class="empty-wrap">
  86 + <text class="empty-text">暂无相关计划数据</text>
  87 + </view>
  88 + </template>
  89 + </z-paging>
  90 + </view>
  91 +</template>
  92 +
  93 +<script setup>
  94 +import { ref, reactive, onMounted } from 'vue';
  95 +import { onLoad } from '@dcloudio/uni-app';
  96 +// Tabs 配置
  97 +const tabList = ref([
  98 + {name: '待完成', id: 'pending'},
  99 + {name: '已失效', id: 'invalid'},
  100 + {name: '已完成', id: 'completed'}
  101 +]);
  102 +const activeTab = ref('pending'); // 当前激活的Tab
  103 +// 搜索相关
  104 +const searchValue = ref(''); // 搜索框值
  105 +// 分页相关
  106 +const pageNo = ref(1); // 当前页码
  107 +const pageSize = ref(10); // 每页条数
  108 +const total = ref(100); // 总记录数(模拟大量数据)
  109 +const planList = ref([]); // 计划列表数据
  110 +// Tab切换事件(清空搜索条件)
  111 +const handleTabChange = () => {
  112 + // 清空搜索框
  113 + searchValue.value = '';
  114 + // 重置分页
  115 + pageNo.value = 1;
  116 + // 重新加载数据
  117 + loadData(true);
  118 +};
  119 +// 搜索事件
  120 +const handleSearch = () => {
  121 + pageNo.value = 1;
  122 + loadData(true);
  123 +};
  124 +// 清空搜索框
  125 +const handleSearchClear = () => {
  126 + pageNo.value = 1;
  127 + loadData(true);
  128 +};
  129 +// 生成模拟数据
  130 +const generateMockData = (page) => {
  131 + const mockList = [];
  132 + const tabMap = {
  133 + pending: '待完成',
  134 + invalid: '已失效',
  135 + completed: '已完成'
  136 + };
  137 + const maintainLevels = ['一级养护', '二级养护', '三级养护'];
  138 + const planTypes = ['日常养护', '专项养护', '应急养护'];
  139 + const streets = ['西长安街街道', '东城区东华门街道', '朝阳区望京街道', '海淀区中关村街道', '丰台区马家堡街道'];
  140 + const roadNames = [
  141 + '长安街', '王府井大街', '中关村南大街', '朝阳北路', '建国路',
  142 + '复兴路', '西直门外大街', '广渠路', '阜成门外大街', '安定门东大街'
  143 + ];
  144 + // 生成当前页数据
  145 + for (let i = 0; i < 35; i++) {
  146 + const idx = (page - 1) * pageSize.value + i;
  147 + mockList.push({
  148 + id: idx + 1,
  149 + roadName: `${roadNames[idx % roadNames.length]}${idx + 1}段`,
  150 + street: streets[Math.floor(Math.random() * streets.length)],
  151 + maintainLevel: maintainLevels[Math.floor(Math.random() * maintainLevels.length)],
  152 + planType: planTypes[Math.floor(Math.random() * planTypes.length)],
  153 + planTime: `2025-${Math.floor(Math.random() * 12) + 1}-${Math.floor(Math.random() * 28) + 1} 至 2025-${Math.floor(Math.random() * 12) + 1}-${Math.floor(Math.random() * 28) + 1}`,
  154 + status: tabMap[activeTab.value]
  155 + });
  156 + }
  157 + return mockList;
  158 +};
  159 +// 加载数据(z-paging 核心方法)
  160 +const loadData = (isRefresh = false) => {
  161 + // 模拟接口请求延迟
  162 + setTimeout(() => {
  163 + const newData = generateMockData(pageNo.value);
  164 + if (isRefresh) {
  165 + planList.value = newData; // 刷新:替换数据
  166 + } else {
  167 + planList.value = [...planList.value, ...newData]; // 加载更多:追加数据
  168 + }
  169 + }, 500);
  170 +};
  171 +// 跳转到计划明细页面
  172 +const gotoDetail = (item) => {
  173 + uni.navigateTo({
  174 + url: `/pages/plan-detail/index?id=${item.id}&status=${activeTab.value}`
  175 + });
  176 +};
  177 +// 页面加载初始化
  178 +onLoad(() => {
  179 + loadData(true);
  180 +});
  181 +</script>
  182 +
  183 +<style scoped lang="scss">
  184 +.page-container {
  185 + min-height: 100vh;
  186 + background-color: #f8f8f8;
  187 +}
  188 +
  189 +/* 顶部固定区域 */
  190 +.top-fixed {
  191 + position: fixed;
  192 + top: 0;
  193 + left: 0;
  194 + right: 0;
  195 + z-index: 999;
  196 + background-color: #fff;
  197 + padding-bottom: 10rpx;
  198 + box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
  199 +}
  200 +
  201 +/* 计划卡片样式 */
  202 +.card-list {
  203 + padding: 170rpx 20rpx 20rpx;
  204 +}
  205 +
  206 +.plan-card {
  207 + background-color: #fff;
  208 + border-radius: 12rpx;
  209 + padding: 20rpx;
  210 + margin-bottom: 15rpx;
  211 + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
  212 +}
  213 +
  214 +.card-content {
  215 + display: flex;
  216 + flex-direction: column;
  217 + gap: 12rpx;
  218 +}
  219 +
  220 +.row-item {
  221 + display: flex;
  222 + align-items: center;
  223 + font-size: 28rpx;
  224 + line-height: 1.5;
  225 +}
  226 +
  227 +.flex-between {
  228 + display: flex;
  229 + justify-content: space-between;
  230 + align-items: center;
  231 +}
  232 +
  233 +.label {
  234 + color: #999;
  235 + width: 140rpx;
  236 + flex-shrink: 0;
  237 +}
  238 +
  239 +.value {
  240 + color: #333;
  241 + flex: 1;
  242 +}
  243 +
  244 +.detail-btn {
  245 + color: #1989fa;
  246 + font-size: 26rpx;
  247 + padding: 0 10rpx;
  248 +}
  249 +
  250 +/* 空数据样式 */
  251 +.empty-wrap {
  252 + display: flex;
  253 + flex-direction: column;
  254 + align-items: center;
  255 + justify-content: center;
  256 + padding: 100rpx 0;
  257 +}
  258 +
  259 +.empty-text {
  260 + font-size: 28rpx;
  261 + color: #999;
  262 +}
  263 +</style>
0 \ No newline at end of file 264 \ No newline at end of file
pages-sub/daily/quick-order/index.vue 0 → 100644
  1 +++ a/pages-sub/daily/quick-order/index.vue
  1 +<script setup lang="ts">
  2 +
  3 +</script>
  4 +
  5 +<template>
  6 +
  7 +</template>
  8 +
  9 +<style scoped>
  10 +
  11 +</style>
0 \ No newline at end of file 12 \ No newline at end of file
pages-sub/daily/work-order/index.vue 0 → 100644
  1 +++ a/pages-sub/daily/work-order/index.vue
  1 +<script setup lang="ts">
  2 +
  3 +</script>
  4 +
  5 +<template>
  6 +
  7 +</template>
  8 +
  9 +<style scoped>
  10 +
  11 +</style>
0 \ No newline at end of file 12 \ No newline at end of file
pages-sub/data/base-data/index.vue 0 → 100644
  1 +++ a/pages-sub/data/base-data/index.vue
  1 +<script setup lang="ts">
  2 +
  3 +</script>
  4 +
  5 +<template>
  6 +
  7 +</template>
  8 +
  9 +<style scoped>
  10 +
  11 +</style>
0 \ No newline at end of file 12 \ No newline at end of file
pages-sub/data/personnel-manage/index.vue 0 → 100644
  1 +++ a/pages-sub/data/personnel-manage/index.vue
  1 +<script setup lang="ts">
  2 +
  3 +</script>
  4 +
  5 +<template>
  6 +
  7 +</template>
  8 +
  9 +<style scoped>
  10 +
  11 +</style>
0 \ No newline at end of file 12 \ No newline at end of file
pages-sub/data/personnel-track/index.vue 0 → 100644
  1 +++ a/pages-sub/data/personnel-track/index.vue
  1 +<script setup lang="ts">
  2 +
  3 +</script>
  4 +
  5 +<template>
  6 +
  7 +</template>
  8 +
  9 +<style scoped>
  10 +
  11 +</style>
0 \ No newline at end of file 12 \ No newline at end of file
pages-sub/data/tree-archive/index.vue 0 → 100644
  1 +++ a/pages-sub/data/tree-archive/index.vue
  1 +<script setup lang="ts">
  2 +
  3 +</script>
  4 +
  5 +<template>
  6 +
  7 +</template>
  8 +
  9 +<style scoped>
  10 +
  11 +</style>
0 \ No newline at end of file 12 \ No newline at end of file
pages-sub/problem/order-manage/index.vue 0 → 100644
  1 +++ a/pages-sub/problem/order-manage/index.vue
  1 +<script setup lang="ts">
  2 +
  3 +</script>
  4 +
  5 +<template>
  6 +
  7 +</template>
  8 +
  9 +<style scoped>
  10 +
  11 +</style>
0 \ No newline at end of file 12 \ No newline at end of file
pages-sub/problem/problem-allot/index.vue 0 → 100644
  1 +++ a/pages-sub/problem/problem-allot/index.vue
  1 +<script setup lang="ts">
  2 +
  3 +</script>
  4 +
  5 +<template>
  6 +
  7 +</template>
  8 +
  9 +<style scoped>
  10 +
  11 +</style>
0 \ No newline at end of file 12 \ No newline at end of file
pages.json 0 → 100644
  1 +++ a/pages.json
  1 +{
  2 + "pages": [
  3 + {
  4 + "path": "pages/login/index",
  5 + "style": {
  6 + "navigationBarTitleText": "登录"
  7 + }
  8 + },
  9 + {
  10 + "path": "pages/index/index",
  11 + "style": {
  12 + "navigationBarTitleText": "首页"
  13 + }
  14 + },
  15 + {
  16 + "path": "pages/workbench/index",
  17 + "style": {
  18 + "navigationBarTitleText": "工作台"
  19 + }
  20 + },
  21 + {
  22 + "path": "pages/mine/index",
  23 + "style": {
  24 + "navigationBarTitleText": "我的"
  25 + }
  26 + }
  27 + ],
  28 + "subPackages": [
  29 + {
  30 + "root": "pages-sub/daily",
  31 + "pages": [
  32 + {
  33 + "path": "patrol-plan/index",
  34 + "style": {
  35 + "navigationBarTitleText": "巡查计划",
  36 + "enablePullDownRefresh": false
  37 + }
  38 + },
  39 + {
  40 + "path": "pages/plan-detail/index",
  41 + "style": {
  42 + "navigationBarTitleText": "计划明细"
  43 + }
  44 + },
  45 + {
  46 + "path": "work-order/index",
  47 + "style": { "navigationBarTitleText": "工单上报" }
  48 + },
  49 + {
  50 + "path": "quick-order/index",
  51 + "style": { "navigationBarTitleText": "快速工单" }
  52 + },
  53 + {
  54 + "path": "12345-order/index",
  55 + "style": { "navigationBarTitleText": "12345工单" }
  56 + },
  57 + {
  58 + "path": "patrol-manage/index",
  59 + "style": { "navigationBarTitleText": "巡查管理" }
  60 + },
  61 + {
  62 + "path": "maintain-manage/index",
  63 + "style": { "navigationBarTitleText": "养护管理" }
  64 + }
  65 + ]
  66 + },
  67 + {
  68 + "root": "pages-sub/problem",
  69 + "pages": [
  70 + {
  71 + "path": "order-manage/index",
  72 + "style": { "navigationBarTitleText": "工单管理" }
  73 + },
  74 + {
  75 + "path": "problem-allot/index",
  76 + "style": { "navigationBarTitleText": "问题分配" }
  77 + }
  78 + ]
  79 + },
  80 + {
  81 + "root": "pages-sub/data",
  82 + "pages": [
  83 + {
  84 + "path": "base-data/index",
  85 + "style": { "navigationBarTitleText": "基础数据" }
  86 + },
  87 + {
  88 + "path": "personnel-track/index",
  89 + "style": { "navigationBarTitleText": "人员轨迹" }
  90 + },
  91 + {
  92 + "path": "personnel-manage/index",
  93 + "style": { "navigationBarTitleText": "人员管理" }
  94 + },
  95 + {
  96 + "path": "tree-archive/index",
  97 + "style": { "navigationBarTitleText": "行道树档案" }
  98 + }
  99 + ]
  100 + }
  101 + ],
  102 + "tabBar": {
  103 + "color": "#666666",
  104 + "selectedColor": "#1989fa",
  105 + "borderStyle": "black",
  106 + "backgroundColor": "#ffffff",
  107 + "list": [
  108 + {
  109 + "pagePath": "pages/index/index",
  110 + "text": "首页",
  111 + "iconPath": "static/icons/home.png",
  112 + "selectedIconPath": "static/icons/home-active.png"
  113 + },
  114 + {
  115 + "pagePath": "pages/workbench/index",
  116 + "text": "工作台",
  117 + "iconPath": "static/icons/mine.png",
  118 + "selectedIconPath": "static/icons/mine-active.png"
  119 + },
  120 + {
  121 + "pagePath": "pages/mine/index",
  122 + "text": "我的",
  123 + "iconPath": "static/icons/mine.png",
  124 + "selectedIconPath": "static/icons/mine-active.png"
  125 + }
  126 + ]
  127 + },
  128 + "easycom": {
  129 + "autoscan": true,
  130 +
  131 + "custom": {
  132 + "^u--(.*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue",
  133 + "^up-(.*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue",
  134 + "^u-([^-].*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue"
  135 + }
  136 + },
  137 + "globalStyle": {
  138 + "navigationBarTextStyle": "black",
  139 + "navigationBarTitleText": "JCSS管理系统",
  140 + "navigationBarBackgroundColor": "#ffffff",
  141 + "backgroundColor": "#f8f8f8"
  142 + }
  143 +}
0 \ No newline at end of file 144 \ No newline at end of file
pages/index/index.vue 0 → 100644
  1 +++ a/pages/index/index.vue
  1 +<template>
  2 + <view class="content">
  3 +<!-- <image class="logo" src="/static/logo.png"></image>-->
  4 + <view class="text-area">
  5 + <text class="title">{{title}}</text>
  6 + </view>
  7 + </view>
  8 +</template>
  9 +
  10 +<script>
  11 + export default {
  12 + data() {
  13 + return {
  14 + title: 'Hello'
  15 + }
  16 + },
  17 + onLoad() {
  18 +
  19 + },
  20 + methods: {
  21 +
  22 + }
  23 + }
  24 +</script>
  25 +
  26 +<style>
  27 + .content {
  28 + display: flex;
  29 + flex-direction: column;
  30 + align-items: center;
  31 + justify-content: center;
  32 + }
  33 +
  34 + .logo {
  35 + height: 200rpx;
  36 + width: 200rpx;
  37 + margin-top: 200rpx;
  38 + margin-left: auto;
  39 + margin-right: auto;
  40 + margin-bottom: 50rpx;
  41 + }
  42 +
  43 + .text-area {
  44 + display: flex;
  45 + justify-content: center;
  46 + }
  47 +
  48 + .title {
  49 + font-size: 36rpx;
  50 + color: #8f8f94;
  51 + }
  52 +</style>
pages/login/index.vue 0 → 100644
  1 +++ a/pages/login/index.vue
  1 +<template>
  2 + <view class="login-page">
  3 + <!-- 登录表单区域 -->
  4 + <view class="login-form">
  5 + <!-- 账号输入框 -->
  6 + <view class="form-item">
  7 + <up-input
  8 + v-model="form.account"
  9 + placeholder="请输入登录账号"
  10 + border="surround"
  11 + clearable
  12 + input-align="left"
  13 + :disabled="isLoading"
  14 + @blur="checkAccount"
  15 + >
  16 + <template #prefix>
  17 + <up-icon name="account" color="#909399" size="20"></up-icon>
  18 + </template>
  19 + </up-input>
  20 + <!-- 账号错误提示 -->
  21 + <view class="error-tip" v-if="error.account">{{ error.account }}</view>
  22 + </view>
  23 +
  24 + <!-- 密码输入框(移除查看密码功能) -->
  25 + <view class="form-item">
  26 + <up-input
  27 + v-model="form.password"
  28 + placeholder="请输入登录密码"
  29 + border="surround"
  30 + clearable
  31 + input-align="left"
  32 + type="password"
  33 + :disabled="isLoading"
  34 + @blur="checkPassword"
  35 + >
  36 + <template #prefix>
  37 + <up-icon name="lock" color="#909399" size="20"></up-icon>
  38 + </template>
  39 + </up-input>
  40 + <!-- 密码错误提示 -->
  41 + <view class="error-tip" v-if="error.password">{{ error.password }}</view>
  42 + </view>
  43 +
  44 + <!-- 登录按钮 -->
  45 + <up-button
  46 + class="login-btn"
  47 + type="primary"
  48 + size="large"
  49 + :loading="isLoading"
  50 + @click="handleLogin"
  51 + >
  52 + 登录
  53 + </up-button>
  54 + </view>
  55 + </view>
  56 +</template>
  57 +
  58 +<script setup>
  59 +import { ref, reactive, onMounted } from 'vue';
  60 +import { useUserStore } from '@/pinia/user';
  61 +import globalConfig from '@/common/config/global';
  62 +
  63 +// 表单数据
  64 +const form = reactive({
  65 + account: '', // 账号
  66 + password: '' // 密码
  67 +});
  68 +
  69 +// 状态管理
  70 +const isLoading = ref(false);
  71 +const error = reactive({
  72 + account: '',
  73 + password: ''
  74 +});
  75 +
  76 +// 实例化 Pinia 用户仓库
  77 +const userStore = useUserStore();
  78 +
  79 +// 页面加载时校验登录态
  80 +onMounted(() => {
  81 + try {
  82 + // 已登录则直接跳首页
  83 + if (userStore.isLogin) {
  84 + uni.switchTab({
  85 + url: globalConfig.router.tabBarList[1].path,
  86 + fail: () => {
  87 + // 非tabBar页面用redirectTo
  88 + uni.redirectTo({ url: '/pages/workbench/index' });
  89 + }
  90 + });
  91 + return;
  92 + }
  93 + } catch (err) {
  94 + console.warn('检查登录状态失败:', err);
  95 + }
  96 +});
  97 +
  98 +// 校验账号
  99 +const checkAccount = () => {
  100 + if (!form.account) {
  101 + error.account = '请输入登录账号';
  102 + } else if (!/^[a-zA-Z0-9_-]{4,16}$/.test(form.account)) {
  103 + error.account = '账号格式错误(4-16位字母/数字/下划线)';
  104 + } else {
  105 + error.account = '';
  106 + }
  107 +};
  108 +
  109 +// 校验密码
  110 +const checkPassword = () => {
  111 + if (!form.password) {
  112 + error.password = '请输入登录密码';
  113 + } else if (form.password.length < 6) {
  114 + error.password = '密码长度不能少于6位';
  115 + } else {
  116 + error.password = '';
  117 + }
  118 +};
  119 +
  120 +// 表单整体校验
  121 +const validateForm = () => {
  122 + checkAccount();
  123 + checkPassword();
  124 + return !error.account && !error.password;
  125 +};
  126 +
  127 +// 登录处理(核心:补充跳转逻辑)
  128 +const handleLogin = async () => {
  129 + if (!validateForm()) return;
  130 +
  131 + isLoading.value = true;
  132 +
  133 + try {
  134 + await userStore.login({
  135 + username: form.account,
  136 + password: form.password
  137 + });
  138 +
  139 + uni.showToast({ title: '登录成功', icon: 'success', duration: 1500 });
  140 +
  141 + // 登录成功后跳转首页(优先tabBar,兼容普通页面)
  142 + setTimeout(() => {
  143 + // 方式1:跳tabBar首页(推荐,需在pages.json配置tabBar)
  144 + uni.switchTab({
  145 + url: globalConfig.router.tabBarList[1].path,
  146 + fail: (err) => {
  147 + console.warn('tabBar跳转失败,切换为普通跳转:', err);
  148 + // 方式2:跳普通首页(非tabBar页面)
  149 + uni.redirectTo({ url: '/pages/workbench/index' });
  150 + }
  151 + });
  152 + }, 1500);
  153 +
  154 + } catch (err) {
  155 + console.error('登录失败详情:', err);
  156 + const errorMsg = err.message === '网络异常,请稍后重试'
  157 + ? '网络异常,请稍后重试'
  158 + : err.message || '登录失败,请检查账号密码';
  159 + uni.showToast({
  160 + title: errorMsg,
  161 + icon: 'none',
  162 + duration: 2000
  163 + });
  164 + } finally {
  165 + isLoading.value = false;
  166 + }
  167 +};
  168 +</script>
  169 +
  170 +<style scoped lang="scss">
  171 +.login-page {
  172 + min-height: 100vh;
  173 + background-color: #f5f5f5;
  174 + padding: 40rpx 30rpx;
  175 + box-sizing: border-box;
  176 +}
  177 +
  178 +.login-form {
  179 + background-color: #fff;
  180 + padding: 40rpx 30rpx;
  181 + border-radius: 12rpx;
  182 + box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
  183 +}
  184 +
  185 +.form-item {
  186 + margin-bottom: 30rpx;
  187 + position: relative;
  188 +}
  189 +
  190 +.error-tip {
  191 + font-size: 24rpx;
  192 + color: #f56c6c;
  193 + margin-top: 10rpx;
  194 + line-height: 1.2;
  195 +}
  196 +
  197 +.login-btn {
  198 + width: 100%;
  199 + height: 88rpx;
  200 + line-height: 88rpx;
  201 + font-size: 32rpx;
  202 + border-radius: 44rpx;
  203 + background-color: #409eff;
  204 + margin-top: 10rpx;
  205 +}
  206 +</style>
0 \ No newline at end of file 207 \ No newline at end of file
pages/mine/index.vue 0 → 100644
  1 +++ a/pages/mine/index.vue
  1 +<script setup lang="ts">
  2 +
  3 +</script>
  4 +
  5 +<template>
  6 +
  7 +</template>
  8 +
  9 +<style scoped>
  10 +
  11 +</style>
0 \ No newline at end of file 12 \ No newline at end of file
pages/workbench/index.vue 0 → 100644
  1 +++ a/pages/workbench/index.vue
  1 +<template>
  2 + <view class="workbench-container">
  3 + <view class="card">
  4 + <view class="card-title">日常管理</view>
  5 + <view class="menu-grid">
  6 + <view class="menu-item" v-for="item in dailyMenuList" :key="item.id" @click="handleMenuClick(item)">
  7 + <image class="menu-icon" :src="item.icon || '/static/imgs/logo.png'" mode="aspectFit"></image>
  8 + <view class="menu-text">{{ item.name }}</view>
  9 + </view>
  10 + </view>
  11 + </view>
  12 +
  13 + <view class="card">
  14 + <view class="card-title">问题管理</view>
  15 + <view class="menu-grid">
  16 + <view class="menu-item" v-for="item in problemMenuList" :key="item.id" @click="handleMenuClick(item)">
  17 + <image class="menu-icon" :src="item.icon || '/static/imgs/logo.png'" mode="aspectFit"></image>
  18 + <view class="menu-text">{{ item.name }}</view>
  19 + </view>
  20 + </view>
  21 + </view>
  22 +
  23 + <view class="card">
  24 + <view class="card-title">数据管理</view>
  25 + <view class="menu-grid">
  26 + <view class="menu-item" v-for="item in dataMenuList" :key="item.id" @click="handleMenuClick(item)">
  27 + <image class="menu-icon" :src="item.icon || '/static/imgs/logo.png'" mode="aspectFit"></image>
  28 + <view class="menu-text">{{ item.name }}</view>
  29 + </view>
  30 + </view>
  31 + </view>
  32 + </view>
  33 +</template>
  34 +
  35 +<script setup>
  36 +import { ref } from 'vue';
  37 +// 关键:导入 uni-app 页面生命周期钩子
  38 +import { onLoad } from '@dcloudio/uni-app';
  39 +import { useUserStore } from '@/pinia/user';
  40 +import { getDailyManageMenu, getProblemManageMenu } from '@/api/workbench';
  41 +
  42 +const userStore = useUserStore();
  43 +const dailyMenuList = ref([
  44 + { id: 1, name: '巡查计划', path: '/pages-sub/daily/patrol-plan/index' },
  45 + { id: 2, name: '工单上报', path: '/pages-sub/daily/work-order/index' },
  46 + { id: 3, name: '快速工单', path: '/pages-sub/daily/quick-order/index' },
  47 + { id: 4, name: '12345工单', path: '/pages-sub/daily/12345-order/index' },
  48 + { id: 5, name: '巡查管理', path: '/pages-sub/daily/patrol-manage/index' },
  49 + { id: 6, name: '养护管理', path: '/pages-sub/daily/maintain-manage/index' }
  50 +]);
  51 +const problemMenuList = ref([]);
  52 +const dataMenuList = ref([
  53 + { id: 1, name: '基础数据', path: '/pages-sub/data/base-data/index' },
  54 + { id: 2, name: '人员轨迹', path: '/pages-sub/data/personnel-track/index' },
  55 + { id: 3, name: '人员管理', path: '/pages-sub/data/personnel-manage/index' },
  56 + { id: 4, name: '行道树档案', path: '/pages-sub/data/tree-archive/index' }
  57 +]);
  58 +
  59 +// 页面加载时加载菜单数据
  60 +onLoad(async () => {
  61 + // await loadMenuData();
  62 +});
  63 +
  64 +const loadMenuData = async () => {
  65 + try {
  66 + const [dailyRes, problemRes] = await Promise.all([getDailyManageMenu(), getProblemManageMenu()]);
  67 + dailyMenuList.value = dailyRes || [
  68 + { id: 1, name: '巡查计划', path: '/pages-sub/daily/patrol-plan/index' },
  69 + { id: 2, name: '工单上报', path: '/pages-sub/daily/work-order/index' },
  70 + { id: 3, name: '快速工单', path: '/pages-sub/daily/quick-order/index' },
  71 + { id: 4, name: '12345工单', path: '/pages-sub/daily/12345-order/index' },
  72 + { id: 5, name: '巡查管理', path: '/pages-sub/daily/patrol-manage/index' },
  73 + { id: 6, name: '养护管理', path: '/pages-sub/daily/maintain-manage/index' }
  74 + ];
  75 + problemMenuList.value = problemRes || [
  76 + { id: 1, name: '工单管理', path: '/pages-sub/problem/order-manage/index' },
  77 + { id: 2, name: '问题分配', path: '/pages-sub/problem/problem-allot/index' }
  78 + ];
  79 + } catch (err) {
  80 + console.error('加载菜单失败:', err);
  81 + // 兜底数据
  82 + dailyMenuList.value = [
  83 + { id: 1, name: '巡查计划', path: '/pages-sub/daily/patrol-plan/index' },
  84 + { id: 2, name: '工单上报', path: '/pages-sub/daily/work-order/index' },
  85 + { id: 3, name: '快速工单', path: '/pages-sub/daily/quick-order/index' },
  86 + { id: 4, name: '12345工单', path: '/pages-sub/daily/12345-order/index' },
  87 + { id: 5, name: '巡查管理', path: '/pages-sub/daily/patrol-manage/index' },
  88 + { id: 6, name: '养护管理', path: '/pages-sub/daily/maintain-manage/index' }
  89 + ];
  90 + problemMenuList.value = [
  91 + { id: 1, name: '工单管理', path: '/pages-sub/problem/order-manage/index' },
  92 + { id: 2, name: '问题分配', path: '/pages-sub/problem/problem-allot/index' }
  93 + ];
  94 + }
  95 +};
  96 +
  97 +const handleMenuClick = (item) => {
  98 + // 权限校验
  99 + if (item.permission && !userStore.permissions.includes(item.permission)) {
  100 + return uni.showToast({ title: '暂无权限', icon: 'none' });
  101 + }
  102 + // 页面跳转
  103 + uni.navigateTo({ url: item.path });
  104 +};
  105 +</script>
  106 +
  107 +<style scoped>
  108 +.workbench-container {
  109 + padding: 20rpx;
  110 + background: #f8f8f8;
  111 + min-height: 100vh;
  112 +}
  113 +.card {
  114 + background: #fff;
  115 + border-radius: 10rpx;
  116 + padding: 20rpx;
  117 + margin-bottom: 20rpx;
  118 +}
  119 +.card-title {
  120 + font-size: 32rpx;
  121 + font-weight: bold;
  122 + margin-bottom: 20rpx;
  123 + padding-bottom: 10rpx;
  124 + border-bottom: 1rpx solid #eee;
  125 +}
  126 +.menu-grid {
  127 + display: flex;
  128 + flex-wrap: wrap;
  129 + justify-content: flex-start;
  130 +}
  131 +.menu-item {
  132 + width: 25%;
  133 + display: flex;
  134 + flex-direction: column;
  135 + align-items: center;
  136 + padding: 20rpx 10rpx;
  137 + box-sizing: border-box;
  138 +}
  139 +.menu-icon {
  140 + width: 80rpx;
  141 + height: 80rpx;
  142 + margin-bottom: 10rpx;
  143 +}
  144 +.menu-text {
  145 + font-size: 24rpx;
  146 + color: #666;
  147 +}
  148 +</style>
  149 +<script setup lang="ts">
  150 +</script>
0 \ No newline at end of file 151 \ No newline at end of file
pinia/index.js 0 → 100644
  1 +++ a/pinia/index.js
  1 +import { createPinia } from 'pinia';
  2 +// 导入持久化插件
  3 +import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
  4 +
  5 +const pinia = createPinia();
  6 +// 注册持久化插件
  7 +pinia.use(piniaPluginPersistedstate);
  8 +
  9 +export default pinia;
0 \ No newline at end of file 10 \ No newline at end of file
pinia/user.js 0 → 100644
  1 +++ a/pinia/user.js
  1 +import { defineStore } from 'pinia';
  2 +import cache from '@/common/utils/cache';
  3 +import globalConfig from '@/common/config/global';
  4 +import { login, getUserInfo, logout, moduleList } from '@/api/user';
  5 +
  6 +export const useUserStore = defineStore('user', {
  7 + // 1. 状态定义(修正 userId 类型 + 字段名)
  8 + state: () => ({
  9 + token: '',
  10 + userInfo: {},
  11 + userId: '',
  12 + moduleListInfo: '',
  13 + expireTime: 0,
  14 + logoutLoading: false // 防重锁移到这里
  15 + }),
  16 +
  17 + // 2. 计算属性(保持不变)
  18 + getters: {
  19 + isLogin: (state) => {
  20 + if (!state.token) return false;
  21 + if (state.expireTime && state.expireTime < Date.now()) {
  22 + return false;
  23 + }
  24 + return true;
  25 + },
  26 + permissions: (state) => state.userInfo.permissions || []
  27 + },
  28 +
  29 + // 3. 核心动作(修正语法错误 + 登录后自动调用户信息接口)
  30 + actions: {
  31 + async login(params) {
  32 + try {
  33 + const res = await login(params);
  34 + const { accessToken, expiresTime, userId } = res;
  35 +
  36 + if (!accessToken) {
  37 + throw new Error('登录失败,未获取到令牌');
  38 + }
  39 +
  40 + // 仅更新 Pinia state(持久化插件会自动同步到 storage)
  41 + this.token = accessToken;
  42 + this.expireTime = expiresTime;
  43 + this.userId = userId;
  44 + this.userInfo = {};
  45 +
  46 + // 移除手动 cache.set() 代码
  47 + // cache.set(globalConfig.cache.tokenKey, accessToken);
  48 + // cache.set(globalConfig.cache.expireTimeKey, expiresTime);
  49 + // cache.set(globalConfig.cache.userIdKey, userId);
  50 +
  51 + // 等待 Pinia 持久化同步(可选,50ms 足够)
  52 + await new Promise(resolve => setTimeout(resolve, 50));
  53 +
  54 + // 获取用户信息
  55 + const userInfo = await this.getUserInfo();
  56 + this.userInfo = userInfo;
  57 + // 移除 cache.set(globalConfig.cache.userInfoKey, userInfo);
  58 +
  59 + // 获取模块列表
  60 + let moduleListInfo = null;
  61 + try {
  62 + moduleListInfo = await this.getModuleList();
  63 + this.moduleListInfo = moduleListInfo;
  64 + // 移除 cache.set(globalConfig.cache.moduleListKey, moduleListInfo);
  65 + } catch (moduleErr) {
  66 + console.warn('获取模块列表失败(不影响登录):', moduleErr);
  67 + uni.showToast({ title: '获取模块列表失败,可正常登录', icon: 'none' });
  68 + }
  69 +
  70 + return { ...res, userInfo, moduleListInfo };
  71 + } catch (err) {
  72 + console.error('登录流程失败:', err);
  73 + throw err;
  74 + }
  75 + },
  76 +
  77 + async getModuleList() {
  78 + try {
  79 + // 前置校验:无 Token 直接抛错,避免无效请求
  80 + if (!this.token) {
  81 + throw new Error('未获取到登录令牌,无法获取模块列表');
  82 + }
  83 +
  84 + // 强制携带 Token(覆盖请求工具的自动携带,避免缓存同步延迟)
  85 + const res = await moduleList({}, {
  86 + header: {
  87 + 'Authorization': `Bearer ${this.token}`
  88 + }
  89 + });
  90 + return res;
  91 + } catch (err) {
  92 + console.error('获取用户菜单失败:', err);
  93 + // 区分 401 错误:仅登录态失效时抛错,避免触发 logout 循环
  94 + if (err?.data?.code === 401 || err?.message.includes('401')) {
  95 + throw new Error('登录态已过期,请重新登录');
  96 + } else {
  97 + throw new Error('获取用户菜单失败,请重新登录');
  98 + }
  99 + }
  100 + },
  101 +
  102 + async getUserInfo() {
  103 + try {
  104 + // 调用用户信息接口(此时 token 已存入缓存,请求工具会自动携带)
  105 + const res = await getUserInfo();
  106 + return res;
  107 + } catch (err) {
  108 + console.error('获取用户信息失败:', err);
  109 + throw new Error('获取用户信息失败,请重新登录');
  110 + }
  111 + },
  112 + logout() {
  113 + const pages = getCurrentPages();
  114 + if (pages.length === 0) return;
  115 +
  116 + const currentPageRoute = pages[pages.length - 1].route;
  117 + const loginPath = globalConfig.router.loginPath
  118 + .replace(/^\//, '')
  119 + .split('?')[0];
  120 +
  121 + if (currentPageRoute === loginPath) {
  122 + // 仅清空 Pinia state(持久化插件自动同步到 storage)
  123 + this.token = '';
  124 + this.userInfo = {};
  125 + this.userId = '';
  126 + this.moduleListInfo = '';
  127 + this.expireTime = 0;
  128 +
  129 + // 移除手动 cache.remove() 代码
  130 + // cache.remove(globalConfig.cache.tokenKey);
  131 + // ... 其他 cache.remove 都删除
  132 + return;
  133 + }
  134 +
  135 + const logoutWithLock = async () => {
  136 + if (this.logoutLoading) return;
  137 + this.logoutLoading = true;
  138 +
  139 + try {
  140 + await logout();
  141 + } catch (err) {
  142 + console.error('退出登录接口调用失败:', err);
  143 + } finally {
  144 + // 清空 state
  145 + this.token = '';
  146 + this.userInfo = {};
  147 + this.userId = '';
  148 + this.moduleListInfo = '';
  149 + this.expireTime = 0;
  150 + this.logoutLoading = false;
  151 +
  152 + // 移除手动 cache.remove() 代码
  153 +
  154 + uni.redirectTo({
  155 + url: globalConfig.router.loginPath
  156 + });
  157 + }
  158 + };
  159 +
  160 + logoutWithLock();
  161 + },
  162 +
  163 +// 新增:状态中添加 logoutLoading 防重锁
  164 + state: () => ({
  165 + token: cache.get(globalConfig.cache.tokenKey) || '',
  166 + userInfo: cache.get(globalConfig.cache.userInfoKey) || {},
  167 + userId: cache.get(globalConfig.cache.userIdKey) || '',
  168 + moduleListInfo: cache.get(globalConfig.cache.moduleListKey) || '',
  169 + expireTime: cache.get(globalConfig.cache.expireTimeKey) || 0,
  170 + logoutLoading: false // 新增:退出登录防重锁
  171 + }),
  172 +
  173 + checkLogin() {
  174 + if (!this.isLogin) {
  175 + // 先判断是否已在登录页,避免重复跳转
  176 + const pages = getCurrentPages();
  177 + if (pages.length === 0) return false;
  178 +
  179 + const currentPageRoute = pages[pages.length - 1].route;
  180 + const loginPath = globalConfig.router.loginPath
  181 + .replace(/^\//, '')
  182 + .split('?')[0];
  183 +
  184 + if (currentPageRoute !== loginPath) {
  185 + uni.redirectTo({
  186 + url: globalConfig.router.loginPath
  187 + });
  188 + }
  189 + return false;
  190 + }
  191 + return true;
  192 + }
  193 + },
  194 +
  195 + // 4. 持久化配置(修正:persist 应放在 store 根层级,非 actions 内)
  196 + persist: {
  197 + enabled: true,
  198 + key: 'user_store', // 自定义存储键名(默认是 store 名 'user')
  199 + storage: {
  200 + getItem: (key) => uni.getStorageSync(key),
  201 + setItem: (key, value) => uni.setStorageSync(key, value),
  202 + removeItem: (key) => uni.removeStorageSync(key)
  203 + },
  204 + // 自定义序列化:将 state 拆分为原有的 jcss_xxx 键(可选)
  205 + serializer: {
  206 + serialize: (state) => {
  207 + // 拆分为多个独立键(和原 cache 格式对齐)
  208 + uni.setStorageSync(globalConfig.cache.tokenKey, state.token);
  209 + uni.setStorageSync(globalConfig.cache.userIdKey, state.userId);
  210 + uni.setStorageSync(globalConfig.cache.expireTimeKey, state.expireTime);
  211 + uni.setStorageSync(globalConfig.cache.userInfoKey, state.userInfo);
  212 + uni.setStorageSync(globalConfig.cache.moduleListKey, state.moduleListInfo);
  213 + return state; // 返回完整 state(兼容 Pinia 默认逻辑)
  214 + },
  215 + deserialize: (value) => {
  216 + // 从多个独立键恢复 state
  217 + return {
  218 + token: uni.getStorageSync(globalConfig.cache.tokenKey) || '',
  219 + userId: uni.getStorageSync(globalConfig.cache.userIdKey) || '',
  220 + expireTime: uni.getStorageSync(globalConfig.cache.expireTimeKey) || 0,
  221 + userInfo: uni.getStorageSync(globalConfig.cache.userInfoKey) || {},
  222 + moduleListInfo: uni.getStorageSync(globalConfig.cache.moduleListKey) || '',
  223 + logoutLoading: false
  224 + };
  225 + }
  226 + },
  227 + paths: [] // 序列化自定义后,paths 可留空
  228 + }
  229 +});
0 \ No newline at end of file 230 \ No newline at end of file
static/icons/home-active.png 0 → 100644

423 Bytes

static/icons/home.png 0 → 100644

379 Bytes

static/icons/mine-active.png 0 → 100644

422 Bytes

static/icons/mine.png 0 → 100644

338 Bytes

static/imgs/logo.png 0 → 100644

3.93 KB

uni.promisify.adaptor.js 0 → 100644
  1 +++ a/uni.promisify.adaptor.js
  1 +uni.addInterceptor({
  2 + returnValue (res) {
  3 + if (!(!!res && (typeof res === "object" || typeof res === "function") && typeof res.then === "function")) {
  4 + return res;
  5 + }
  6 + return new Promise((resolve, reject) => {
  7 + res.then((res) => {
  8 + if (!res) return resolve(res)
  9 + return res[0] ? reject(res[0]) : resolve(res[1])
  10 + });
  11 + });
  12 + },
  13 +});
0 \ No newline at end of file 14 \ No newline at end of file
uni.scss 0 → 100644
  1 +++ a/uni.scss
  1 +/* uni.scss */
  2 +@import '@/uni_modules/uview-plus/theme.scss';
0 \ No newline at end of file 3 \ No newline at end of file
vite.config.js 0 → 100644
  1 +++ a/vite.config.js
  1 +import { defineConfig } from "vite";
  2 +import uni from "@dcloudio/vite-plugin-uni";
  3 +import { visualizer } from "rollup-plugin-visualizer";
  4 +
  5 +// https://vitejs.dev/config/
  6 +export default defineConfig({
  7 + plugins: [
  8 + uni(),
  9 + visualizer()
  10 + ],
  11 + css: {
  12 + preprocessorOptions: {
  13 + scss: {
  14 + // 取消sass废弃API的报警
  15 + silenceDeprecations: ['legacy-js-api', 'color-functions', 'import'],
  16 + },
  17 + },
  18 + },
  19 + server: {
  20 + port: 5100,
  21 + fs: {
  22 + // Allow serving files from one level up to the project root
  23 + allow: ['..']
  24 + }
  25 + },
  26 +});
0 \ No newline at end of file 27 \ No newline at end of file