lisy hai 1 mes
pai
achega
693f60003c

+ 1 - 1
src/assets/styles/public.scss

@@ -92,7 +92,7 @@ $colors: (
     fa: #fafafa,
     gl: #affec2,
     // 主题色
-    primary: #2A6D52,
+    primary: #1D9C3E,
     light-black: #4d4d4d,
     // 成功
     success: #67c23a,

+ 69 - 0
src/components/ut-action-sheet/ut-action-sheet.vue

@@ -0,0 +1,69 @@
+<template>
+    <view @click.stop="showModel = true">
+        <slot></slot>
+    </view>
+    <up-action-sheet :actions="options" :title="title" :show="showModel" @select="selectChange" :closeOnClickOverlay="true" :closeOnClickAction="true" @close="close"></up-action-sheet>
+</template>
+<script setup>
+import { ref, watch } from 'vue';
+
+const props = defineProps({
+    show: {
+        type: Boolean,
+        default: false,
+    },
+    modelValue: {
+        type: Array,
+        default: () => [],
+    },
+
+    title: {
+        type: String,
+        default: '系统提示',
+    },
+    showTitle: {
+        type: Boolean,
+        default: true,
+    },
+    tabs: {
+        type: Array,
+        default: () => [],
+    },
+});
+const options = computed(() => {
+    return props.tabs.map((item) => {
+        return {
+            name: item.label,
+            value: item.value,
+        };
+    });
+});
+const showModel = ref(props.show);
+const emit = defineEmits(['close', 'confirm', 'open', 'update:show', 'update:modelValue', 'change']);
+const checkeds = ref({});
+const close = () => {
+    showModel.value = false;
+    // 如果有初始值,恢复初始值
+    checkeds.value = { ...initialCheckeds.value };
+    emit('update:show', false);
+    emit('close');
+};
+
+const clickItem = (value) => {
+    checkeds.value[value] = !checkeds.value[value];
+};
+// 未选择之前的
+const initialCheckeds = ref({});
+const selectChange = (item) => {
+    console.log(item);
+    emit('update:modelValue', item.value);
+    emit('change', item.value);
+    close();
+};
+watch(
+    () => props.modelValue,
+    (newVal) => {},
+    { immediate: true }
+);
+</script>
+<style lang="scss" scoped></style>

+ 36 - 0
src/components/ut-empty/ut-empty.vue

@@ -0,0 +1,36 @@
+<template>
+    <view class="ut-empty d-flex flex-cln j-c a-c pd-30">
+        <view class="ut-empty__icon">
+            <image :style="{ width, height }" :src="image" mode="aspectFit"></image>
+        </view>
+        <view class="ut-empty__text f-s-24" style="margin-top: -50rpx;" :style="{color}">
+            <slot>
+                {{ text }}
+            </slot>
+        </view>
+    </view>
+</template>
+<script setup>
+const props = defineProps({
+    text: {
+        type: String,
+        default: '暂无数据',
+    },
+    width: {
+        type: String,
+        default: '300rpx',
+    },
+    height: {
+        type: String,
+        default: '300rpx',
+    },
+    image: {
+        type: String,
+        default: 'https://ta.zycpzs.cn/oss-file/smart-trace/szyy/images/common/no_data.png',
+    },
+    color: {
+        type: String,
+        default: '#ccc',
+    },
+});
+</script>

+ 102 - 0
src/components/ut-search/ut-search.vue

@@ -0,0 +1,102 @@
+<template>
+    <!-- 搜索框 -->
+    <view class="search-input d-flex a-c" :class="{ 'up-border': border }" :style="{ margin, background: bgColor, height,borderRadius}">
+        <u-input v-model="value" ref="searchInputRef" cursor cursorColor="#333" clearable type="text" :focus="focused" border="none" @change="inputSearch" @clear="clear" @confirm="search" confirmType="search" :fontSize="fontSize" :maxlength="maxlength" :placeholder="placeholder">
+            <template #suffix>
+                <view @click.stop="search" class="d-flex j-c a-c" style="padding: 0 26rpx;">
+                    <image class="search_icon" src="https://ta.zycpzs.cn/oss-file/smart-trace/szyy/images/common/search_icon.png" mode="widthFix" />
+                </view>
+            </template>
+        </u-input>
+    </view>
+</template>
+<script setup name="search">
+import { defineProps, defineEmits, ref, watch } from 'vue';
+import { debounce } from 'uview-plus';
+const props = defineProps({
+    modelValue: {
+        type: String,
+        default: ''
+    },
+    margin: {
+        type: String,
+        default: '24rpx'
+    },
+    placeholder: {
+        type: String,
+        default: '请输入搜索内容'
+    },
+    maxlength: {
+        type: Number,
+        default: 140
+    },
+    fontSize: {
+        type: String,
+        default: '30rpx'
+    },
+    height: {
+        type: String,
+        default: '86rpx'
+    },
+    border: {
+        type: Boolean,
+        default: true
+    },
+    // 背景颜色
+    bgColor: {
+        type: String,
+        default: '#fff'
+    },
+    borderRadius: {
+        type: String,
+        default: '84rpx'
+    }
+});
+const value = ref(props.modelValue);
+const emit = defineEmits(['search', 'update:modelValue', 'change']);
+const inputSearch = (e) => {
+    debounce(() => {
+        emit('update:modelValue', e);
+        emit('change', e);
+    }, 500);
+};
+const search = (e) => {
+    debounce(() => {
+        emit('update:modelValue', value.value);
+        emit('search', value.value);
+    }, 500);
+};
+const clear = () => {
+    emit('update:modelValue', '');
+    emit('change', '');
+    emit('search', '');
+};
+const searchInputRef = ref(null);
+const focused = ref(false);
+const doFocus = () => {
+    focused.value = true;
+    // searchInputRef.value.doFocus();
+};
+// 监听输入框的输入事件,触发更新modelValue的值
+watch(
+    () => props.modelValue,
+    (val) => {
+        value.value = val;
+    }
+);
+defineExpose({
+    doFocus
+});
+</script>
+<style lang="scss" scoped>
+.search-input {
+    height: 86rpx;
+    background-color: #fff;
+    padding-left: 30rpx;
+}
+
+.search_icon {
+    width: 38rpx;
+    height: 38rpx;
+}
+</style>

+ 25 - 0
src/main.ts

@@ -6,10 +6,35 @@ import { selectDictLabel, selectDictLabels } from './utils/ruoyi';
 import { useDict } from '@/utils/dict';
 import uviewPlus from 'uview-plus';
 import { navigateBackOrHome, showToast } from '@/utils/common';
+import routerGuard from '@/uni_modules/hh-router-guard/src/index';
 export function createApp() {
     const app = createSSRApp(App);
     app.use(Pinia.createPinia());
     app.use(uviewPlus);
+    app.use(routerGuard, {
+        // 白名单:无需登录即可访问的页面路径
+        whiteList: [
+            '/pages/login/login', // 登录页
+            '/pages/plant/index',
+        ],
+
+        // 自定义登录检查函数(返回 true 表示已登录)
+        checkLogin: () => {
+            const token = uni.getStorageSync('token');
+            return !!token;
+        },
+
+        // 登录页面路径 === 需要替换为实际项目中登录页面的路径
+        loginPath: '/pages/login/login',
+
+        // 未登录时的处理逻辑
+        loginHandler: (to: string) => {
+            console.log(to);
+            uni.navigateTo({
+                url: `/pages/login/login?redirect=${encodeURIComponent(to)}`,
+            });
+        },
+    });
     app.config.globalProperties.navigateBackOrHome = navigateBackOrHome;
     app.config.globalProperties.showToast = showToast;
     app.config.globalProperties.selectDictLabel = selectDictLabel;

+ 2 - 2
src/pages.json

@@ -29,13 +29,13 @@
         {
             "path": "pages/plant/index",
             "style": {
-                "navigationBarTitleText": "种植端",
+                "navigationBarTitleText": "种植端"
             }
         },
         {
             "path": "pages/production/index",
             "style": {
-                "navigationBarTitleText": "生产端",
+                "navigationBarTitleText": "生产端"
             }
         }
     ],

+ 89 - 5
src/pages/plant/index.vue

@@ -1,15 +1,89 @@
 <template>
-    <z-paging ref="paging" v-model="list" @onRefresh="onRefresh">
-        <template #top>
-            <up-navbar title="种植端首页" @leftClick="navigateBackOrHome()" :fixed="false"></up-navbar>
+    <z-paging ref="paging" v-model="list" @onRefresh="onRefresh" bgColor="#f7f7f7">
+        <up-navbar :fixed="false" bgColor="rgba(0,0,0,0)">
+            <template #left>
+                <view class="d-flex a-c" :style="{ width: `${bubble.left - 30}px` }">
+                    <image class="home_icon mr-20" src="https://ta.zycpzs.cn/oss-file/smart-trace/szyy/images/common/home.png" mode="widthFix" />
+                    <text class="f-s-42 c-333 f-w-5 mr-40">中药材种植全链条追溯</text>
+                    <view class="flex1"></view>
+                </view>
+            </template>
+        </up-navbar>
+        <view class="h-500 w-100%" style="background: linear-gradient(to left, #d2f7d5, #eafad8); position: absolute; top: 0; left: 0; z-index: -1"></view>
+        <template>
+            <view class="user-page-header pd-10 d-flex a-c mg-14 p-rtv">
+                <view class="user-page-header-avatar mr-20 p-rtv">
+                    <up-avatar size="116rpx" :src="avatar || 'https://ta.zycpzs.cn/oss-file/smart-trace/szyy/images/common/avatar.png'"></up-avatar>
+                </view>
+                <view class="flex1 ov-hd mr-40">
+                    <view class="p-rtv d-flex a-c mb-6">
+                        <view class="flex1 ov-hd f-s-32 c-333 d-flex a-ed">
+                            <text class="mr-12 up-line-1 f-w-5">{{ name }}</text>
+                            <text class="c-999 f-s-24">{{ setCipByNum(phone, 3, 4) || '-' }}</text>
+                        </view>
+                    </view>
+                    <view class="f-s-22 mr-10 b-radius pt-4 pb-4 pl-10 pr-10 c-primary" style="width: max-content; background-color: #b7e8bc">
+                        {{ cpyname }}
+                    </view>
+                </view>
+            </view>
+        </template>
+        <template>
+            <view class="p-rtv">
+                <view class="pd-10 mg-14">
+                    <view class="b-radius pd-4" style="border: 1rpx solid #fff; background: linear-gradient(90deg, #c1f3c5 0%, rgba(193, 243, 197, 0.5) 20%, rgba(255, 255, 255, 0.5) 35%, rgba(255, 255, 255, 0.5) 50%, rgba(232, 255, 234, 0.5) 100%, #e8ffea 100%), linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.3) 30%, rgba(255, 255, 255, 0.3) 80%, transparent 100%)">
+                        <view class="b-radius pd-10" style="border: 1rpx solid #baedbf">
+                            <view class="d-flex pr-15 mb-15">
+                                <view></view>
+                                <view>单位主营物种</view>
+                                <view class="flex1"></view>
+                                <view class="f-s-22 c-primary">去修改{{ '>' }}</view>
+                            </view>
+                            <view class="c-#333 f-s-24 d-flex pl-30 pr-15 pb-15">
+                                <view class="ov-hd tx-ov w-s-no">三七、天麻、徐长卿、白及、徐长卿、白及、三七、天麻、徐长卿、白及、徐长卿、白及</view>
+                                <view class="flex1 w-s-no">等120个物种</view>
+                            </view>
+                        </view>
+                    </view>
+                </view>
+                <view class="b-radius pd-16 bg-#f7f7f7" style="border: 1rpx solid #fff; margin-top: -40rpx">
+                    <view class="d-flex a-c">
+                        <view class="c-333 f-s-32 f-w-5">基地与地块管理</view>
+                        <view class="flex1"></view>
+                        <view class="c-primary f-s-22">GAP基地获评信息管理></view>
+                    </view>
+                    <view class="d-flex a-c pt-20">
+                        <view class="w-200">
+                            <ut-action-sheet v-model="form.type" :tabs="[{ label: '全部', value: '' }]" @change="onRefresh" title="选择原料类型">
+                                <view class="d-flex search-select-item a-c">
+                                    <view class="flex1 ov-hd f-s-28 c-333 text-center f-w-5">{{ '全部' }}</view>
+                                    <up-icon size="20rpx" color="#2A6D52" name="arrow-down-fill" class="mr-10"></up-icon>
+                                </view>
+                            </ut-action-sheet>
+                        </view>
+                        <view class="h-86 pl-20 pr-20">
+                            <ut-search ref="searchRef" v-model="form.keywords" @search="changeSeach" margin="0" :border="false" :placeholder="form.placeholder" bgColor="#fff" height="86rpx" borderRadius="10rpx"></ut-search>
+                        </view>
+                    </view>
+                    <template #empty>
+                        <ut-empty class="mg-at">尚未添加绘制种养殖基地信息~</ut-empty>
+                    </template>
+                </view>
+            </view>
         </template>
-        <view class="c-primary">种植端首页</view>
     </z-paging>
 </template>
 <script setup lang="ts">
 import { useClientRequest } from '@/utils/request';
+import { setCipByNum } from '@/utils/public';
 const list = ref([]);
 const paging = ref();
+const bubble = ref(uni.getMenuButtonBoundingClientRect());
+const avatar = ref();
+const name = ref('神奇大侠');
+const phone = ref('17708862791');
+const cpyname = ref('智慧溯源有限公司');
+const form = ref({ type: '', placeholder: '搜基地名称、编号、地址、负责人' });
 const onRefresh = () => {
     paging.value.reload();
 };
@@ -18,5 +92,15 @@ setTimeout(() => {
 }, 2000);
 </script>
 <style lang="scss" scoped>
-@import '@/assets/styles/theme.scss';
+// @import '@/assets/styles/theme.scss';
+.search-select-item {
+    height: 86rpx;
+    background-color: #fff;
+    border-radius: 10rpx;
+    box-sizing: border-box;
+    padding-left: 6rpx;
+    padding-right: 20rpx;
+    padding-top: 14rpx;
+    padding-bottom: 14rpx;
+}
 </style>

+ 42 - 0
src/types/hh-router-guard.d.ts

@@ -0,0 +1,42 @@
+/**
+ * Type declarations for hh-router-guard module
+ */
+
+declare module '@/uni_modules/hh-router-guard/src/index' {
+  import type { App } from 'vue';
+  
+  export interface RouterGuardOptions {
+    whiteList?: string[];
+    loginPath?: string;
+    checkLogin?: () => boolean;
+    loginHandler?: (to: string) => void;
+    errorHandler?: (error: Error) => void;
+  }
+  
+  export interface RouterGuardInstance {
+    install: (app: App, options?: RouterGuardOptions) => void;
+  }
+  
+  const routerGuard: RouterGuardInstance;
+  export default routerGuard;
+}
+
+// Also declare the module for the types file
+declare module '@/uni_modules/hh-router-guard/src/types' {
+  export interface RouterGuardOptions {
+    whiteList?: string[];
+    loginPath?: string;
+    checkLogin?: () => boolean;
+    loginHandler?: (to: string) => void;
+    errorHandler?: (error: Error) => void;
+  }
+  
+  export interface RouterGuardInstance {
+    config: Required<RouterGuardOptions>;
+    check: (path: string) => boolean;
+  }
+  
+  export interface Plugin {
+    install: (app: import('vue').App, ...options: any[]) => any;
+  }
+}

+ 1 - 1
src/uni.scss

@@ -18,7 +18,7 @@
 @import '@/assets/styles/common.scss';
 @import '@/assets/styles/uview-plus.scss';
 /* 行为相关颜色 */
-$uni-color-primary: #007aff;
+$uni-color-primary: #1D9C3E;
 $uni-color-success: #4cd964;
 $uni-color-warning: #f0ad4e;
 $uni-color-error: #dd524d;

+ 206 - 0
src/uni_modules/hh-router-guard/README.md

@@ -0,0 +1,206 @@
+# hh-router-guard
+
+## 一、插件简介
+`hh-router-guard` 是一款专为 **UniApp** 框架设计的路由守卫插件,基于 **Vue 3** 构建。它提供了强大的页面访问权限控制、登录拦截和白名单功能,帮助开发者轻松管理应用的路由权限,提升应用安全性和用户体验。
+
+通过简单配置,您可以:
+- 拦截所有页面跳转请求,实现统一权限校验
+- 强制未登录用户跳转至登录页面
+- 灵活定义无需登录即可访问的白名单页面
+- 自定义登录状态检查逻辑和错误处理机制
+
+## 二、版本信息
+- 当前版本:`1.0.0`
+- 更新日期:2025-05-12
+- 兼容性:支持所有 UniApp 支持的平台(微信小程序、H5、App、支付宝小程序等)
+
+## 三、安装与引入
+
+### 方式一:从 DCloud 插件市场下载
+1. 访问 [hh-router-guard 插件详情页](https://ext.dcloud.net.cn/plugin?id=XXXX)
+2. 点击「使用 HBuilderX 导入插件」按钮,将插件导入到您的项目中
+3. 插件会自动被放置在项目的 `uni_modules` 目录下
+
+### 方式二:手动导入
+1. 下载插件源码压缩包
+2. 将解压后的文件复制到项目的 `uni_modules/hh-router-guard` 目录
+
+### 引入插件
+在项目的 `main.js` 中引入并注册插件:
+
+```javascript
+import { createSSRApp } from 'vue'
+import App from './App.vue'
+// 从 uni_modules 引入插件
+import routerGuard from '@/uni_modules/hh-router-guard/src/index'
+
+export function createApp() {
+  const app = createSSRApp(App)
+  
+  // 安装路由守卫插件
+  app.use(routerGuard, {
+    // 配置选项
+  })
+  
+  return { app }
+}
+```
+
+## 四、快速上手
+
+### 基础配置
+以下是一个基础配置示例,展示如何设置白名单和登录检查逻辑:
+
+```javascript
+app.use(routerGuard, {
+  // 白名单:无需登录即可访问的页面路径
+  whiteList: [
+    '/pages/login/index',      // 登录页
+    '/pages/public/*',         // 所有公共页面
+    '/pages/about',            // 关于页
+  ],
+  
+  // 自定义登录检查函数(返回 true 表示已登录)
+  checkLogin: () => {
+    const token = uni.getStorageSync('token')
+    return !!token
+  },
+  
+  // 登录页面路径 === 需要替换为实际项目中登录页面的路径
+  loginPath: '/pages/login/index',
+  
+  // 未登录时的处理逻辑
+  loginHandler: (to) => {
+    uni.navigateTo({
+      url: `${loginPath}?redirect=${encodeURIComponent(to)}`
+    })
+  }
+})
+```
+
+### 完整配置选项
+| 选项           | 类型       | 必填 | 默认值                     | 描述                                                                 |
+|----------------|------------|------|----------------------------|----------------------------------------------------------------------|
+| `whiteList`    | `Array`    | 否   | `['/pages/login/index']`   | 白名单页面路径数组,支持通配符 `*`                                   |
+| `checkLogin`   | `Function` | 否   | `() => uni.getStorageSync('token')` | 自定义登录检查函数,返回布尔值表示是否已登录                  |
+| `loginPath`    | `String`   | 否   | `/pages/login/index`       | 登录页面的路径                                                       |
+| `loginHandler` | `Function` | 否   | 跳转到登录页并携带 redirect 参数 | 未登录时的处理函数,接收目标路径作为参数                     |
+| `errorHandler` | `Function` | 否   | 打印错误信息               | 错误处理函数,用于捕获插件运行时的异常                               |
+
+## 五、高级用法
+
+### 自定义错误处理
+您可以通过 `errorHandler` 选项自定义错误处理逻辑:
+
+```javascript
+app.use(routerGuard, {
+  // ...其他配置
+  errorHandler: (error) => {
+    uni.showToast({
+      title: `路由错误: ${error.message}`,
+      icon: 'none'
+    })
+  }
+})
+```
+
+### 在组件中使用
+插件会在 Vue 实例上挂载 `$routerGuard` 对象,您可以在组件中访问:
+
+####  选项式 API 写法(Vue 3 兼容模式)
+Vue 3 的选项式 API 与 Vue 2 语法基本兼容,this.$routerGuard 仍然可以直接访问(因为插件通过 app.config.globalProperties 挂载了全局属性),只需保持组件结构一致即可:
+```javascript
+// Vue 3 选项式 API 写法
+export default {
+  methods: {
+    checkPermission() {
+      // 与 Vue 2 写法一致,通过 this 访问
+      const isAllowed = this.$routerGuard.check('/pages/protected')
+      console.log('当前用户是否有权限:', isAllowed)
+    }
+  }
+}
+```
+#### 组合式 API 写法(<script setup> 推荐)
+在 Vue 3 组合式 API 中,不推荐使用 this,而是通过插件提供的 useRouterGuard 钩子获取实例(源码中 src/useRouterGuard.ts 已定义该钩子):
+```vue
+<!-- Vue 3 组合式 API 写法(<script setup>) -->
+<script setup>
+// 导入组合式 API 钩子
+import { useRouterGuard } from '@/uni_modules/hh-router-guard/src/useRouterGuard'
+
+// 获取路由守卫实例
+const routerGuard = useRouterGuard()
+
+// 定义方法
+const checkPermission = () => {
+  const isAllowed = routerGuard.check('/pages/protected')
+  console.log('当前用户是否有权限:', isAllowed)
+}
+</script>
+```
+
+## 六、示例项目
+以下是一个完整的项目示例结构,展示如何集成和使用 `hh-router-guard`:
+
+```
+your-project/
+├── pages/
+│   ├── login/
+│   │   └── index.vue         # 登录页面
+│   ├── public/
+│   │   ├── index.vue         # 公共页面
+│   │   └── about.vue         # 关于页面
+│   └── protected/
+│       └── index.vue         # 需要登录才能访问的页面
+├── uni_modules/
+│   └── hh-router-guard/     # 插件目录
+├── App.vue
+└── main.js                   # 引入和配置插件的文件
+```
+
+## 七、更新日志
+### v1.1.1 (2025-10-22)
+- 修改README文档
+### v1.1.0 (2025-06-05)
+- 增加TypeScript支持
+### v1.0.0 (2025-05-12)
+- 初始版本发布,支持基本的路由拦截、白名单和登录检查功能
+- 新增:支持自定义错误处理函数
+- 优化:增强插件的容错能力,避免因配置错误导致应用崩溃
+- 改进:添加版本信息输出,方便追踪插件版本
+
+## 八、常见问题
+
+### 1. 如何解决 "routerGuard is not defined" 错误?
+- 确保插件路径正确,特别是从 `uni_modules` 引入时
+- 检查插件是否正确导出(使用 `export default`)
+- 尝试重启 HBuilderX 清除缓存
+
+### 2. 白名单路径支持哪些匹配模式?
+- 完全匹配:如 `/pages/login/index`
+- 前缀匹配:如 `/pages/public/*` 会匹配所有以 `/pages/public/` 开头的路径
+
+### 3. 如何在小程序和 H5 中使用不同的登录逻辑?
+您可以在 `checkLogin` 函数中通过 `uni.getSystemInfoSync().platform` 判断运行环境,实现差异化逻辑:
+
+```javascript
+checkLogin: () => {
+  if (uni.getSystemInfoSync().platform === 'h5') {
+    // H5 平台的登录检查逻辑
+    return localStorage.getItem('token') !== null
+  } else {
+    // 小程序平台的登录检查逻辑
+    return uni.getStorageSync('token') !== ''
+  }
+}
+```
+
+## 九、贡献与反馈
+如果您在使用过程中遇到问题或有任何建议,欢迎:
+- 在 [插件市场评论区](https://ext.dcloud.net.cn/plugin?id=23410) 留言反馈
+- 提交 Issues 到 [Github 仓库](https://github.com/hh-d/hh-router-guard/issues)
+- 提交 Pull Requests 参与开发
+
+## 十、许可证
+本插件采用 [MIT 许可证](https://opensource.org/licenses/MIT) 发布,您可以自由使用、修改和分发。

+ 2 - 0
src/uni_modules/hh-router-guard/changelog.md

@@ -0,0 +1,2 @@
+## 1.1.2(2025-10-22)
+- 修改README文档

+ 95 - 0
src/uni_modules/hh-router-guard/package.json

@@ -0,0 +1,95 @@
+{
+  "id": "hh-router-guard",
+  "name": "UniApp 智能路由拦截插件 - hh-router-guard",
+  "displayName": "UniApp 智能路由拦截插件 - hh-router-guard",
+  "keywords": [
+    "uniapp",
+    "vue3",
+    "路由守卫",
+    "权限控制",
+    "登录拦截"
+],
+  "repository": "https://github.com/hh-d/hh-router-guard",
+  "version": "1.1.2",
+  "description": "hh-router-guard 是专为 UniApp 打造的路由守卫插件,基于 Vue 3 开发。支持智能登录拦截、灵活白名单配置,可自定义登录校验逻辑,具备强大错误处理能力,多端无缝兼容,轻量高效",
+  "dependencies": {},
+  "uni_modules": {
+    "description": "Uniapp路由守卫插件,提供页面访问权限控制、登录拦截和白名单功能",
+    "compatible": {
+      "app-plus": true,
+      "h5": true,
+      "mp-weixin": true,
+      "mp-alipay": true,
+      "mp-baidu": true,
+      "mp-toutiao": true,
+      "mp-qq": true
+    },
+    "platforms": {
+      "cloud": {
+        "tcb": "√",
+        "aliyun": "√",
+        "alipay": "√"
+      },
+      "client": {
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "n",
+          "app-harmony": "u",
+          "app-uvue": "u"
+        },
+        "H5-mobile": {
+          "Safari": "y",
+          "Android Browser": "y",
+          "微信浏览器(Android)": "y",
+          "QQ浏览器(Android)": "y"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "y",
+          "Edge": "y",
+          "Firefox": "y",
+          "Safari": "y"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "y",
+          "百度": "y",
+          "QQ": "y"
+        },
+        "快应用": {
+          "华为": "y",
+          "联盟": "y"
+        }
+      },
+      "extra": {
+        "mp-weixin": {
+          "usingComponents": {}
+        }
+      }
+    },
+    "author": "Eli_Huang",
+    "license": "MIT",
+    "bugs": {
+      "url": "https://github.com/hh-d/hh-router-guard/issues"
+    },
+    "homepage": "https://ext.dcloud.net.cn/plugin?id=23410"
+  },
+  "dcloudext": {
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "插件不采集任何数据",
+      "permissions": "无"
+    },
+    "npmurl": "",
+    "darkmode": "x",
+    "i18n": "x",
+    "widescreen": "√"
+  },
+"engines": {
+    "uni-app": "^3.6.11",
+    "uni-app-x": "^3.6.11"
+  }
+}

+ 124 - 0
src/uni_modules/hh-router-guard/src/index.ts

@@ -0,0 +1,124 @@
+/**
+ * hh-router-guard - 基于Vue 3的Uniapp路由守卫插件
+ * 提供页面访问权限控制、登录拦截和白名单功能
+ */
+import type { App } from 'vue';
+import { inWhiteList } from './utils';
+import { 
+  RouterGuardOptions,
+  RouterGuardInstance,
+	Plugin,
+} from './types';
+
+// 定义默认配置
+const defaultOptions: Required<RouterGuardOptions> = {
+  whiteList: ['/pages/login/index'],
+  loginPath: '/pages/login/index',
+  checkLogin: () => !!uni.getStorageSync('token'),
+  loginHandler: (to) => {
+    uni.navigateTo({
+      url: `${defaultOptions.loginPath}?redirect=${encodeURIComponent(to)}`
+    });
+  },
+  errorHandler: (error) => {
+    console.error('[hh-router-guard] 错误:', error);
+  }
+};
+
+// 插件主类
+class RouterGuard implements Plugin {
+  // 插件版本号
+  public static version: string = '1.1.0';
+  
+  // 安装插件
+  public install(app: App, options: RouterGuardOptions = {}): void {
+    // 合并默认配置
+    const config: Required<RouterGuardOptions> = {
+      ...defaultOptions,
+      ...options
+    };
+
+    // 配置验证
+    this.validateConfig(config);
+
+    // 注册全局属性
+    app.config.globalProperties.$routerGuard = {
+      config,
+      check: (path: string) => this.handleRouteIntercept(path, config)
+    } as RouterGuardInstance;
+
+    // 注册路由拦截器
+    this.registerInterceptors(config);
+
+    console.log(`[hh-router-guard v${RouterGuard.version}] 路由守卫已启用`);
+  }
+
+  // 验证配置选项
+  private validateConfig(config: Required<RouterGuardOptions>): void {
+    if (!Array.isArray(config.whiteList)) {
+      config.errorHandler(new Error('whiteList 必须是数组'));
+      config.whiteList = [];
+    }
+
+    if (typeof config.checkLogin !== 'function') {
+      config.errorHandler(new Error('checkLogin 必须是函数'));
+      config.checkLogin = defaultOptions.checkLogin;
+    }
+
+    if (typeof config.loginHandler !== 'function') {
+      config.errorHandler(new Error('loginHandler 必须是函数'));
+      config.loginHandler = defaultOptions.loginHandler;
+    }
+
+    if (typeof config.errorHandler !== 'function') {
+      config.errorHandler = defaultOptions.errorHandler;
+    }
+  }
+
+  // 注册路由拦截器
+  private registerInterceptors(config: Required<RouterGuardOptions>): void {
+    const interceptors: Array<keyof typeof uni> = ['navigateTo', 'redirectTo', 'reLaunch', 'switchTab'];
+    
+    interceptors.forEach(method => {
+      uni.addInterceptor(method, {
+        invoke: (args: { url: string }) => {
+          try {
+            return this.handleRouteIntercept(args.url, config);
+          } catch (error) {
+            config.errorHandler(error instanceof Error ? error : new Error(String(error)));
+            return true; // 发生错误时默认放行
+          }
+        }
+      });
+    });
+  }
+
+  // 处理路由拦截逻辑
+  private handleRouteIntercept(url: string, config: Required<RouterGuardOptions>): boolean {
+    try {
+      const path = url.split('?')[0];
+      
+      // 使用工具函数检查白名单
+      if (inWhiteList(path, config.whiteList)) {
+        return true;
+      }
+      
+      // 检查登录状态
+      const isLoggedIn = config.checkLogin();
+      
+      if (!isLoggedIn) {
+        // 未登录,执行登录处理逻辑
+        config.loginHandler(url);
+        return false;
+      }
+      
+      return true;
+    } catch (error) {
+      config.errorHandler(error instanceof Error ? error : new Error(String(error)));
+      return true; // 发生错误时默认放行
+    }
+  }
+}
+
+// 导出插件实例
+export default new RouterGuard();

+ 42 - 0
src/uni_modules/hh-router-guard/src/types.ts

@@ -0,0 +1,42 @@
+/**
+ * hh-router-guard 插件类型定义
+ */
+
+// 白名单路径格式
+export type WhitePath = string;
+
+// 登录检查函数类型
+export type CheckLoginFunction = () => boolean;
+
+// 登录处理函数类型
+export type LoginHandlerFunction = (to: string) => void;
+
+// 错误处理函数类型
+export type ErrorHandlerFunction = (error: Error) => void;
+
+// 插件配置选项接口
+export interface RouterGuardOptions {
+  whiteList?: WhitePath[];
+  loginPath?: string;
+  checkLogin?: CheckLoginFunction;
+  loginHandler?: LoginHandlerFunction;
+  errorHandler?: ErrorHandlerFunction;
+}
+
+// 插件实例接口
+export interface RouterGuardInstance {
+  config: Required<RouterGuardOptions>;
+  check: (path: string) => boolean;
+}
+
+// Vue 3 插件接口定义
+export interface Plugin {
+  install: (app: import('vue').App, ...options: any[]) => any;
+}
+
+// 扩展Vue应用类型
+declare module 'vue' {
+  interface ComponentCustomProperties {
+    $routerGuard: RouterGuardInstance;
+  }
+}

+ 15 - 0
src/uni_modules/hh-router-guard/src/useRouterGuard.ts

@@ -0,0 +1,15 @@
+/**
+ * 组合式API钩子:在setup中获取路由守卫实例
+ */
+import { inject } from 'vue';
+import { RouterGuardInstance } from './types';
+
+export function useRouterGuard(): RouterGuardInstance {
+  const routerGuard = inject('routerGuard') as RouterGuardInstance | undefined;
+  
+  if (!routerGuard) {
+    throw new Error('请先安装 hh-router-guard 插件');
+  }
+  
+  return routerGuard;
+}

+ 37 - 0
src/uni_modules/hh-router-guard/src/utils.ts

@@ -0,0 +1,37 @@
+/**
+ * 检查路径是否匹配规则
+ * @param path - 待检查的路径
+ * @param pattern - 匹配规则(支持通配符*)
+ * @returns 是否匹配
+ */
+export function matchPath(path: string, pattern: string): boolean {
+  try {
+    if (pattern.endsWith('*')) {
+      return path.startsWith(pattern.slice(0, -1))
+    }
+    
+    return path === pattern
+  } catch (error) {
+    console.error('[hh-router-guard] 路径匹配错误:', error)
+    return false
+  }
+}
+
+/**
+ * 检查路径是否在白名单中
+ * @param path - 待检查的路径
+ * @param whiteList - 白名单列表
+ * @returns 是否在白名单中
+ */
+export function inWhiteList(path: string, whiteList: string[]): boolean {
+  try {
+    if (!Array.isArray(whiteList)) {
+      return false
+    }
+    
+    return whiteList.some(pattern => matchPath(path, pattern))
+  } catch (error) {
+    console.error('[hh-router-guard] 白名单检查错误:', error)
+    return false
+  }
+}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
stats.html


+ 2 - 0
unocss.config.js

@@ -107,8 +107,10 @@ export default defineConfig({
         [/^f-w-([\.\d]+)$/, ([_, num]) => ({ 'font-weight': `${num > 100 ? num : num * 100}` })],
         // 宽
         [/^w-([\.\d]+)$/, ([_, num]) => ({ width: `${num}rpx` })],
+        [/^w-([\.\d]+)%$/, ([_, num]) => ({ width: `${num}%` })],
         // 高
         [/^h-([\.\d]+)$/, ([_, num]) => ({ height: `${num}rpx` })],
+        [/^h-([\.\d]+)%$/, ([_, num]) => ({ height: `${num}%` })],
         // 带透明度的可传入颜色计算rgba值加背景
         // 行高
         [/^lh-([\.\d]+)$/, ([_, num]) => ({ 'line-height': `${num}` })],

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio