Compare commits

..

4 Commits

Author SHA1 Message Date
dzq 77ff54efa3 feat(用户中心): 添加代绑汇邦云功能
- 新增 BindHBYCloud 组件用于处理汇邦云账号绑定
- 在个人中心页面添加绑定入口按钮
- 实现动态码、姓名和身份证号的表单验证
- 处理绑定成功/失败后的反馈和状态重置
2025-11-06 17:13:36 +08:00
dzq f3b55de67d docs: 添加项目文档和更新权限配置
添加CLAUDE.md项目文档,详细说明技术栈、架构和开发指南
更新settings.local.json权限配置,添加tree命令权限
2025-11-06 16:59:40 +08:00
dzq 1e5315377e feat(微信绑定): 添加微信小程序用户绑定接口
新增微信小程序用户绑定接口 bindWxMpUser 及相关类型定义 BindWxMpUserCommand
2025-11-06 16:53:45 +08:00
dzq f8b616a064 fix(产品列表): 修复搜索查询未去除空格的问题
在搜索功能中增加trim()处理,确保查询字符串去除前后空格后再进行匹配。同时将product-item的key从id改为cellId以避免潜在冲突
2025-11-03 09:03:44 +08:00
8 changed files with 346 additions and 7 deletions

View File

@ -1,7 +1,8 @@
{
"permissions": {
"allow": [
"Bash(mkdir:*)"
"Bash(mkdir:*)",
"Bash(tree -L 3 -I 'node_modules|dist')"
],
"deny": [],
"ask": []

193
CLAUDE.md Normal file
View File

@ -0,0 +1,193 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is a mobile web application built with **Vue 3**, **Vite**, **TypeScript**, and **Vant**. It's a commercial cabinet management system with features including cabinet management, approval workflows, order management, and rental tracking.
## Technology Stack
- **Frontend**: Vue 3 (Composition API), TypeScript (strict mode)
- **UI Library**: Vant (mobile-optimized components)
- **Build Tool**: Vite 6.1
- **State Management**: Pinia
- **Routing**: Vue Router with hash/history mode
- **HTTP Client**: Axios with interceptors
- **CSS Framework**: UnoCSS (atomic CSS)
- **Testing**: Vitest + Happy DOM
- **Linting**: ESLint with @antfu/eslint-config
- **Package Manager**: pnpm
## Common Commands
```bash
# Install dependencies
pnpm i
# Start development server (port 3333, opens browser)
pnpm dev
# Build for staging environment
pnpm build:staging
# Build for production (includes zip)
pnpm build
# Preview production build
pnpm preview
# Lint and auto-fix code
pnpm lint
# Run unit tests
pnpm test
```
## Environment Configuration
Three environment files exist:
- `.env.development` - Development settings
- `.env.staging` - Staging settings
- `.env.production` - Production settings
Key environment variables:
- `VITE_BASE_URL` - API base URL
- `VITE_PUBLIC_PATH` - Public path for deployment
- `VITE_ROUTER_HISTORY` - Router mode (`hash` or `history`)
## Path Aliases
- `@/` - src directory
- `@@/` - src/common directory
- `@img/` - src/assets/images directory
## Code Architecture
### Entry Point (`src/main.ts`)
Application bootstrapping:
1. Creates Vue app instance
2. Installs plugins (global components, custom directives)
3. Mounts app after router is ready
### Routing (`src/router/`)
- **index.ts**: Main router configuration with two route groups:
- `systemRoutes`: Error pages (403, 404)
- `routes`: Business routes (cabinet, approval, orders, rentals, products)
- Each route includes layout config (navbar, tabbar) and metadata
- **guard.ts**: Route navigation guards for authentication and permission checks
### HTTP Client (`src/http/axios.ts`)
- Axios instance with request/response interceptors
- Automatic token handling via cookies
- Standardized error handling for HTTP status codes
- Business logic codes (0 = success, 401 = unauthorized)
### State Management (`src/pinia/`)
Pinia stores for application state. Check `src/pinia/stores/` for store implementations.
### API Layer (`src/common/apis/`)
Organized by business domain:
- `ab98/` - AB98 login integration
- `approval/` - Approval workflow APIs
- `cabinet/` - Cabinet management APIs
- `manage/` - Product management APIs
- `shop/` - Shop-related APIs
- `users/` - User management APIs
### Utilities (`src/common/`)
- `utils/` - Helper functions (cache, datetime, validation, permissions, wx)
- `composables/` - Vue composables (watermark, grayscale, dark mode)
- `components/` - Shared components
- `constants/` - Application constants
### Plugins (`src/plugins/`)
- `index.ts` - Plugin registration
- `console.ts` - VConsole integration for mobile debugging
- `permission-directive.ts` - v-permission directive for button-level permissions
## Code Style & Linting
- **ESLint Config**: @antfu/eslint-config with custom rules
- **Indentation**: 2 spaces
- **Quotes**: Double quotes
- **Semicolons**: No
- **Formatting**: ESLint handles formatting automatically
- **Import Sorting**: Enforced via ESLint (perfectionist/sort-imports)
- **Vue SFC Order**: [script, template, style]
**Important**:
- Disable Prettier in VS Code settings (already configured)
- ESLint auto-fixes on save
- Stylistic rules are silently enforced
## Testing
Tests use **Vitest** with **Happy DOM** environment:
- Test files: `tests/**/*.test.{ts,js}`
- Components use `@vue/test-utils`
- Example test: `tests/demo.test.ts`
## Build Configuration (`vite.config.ts`)
- **Port**: 3333 (auto-opens browser)
- **Proxy**: `/api/v1` → Apifox mock server
- **Legacy Support**: @vitejs/plugin-legacy enabled for older browsers
- **Manual Chunks**: Vue ecosystem packages separated
- **SVG Handling**: vite-svg-loader for importing SVGs as components
- **Auto Imports**: unplugin-auto-import for Vue/Vue Router/Pinia APIs
- **Component Imports**: unplugin-vue-components with VantResolver
## Mobile-Specific Features
1. **Touch Emulator**: @vant/touch-emulator (desktop testing)
2. **PX to VW**: PostCSS plugin for mobile viewport adaptation
3. **VConsole**: Mobile debugging console (src/plugins/console.ts)
4. **Viewport**: Mobile-first responsive design with px→vw conversion
5. **Atomic CSS**: UnoCSS for utility-first styling
## Development Tips
- **Component Auto-Import**: Vant components auto-import, no manual imports needed
- **Keep Alive**: Some routes use `keepAlive: true` for route caching
- **Route Caching**: Configured in Pinia store (src/pinia/stores/keep-alive.ts)
- **Watermark**: Defensive watermarking composable (src/common/composables/useWatermark.ts)
- **Theme**: Dark mode support via CSS variables
## Key Business Modules
1. **Cabinet Management** (`/cabinet`) - Cabinet control interface
2. **Approval System** (`/approval/*`) - Workflow approval processes
3. **Order Management** (`/order*`) - Order listing and details
4. **Product Catalog** (`/product/*`) - Product browsing and checkout
5. **Rental Tracking** (`/rental-list`) - Cabinet rental management
6. **User Profile** (`/me`) - User account management
7. **AB98 Login** (`/ab98`) - External authentication
## VS Code Configuration
Recommended extensions (`.vscode/extensions.json`):
- vue.volar (Vue language support)
- dbaeumer.vscode-eslint (ESLint integration)
- antfu.unocss (UnoCSS support)
- vitest.explorer (Test runner)
Configured settings:
- Use workspace TypeScript version
- Disable default formatter, use ESLint
- Auto-fix ESLint on save
- Disable stylistic rule notifications (but still auto-fix)
## Commit Message Convention
Use conventional commits:
- `feat`: New feature
- `fix`: Bug fix
- `perf`: Performance improvement
- `refactor`: Code refactoring
- `docs`: Documentation
- `types`: Type changes
- `test`: Unit tests
- `ci`: CI/CD
- `revert`: Revert changes
- `chore`: Maintenance tasks

View File

@ -8,11 +8,12 @@
"scripts": {
"dev": "vite",
"build:staging": "vue-tsc && vite build --mode staging",
"build": "vue-tsc && vite build",
"build": "vue-tsc && vite build && pnpm run zip",
"preview": "vite preview",
"lint": "eslint . --fix",
"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"
},
"dependencies": {
"@vant/touch-emulator": "1.4.0",

View File

@ -1,6 +1,7 @@
import { request } from '@/http/axios'
import {
BindQyUserCommand,
BindWxMpUserCommand,
GetTokenParams,
LoginData,
LogoutResponse,
@ -71,4 +72,12 @@ export function bindQyUserApi(data: BindQyUserCommand) {
method: 'post',
data
})
}
export function bindWxMpUser(data: BindWxMpUserCommand) {
return request<ApiResponseData<string>>({
url: '/wx/login/bindWxMpUser',
method: 'post',
data
})
}

View File

@ -64,4 +64,10 @@ export interface BindQyUserCommand {
qyUserId: number;
name: string;
idNum: string;
}
export interface BindWxMpUserCommand {
dynamicCode: string;
name: string;
idNum: string;
}

View File

@ -0,0 +1,109 @@
<script setup lang="ts">
import { ref } from 'vue'
import { bindWxMpUser } from '@/common/apis/ab98'
import { showLoadingToast, showSuccessToast, showFailToast } from 'vant'
interface Props {
show: boolean
}
const props = defineProps<Props>()
const emit = defineEmits<{
(e: 'update:show', value: boolean): void
(e: 'success'): void
}>()
const formData = ref({
dynamicCode: '',
name: '',
idNum: ''
})
const loading = ref(false)
const handleClose = () => {
emit('update:show', false)
}
const handleSubmit = async () => {
if (!formData.value.dynamicCode || !formData.value.name || !formData.value.idNum) {
showFailToast('请填写完整信息')
return
}
loading.value = true
showLoadingToast({
message: '绑定中...',
forbidClick: true
})
try {
await bindWxMpUser({
dynamicCode: formData.value.dynamicCode,
name: formData.value.name,
idNum: formData.value.idNum
})
showSuccessToast('绑定成功')
emit('success')
handleClose()
//
formData.value = {
dynamicCode: '',
name: '',
idNum: ''
}
} catch (error) {
console.error('绑定失败:', error)
showFailToast('绑定失败,请检查信息是否正确')
} finally {
loading.value = false
}
}
</script>
<template>
<van-dialog
:show="props.show"
title="代绑汇邦云"
show-cancel-button
confirm-button-text="绑定"
cancel-button-text="取消"
:before-close="handleClose"
@confirm="handleSubmit"
>
<div class="bind-form p-4">
<van-form>
<van-cell-group inset>
<van-field
v-model="formData.dynamicCode"
label="动态码"
placeholder="请输入动态码"
required
/>
<van-field
v-model="formData.name"
label="姓名"
placeholder="请输入姓名"
required
/>
<van-field
v-model="formData.idNum"
label="身份证号"
placeholder="请输入身份证号"
required
/>
</van-cell-group>
</van-form>
</div>
</van-dialog>
</template>
<style lang="scss" scoped>
.bind-form {
max-height: 400px;
overflow-y: auto;
}
</style>

View File

@ -4,7 +4,8 @@ import { useWxStore } from '@/pinia/stores/wx'
import { useAb98UserStore } from '@/pinia/stores/ab98-user'
import { storeToRefs } from 'pinia'
import { publicPath } from "@/common/utils/path"
import { showConfirmDialog } from 'vant';
import { showConfirmDialog } from 'vant'
import BindHBYCloud from '@/common/components/BindHBYCloud.vue'
const router = useRouter();
const route = useRoute();
@ -39,6 +40,13 @@ const handleLogout = () => {
});
};
const showBindDialog = ref(false);
const handleBindSuccess = () => {
//
console.log('绑定汇邦云成功');
};
wxStore.refreshBalance();
</script>
@ -107,6 +115,12 @@ wxStore.refreshBalance();
<span>我的柜子</span>
</div>
</van-col>
<van-col span="8">
<div class="custom-btn" @click="showBindDialog = true">
<van-icon name="friends-o" size="20px" />
<span>代绑汇邦云</span>
</div>
</van-col>
<!-- <van-col span="6">
<div v-if="wxStore.isCabinetAdmin" class="custom-btn" @click="router.push('/manage/goods')">
<van-icon name="comment-o" size="20px" />
@ -131,9 +145,15 @@ wxStore.refreshBalance();
<span>耗材核销</span>
</div>
</van-col>
<van-col span="8"></van-col>
<!-- <van-col span="8"></van-col> -->
</van-row>
</div>
<!-- 代绑汇邦云弹窗 -->
<BindHBYCloud
v-model:show="showBindDialog"
@success="handleBindSuccess"
/>
</template>
<style lang="scss" scoped>

View File

@ -80,7 +80,7 @@ function getCartItemCount(cellId: number) {
function filterProductsByName(products: Product[], query: string) {
if (!query) return products;
return products.filter(p =>
p.name.toLowerCase().includes(query.toLowerCase())
p.name.toLowerCase().includes(query.trim().toLowerCase())
);
}
@ -123,7 +123,7 @@ function handleCheckout() {
<div ref="scrollContainer" class="product-list">
<van-search v-model="searchQuery" placeholder="搜索商品名称" shape="round" class="search-box" />
<div class="category-section">
<van-cell v-for="product in currentProducts" :key="product.id" class="product-item">
<van-cell v-for="product in currentProducts" :key="product.cellId" class="product-item">
<template #icon>
<van-image :src="product.image" width="80" height="80" @click.stop="showProductDetail(product.id)"
class="product-image">