Compare commits
4 Commits
afdc9690e1
...
77ff54efa3
| Author | SHA1 | Date |
|---|---|---|
|
|
77ff54efa3 | |
|
|
f3b55de67d | |
|
|
1e5315377e | |
|
|
f8b616a064 |
|
|
@ -1,7 +1,8 @@
|
|||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(mkdir:*)"
|
||||
"Bash(mkdir:*)",
|
||||
"Bash(tree -L 3 -I 'node_modules|dist')"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { request } from '@/http/axios'
|
||||
import {
|
||||
BindQyUserCommand,
|
||||
BindWxMpUserCommand,
|
||||
GetTokenParams,
|
||||
LoginData,
|
||||
LogoutResponse,
|
||||
|
|
@ -72,3 +73,11 @@ export function bindQyUserApi(data: BindQyUserCommand) {
|
|||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function bindWxMpUser(data: BindWxMpUserCommand) {
|
||||
return request<ApiResponseData<string>>({
|
||||
url: '/wx/login/bindWxMpUser',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
|
@ -65,3 +65,9 @@ export interface BindQyUserCommand {
|
|||
name: string;
|
||||
idNum: string;
|
||||
}
|
||||
|
||||
export interface BindWxMpUserCommand {
|
||||
dynamicCode: string;
|
||||
name: string;
|
||||
idNum: string;
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
Loading…
Reference in New Issue