Compare commits

..

No commits in common. "77ff54efa3bc8cd43f8f4327b5b667b073d9b7aa" and "afdc9690e1c978174b48b47ef4d0e6886659caf1" have entirely different histories.

8 changed files with 7 additions and 346 deletions

View File

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

193
CLAUDE.md
View File

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

View File

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

View File

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

View File

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

View File

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