feat(订单): 新增商品借还动态功能
- 添加商品借还动态页面及路由配置 - 实现借还动态列表展示、下拉刷新和加载更多功能 - 支持预览商品封面、归还图片和审核图片 - 新增相关API接口和类型定义 - 调整底部导航栏,添加商品动态入口 - 优化审批中心与耗材核销的导航位置 - 更新README文档,移除lint相关命令 - 配置Vite代理,调整API基础路径
This commit is contained in:
parent
45ac3a9bd2
commit
3b0dada98c
|
|
@ -1,7 +1,7 @@
|
||||||
# 开发环境的环境变量(命名必须以 VITE_ 开头)
|
# 开发环境的环境变量(命名必须以 VITE_ 开头)
|
||||||
|
|
||||||
## 后端接口地址(如果解决跨域问题采用反向代理就只需写相对路径)
|
## 后端接口地址(如果解决跨域问题采用反向代理就只需写相对路径)
|
||||||
VITE_BASE_URL = http://localhost:8090/api
|
VITE_BASE_URL = /api
|
||||||
|
|
||||||
## 开发环境域名和静态资源公共路径(一般 / 或 ./ 都可以)
|
## 开发环境域名和静态资源公共路径(一般 / 或 ./ 都可以)
|
||||||
VITE_PUBLIC_PATH = /
|
VITE_PUBLIC_PATH = /
|
||||||
|
|
|
||||||
27
CLAUDE.md
27
CLAUDE.md
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -82,14 +82,6 @@ pnpm preview
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
```bash
|
|
||||||
# Code linting and formatting
|
|
||||||
pnpm lint
|
|
||||||
|
|
||||||
# Unit tests
|
|
||||||
pnpm test
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
|
||||||
|
|
@ -82,14 +82,6 @@ pnpm preview
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
```bash
|
|
||||||
# 代码校验与格式化
|
|
||||||
pnpm lint
|
|
||||||
|
|
||||||
# 单元测试
|
|
||||||
pnpm test
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
|
||||||
|
|
@ -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) => boolean | Promise<boolean></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) => boolean | Promise<boolean></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) 移除该插件。
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -76,15 +76,6 @@ pnpm build:staging
|
||||||
pnpm build
|
pnpm build
|
||||||
```
|
```
|
||||||
|
|
||||||
### 代码检查
|
|
||||||
```bash
|
|
||||||
# 代码校验与格式化
|
|
||||||
pnpm lint
|
|
||||||
|
|
||||||
# 单元测试
|
|
||||||
pnpm test
|
|
||||||
```
|
|
||||||
|
|
||||||
## 核心模块说明
|
## 核心模块说明
|
||||||
|
|
||||||
### 1. 路由系统 (src/router/)
|
### 1. 路由系统 (src/router/)
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -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: '/'
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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: {
|
||||||
|
|
|
||||||
|
|
@ -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']
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
// 是否允许跨域
|
// 是否允许跨域
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue