feat(订单): 新增商品借还动态功能

- 添加商品借还动态页面及路由配置
- 实现借还动态列表展示、下拉刷新和加载更多功能
- 支持预览商品封面、归还图片和审核图片
- 新增相关API接口和类型定义
- 调整底部导航栏,添加商品动态入口
- 优化审批中心与耗材核销的导航位置
- 更新README文档,移除lint相关命令
- 配置Vite代理,调整API基础路径
This commit is contained in:
dzq 2025-12-10 17:56:46 +08:00
parent 45ac3a9bd2
commit 3b0dada98c
15 changed files with 1213 additions and 82 deletions

View File

@ -1,7 +1,7 @@
# 开发环境的环境变量(命名必须以 VITE_ 开头) # 开发环境的环境变量(命名必须以 VITE_ 开头)
## 后端接口地址(如果解决跨域问题采用反向代理就只需写相对路径) ## 后端接口地址(如果解决跨域问题采用反向代理就只需写相对路径)
VITE_BASE_URL = http://localhost:8090/api VITE_BASE_URL = /api
## 开发环境域名和静态资源公共路径(一般 / 或 ./ 都可以) ## 开发环境域名和静态资源公共路径(一般 / 或 ./ 都可以)
VITE_PUBLIC_PATH = / VITE_PUBLIC_PATH = /

View File

@ -28,30 +28,21 @@ pnpm i
# Start development server (port 3333, opens browser) # Start development server (port 3333, opens browser)
pnpm dev pnpm dev
# Build for staging environment
pnpm build:staging
# Build for production (includes zip) # Build for production (includes zip)
pnpm build pnpm build
# Preview production build
pnpm preview
# Lint and auto-fix code
pnpm lint
# Run unit tests
pnpm test
``` ```
## Environment Configuration ## Environment Configuration
Three environment files exist: Three environment files exist:
- `.env.development` - Development settings - `.env.development` - Development settings
- `.env.staging` - Staging settings - `.env.staging` - Staging settings
- `.env.production` - Production settings - `.env.production` - Production settings
Key environment variables: Key environment variables:
- `VITE_BASE_URL` - API base URL - `VITE_BASE_URL` - API base URL
- `VITE_PUBLIC_PATH` - Public path for deployment - `VITE_PUBLIC_PATH` - Public path for deployment
- `VITE_ROUTER_HISTORY` - Router mode (`hash` or `history`) - `VITE_ROUTER_HISTORY` - Router mode (`hash` or `history`)
@ -65,12 +56,15 @@ Key environment variables:
## Code Architecture ## Code Architecture
### Entry Point (`src/main.ts`) ### Entry Point (`src/main.ts`)
Application bootstrapping: Application bootstrapping:
1. Creates Vue app instance 1. Creates Vue app instance
2. Installs plugins (global components, custom directives) 2. Installs plugins (global components, custom directives)
3. Mounts app after router is ready 3. Mounts app after router is ready
### Routing (`src/router/`) ### Routing (`src/router/`)
- **index.ts**: Main router configuration with two route groups: - **index.ts**: Main router configuration with two route groups:
- `systemRoutes`: Error pages (403, 404) - `systemRoutes`: Error pages (403, 404)
- `routes`: Business routes (cabinet, approval, orders, rentals, products) - `routes`: Business routes (cabinet, approval, orders, rentals, products)
@ -78,16 +72,20 @@ Application bootstrapping:
- **guard.ts**: Route navigation guards for authentication and permission checks - **guard.ts**: Route navigation guards for authentication and permission checks
### HTTP Client (`src/http/axios.ts`) ### HTTP Client (`src/http/axios.ts`)
- Axios instance with request/response interceptors - Axios instance with request/response interceptors
- Automatic token handling via cookies - Automatic token handling via cookies
- Standardized error handling for HTTP status codes - Standardized error handling for HTTP status codes
- Business logic codes (0 = success, 401 = unauthorized) - Business logic codes (0 = success, 401 = unauthorized)
### State Management (`src/pinia/`) ### State Management (`src/pinia/`)
Pinia stores for application state. Check `src/pinia/stores/` for store implementations. Pinia stores for application state. Check `src/pinia/stores/` for store implementations.
### API Layer (`src/common/apis/`) ### API Layer (`src/common/apis/`)
Organized by business domain: Organized by business domain:
- `ab98/` - AB98 login integration - `ab98/` - AB98 login integration
- `approval/` - Approval workflow APIs - `approval/` - Approval workflow APIs
- `cabinet/` - Cabinet management APIs - `cabinet/` - Cabinet management APIs
@ -96,12 +94,14 @@ Organized by business domain:
- `users/` - User management APIs - `users/` - User management APIs
### Utilities (`src/common/`) ### Utilities (`src/common/`)
- `utils/` - Helper functions (cache, datetime, validation, permissions, wx) - `utils/` - Helper functions (cache, datetime, validation, permissions, wx)
- `composables/` - Vue composables (watermark, grayscale, dark mode) - `composables/` - Vue composables (watermark, grayscale, dark mode)
- `components/` - Shared components - `components/` - Shared components
- `constants/` - Application constants - `constants/` - Application constants
### Plugins (`src/plugins/`) ### Plugins (`src/plugins/`)
- `index.ts` - Plugin registration - `index.ts` - Plugin registration
- `console.ts` - VConsole integration for mobile debugging - `console.ts` - VConsole integration for mobile debugging
- `permission-directive.ts` - v-permission directive for button-level permissions - `permission-directive.ts` - v-permission directive for button-level permissions
@ -117,6 +117,7 @@ Organized by business domain:
- **Vue SFC Order**: [script, template, style] - **Vue SFC Order**: [script, template, style]
**Important**: **Important**:
- Disable Prettier in VS Code settings (already configured) - Disable Prettier in VS Code settings (already configured)
- ESLint auto-fixes on save - ESLint auto-fixes on save
- Stylistic rules are silently enforced - Stylistic rules are silently enforced
@ -124,6 +125,7 @@ Organized by business domain:
## Testing ## Testing
Tests use **Vitest** with **Happy DOM** environment: Tests use **Vitest** with **Happy DOM** environment:
- Test files: `tests/**/*.test.{ts,js}` - Test files: `tests/**/*.test.{ts,js}`
- Components use `@vue/test-utils` - Components use `@vue/test-utils`
- Example test: `tests/demo.test.ts` - Example test: `tests/demo.test.ts`
@ -167,12 +169,14 @@ Tests use **Vitest** with **Happy DOM** environment:
## VS Code Configuration ## VS Code Configuration
Recommended extensions (`.vscode/extensions.json`): Recommended extensions (`.vscode/extensions.json`):
- vue.volar (Vue language support) - vue.volar (Vue language support)
- dbaeumer.vscode-eslint (ESLint integration) - dbaeumer.vscode-eslint (ESLint integration)
- antfu.unocss (UnoCSS support) - antfu.unocss (UnoCSS support)
- vitest.explorer (Test runner) - vitest.explorer (Test runner)
Configured settings: Configured settings:
- Use workspace TypeScript version - Use workspace TypeScript version
- Disable default formatter, use ESLint - Disable default formatter, use ESLint
- Auto-fix ESLint on save - Auto-fix ESLint on save
@ -181,6 +185,7 @@ Configured settings:
## Commit Message Convention ## Commit Message Convention
Use conventional commits: Use conventional commits:
- `feat`: New feature - `feat`: New feature
- `fix`: Bug fix - `fix`: Bug fix
- `perf`: Performance improvement - `perf`: Performance improvement

View File

@ -82,14 +82,6 @@ pnpm preview
<br> <br>
```bash
# Code linting and formatting
pnpm lint
# Unit tests
pnpm test
```
</details> </details>
<details> <details>

View File

@ -82,14 +82,6 @@ pnpm preview
<br> <br>
```bash
# 代码校验与格式化
pnpm lint
# 单元测试
pnpm test
```
</details> </details>
<details> <details>

View File

@ -0,0 +1,300 @@
> 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [develop365.gitlab.io](https://develop365.gitlab.io/vant/zh-CN/image-preview/#/zh-CN/image-preview)
> ImagePreview 图片预览 vant-image-preview Doc | 组件 中文文档 documentation | v4.9.6 v4.0 v4.x | Vant UI (for v......
### 介绍
图片放大预览,支持组件调用和函数调用两种方式。
### 引入
通过以下方式来全局注册组件,更多注册方式请参考[组件注册](https://develop365.gitlab.io/vant/zh-CN/advanced-usage#zu-jian-zhu-ce//vant/zh-CN/advanced-usage)。
```
import { createApp } from 'vue';
import { ImagePreview } from 'vant';
const app = createApp();
app.use(ImagePreview);
```
### 函数调用
为了便于使用 `ImagePreview`Vant 提供了一系列辅助函数,通过辅助函数可以快速唤起全局的图片预览组件。
比如使用 `showImagePreview` 函数,调用后会直接在页面中渲染对应的图片预览组件。
```
import { showImagePreview } from 'vant';
showImagePreview(['https://fastly.jsdelivr.net/npm/@vant/assets/apple-1.jpeg']);
```
代码演示
----
### 基础用法
在调用 `showImagePreview` 时,直接传入图片数组,即可展示图片预览。
```
import { showImagePreview } from 'vant';
showImagePreview([
'https://fastly.jsdelivr.net/npm/@vant/assets/apple-1.jpeg',
'https://fastly.jsdelivr.net/npm/@vant/assets/apple-2.jpeg',
]);
```
### 指定初始位置
`showImagePreview` 支持传入配置对象,并通过 `startPosition` 选项指定图片的初始位置(索引值)。
```
import { showImagePreview } from 'vant';
showImagePreview({
images: [
'https://fastly.jsdelivr.net/npm/@vant/assets/apple-1.jpeg',
'https://fastly.jsdelivr.net/npm/@vant/assets/apple-2.jpeg',
],
startPosition: 1,
});
```
### 展示关闭按钮
开启 `closeable` 选项后,会在弹出层的右上角显示关闭图标,并且可以通过 `close-icon` 属性自定义图标,使用`close-icon-position` 属性可以自定义图标位置。
```
import { showImagePreview } from 'vant';
showImagePreview({
images: [
'https://fastly.jsdelivr.net/npm/@vant/assets/apple-1.jpeg',
'https://fastly.jsdelivr.net/npm/@vant/assets/apple-2.jpeg',
],
closeable: true,
});
```
### 监听关闭事件
通过 `onClose` 选项监听图片预览的关闭事件。
```
import { showToast, showImagePreview } from 'vant';
showImagePreview({
images: [
'https://fastly.jsdelivr.net/npm/@vant/assets/apple-1.jpeg',
'https://fastly.jsdelivr.net/npm/@vant/assets/apple-2.jpeg',
],
onClose() {
showToast('关闭');
},
});
```
### 异步关闭
通过 `beforeClose` 属性可以传入一个回调函数,在图片预览关闭前进行特定操作。
```
import { showImagePreview } from 'vant';
const instance = showImagePreview({
images: [
'https://fastly.jsdelivr.net/npm/@vant/assets/apple-1.jpeg',
'https://fastly.jsdelivr.net/npm/@vant/assets/apple-2.jpeg',
],
beforeClose: () => false,
});
setTimeout(() => {
// 调用实例上的 close 方法手动关闭图片预览
instance.close();
}, 2000);
```
### 使用 ImagePreview 组件
如果需要在 ImagePreview 内嵌入组件或其他自定义内容,可以直接使用 ImagePreview 组件,并使用 `index` 插槽进行定制。使用前需要通过 `app.use` 等方式注册组件。
```
<van-image-preview v-model:show="show" :images="images" @change="onChange">
<template v-slot:index>第{{ index + 1 }}页</template>
</van-image-preview>
```
```
import { ref } from 'vue';
export default {
setup() {
const show = ref(false);
const index = ref(0);
const images = [
'https://fastly.jsdelivr.net/npm/@vant/assets/apple-1.jpeg',
'https://fastly.jsdelivr.net/npm/@vant/assets/apple-2.jpeg',
];
const onChange = (newIndex) => {
index.value = newIndex;
};
return {
show,
index,
images,
onChange,
};
},
};
```
### 使用 image 插槽
当以组件调用的方式使用 ImagePreview 时,可以通过 `image` 插槽来插入自定义的内容,比如展示一个视频内容。在这个例子中,你可以将 `close-on-click-image` 属性设置为 `false`,这样当你点击视频时就不会意外关闭预览了。
```
<van-image-preview
v-model:show="show"
:images="images"
:close-on-click-image="false"
>
<template #image="{ src }">
<video style="width: 100%;" controls>
<source :src="src" />
</video>
</template>
</van-image-preview>
```
```
import { ref } from 'vue';
export default {
setup() {
const show = ref(false);
const images = [
'https://www.w3school.com.cn/i/movie.ogg',
'https://www.w3school.com.cn/i/movie.ogg',
'https://www.w3school.com.cn/i/movie.ogg',
];
return {
show,
images,
};
},
};
```
当你通过 `image` 插槽自定义图片时,可以通过插槽的参数绑定 `style` 样式和 `onLoad` 回调函数,这可以让 `<img>` 标签支持图片缩放。
```
<van-image-preview
v-model:show="show"
:images="images"
:close-on-click-image="false"
>
<template #image="{ src, style, onLoad }">
<img :src="src" :style="[{ width: '100%' }, style]" @load="onLoad" />
</template>
</van-image-preview>
```
API
---
### 方法
Vant 中导出了以下 ImagePreview 相关的辅助函数:
<table><thead><tr><th>方法名</th><th>说明</th><th>参数</th><th>返回值</th></tr></thead><tbody><tr><td>showImagePreview</td><td>展示一个全屏的图片预览组件</td><td><em>string[] | ImagePreviewOptions</em></td><td>ImagePreview 实例</td></tr></tbody></table>
### ImagePreviewOptions
调用 `showImagePreview` 方法时,支持传入以下选项:
<table><thead><tr><th>参数名</th><th>说明</th><th>类型</th><th>默认值</th></tr></thead><tbody><tr><td>images</td><td>需要预览的图片 URL 数组</td><td><em>string[]</em></td><td><code>[]</code></td></tr><tr><td>startPosition</td><td>图片预览起始位置索引</td><td><em>number | string</em></td><td><code>0</code></td></tr><tr><td>swipeDuration</td><td>动画时长,单位为 <code>ms</code></td><td><em>number | string</em></td><td><code>300</code></td></tr><tr><td>showIndex</td><td>是否显示页码</td><td><em>boolean</em></td><td><code>true</code></td></tr><tr><td>showIndicators</td><td>是否显示轮播指示器</td><td><em>boolean</em></td><td><code>false</code></td></tr><tr><td>loop</td><td>是否开启循环播放</td><td><em>boolean</em></td><td><code>true</code></td></tr><tr><td>doubleScale <code>v4.7.2</code></td><td>是否启用双击缩放手势,禁用后,点击时会立即关闭图片预览</td><td><em>boolean</em></td><td><code>true</code></td></tr><tr><td>onClose</td><td>关闭时的回调函数</td><td><em>Function</em></td><td>-</td></tr><tr><td>onChange</td><td>切换图片时的回调函数,回调参数为当前索引</td><td><em>Function</em></td><td>-</td></tr><tr><td>onScale</td><td>缩放图片时的回调函数,回调参数为当前索引和当前缩放值组成的对象</td><td><em>Function</em></td><td>-</td></tr><tr><td>beforeClose</td><td>关闭前的回调函数,返回 <code>false</code> 可阻止关闭,支持返回 Promise</td><td><em>(active: number) =&gt; boolean | Promise&lt;boolean&gt;</em></td><td>-</td></tr><tr><td>closeOnPopstate</td><td>是否在页面回退时自动关闭</td><td><em>boolean</em></td><td><code>true</code></td></tr><tr><td>closeOnClickImage <code>v4.8.3</code></td><td>是否在点击图片后关闭图片预览</td><td><em>boolean</em></td><td><code>true</code></td></tr><tr><td>closeOnClickOverlay <code>v4.6.4</code></td><td>是否在点击遮罩层后关闭图片预览</td><td><em>boolean</em></td><td><code>true</code></td></tr><tr><td>vertical <code>v4.8.6</code></td><td>是否开启纵向手势滑动</td><td><em>boolean</em></td><td><code>false</code></td></tr><tr><td>className</td><td>自定义类名 (应用在图片预览的弹出层)</td><td><em>string | Array | object</em></td><td>-</td></tr><tr><td>maxZoom</td><td>手势缩放时,最大缩放比例</td><td><em>number | string</em></td><td><code>3</code></td></tr><tr><td>minZoom</td><td>手势缩放时,最小缩放比例</td><td><em>number | string</em></td><td><code>1/3</code></td></tr><tr><td>closeable</td><td>是否显示关闭图标</td><td><em>boolean</em></td><td><code>false</code></td></tr><tr><td>closeIcon</td><td>关闭图标名称或图片链接</td><td><em>string</em></td><td><code>clear</code></td></tr><tr><td>closeIconPosition</td><td>关闭图标位置,可选值为 <code>top-left</code><br><code>bottom-left</code> <code>bottom-right</code></td><td><em>string</em></td><td><code>top-right</code></td></tr><tr><td>transition</td><td>动画类名,等价于 <a href="https://cn.vuejs.org/api/built-in-components.html#transition" target="_blank">transition</a><code>name</code> 属性</td><td><em>string</em></td><td><code>van-fade</code></td></tr><tr><td>overlayClass</td><td>自定义遮罩层类名</td><td><em>string | Array | object</em></td><td>-</td></tr><tr><td>overlayStyle</td><td>自定义遮罩层样式</td><td><em>object</em></td><td>-</td></tr><tr><td>teleport</td><td>指定挂载的节点,等同于 Teleport 组件的 <a href="https://cn.vuejs.org/api/built-in-components.html#teleport" target="_blank">to 属性</a></td><td><em>string | Element</em></td><td>-</td></tr></tbody></table>
### Props
通过组件调用 `ImagePreview` 时,支持以下 Props
<table><thead><tr><th>参数</th><th>说明</th><th>类型</th><th>默认值</th></tr></thead><tbody><tr><td>v-model:show</td><td>是否展示图片预览</td><td><em>boolean</em></td><td><code>false</code></td></tr><tr><td>images</td><td>需要预览的图片 URL 数组</td><td><em>string[]</em></td><td><code>[]</code></td></tr><tr><td>start-position</td><td>图片预览起始位置索引</td><td><em>number | string</em></td><td><code>0</code></td></tr><tr><td>swipe-duration</td><td>动画时长,单位为 ms</td><td><em>number | string</em></td><td><code>300</code></td></tr><tr><td>show-index</td><td>是否显示页码</td><td><em>boolean</em></td><td><code>true</code></td></tr><tr><td>show-indicators</td><td>是否显示轮播指示器</td><td><em>boolean</em></td><td><code>false</code></td></tr><tr><td>loop</td><td>是否开启循环播放</td><td><em>boolean</em></td><td><code>true</code></td></tr><tr><td>double-scale <code>v4.7.2</code></td><td>是否启用双击缩放手势,禁用后,点击时会立即关闭图片预览</td><td><em>boolean</em></td><td><code>true</code></td></tr><tr><td>before-close</td><td>关闭前的回调函数,返回 <code>false</code> 可阻止关闭,支持返回 Promise</td><td><em>(active: number) =&gt; boolean | Promise&lt;boolean&gt;</em></td><td>-</td></tr><tr><td>close-on-popstate</td><td>是否在页面回退时自动关闭</td><td><em>boolean</em></td><td><code>true</code></td></tr><tr><td>close-on-click-image <code>v4.8.3</code></td><td>是否在点击图片后关闭图片预览</td><td><em>boolean</em></td><td><code>true</code></td></tr><tr><td>close-on-click-overlay <code>v4.6.4</code></td><td>是否在点击遮罩层后关闭图片预览</td><td><em>boolean</em></td><td><code>true</code></td></tr><tr><td>vertical <code>v4.8.6</code></td><td>是否开启纵向手势滑动</td><td><em>boolean</em></td><td><code>false</code></td></tr><tr><td>class-name</td><td>自定义类名</td><td><em>string | Array | object</em></td><td>-</td></tr><tr><td>max-zoom</td><td>手势缩放时,最大缩放比例</td><td><em>number | string</em></td><td><code>3</code></td></tr><tr><td>min-zoom</td><td>手势缩放时,最小缩放比例</td><td><em>number | string</em></td><td><code>1/3</code></td></tr><tr><td>closeable</td><td>是否显示关闭图标</td><td><em>boolean</em></td><td><code>false</code></td></tr><tr><td>close-icon</td><td>关闭图标名称或图片链接</td><td><em>string</em></td><td><code>clear</code></td></tr><tr><td>close-icon-position</td><td>关闭图标位置,可选值为 <code>top-left</code><br><code>bottom-left</code> <code>bottom-right</code></td><td><em>string</em></td><td><code>top-right</code></td></tr><tr><td>transition</td><td>动画类名,等价于 <a href="https://cn.vuejs.org/api/built-in-components.html#transition" target="_blank">transition</a><code>name</code> 属性</td><td><em>string</em></td><td><code>van-fade</code></td></tr><tr><td>overlay-class</td><td>自定义遮罩层类名</td><td><em>string | Array | object</em></td><td>-</td></tr><tr><td>overlay-style</td><td>自定义遮罩层样式</td><td><em>object</em></td><td>-</td></tr><tr><td>teleport</td><td>指定挂载的节点,等同于 Teleport 组件的 <a href="https://cn.vuejs.org/api/built-in-components.html#teleport" target="_blank">to 属性</a></td><td><em>string | Element</em></td><td>-</td></tr></tbody></table>
### Events
通过组件调用 `ImagePreview` 时,支持以下事件:
<table><thead><tr><th>事件名</th><th>说明</th><th>回调参数</th></tr></thead><tbody><tr><td>close</td><td>关闭时触发</td><td><em>{index: number, url: string}</em></td></tr><tr><td>closed</td><td>关闭且且动画结束后触发</td><td>-</td></tr><tr><td>change</td><td>切换当前图片时触发</td><td><em>index: number</em></td></tr><tr><td>scale</td><td>缩放当前图片时触发</td><td><em>{index: number, scale: number}</em></td></tr><tr><td>long-press</td><td>长按当前图片时触发</td><td><em>{index: number}</em></td></tr></tbody></table>
### 方法
通过组件调用 `ImagePreview` 时,通过 ref 可以获取到 ImagePreview 实例并调用实例方法,详见[组件实例方法](https://develop365.gitlab.io/vant/zh-CN/advanced-usage#zu-jian-shi-li-fang-fa//vant/zh-CN/advanced-usage)。
<table><thead><tr><th>方法名</th><th>说明</th><th>参数</th><th>返回值</th></tr></thead><tbody><tr><td>resetScale <code>4.7.4</code></td><td>重置当前图片的缩放比</td><td>-</td><td>-</td></tr><tr><td>swipeTo</td><td>切换到指定位置</td><td><em>index: number, options?: SwipeToOptions</em></td><td>-</td></tr></tbody></table>
### 类型定义
组件导出以下类型定义:
```
import type {
ImagePreviewProps,
ImagePreviewOptions,
ImagePreviewInstance,
ImagePreviewScaleEventParams,
} from 'vant';
```
`ImagePreviewInstance` 是组件实例的类型,用法如下:
```
import { ref } from 'vue';
import type { ImagePreviewInstance } from 'vant';
const imagePreviewRef = ref<ImagePreviewInstance>();
imagePreviewRef.value?.swipeTo(1);
```
### Slots
通过组件调用 `ImagePreview` 时,支持以下插槽:
<table><thead><tr><th>名称</th><th>说明</th><th>参数</th></tr></thead><tbody><tr><td>index</td><td>自定义页码内容</td><td><em>{index: 当前图片的索引}</em></td></tr><tr><td>cover</td><td>自定义覆盖在图片预览上方的内容</td><td>-</td></tr><tr><td>image</td><td>自定义图片内容</td><td><em>{src: 当前资源地址, onLoad: 加载图片函数, style: 当前图片样式}</em></td></tr></tbody></table>
### onClose 回调参数
<table><thead><tr><th>参数名</th><th>说明</th><th>类型</th></tr></thead><tbody><tr><td>url</td><td>当前图片 URL</td><td><em>string</em></td></tr><tr><td>index</td><td>当前图片的索引值</td><td><em>number</em></td></tr></tbody></table>
### onScale 回调参数
<table><thead><tr><th>参数名</th><th>说明</th><th>类型</th></tr></thead><tbody><tr><td>index</td><td>当前图片的索引值</td><td><em>number</em></td></tr><tr><td>scale</td><td>当前图片的缩放值</td><td><em>number</em></td></tr></tbody></table>
主题定制
----
### 样式变量
组件提供了下列 CSS 变量,可用于自定义样式,使用方法请参考 [ConfigProvider 组件](https://develop365.gitlab.io/vant/zh-CN/config-provider/#/zh-CN/config-provider)。
<table><thead><tr><th>名称</th><th>默认值</th><th>描述</th></tr></thead><tbody><tr><td>--van-image-preview-index-text-color</td><td><em>var(--van-white)</em></td><td>-</td></tr><tr><td>--van-image-preview-index-font-size</td><td><em>var(--van-font-size-md)</em></td><td>-</td></tr><tr><td>--van-image-preview-index-line-height</td><td><em>var(--van-line-height-md)</em></td><td>-</td></tr><tr><td>--van-image-preview-index-text-shadow</td><td><em>0 1px 1px var(--van-gray-8)</em></td><td>-</td></tr><tr><td>--van-image-preview-overlay-background</td><td><em>rgba(0, 0, 0, 0.9)</em></td><td>-</td></tr><tr><td>--van-image-preview-close-icon-size</td><td><em>22px</em></td><td>-</td></tr><tr><td>--van-image-preview-close-icon-color</td><td><em>var(--van-gray-5)</em></td><td>-</td></tr><tr><td>--van-image-preview-close-icon-margin</td><td><em>var(--van-padding-md)</em></td><td>-</td></tr><tr><td>--van-image-preview-close-icon-z-index</td><td><em>1</em></td><td>-</td></tr></tbody></table>
常见问题
----
### 引用 showImagePreview 时出现编译报错?
如果引用 `showImagePreview` 方法时出现以下报错,说明项目中使用了 `babel-plugin-import` 插件,导致代码被错误编译。
```
These dependencies were not found:
* vant/es/show-image-preview in ./src/xxx.js
* vant/es/show-image-preview/style in ./src/xxx.js
```
Vant 从 4.0 版本开始不再支持 `babel-plugin-import` 插件,请参考 [迁移指南](https://develop365.gitlab.io/vant/zh-CN/migrate-from-v3#yi-chu-babel-plugin-import//vant/zh-CN/migrate-from-v3) 移除该插件。

View File

@ -375,10 +375,6 @@ pnpm dev
pnpm build pnpm build
pnpm build:staging pnpm build:staging
# 代码检查
pnpm lint
pnpm test
# 依赖管理 # 依赖管理
pnpm i pnpm i
pnpm update pnpm update

View File

@ -76,15 +76,6 @@ pnpm build:staging
pnpm build pnpm build
``` ```
### 代码检查
```bash
# 代码校验与格式化
pnpm lint
# 单元测试
pnpm test
```
## 核心模块说明 ## 核心模块说明
### 1. 路由系统 (src/router/) ### 1. 路由系统 (src/router/)

View File

@ -10,7 +10,6 @@
"build:staging": "vue-tsc && vite build --mode staging", "build:staging": "vue-tsc && vite build --mode staging",
"build": "vue-tsc && vite build && npm run zip", "build": "vue-tsc && vite build && npm run zip",
"preview": "vite preview", "preview": "vite preview",
"lint": "eslint . --fix",
"prepare": "husky", "prepare": "husky",
"test": "vitest", "test": "vitest",
"zip": "7z a -tzip dist/shop-web-%date:~0,4%%date:~5,2%%date:~8,2%-%time:~0,2%%time:~3,2%%time:~6,2%.zip .\\dist\\* -xr!*.zip" "zip": "7z a -tzip dist/shop-web-%date:~0,4%%date:~5,2%%date:~8,2%-%time:~0,2%%time:~3,2%%time:~6,2%.zip .\\dist\\* -xr!*.zip"

View File

@ -0,0 +1,80 @@
import type { PageDTO, ResponseData } from "../type"
import { request } from "@/http/axios"
/** 借还动态查询参数 */
export interface SearchBorrowReturnDynamicQuery {
/** 商品ID精确筛选 */
goodsId?: number
/** 格口ID精确筛选 */
cellId?: number
/** 状态筛选(仅对归还记录有效) */
status?: number
/** 动态类型筛选 */
dynamicType?: number
/** 页码默认1 */
pageNum?: number
/** 每页大小默认10 */
pageSize?: number
}
/** 借还动态响应数据 */
export interface BorrowReturnDynamicDTO {
/** 订单商品ID */
orderGoodsId: number
/** 动态类型0-借出 1-归还) */
dynamicType: number
/** 动态类型描述 */
dynamicTypeStr: string
/** 订单ID */
orderId: number
/** 订单创建时间/借出时间 */
orderTime: string
/** 商品ID */
goodsId: number
/** 商品名称 */
goodsName: string
/** 商品封面图片 */
coverImg: string
/** 商品单价 */
goodsPrice: number
/** 数量 */
quantity: number
/** 支付方式 */
paymentMethod: string
/** 订单姓名 */
orderName: string
/** 订单手机号 */
orderMobile: string
/** 格口ID */
cellId: number
/** 格口号 */
cellNo: number
/** 柜机ID */
cabinetId: number
/** 柜机名称 */
cabinetName: string
/** 归还/审批时间(归还记录时有效) */
operateTime?: string
/** 审批ID归还记录时有效 */
approvalId?: number
/** 审批状态(归还记录时有效) */
status: number
/** 状态描述 */
statusStr: string
/** 审批人(归还记录时有效) */
auditName?: string
/** 审核说明(归还记录时有效) */
auditRemark?: string
/** 归还图片(归还记录时有效) */
images?: string
/** 审核图片(归还记录时有效) */
auditImages?: string
}
export function getBorrowReturnDynamicApi(query: SearchBorrowReturnDynamicQuery) {
return request<ResponseData<PageDTO<BorrowReturnDynamicDTO>>>({
url: "order/borrow-return-dynamic",
method: "get",
params: query
})
}

View File

@ -23,11 +23,18 @@ const tabbarItemList = computed(() => {
title: '柜机管理', title: '柜机管理',
icon: 'manager-o', icon: 'manager-o',
path: '/cabinet' path: '/cabinet'
}, { },
/* {
title: '审批中心', title: '审批中心',
icon: 'records-o', icon: 'records-o',
path: '/approval/list' path: '/approval/list'
}, { }, */
{
title: '商品动态',
icon: 'exchange',
path: '/order/borrow-return-dynamic'
},
{
title: '我的', title: '我的',
icon: 'user-o', icon: 'user-o',
path: '/' path: '/'

View File

@ -133,18 +133,19 @@ wxStore.refreshBalance();
<span>柜机管理</span> <span>柜机管理</span>
</div> </div>
</van-col> </van-col>
<van-col span="8"> -->
<div v-if="wxStore.isCabinetAdmin" class="custom-btn" @click="router.push('/approval/list')">
<van-icon name="comment-o" size="20px" />
<span>审批中心</span>
</div>
</van-col> -->
<van-col span="8"> <van-col span="8">
<div v-if="wxStore.isCabinetAdmin" class="custom-btn" @click="router.push('/approvalAsset/list')"> <div v-if="wxStore.isCabinetAdmin" class="custom-btn" @click="router.push('/approvalAsset/list')">
<van-icon name="comment-o" size="20px" /> <van-icon name="comment-o" size="20px" />
<span>耗材核销</span> <span>耗材核销</span>
</div> </div>
</van-col> </van-col>
<van-col span="8">
<div v-if="wxStore.isCabinetAdmin" class="custom-btn" @click="router.push('/approval/list')">
<van-icon name="comment-o" size="20px" />
<span>审批中心</span>
</div>
</van-col>
<van-col span="8"></van-col> <van-col span="8"></van-col>
</van-row> </van-row>
</div> </div>

View File

@ -0,0 +1,747 @@
<script setup lang="ts">
import type { BorrowReturnDynamicDTO, SearchBorrowReturnDynamicQuery } from "@/common/apis/manage/order"
import { getBorrowReturnDynamicApi } from "@/common/apis/manage/order"
import { showToast, showImagePreview } from "vant"
import { computed, onMounted, ref } from "vue"
//
const loading = ref(false)
//
const refreshing = ref(false)
//
const dynamicList = ref<BorrowReturnDynamicDTO[]>([])
//
const queryParams = ref<SearchBorrowReturnDynamicQuery>({
pageNum: 1,
pageSize: 10
})
//
const total = ref(0)
//
const hasMore = computed(() => dynamicList.value.length < total.value)
//
async function fetchBorrowReturnDynamic(isLoadMore = false) {
if (!isLoadMore) {
loading.value = true
}
try {
const res = await getBorrowReturnDynamicApi(queryParams.value)
if (res.code === 0) {
if (isLoadMore) {
//
dynamicList.value = [...dynamicList.value, ...(res.data?.rows || [])]
} else {
//
dynamicList.value = res.data?.rows || []
}
total.value = res.data?.total || 0
} else {
showToast(res.msg || "获取数据失败")
}
} catch (_error) {
showToast("网络错误,请重试")
} finally {
loading.value = false
refreshing.value = false
}
}
//
function loadMore() {
if (loading.value || !hasMore.value) return
queryParams.value.pageNum!++
fetchBorrowReturnDynamic(true)
}
//
function onRefresh() {
refreshing.value = true
queryParams.value.pageNum = 1
dynamicList.value = []
fetchBorrowReturnDynamic()
}
//
function getImageArray(imagesStr?: string): string[] {
if (!imagesStr || imagesStr.trim() === "") return []
return imagesStr.split(",").filter(img => img.trim() !== "")
}
//
function previewCoverImage(imgUrl: string) {
if (!imgUrl) return
showImagePreview([imgUrl])
}
//
function previewReturnImages(images: string[], startIndex = 0) {
if (!images || images.length === 0) return
showImagePreview({
images,
startPosition: startIndex,
closeable: true
})
}
//
function previewAuditImages(images: string[], startIndex = 0) {
if (!images || images.length === 0) return
showImagePreview({
images,
startPosition: startIndex,
closeable: true
})
}
//
onMounted(() => {
fetchBorrowReturnDynamic()
})
</script>
<template>
<div class="borrow-return-dynamic-page">
<!-- 下拉刷新 -->
<van-pull-refresh v-model="refreshing" @refresh="onRefresh">
<!-- 时间线容器 -->
<div class="timeline-container">
<!-- 时间线线条 -->
<div class="timeline-line" />
<!-- 动态条目 -->
<div
v-for="item in dynamicList"
:key="item.orderGoodsId"
class="timeline-item"
>
<!-- 时间点 -->
<div class="timeline-dot">
<div class="dot" :class="`dot-type-${item.dynamicType}`" />
</div>
<!-- 内容卡片 -->
<div class="dynamic-card">
<!-- 时间显示 -->
<div class="dynamic-time">
{{ item.dynamicType === 0 ? item.orderTime : item.operateTime }}
</div>
<!-- 卡片头部类型和状态 -->
<div class="card-header">
<span class="dynamic-type" :class="`type-${item.dynamicType}`">
{{ item.dynamicTypeStr }}
</span>
<span v-if="item.dynamicType === 1" class="audit-status" :class="`status-${item.status}`">
{{ item.statusStr }}
</span>
</div>
<!-- 分隔线 -->
<van-divider />
<!-- 商品信息 -->
<div class="goods-info">
<!-- 商品图片 -->
<van-image
v-if="item.coverImg"
:src="item.coverImg"
width="70"
height="70"
class="goods-image"
fit="cover"
@click="previewCoverImage(item.coverImg)"
/>
<div v-else class="goods-image-placeholder">
<div class="placeholder-icon">
商品
</div>
</div>
<div class="goods-details">
<div class="goods-name">
{{ item.goodsName }}
</div>
<div class="goods-price">
¥{{ item.goodsPrice.toFixed(2) }}
</div>
<div class="order-info">
<div class="order-name">
{{ item.orderName }}
</div>
<div class="order-mobile">
{{ item.orderMobile }}
</div>
</div>
<div class="cabinet-info">
<span class="cabinet-name">{{ item.cabinetName }}</span>
<span class="cell-no">格口号: {{ item.cellNo }}</span>
</div>
</div>
</div>
<!-- 归还图片和审核图片 -->
<div v-if="item.dynamicType === 1" class="image-section">
<!-- 归还图片 -->
<div v-if="getImageArray(item.images).length > 0" class="image-group">
<div class="image-label">
归还图片
</div>
<div class="image-list">
<van-image
v-for="(img, imgIndex) in getImageArray(item.images)"
:key="imgIndex"
:src="img"
width="50"
height="50"
fit="cover"
class="return-image"
@click="previewReturnImages(getImageArray(item.images), imgIndex)"
/>
</div>
</div>
<!-- 审核图片 -->
<div v-if="getImageArray(item.auditImages).length > 0" class="image-group">
<div class="image-label">
审核图片
</div>
<div class="image-list">
<van-image
v-for="(img, imgIndex) in getImageArray(item.auditImages)"
:key="imgIndex"
:src="img"
width="50"
height="50"
fit="cover"
class="audit-image"
@click="previewAuditImages(getImageArray(item.auditImages), imgIndex)"
/>
</div>
</div>
</div>
<!-- 审批人和审核说明 -->
<div v-if="item.dynamicType === 1 && (item.auditName || item.auditRemark)" class="audit-info">
<div v-if="item.auditName" class="audit-person">
<span class="label">审批人:</span>
<span class="value">{{ item.auditName }}</span>
</div>
<div v-if="item.auditRemark && item.auditRemark != '自动审批'" class="audit-remark">
<span class="label">审核说明:</span>
<span class="value">{{ item.auditRemark }}</span>
</div>
</div>
</div>
</div>
<!-- 加载更多 -->
<div v-if="hasMore" class="load-more">
<van-button
:loading="loading"
loading-text="加载中..."
@click="loadMore"
block
>
加载更多
</van-button>
</div>
<div v-else-if="dynamicList.length > 0" class="no-more">
没有更多了
</div>
</div>
<!-- 空状态 -->
<div v-if="!loading && dynamicList.length === 0" class="empty-state">
<van-empty description="暂无借还动态" />
</div>
</van-pull-refresh>
</div>
</template>
<style scoped>
.borrow-return-dynamic-page {
min-height: 100vh;
background: linear-gradient(135deg, #f5f7fa 0%, #f0f4f8 100%);
padding: 16px;
padding-bottom: 32px;
}
.dynamic-time {
padding: 10px 16px;
font-size: 13px;
color: #666;
background: rgba(255, 255, 255, 0.8);
border-radius: 6px 6px 0 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
font-weight: 500;
letter-spacing: 0.3px;
}
.timeline-container {
position: relative;
margin-top: 8px;
min-height: 90vh;
padding-left: 32px;
}
.timeline-line {
position: absolute;
left: 16px;
top: 0;
bottom: 0;
width: 3px;
background: linear-gradient(
to bottom,
rgba(25, 137, 250, 0.2) 0%,
rgba(25, 137, 250, 0.6) 30%,
rgba(25, 137, 250, 0.8) 50%,
rgba(25, 137, 250, 0.6) 70%,
rgba(25, 137, 250, 0.2) 100%
);
border-radius: 2px;
box-shadow: 0 0 10px rgba(25, 137, 250, 0.15);
}
.timeline-item {
position: relative;
margin-bottom: 20px;
transition:
transform 0.3s ease,
opacity 0.3s ease;
}
.timeline-item:hover {
transform: translateY(-2px);
}
.timeline-dot {
position: absolute;
left: -32px;
top: 0;
display: flex;
flex-direction: column;
align-items: center;
width: 32px;
z-index: 2;
}
.dot {
width: 14px;
height: 14px;
border-radius: 50%;
background-color: #1989fa;
border: 3px solid white;
box-shadow:
0 0 0 3px #1989fa,
0 0 15px rgba(25, 137, 250, 0.4);
transition: all 0.3s ease;
position: relative;
}
.dot::after {
content: "";
position: absolute;
top: -3px;
left: -3px;
right: -3px;
bottom: -3px;
border-radius: 50%;
background: inherit;
filter: blur(5px);
opacity: 0.4;
z-index: -1;
}
.dot-type-0 {
background-color: #1989fa;
box-shadow:
0 0 0 3px #1989fa,
0 0 15px rgba(25, 137, 250, 0.4);
}
.dot-type-1 {
background-color: #07c160;
box-shadow:
0 0 0 3px #07c160,
0 0 15px rgba(7, 193, 96, 0.4);
}
.timeline-time {
margin-top: 8px;
font-size: 12px;
color: #666;
text-align: center;
line-height: 1.4;
white-space: nowrap;
}
.dynamic-card {
border-radius: 10px;
overflow: hidden;
box-shadow:
0 4px 20px rgba(0, 0, 0, 0.08),
0 2px 6px rgba(0, 0, 0, 0.04),
inset 0 1px 0 rgba(255, 255, 255, 0.8);
background-color: white;
border: 1px solid rgba(0, 0, 0, 0.05);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
}
.dynamic-card:hover {
box-shadow:
0 8px 30px rgba(0, 0, 0, 0.12),
0 4px 12px rgba(0, 0, 0, 0.06),
inset 0 1px 0 rgba(255, 255, 255, 0.9);
transform: translateY(-4px);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
background: linear-gradient(to right, rgba(25, 137, 250, 0.03), rgba(7, 193, 96, 0.03));
border-bottom: 1px solid rgba(0, 0, 0, 0.04);
}
.dynamic-type {
font-size: 16px;
font-weight: 600;
letter-spacing: 0.5px;
position: relative;
}
.type-0 {
color: #1989fa;
}
.type-1 {
color: #07c160;
}
.audit-status {
font-size: 12px;
padding: 3px 10px;
border-radius: 16px;
font-weight: 600;
letter-spacing: 0.3px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease;
}
.audit-status:hover {
transform: scale(1.05);
}
.status-1 {
background: linear-gradient(135deg, #fff8e1 0%, #ffecb3 100%);
color: #f57c00;
border: 1px solid rgba(245, 124, 0, 0.2);
}
.status-2 {
background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
color: #07c160;
border: 1px solid rgba(7, 193, 96, 0.2);
}
.status-3 {
background: linear-gradient(135deg, #ffebee 0%, #ffcdd2 100%);
color: #ee0a24;
border: 1px solid rgba(238, 10, 36, 0.2);
}
.goods-info {
display: flex;
padding: 16px;
gap: 12px;
align-items: center;
}
.goods-image {
border-radius: 8px;
overflow: hidden;
flex-shrink: 0;
border: 2px solid white;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
cursor: pointer;
}
.goods-image:hover {
transform: scale(1.05);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15);
}
.goods-image-placeholder {
width: 70px;
height: 70px;
flex-shrink: 0;
background: linear-gradient(135deg, #f5f5f5 0%, #e0e0e0 100%);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
border: 2px dashed rgba(0, 0, 0, 0.1);
}
.placeholder-icon {
color: #999;
font-size: 14px;
font-weight: 500;
}
.goods-details {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
}
.goods-name {
font-size: 16px;
font-weight: 600;
color: #333;
line-height: 1.4;
letter-spacing: 0.2px;
}
.goods-price {
font-size: 18px;
font-weight: 700;
color: #ee0a24;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.order-info {
display: flex;
gap: 12px;
font-size: 13px;
color: #666;
background: rgba(0, 0, 0, 0.02);
padding: 8px 10px;
border-radius: 6px;
margin-top: 4px;
}
.order-info .order-name,
.order-info .order-mobile {
display: flex;
align-items: center;
gap: 6px;
}
.cabinet-info {
display: flex;
justify-content: space-between;
font-size: 13px;
color: #666;
background: rgba(25, 137, 250, 0.04);
padding: 8px 10px;
border-radius: 6px;
margin-top: 4px;
}
.cabinet-name {
font-weight: 500;
color: #333;
}
.cell-no {
color: #1989fa;
font-weight: 600;
background: rgba(25, 137, 250, 0.1);
padding: 2px 8px;
border-radius: 12px;
font-size: 13px;
}
.image-section {
padding: 0 16px 16px;
display: flex;
gap: 16px;
}
.image-group {
flex: 1;
}
.image-label {
font-size: 13px;
color: #666;
margin-bottom: 8px;
font-weight: 500;
padding-left: 8px;
border-left: 3px solid #1989fa;
}
.image-list {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.return-image,
.audit-image {
border-radius: 6px;
overflow: hidden;
border: 2px solid white;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
cursor: pointer;
}
.return-image:hover,
.audit-image:hover {
transform: translateY(-3px);
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.15);
}
.audit-info {
padding: 0 16px 16px;
display: flex;
flex-direction: column;
gap: 8px;
font-size: 13px;
}
.audit-person,
.audit-remark {
display: flex;
gap: 8px;
align-items: flex-start;
background: rgba(0, 0, 0, 0.02);
padding: 10px;
border-radius: 8px;
}
.label {
color: #666;
min-width: 70px;
font-weight: 500;
}
.value {
color: #333;
flex: 1;
line-height: 1.5;
}
.load-more {
text-align: center;
padding: 20px 0;
}
.load-more .van-button {
background: linear-gradient(135deg, #1989fa 0%, #36b5ff 100%);
color: white;
border: none;
border-radius: 25px;
padding: 12px 24px;
font-weight: 600;
font-size: 15px;
box-shadow: 0 4px 15px rgba(25, 137, 250, 0.3);
transition: all 0.3s ease;
}
.load-more .van-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(25, 137, 250, 0.4);
}
.load-more .van-button:active {
transform: translateY(0);
}
.no-more {
text-align: center;
padding: 24px 0;
color: #999;
font-size: 14px;
font-style: italic;
}
.empty-state {
padding: 80px 0;
}
/* 响应式调整 */
@media (max-width: 400px) {
.borrow-return-dynamic-page {
padding: 12px;
}
.timeline-container {
padding-left: 30px;
}
.timeline-dot {
left: -30px;
width: 30px;
}
.goods-info {
flex-direction: column;
gap: 16px;
align-items: flex-start;
}
.goods-image,
.goods-image-placeholder {
width: 100%;
height: auto;
aspect-ratio: 1;
}
.image-section {
flex-direction: column;
gap: 16px;
}
.order-info {
flex-direction: column;
gap: 8px;
}
}
/* 动画效果 */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(15px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.timeline-item {
animation: fadeIn 0.4s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
/* 大屏幕优化 */
@media (min-width: 768px) {
.borrow-return-dynamic-page {
max-width: 800px;
margin: 0 auto;
padding-top: 24px;
}
.dynamic-card {
border-radius: 16px;
}
}
/* 美化分隔线 */
.van-divider {
margin: 0 16px;
border-color: rgba(0, 0, 0, 0.06);
}
/* 美化下拉刷新区域 */
.van-pull-refresh {
min-height: calc(100vh - 32px);
}
</style>

View File

@ -30,34 +30,34 @@ export const systemRoutes: RouteRecordRaw[] = [
/** 业务页面 */ /** 业务页面 */
export const routes: RouteRecordRaw[] = [ export const routes: RouteRecordRaw[] = [
{ {
path: '/approval/submit', path: "/approval/submit",
component: () => import('@/pages/approval/submit.vue'), component: () => import("@/pages/approval/submit.vue"),
meta: { requiresAuth: true } meta: { requiresAuth: true }
}, },
{ {
path: '/approval/handle/:approvalId', path: "/approval/handle/:approvalId",
component: () => import('@/pages/approval/handle.vue'), component: () => import("@/pages/approval/handle.vue"),
meta: { requiresAuth: true } meta: { requiresAuth: true }
}, },
{ {
path: '/approval/handleApply/:approvalId', path: "/approval/handleApply/:approvalId",
component: () => import('@/pages/approval/handleApply.vue'), component: () => import("@/pages/approval/handleApply.vue"),
meta: { requiresAuth: true } meta: { requiresAuth: true }
}, },
{ {
path: '/order-success', path: "/order-success",
name: 'OrderSuccess', name: "OrderSuccess",
component: () => import('@/pages/order/Success.vue'), component: () => import("@/pages/order/Success.vue"),
meta: { meta: {
requiresAuth: true requiresAuth: true
} }
}, },
{ {
path: '/order-list', path: "/order-list",
name: 'OrderList', name: "OrderList",
component: () => import('@/pages/order/components/OrderList.vue'), component: () => import("@/pages/order/components/OrderList.vue"),
meta: { meta: {
title: '订单列表', title: "订单列表",
layout: { layout: {
navBar: { navBar: {
showNavBar: true, showNavBar: true,
@ -68,11 +68,11 @@ export const routes: RouteRecordRaw[] = [
} }
}, },
{ {
path: '/order/:id', path: "/order/:id",
name: 'OrderDetail', name: "OrderDetail",
component: () => import('@/pages/order/index.vue'), component: () => import("@/pages/order/index.vue"),
meta: { meta: {
title: '订单详情', title: "订单详情",
layout: { layout: {
navBar: { navBar: {
showNavBar: true, showNavBar: true,
@ -83,11 +83,30 @@ export const routes: RouteRecordRaw[] = [
} }
}, },
{ {
path: '/rental-list', path: "/order/borrow-return-dynamic",
name: 'RentalList', name: "BorrowReturnDynamic",
component: () => import('@/pages/rental/index.vue'), component: () => import("@/pages/order/borrow-return-dynamic.vue"),
meta: { meta: {
title: '我的柜子', title: "商品动态",
layout: {
navBar: {
showNavBar: true,
showLeftArrow: true
},
tabbar: {
showTabbar: true,
icon: "manager-o"
}
},
requiresAuth: true
}
},
{
path: "/rental-list",
name: "RentalList",
component: () => import("@/pages/rental/index.vue"),
meta: {
title: "我的柜子",
layout: { layout: {
navBar: { navBar: {
showNavBar: true, showNavBar: true,
@ -106,11 +125,11 @@ export const routes: RouteRecordRaw[] = [
} }
}, },
{ {
path: '/cabinet', path: "/cabinet",
component: () => import('@/pages/cabinet/index.vue'), component: () => import("@/pages/cabinet/index.vue"),
name: "Cabinet", name: "Cabinet",
meta: { meta: {
title: '柜机管理', title: "柜机管理",
keepAlive: true, keepAlive: true,
layout: { layout: {
navBar: { navBar: {
@ -125,11 +144,11 @@ export const routes: RouteRecordRaw[] = [
} }
}, },
{ {
path: '/approval/list', path: "/approval/list",
component: () => import('@/pages/approval/list.vue'), component: () => import("@/pages/approval/list.vue"),
name: "Approval", name: "Approval",
meta: { meta: {
title: '审批中心', title: "审批中心",
keepAlive: true, keepAlive: true,
layout: { layout: {
navBar: { navBar: {
@ -144,11 +163,11 @@ export const routes: RouteRecordRaw[] = [
} }
}, },
{ {
path: '/approvalAsset/list', path: "/approvalAsset/list",
component: () => import('@/pages/approvalAsset/list.vue'), component: () => import("@/pages/approvalAsset/list.vue"),
name: "ApprovalAsset", name: "ApprovalAsset",
meta: { meta: {
title: '耗材核销', title: "耗材核销",
keepAlive: false, keepAlive: false,
layout: { layout: {
navBar: { navBar: {

View File

@ -30,6 +30,7 @@ declare module 'vue' {
VanNavBar: typeof import('vant/es')['NavBar'] VanNavBar: typeof import('vant/es')['NavBar']
VanPicker: typeof import('vant/es')['Picker'] VanPicker: typeof import('vant/es')['Picker']
VanPopup: typeof import('vant/es')['Popup'] VanPopup: typeof import('vant/es')['Popup']
VanPullRefresh: typeof import('vant/es')['PullRefresh']
VanRow: typeof import('vant/es')['Row'] VanRow: typeof import('vant/es')['Row']
VanSearch: typeof import('vant/es')['Search'] VanSearch: typeof import('vant/es')['Search']
VanSidebar: typeof import('vant/es')['Sidebar'] VanSidebar: typeof import('vant/es')['Sidebar']

View File

@ -37,8 +37,9 @@ export default defineConfig(({ mode }) => {
open: true, open: true,
// 反向代理 // 反向代理
proxy: { proxy: {
"/api/v1": { "/api": {
target: "https://apifoxmock.com/m1/2930465-2145633-default", target: "https://wxshop.ab98.cn/shop-api",
debug: true,
// 是否为 WebSocket // 是否为 WebSocket
ws: false, ws: false,
// 是否允许跨域 // 是否允许跨域