更新
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
# Order Detail Address Display Fix (订单详情地址显示修复)
|
||||
|
||||
## Issue
|
||||
The order detail view was only showing the detailed address (`shipping_address`) without displaying the province, city, and district information.
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. Added Computed Property for Full Address
|
||||
**File**: `admin/src/views/consumer/prescription/order_list.vue`
|
||||
|
||||
Added `detailFullAddress` computed property that combines all address components:
|
||||
|
||||
```typescript
|
||||
// 完整收货地址(省市区 + 详细地址)
|
||||
const detailFullAddress = computed(() => {
|
||||
const d = detailData.value
|
||||
if (!d) return '—'
|
||||
|
||||
const parts = []
|
||||
if (d.shipping_province) parts.push(d.shipping_province)
|
||||
if (d.shipping_city) parts.push(d.shipping_city)
|
||||
if (d.shipping_district) parts.push(d.shipping_district)
|
||||
if (d.shipping_address) parts.push(d.shipping_address)
|
||||
|
||||
return parts.length > 0 ? parts.join(' ') : '—'
|
||||
})
|
||||
```
|
||||
|
||||
### 2. Updated Display Template
|
||||
Changed the shipping address display from:
|
||||
```vue
|
||||
<el-descriptions-item label="收货地址" :span="2">
|
||||
{{ detailData.shipping_address }}
|
||||
</el-descriptions-item>
|
||||
```
|
||||
|
||||
To:
|
||||
```vue
|
||||
<el-descriptions-item label="收货地址" :span="2">
|
||||
{{ detailFullAddress }}
|
||||
</el-descriptions-item>
|
||||
```
|
||||
|
||||
## Display Format
|
||||
|
||||
### Before:
|
||||
```
|
||||
收货地址: 顶顶顶
|
||||
```
|
||||
|
||||
### After:
|
||||
```
|
||||
收货地址: 北京市 北京市 丰台区 顶顶顶
|
||||
```
|
||||
|
||||
## Logic
|
||||
|
||||
The `detailFullAddress` computed property:
|
||||
1. Checks if `detailData` exists
|
||||
2. Collects non-empty address components in order:
|
||||
- `shipping_province` (省)
|
||||
- `shipping_city` (市)
|
||||
- `shipping_district` (区/县)
|
||||
- `shipping_address` (详细地址)
|
||||
3. Joins them with spaces
|
||||
4. Returns '—' if no address components exist
|
||||
|
||||
## Examples
|
||||
|
||||
### Complete Address:
|
||||
```
|
||||
Input:
|
||||
shipping_province: "四川省"
|
||||
shipping_city: "成都市"
|
||||
shipping_district: "武侯区"
|
||||
shipping_address: "天府大道中段666号"
|
||||
|
||||
Output:
|
||||
四川省 成都市 武侯区 天府大道中段666号
|
||||
```
|
||||
|
||||
### Partial Address (Missing Province):
|
||||
```
|
||||
Input:
|
||||
shipping_province: null
|
||||
shipping_city: "成都市"
|
||||
shipping_district: "武侯区"
|
||||
shipping_address: "天府大道中段666号"
|
||||
|
||||
Output:
|
||||
成都市 武侯区 天府大道中段666号
|
||||
```
|
||||
|
||||
### Only Detailed Address:
|
||||
```
|
||||
Input:
|
||||
shipping_province: null
|
||||
shipping_city: null
|
||||
shipping_district: null
|
||||
shipping_address: "天府大道中段666号"
|
||||
|
||||
Output:
|
||||
天府大道中段666号
|
||||
```
|
||||
|
||||
### No Address:
|
||||
```
|
||||
Input:
|
||||
shipping_province: null
|
||||
shipping_city: null
|
||||
shipping_district: null
|
||||
shipping_address: null
|
||||
|
||||
Output:
|
||||
—
|
||||
```
|
||||
|
||||
## Testing Steps
|
||||
|
||||
### 1. Test Complete Address Display
|
||||
1. Open an order that has province/city/district data
|
||||
2. Check the "收货地址" field in the detail view
|
||||
3. Verify it shows: `省 市 区 详细地址`
|
||||
|
||||
### 2. Test Legacy Orders (No Region Data)
|
||||
1. Open an old order that only has `shipping_address`
|
||||
2. Verify it still displays the detailed address correctly
|
||||
3. Should show just the detailed address without extra spaces
|
||||
|
||||
### 3. Test Empty Address
|
||||
1. Create an order without any address information
|
||||
2. Verify it displays "—" instead of empty string
|
||||
|
||||
### 4. Test Different Address Combinations
|
||||
Test various combinations:
|
||||
- Full address (province + city + district + detail)
|
||||
- City + district + detail (no province)
|
||||
- District + detail (no province/city)
|
||||
- Detail only (no region data)
|
||||
- Empty (no data at all)
|
||||
|
||||
## Related Files
|
||||
|
||||
### Frontend:
|
||||
- `admin/src/views/consumer/prescription/order_list.vue`
|
||||
- Added `detailFullAddress` computed property
|
||||
- Updated shipping address display template
|
||||
|
||||
### Backend:
|
||||
- `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
|
||||
- Already returns `shipping_province`, `shipping_city`, `shipping_district` in detail response
|
||||
|
||||
### Database:
|
||||
- `tcm_prescription_order` table
|
||||
- Columns: `shipping_province`, `shipping_city`, `shipping_district`, `shipping_address`
|
||||
|
||||
## Benefits
|
||||
|
||||
✅ **Complete Information**: Users can now see the full address including province/city/district
|
||||
✅ **Better Readability**: Address components are clearly separated with spaces
|
||||
✅ **Backward Compatible**: Works with legacy orders that only have detailed address
|
||||
✅ **Graceful Handling**: Shows "—" when no address data exists
|
||||
✅ **Flexible**: Handles partial address data (missing province, city, or district)
|
||||
|
||||
## Summary
|
||||
|
||||
The order detail view now displays the complete shipping address including province, city, district, and detailed address, providing users with full address information at a glance!
|
||||
@@ -0,0 +1,191 @@
|
||||
# Order List Region Selector Update (订单列表省市区选择器更新)
|
||||
|
||||
## Changes Made
|
||||
|
||||
Updated the order list view (`order_list.vue`) to use the same API-based region data loading as the prescription index view.
|
||||
|
||||
### 1. Replaced Static Region Data with API Loading
|
||||
**File**: `admin/src/views/consumer/prescription/order_list.vue`
|
||||
|
||||
#### Before:
|
||||
- Static hardcoded region data with limited provinces/cities
|
||||
- Only included: 四川省, 北京市, 上海市, 广东省
|
||||
|
||||
#### After:
|
||||
- Dynamic API-based region data loading
|
||||
- Fetches complete China region data from `/api-proxy/area/ChinaCitys.json`
|
||||
- Includes all provinces, cities, and districts
|
||||
- Falls back to basic data if API fails
|
||||
|
||||
### 2. Added Data Transformation Function
|
||||
Added `transformRegionData()` function to convert API format to el-cascader format:
|
||||
- API format: `{province, citys: [{city, areas: [{area}]}]}`
|
||||
- Cascader format: `{value, label, children: [{value, label, children: [{value, label}]}]}`
|
||||
|
||||
### 3. Updated Cascader Component
|
||||
Added missing props to the cascader:
|
||||
- `emitPath: true` - Returns full path array instead of just last value
|
||||
- `filterable` - Enables search functionality
|
||||
|
||||
### 4. Updated onMounted Hook
|
||||
Changed from:
|
||||
```typescript
|
||||
onMounted(() => {
|
||||
getLists()
|
||||
})
|
||||
```
|
||||
|
||||
To:
|
||||
```typescript
|
||||
onMounted(async () => {
|
||||
await loadRegionData()
|
||||
getLists()
|
||||
})
|
||||
```
|
||||
|
||||
### 5. Added Region Fields to Quick Edit
|
||||
Updated the quick tracking number edit function to include region fields:
|
||||
```typescript
|
||||
shipping_province: d.shipping_province || '',
|
||||
shipping_city: d.shipping_city || '',
|
||||
shipping_district: d.shipping_district || '',
|
||||
```
|
||||
|
||||
This ensures region data is preserved when only updating tracking numbers.
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Loading Region Data:
|
||||
1. Component mounts
|
||||
2. `loadRegionData()` fetches from `/api-proxy/area/ChinaCitys.json`
|
||||
3. `transformRegionData()` converts API format to cascader format
|
||||
4. `regionOptions` is populated with all China regions
|
||||
5. If API fails, falls back to basic region data
|
||||
|
||||
### Editing Order with Region:
|
||||
1. User opens edit dialog
|
||||
2. Region data is loaded from order: `[province, city, district]`
|
||||
3. Cascader displays the selected region
|
||||
4. User can change region selection
|
||||
5. On save, region array is split into three fields:
|
||||
- `shipping_province`
|
||||
- `shipping_city`
|
||||
- `shipping_district`
|
||||
6. Backend saves the three separate fields
|
||||
|
||||
### Quick Edit (Tracking Number):
|
||||
1. User clicks quick edit for tracking number
|
||||
2. System fetches full order details
|
||||
3. Includes region fields in the update payload
|
||||
4. Region data is preserved while updating tracking number
|
||||
|
||||
## Code Structure
|
||||
|
||||
### Region Data Loading:
|
||||
```typescript
|
||||
const regionOptions = ref([])
|
||||
|
||||
const transformRegionData = (data: any[]) => {
|
||||
// Converts API format to cascader format
|
||||
}
|
||||
|
||||
const loadRegionData = async () => {
|
||||
// Fetches from API and transforms data
|
||||
// Falls back to basic data on error
|
||||
}
|
||||
```
|
||||
|
||||
### Region Field Handling in Edit:
|
||||
```typescript
|
||||
// Loading data into form:
|
||||
const province = d.shipping_province || ''
|
||||
const city = d.shipping_city || ''
|
||||
const district = d.shipping_district || ''
|
||||
if (province || city || district) {
|
||||
editForm.region = [province, city, district].filter(v => v !== '')
|
||||
}
|
||||
|
||||
// Saving data from form:
|
||||
if (editForm.region && editForm.region.length > 0) {
|
||||
payload.shipping_province = editForm.region[0] || ''
|
||||
payload.shipping_city = editForm.region[1] || ''
|
||||
payload.shipping_district = editForm.region[2] || ''
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Steps
|
||||
|
||||
### 1. Test Region Data Loading
|
||||
1. Open order list page
|
||||
2. Check browser console for:
|
||||
```
|
||||
开始加载省市区数据...
|
||||
响应状态: 200
|
||||
加载的数据条数: XX
|
||||
regionOptions 已设置,条数: XX
|
||||
```
|
||||
3. If you see errors, check that dev server was restarted
|
||||
|
||||
### 2. Test Edit Order with Region
|
||||
1. Click "编辑" on any order
|
||||
2. Check that the region cascader shows the current region (if set)
|
||||
3. Change the region selection
|
||||
4. Save the order
|
||||
5. Verify database is updated:
|
||||
```sql
|
||||
SELECT shipping_province, shipping_city, shipping_district
|
||||
FROM tcm_prescription_order
|
||||
WHERE id = XX;
|
||||
```
|
||||
|
||||
### 3. Test Region Search
|
||||
1. Open edit dialog
|
||||
2. Click on region cascader
|
||||
3. Type a province/city name in the search box
|
||||
4. Verify search results appear
|
||||
5. Select a region from search results
|
||||
|
||||
### 4. Test Quick Edit Preserves Region
|
||||
1. Find an order with region data set
|
||||
2. Click "快递单号" quick edit button
|
||||
3. Update tracking number
|
||||
4. Save
|
||||
5. Verify region data is still present in database
|
||||
|
||||
### 5. Test Fallback Data
|
||||
1. Stop the dev server
|
||||
2. Open order list page
|
||||
3. Verify fallback region data is used (四川省, 北京市)
|
||||
4. Restart dev server
|
||||
5. Refresh page
|
||||
6. Verify full region data is loaded
|
||||
|
||||
## Related Files
|
||||
|
||||
### Frontend:
|
||||
- `admin/src/views/consumer/prescription/order_list.vue`
|
||||
- Added `loadRegionData()` and `transformRegionData()` functions
|
||||
- Updated `onMounted()` to load region data
|
||||
- Updated cascader component with `emitPath: true` and `filterable`
|
||||
- Added region fields to quick edit function
|
||||
- Region field handling already existed in main edit function
|
||||
|
||||
### Backend:
|
||||
- `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
|
||||
- Already handles `shipping_province`, `shipping_city`, `shipping_district` in both `create()` and `edit()` methods
|
||||
|
||||
### Configuration:
|
||||
- `admin/vite.config.ts`
|
||||
- Proxy configuration for `/api-proxy` already exists
|
||||
|
||||
## Summary
|
||||
|
||||
✅ Replaced static region data with API-based loading
|
||||
✅ Added data transformation function
|
||||
✅ Updated cascader with `emitPath: true` and `filterable`
|
||||
✅ Updated `onMounted()` to load region data
|
||||
✅ Added region fields to quick edit function
|
||||
✅ Fallback data available if API fails
|
||||
✅ Search functionality enabled
|
||||
|
||||
The order list now uses the same comprehensive region data as the prescription index, providing access to all provinces, cities, and districts in China!
|
||||
@@ -0,0 +1,170 @@
|
||||
# Region Database Save Fix (省市区数据库保存修复)
|
||||
|
||||
## Issue
|
||||
The region fields (province, city, district) were being sent in the API request payload but were not being saved to the database.
|
||||
|
||||
## Root Cause Analysis
|
||||
|
||||
### Frontend Issue
|
||||
The frontend was sending the wrong parameter names:
|
||||
- Sent: `province`, `city`, `district`
|
||||
- Expected by backend: `shipping_province`, `shipping_city`, `shipping_district`
|
||||
|
||||
### Backend Issue
|
||||
The `create()` method in `PrescriptionOrderLogic.php` was not handling the province/city/district fields at all, even though the `edit()` method had the code to handle them.
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. Frontend - Fixed Parameter Names
|
||||
**File**: `admin/src/views/consumer/prescription/index.vue`
|
||||
|
||||
Changed the payload to use the correct parameter names:
|
||||
|
||||
```typescript
|
||||
// Before:
|
||||
payload.province = createOrderForm.region[0]
|
||||
payload.city = createOrderForm.region[1]
|
||||
payload.district = createOrderForm.region[2]
|
||||
|
||||
// After:
|
||||
payload.shipping_province = createOrderForm.region[0]
|
||||
payload.shipping_city = createOrderForm.region[1]
|
||||
payload.shipping_district = createOrderForm.region[2]
|
||||
```
|
||||
|
||||
### 2. Backend - Added Region Fields to create() Method
|
||||
**File**: `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
|
||||
|
||||
Added code to handle province/city/district fields in the `create()` method:
|
||||
|
||||
```php
|
||||
// 处理省市区字段
|
||||
if (isset($params['shipping_province'])) {
|
||||
$order->shipping_province = (string) $params['shipping_province'];
|
||||
}
|
||||
if (isset($params['shipping_city'])) {
|
||||
$order->shipping_city = (string) $params['shipping_city'];
|
||||
}
|
||||
if (isset($params['shipping_district'])) {
|
||||
$order->shipping_district = (string) $params['shipping_district'];
|
||||
}
|
||||
```
|
||||
|
||||
This matches the existing code in the `edit()` method.
|
||||
|
||||
## Data Flow (After Fix)
|
||||
|
||||
### Create Order Flow:
|
||||
1. User selects region: 北京市 → 北京市 → 丰台区
|
||||
2. Frontend stores: `createOrderForm.region = ['北京市', '北京市', '丰台区']`
|
||||
3. Frontend sends payload:
|
||||
```json
|
||||
{
|
||||
"shipping_province": "北京市",
|
||||
"shipping_city": "北京市",
|
||||
"shipping_district": "丰台区",
|
||||
...other fields
|
||||
}
|
||||
```
|
||||
4. Backend `create()` method processes:
|
||||
```php
|
||||
$order->shipping_province = "北京市";
|
||||
$order->shipping_city = "北京市";
|
||||
$order->shipping_district = "丰台区";
|
||||
```
|
||||
5. Database columns are populated:
|
||||
```
|
||||
shipping_province: 北京市
|
||||
shipping_city: 北京市
|
||||
shipping_district: 丰台区
|
||||
```
|
||||
|
||||
## Testing Steps
|
||||
|
||||
### 1. Test Create Order with Region
|
||||
1. Open prescription list page
|
||||
2. Click "创建订单" on any prescription
|
||||
3. Fill in required fields:
|
||||
- Select patient
|
||||
- Enter recipient name and phone
|
||||
- **Select region**: 北京市 → 北京市 → 丰台区
|
||||
- Enter detailed address
|
||||
4. Click "创建业务订单"
|
||||
5. Check browser Network tab:
|
||||
- Verify payload includes:
|
||||
```json
|
||||
{
|
||||
"shipping_province": "北京市",
|
||||
"shipping_city": "北京市",
|
||||
"shipping_district": "丰台区"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Verify Database
|
||||
After creating the order, check the database:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
id,
|
||||
order_no,
|
||||
shipping_province,
|
||||
shipping_city,
|
||||
shipping_district,
|
||||
shipping_address
|
||||
FROM tcm_prescription_order
|
||||
ORDER BY id DESC
|
||||
LIMIT 1;
|
||||
```
|
||||
|
||||
Expected result:
|
||||
```
|
||||
| id | order_no | shipping_province | shipping_city | shipping_district | shipping_address |
|
||||
|----|----------|-------------------|---------------|-------------------|------------------|
|
||||
| XX | POXXXXXX | 北京市 | 北京市 | 丰台区 | 顶顶顶 |
|
||||
```
|
||||
|
||||
### 3. Test Different Regions
|
||||
Test with various regions to ensure all work correctly:
|
||||
- 四川省 → 成都市 → 武侯区
|
||||
- 上海市 → 上海市 → 浦东新区
|
||||
- 广东省 → 深圳市 → 南山区
|
||||
|
||||
### 4. Test Edit Order
|
||||
1. Edit an existing order
|
||||
2. Change the region
|
||||
3. Save and verify the database is updated
|
||||
|
||||
## Database Schema
|
||||
|
||||
The database should have these columns (from `add_shipping_region_fields.sql`):
|
||||
|
||||
```sql
|
||||
ALTER TABLE `tcm_prescription_order`
|
||||
ADD COLUMN `shipping_province` VARCHAR(50) DEFAULT NULL COMMENT '收货省份' AFTER `shipping_address`,
|
||||
ADD COLUMN `shipping_city` VARCHAR(50) DEFAULT NULL COMMENT '收货城市' AFTER `shipping_province`,
|
||||
ADD COLUMN `shipping_district` VARCHAR(50) DEFAULT NULL COMMENT '收货区县' AFTER `shipping_city`;
|
||||
```
|
||||
|
||||
## Related Files
|
||||
|
||||
### Frontend:
|
||||
- `admin/src/views/consumer/prescription/index.vue`
|
||||
- Changed parameter names in `submitCreateOrderFromPrescription()`
|
||||
|
||||
### Backend:
|
||||
- `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
|
||||
- Added province/city/district handling in `create()` method
|
||||
- Already had province/city/district handling in `edit()` method
|
||||
|
||||
### Database:
|
||||
- `add_shipping_region_fields.sql` (migration file)
|
||||
|
||||
## Summary
|
||||
|
||||
✅ Fixed frontend parameter names: `province` → `shipping_province`, etc.
|
||||
✅ Added province/city/district handling to backend `create()` method
|
||||
✅ Backend `edit()` method already had the correct code
|
||||
✅ Database columns will now be populated correctly on order creation
|
||||
✅ Database columns will be updated correctly on order edit
|
||||
|
||||
The region fields are now properly saved to the database for both create and edit operations!
|
||||
@@ -0,0 +1,72 @@
|
||||
# 省市区数据加载说明
|
||||
|
||||
## 问题
|
||||
省市区下拉框显示为空
|
||||
|
||||
## 原因
|
||||
1. Vite代理配置需要重启开发服务器才能生效
|
||||
2. 如果代理加载失败,会使用后备数据
|
||||
|
||||
## 解决步骤
|
||||
|
||||
### 1. 重启开发服务器
|
||||
```bash
|
||||
# 停止当前运行的开发服务器 (Ctrl+C)
|
||||
# 然后重新启动
|
||||
cd admin
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### 2. 检查控制台日志
|
||||
打开浏览器开发者工具的Console标签,应该能看到:
|
||||
- "开始加载省市区数据..."
|
||||
- "响应状态: 200"
|
||||
- "加载的数据条数: XX"
|
||||
- "regionOptions 已设置,条数: XX"
|
||||
|
||||
### 3. 如果代理失败
|
||||
如果看到错误信息,会自动使用后备数据,包含:
|
||||
- 四川省(成都市、绵阳市)
|
||||
- 北京市
|
||||
- 上海市
|
||||
- 广东省(广州市、深圳市)
|
||||
|
||||
## 配置说明
|
||||
|
||||
### Vite代理配置 (admin/vite.config.ts)
|
||||
```typescript
|
||||
server: {
|
||||
proxy: {
|
||||
'/api-proxy': {
|
||||
target: 'https://admin.zhenyangtang.com.cn',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api-proxy/, '')
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 前端请求 (admin/src/views/consumer/prescription/index.vue)
|
||||
```javascript
|
||||
const response = await fetch('/api-proxy/area/ChinaCitys.json')
|
||||
```
|
||||
|
||||
## 生产环境配置
|
||||
|
||||
生产环境需要在nginx配置代理:
|
||||
|
||||
```nginx
|
||||
location /api-proxy/ {
|
||||
proxy_pass https://admin.zhenyangtang.com.cn/;
|
||||
proxy_set_header Host admin.zhenyangtang.com.cn;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
```
|
||||
|
||||
## 测试
|
||||
1. 打开创建订单对话框
|
||||
2. 查看"省市区"下拉框
|
||||
3. 应该能看到省份列表
|
||||
4. 选择省份后能看到城市列表
|
||||
5. 选择城市后能看到区县列表
|
||||
@@ -0,0 +1,153 @@
|
||||
# Region Field Submission Fix (省市区字段提交修复)
|
||||
|
||||
## Issue
|
||||
The region selector (省市区) in the create order form was not submitting the selected province, city, and district values to the database.
|
||||
|
||||
## Root Cause
|
||||
1. The cascader component had `emitPath: false`, which only returned the last selected value (district name) instead of the full path
|
||||
2. The `submitCreateOrderFromPrescription()` function was not including the region fields in the payload sent to the backend
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. Updated Cascader Configuration
|
||||
**File**: `admin/src/views/consumer/prescription/index.vue`
|
||||
|
||||
Changed `emitPath` from `false` to `true` to get the full path array:
|
||||
|
||||
```typescript
|
||||
// Before:
|
||||
:props="{ value: 'name', label: 'name', children: 'children', emitPath: false, checkStrictly: false }"
|
||||
|
||||
// After:
|
||||
:props="{ value: 'name', label: 'name', children: 'children', emitPath: true, checkStrictly: false }"
|
||||
```
|
||||
|
||||
**Effect**: Now `createOrderForm.region` will contain an array like `['四川省', '成都市', '锦江区']` instead of just `'锦江区'`
|
||||
|
||||
### 2. Updated Form Submission
|
||||
**File**: `admin/src/views/consumer/prescription/index.vue`
|
||||
|
||||
Added code to extract province, city, and district from the region array and include them in the payload:
|
||||
|
||||
```typescript
|
||||
// 添加省市区字段
|
||||
if (createOrderForm.region && createOrderForm.region.length >= 3) {
|
||||
payload.province = createOrderForm.region[0]
|
||||
payload.city = createOrderForm.region[1]
|
||||
payload.district = createOrderForm.region[2]
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Updated handleRegionChange Comment
|
||||
Added clarification that the value is now an array of the full path.
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Before Fix:
|
||||
1. User selects: 四川省 → 成都市 → 锦江区
|
||||
2. `createOrderForm.region` = `'锦江区'` (only last value)
|
||||
3. Payload sent to backend: **No province/city/district fields**
|
||||
4. Database: province, city, district columns remain empty
|
||||
|
||||
### After Fix:
|
||||
1. User selects: 四川省 → 成都市 → 锦江区
|
||||
2. `createOrderForm.region` = `['四川省', '成都市', '锦江区']` (full path)
|
||||
3. Payload sent to backend:
|
||||
```json
|
||||
{
|
||||
"province": "四川省",
|
||||
"city": "成都市",
|
||||
"district": "锦江区",
|
||||
...other fields
|
||||
}
|
||||
```
|
||||
4. Database: province, city, district columns are populated correctly
|
||||
|
||||
## Testing Steps
|
||||
|
||||
### 1. Test Region Selection and Submission
|
||||
1. Open the prescription list page
|
||||
2. Click "创建订单" button on any prescription
|
||||
3. Fill in the required fields:
|
||||
- Select a patient (患者)
|
||||
- Enter recipient name (收货人)
|
||||
- Enter recipient phone (收货手机)
|
||||
- **Select province/city/district** (省市区): e.g., 四川省 → 成都市 → 锦江区
|
||||
- Enter detailed address (详细地址)
|
||||
4. Click "创建业务订单" button
|
||||
5. Check browser Network tab:
|
||||
- Find the API request to create order
|
||||
- Verify the payload includes:
|
||||
```json
|
||||
{
|
||||
"province": "四川省",
|
||||
"city": "成都市",
|
||||
"district": "锦江区"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Verify Database Storage
|
||||
After creating an order, check the database:
|
||||
|
||||
```sql
|
||||
SELECT id, order_no, province, city, district, shipping_address
|
||||
FROM tcm_prescription_order
|
||||
ORDER BY id DESC
|
||||
LIMIT 1;
|
||||
```
|
||||
|
||||
Expected result:
|
||||
```
|
||||
| id | order_no | province | city | district | shipping_address |
|
||||
|----|----------|----------|--------|----------|------------------|
|
||||
| XX | XXXXXX | 四川省 | 成都市 | 锦江区 | 中关村... |
|
||||
```
|
||||
|
||||
### 3. Test Different Regions
|
||||
Test with various regions to ensure the data structure works correctly:
|
||||
- 北京市 → 北京市 → 朝阳区
|
||||
- 上海市 → 上海市 → 浦东新区
|
||||
- 广东省 → 深圳市 → 南山区
|
||||
|
||||
### 4. Test Edge Cases
|
||||
1. **No region selected**: Should still allow submission (if not required)
|
||||
2. **Only province selected**: Should not submit incomplete data
|
||||
3. **Clear selection**: Click the clear button (×) and verify region is cleared
|
||||
|
||||
## Backend Verification
|
||||
|
||||
The backend should already have the database columns from the migration `add_shipping_region_fields.sql`:
|
||||
- `province` (varchar)
|
||||
- `city` (varchar)
|
||||
- `district` (varchar)
|
||||
|
||||
If the backend validation or logic needs to be updated, check:
|
||||
- `server/app/adminapi/validate/tcm/PrescriptionOrderValidate.php`
|
||||
- `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
|
||||
|
||||
## Console Debugging
|
||||
|
||||
If you need to debug, check the browser console for the log message:
|
||||
```
|
||||
选择的省市区: ['四川省', '成都市', '锦江区']
|
||||
```
|
||||
|
||||
This confirms the cascader is emitting the correct data structure.
|
||||
|
||||
## Related Files
|
||||
|
||||
### Frontend:
|
||||
- `admin/src/views/consumer/prescription/index.vue` (main changes)
|
||||
|
||||
### Backend:
|
||||
- `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php` (should handle province/city/district)
|
||||
- `add_shipping_region_fields.sql` (database migration)
|
||||
|
||||
## Summary
|
||||
|
||||
✅ Changed cascader `emitPath` to `true` to get full path array
|
||||
✅ Added province/city/district extraction in form submission
|
||||
✅ Payload now includes all three region fields
|
||||
✅ Database columns will be populated correctly
|
||||
|
||||
The region selector now properly submits province, city, and district values to the database.
|
||||
@@ -0,0 +1,199 @@
|
||||
# Region Selector and Dosage Fields Fix
|
||||
|
||||
## Issues Identified
|
||||
|
||||
### 1. Region Selector Empty (省市区下拉为空)
|
||||
**Problem**: The region cascader dropdown is empty when opening the create order form.
|
||||
|
||||
**Root Cause**: The Vite proxy configuration is correct, but the development server needs to be restarted for the proxy to take effect.
|
||||
|
||||
**Solution**:
|
||||
- The proxy configuration in `admin/vite.config.ts` is already set up correctly:
|
||||
```typescript
|
||||
proxy: {
|
||||
'/api-proxy': {
|
||||
target: 'https://admin.zhenyangtang.com.cn',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api-proxy/, '')
|
||||
}
|
||||
}
|
||||
```
|
||||
- The `transformRegionData()` function correctly converts the API format to el-cascader format
|
||||
- **ACTION REQUIRED**: Restart the development server
|
||||
|
||||
### 2. Dosage Dropdown Not Showing Selected Value on First Load
|
||||
**Problem**: When editing a prescription, the dosage dropdown (用量) appears empty on first load, but shows correctly when reopening the dialog.
|
||||
|
||||
**Root Cause**: Timing issue with the `isLoadingData` flag. The `nextTick()` callback was executing before all data was fully rendered.
|
||||
|
||||
**Solution**: Changed from `nextTick()` to `setTimeout(..., 0)` to ensure all data is rendered before re-enabling the watch function.
|
||||
|
||||
**Changed in**: `admin/src/views/consumer/prescription/index.vue`
|
||||
```typescript
|
||||
// Before:
|
||||
nextTick(() => {
|
||||
isLoadingData.value = false
|
||||
})
|
||||
|
||||
// After:
|
||||
setTimeout(() => {
|
||||
isLoadingData.value = false
|
||||
}, 0)
|
||||
```
|
||||
|
||||
### 3. Dosage Fields Not Saving to Database
|
||||
**Problem**: User reported that dosage fields (用量、单位、代煎、每贴出包数) are not being saved to the database.
|
||||
|
||||
**Analysis**:
|
||||
- ✅ Backend validation (`PrescriptionValidate.php`) already includes all dosage fields in `sceneAdd()` and `sceneEdit()`
|
||||
- ✅ Frontend component (`tcm-prescription/index.vue`) includes all fields in `handleSave()`
|
||||
- ✅ Frontend prescription list view (`prescription/index.vue`) includes all fields in form submission
|
||||
- ✅ Watch function properly resets values when prescription type changes
|
||||
- ✅ `isLoadingData` flag prevents watch from overwriting loaded data
|
||||
|
||||
**Verification Needed**:
|
||||
1. Check if database migration was executed: `add_prescription_dosage_fields.sql`
|
||||
2. Check browser console for any API errors when saving
|
||||
3. Verify that the API response shows the fields were saved
|
||||
|
||||
## Files Modified
|
||||
|
||||
### 1. admin/src/views/consumer/prescription/index.vue
|
||||
- Changed `nextTick()` to `setTimeout(..., 0)` in `handleEdit()` function (line ~2187)
|
||||
|
||||
## Files Already Correct (No Changes Needed)
|
||||
|
||||
### 1. admin/vite.config.ts
|
||||
- Proxy configuration is correct
|
||||
|
||||
### 2. admin/src/views/consumer/prescription/index.vue
|
||||
- `transformRegionData()` function correctly converts API format
|
||||
- `loadRegionData()` function properly fetches and transforms data
|
||||
- Watch function includes `isLoadingData` flag to prevent overwriting
|
||||
- All dosage fields are included in form submission
|
||||
|
||||
### 3. admin/src/components/tcm-prescription/index.vue
|
||||
- All dosage fields are included in `handleSave()`
|
||||
- Watch function properly handles prescription type changes
|
||||
- bags_per_dose field is displayed for 饮片 type
|
||||
|
||||
### 4. server/app/adminapi/validate/tcm/PrescriptionValidate.php
|
||||
- All dosage fields are included in `sceneAdd()` and `sceneEdit()`
|
||||
|
||||
## Testing Steps
|
||||
|
||||
### Test 1: Region Selector
|
||||
1. **Restart the development server**:
|
||||
```bash
|
||||
# Stop current server (Ctrl+C)
|
||||
cd admin
|
||||
npm run dev
|
||||
```
|
||||
2. Open the create order form (创建订单)
|
||||
3. Click on the "省市区" cascader
|
||||
4. Verify that all provinces are displayed
|
||||
5. Select a province and verify cities are displayed
|
||||
6. Select a city and verify districts are displayed
|
||||
7. Check browser console for any errors
|
||||
|
||||
### Test 2: Dosage Dropdown Display
|
||||
1. Create a new prescription with type "饮片" and dosage "100ml"
|
||||
2. Save the prescription
|
||||
3. **Refresh the page** (F5)
|
||||
4. Edit the same prescription
|
||||
5. Verify that the dosage dropdown shows "100ml" immediately on first load
|
||||
6. Close and reopen the edit dialog
|
||||
7. Verify it still shows "100ml"
|
||||
|
||||
### Test 3: Dosage Fields Saving
|
||||
1. Create a new prescription:
|
||||
- Type: 饮片
|
||||
- Dosage: 150ml
|
||||
- Decoction: 代煎
|
||||
- Bags per dose: 3包
|
||||
2. Save the prescription
|
||||
3. Check browser Network tab for the API request payload
|
||||
4. Verify the payload includes:
|
||||
```json
|
||||
{
|
||||
"dosage_amount": 150,
|
||||
"dosage_unit": "ml",
|
||||
"need_decoction": true,
|
||||
"bags_per_dose": 3
|
||||
}
|
||||
```
|
||||
5. Refresh the page and edit the prescription
|
||||
6. Verify all fields display the saved values
|
||||
|
||||
### Test 4: Different Prescription Types
|
||||
1. Test 浓缩水丸:
|
||||
- Dosage: 1-10g dropdown
|
||||
- No decoction option
|
||||
- No bags_per_dose field
|
||||
2. Test 饮片:
|
||||
- Dosage: 50-250ml dropdown
|
||||
- Decoction radio buttons (代煎/不代煎)
|
||||
- Bags per dose: 1-9包 dropdown
|
||||
3. Test 其他类型 (颗粒、丸剂、散剂、膏方、汤剂):
|
||||
- Dosage: Free input with decimal support
|
||||
- No decoction option
|
||||
- No bags_per_dose field
|
||||
|
||||
## Database Verification
|
||||
|
||||
If dosage fields are still not saving, verify the database schema:
|
||||
|
||||
```sql
|
||||
-- Check if columns exist
|
||||
DESCRIBE tcm_prescription;
|
||||
|
||||
-- Should show these columns:
|
||||
-- dosage_amount (decimal or varchar)
|
||||
-- dosage_unit (varchar)
|
||||
-- need_decoction (tinyint)
|
||||
-- bags_per_dose (int)
|
||||
-- times_per_day (int)
|
||||
|
||||
-- If columns are missing, run the migration:
|
||||
SOURCE add_prescription_dosage_fields.sql;
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Region Selector Still Empty After Restart
|
||||
1. Check browser console for CORS errors
|
||||
2. Verify the proxy is working:
|
||||
```bash
|
||||
# In browser console:
|
||||
fetch('/api-proxy/area/ChinaCitys.json').then(r => r.json()).then(console.log)
|
||||
```
|
||||
3. Check if the API endpoint is accessible:
|
||||
```bash
|
||||
curl https://admin.zhenyangtang.com.cn/area/ChinaCitys.json
|
||||
```
|
||||
|
||||
### Dosage Dropdown Still Empty on First Load
|
||||
1. Check browser console for errors
|
||||
2. Verify `dosage_amount` is a number in the database (not string)
|
||||
3. Add console.log in `handleEdit()` to debug:
|
||||
```typescript
|
||||
console.log('dosage_amount from DB:', row.dosage_amount, typeof row.dosage_amount)
|
||||
console.log('dosage_amount after conversion:', editForm.dosage_amount, typeof editForm.dosage_amount)
|
||||
```
|
||||
|
||||
### Dosage Fields Not Saving
|
||||
1. Check browser Network tab for API errors
|
||||
2. Verify database migration was executed
|
||||
3. Check backend logs for validation errors
|
||||
4. Verify the API endpoint is receiving the fields:
|
||||
```php
|
||||
// In PrescriptionLogic.php
|
||||
Log::write('Prescription params: ' . json_encode($params));
|
||||
```
|
||||
|
||||
## Summary
|
||||
|
||||
- ✅ Region selector: Proxy configured, needs server restart
|
||||
- ✅ Dosage dropdown: Fixed timing issue with `setTimeout`
|
||||
- ✅ Dosage fields saving: All code is correct, verify database migration
|
||||
- ✅ bags_per_dose: Already implemented in both component and list view
|
||||
@@ -0,0 +1,293 @@
|
||||
# Service Package Multi-Select Implementation (服务套餐多选实现)
|
||||
|
||||
## Changes Made
|
||||
|
||||
Updated the service package field from a simple text input to a multi-select dropdown with data loaded from the dictionary API.
|
||||
|
||||
### 1. Added Dictionary API Import
|
||||
**File**: `admin/src/views/consumer/prescription/order_list.vue`
|
||||
|
||||
```typescript
|
||||
import { getDictData } from '@/api/app'
|
||||
```
|
||||
|
||||
### 2. Added Service Package Options State
|
||||
```typescript
|
||||
// 服务套餐选项
|
||||
const servicePackageOptions = ref<Array<{ name: string; value: string }>>([])
|
||||
```
|
||||
|
||||
### 3. Added Loading Function
|
||||
```typescript
|
||||
// 加载服务套餐选项
|
||||
const loadServicePackageOptions = async () => {
|
||||
try {
|
||||
const data = await getDictData({ type: 'server_order' })
|
||||
servicePackageOptions.value = (data?.server_order || []).filter((item: any) => item.status !== 0)
|
||||
console.log('服务套餐选项已加载:', servicePackageOptions.value.length)
|
||||
} catch (error) {
|
||||
console.error('加载服务套餐选项失败:', error)
|
||||
servicePackageOptions.value = []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Updated onMounted Hook
|
||||
```typescript
|
||||
onMounted(async () => {
|
||||
await loadRegionData()
|
||||
await loadServicePackageOptions() // Added
|
||||
getLists()
|
||||
})
|
||||
```
|
||||
|
||||
### 5. Changed Form Field Type
|
||||
Changed `editForm.service_package` from string to array:
|
||||
```typescript
|
||||
// Before:
|
||||
service_package: '',
|
||||
|
||||
// After:
|
||||
service_package: [] as string[],
|
||||
```
|
||||
|
||||
### 6. Updated Edit Form UI
|
||||
Changed from text input to multi-select:
|
||||
|
||||
**Before:**
|
||||
```vue
|
||||
<el-input v-model="editForm.service_package" maxlength="100" />
|
||||
```
|
||||
|
||||
**After:**
|
||||
```vue
|
||||
<el-select
|
||||
v-model="editForm.service_package"
|
||||
multiple
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
placeholder="请选择服务套餐"
|
||||
clearable
|
||||
class="w-full"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in servicePackageOptions"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
```
|
||||
|
||||
### 7. Updated Data Loading Logic
|
||||
Added logic to handle both array and string formats when loading data:
|
||||
|
||||
```typescript
|
||||
// 处理服务套餐:如果是字符串,转换为数组
|
||||
if (d.service_package) {
|
||||
if (Array.isArray(d.service_package)) {
|
||||
editForm.service_package = d.service_package
|
||||
} else if (typeof d.service_package === 'string') {
|
||||
editForm.service_package = d.service_package.split(',').filter(v => v.trim() !== '')
|
||||
} else {
|
||||
editForm.service_package = []
|
||||
}
|
||||
} else {
|
||||
editForm.service_package = []
|
||||
}
|
||||
```
|
||||
|
||||
### 8. Updated Form Submission
|
||||
Convert array back to comma-separated string for backend:
|
||||
|
||||
```typescript
|
||||
service_package: Array.isArray(editForm.service_package) ? editForm.service_package.join(',') : '',
|
||||
```
|
||||
|
||||
### 9. Added Display Formatter
|
||||
Added `formatServicePackage()` function to display service packages in detail view:
|
||||
|
||||
```typescript
|
||||
// 格式化服务套餐显示
|
||||
function formatServicePackage(value: any): string {
|
||||
if (!value) return '—'
|
||||
|
||||
let packages: string[] = []
|
||||
if (Array.isArray(value)) {
|
||||
packages = value
|
||||
} else if (typeof value === 'string') {
|
||||
packages = value.split(',').filter(v => v.trim() !== '')
|
||||
}
|
||||
|
||||
if (packages.length === 0) return '—'
|
||||
|
||||
// 将值转换为名称
|
||||
const names = packages.map(val => {
|
||||
const option = servicePackageOptions.value.find(opt => opt.value === val)
|
||||
return option ? option.name : val
|
||||
})
|
||||
|
||||
return names.join('、')
|
||||
}
|
||||
```
|
||||
|
||||
### 10. Updated Detail Display
|
||||
```vue
|
||||
<el-descriptions-item label="服务套餐">
|
||||
{{ formatServicePackage(detailData.service_package) }}
|
||||
</el-descriptions-item>
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
### Loading Options:
|
||||
1. Component mounts
|
||||
2. `loadServicePackageOptions()` calls API: `/adminapi/config/dict?type=server_order`
|
||||
3. Filters out disabled options (status !== 0)
|
||||
4. Stores in `servicePackageOptions`
|
||||
|
||||
### Editing Order:
|
||||
1. User opens edit dialog
|
||||
2. Backend returns `service_package` as string: `"package1,package2,package3"`
|
||||
3. Frontend splits string into array: `['package1', 'package2', 'package3']`
|
||||
4. Multi-select displays selected options
|
||||
5. User can add/remove selections
|
||||
6. On save, array is joined back to string: `"package1,package2,package3"`
|
||||
7. Backend receives comma-separated string
|
||||
|
||||
### Displaying in Detail:
|
||||
1. Backend returns `service_package` as string
|
||||
2. `formatServicePackage()` splits string into array
|
||||
3. Maps values to display names using `servicePackageOptions`
|
||||
4. Joins names with Chinese comma: `"套餐A、套餐B、套餐C"`
|
||||
|
||||
## UI Features
|
||||
|
||||
### Multi-Select Features:
|
||||
- **Multiple Selection**: Users can select multiple service packages
|
||||
- **Collapse Tags**: Selected items are collapsed with a count badge when many are selected
|
||||
- **Collapse Tags Tooltip**: Hover over collapsed tags to see all selections
|
||||
- **Clearable**: Users can clear all selections with one click
|
||||
- **Searchable**: Built-in search functionality (default for el-select)
|
||||
|
||||
### Display Format:
|
||||
- Detail view: `套餐A、套餐B、套餐C` (Chinese comma separator)
|
||||
- Edit form: Tag-style display with collapse for many items
|
||||
- Empty state: Shows `—` when no packages selected
|
||||
|
||||
## Backend Compatibility
|
||||
|
||||
### Database Storage:
|
||||
The backend stores service packages as a comma-separated string in the database:
|
||||
```
|
||||
service_package: "package1,package2,package3"
|
||||
```
|
||||
|
||||
### API Response:
|
||||
The backend returns the string as-is, and the frontend handles the conversion.
|
||||
|
||||
### API Request:
|
||||
The frontend sends the string format, maintaining backward compatibility.
|
||||
|
||||
## Testing Steps
|
||||
|
||||
### 1. Test Options Loading
|
||||
1. Open order list page
|
||||
2. Check browser console for: `服务套餐选项已加载: X`
|
||||
3. Open edit dialog
|
||||
4. Click on service package dropdown
|
||||
5. Verify options are displayed
|
||||
|
||||
### 2. Test Multi-Selection
|
||||
1. Edit an order
|
||||
2. Click service package dropdown
|
||||
3. Select multiple packages
|
||||
4. Verify tags appear below the dropdown
|
||||
5. Select many packages to test collapse behavior
|
||||
6. Hover over collapsed tags to see tooltip
|
||||
|
||||
### 3. Test Saving
|
||||
1. Select multiple service packages
|
||||
2. Save the order
|
||||
3. Check browser Network tab
|
||||
4. Verify payload contains: `service_package: "value1,value2,value3"`
|
||||
5. Verify database is updated
|
||||
|
||||
### 4. Test Display
|
||||
1. Open order detail view
|
||||
2. Check "服务套餐" field
|
||||
3. Verify it shows: `套餐A、套餐B、套餐C`
|
||||
4. Test with orders that have:
|
||||
- Multiple packages
|
||||
- Single package
|
||||
- No packages (should show `—`)
|
||||
|
||||
### 5. Test Backward Compatibility
|
||||
1. Edit an old order that has service_package as string
|
||||
2. Verify it loads correctly into the multi-select
|
||||
3. Verify you can modify and save
|
||||
|
||||
### 6. Test Clear Functionality
|
||||
1. Edit an order with service packages selected
|
||||
2. Click the clear button (×) on the select
|
||||
3. Verify all selections are cleared
|
||||
4. Save and verify database is updated to empty string
|
||||
|
||||
## Dictionary API Format
|
||||
|
||||
The API endpoint `/adminapi/config/dict?type=server_order` should return:
|
||||
|
||||
```json
|
||||
{
|
||||
"server_order": [
|
||||
{
|
||||
"name": "套餐A",
|
||||
"value": "package_a",
|
||||
"status": 1
|
||||
},
|
||||
{
|
||||
"name": "套餐B",
|
||||
"value": "package_b",
|
||||
"status": 1
|
||||
},
|
||||
{
|
||||
"name": "套餐C (已停用)",
|
||||
"value": "package_c",
|
||||
"status": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: Options with `status: 0` are filtered out and not displayed.
|
||||
|
||||
## Related Files
|
||||
|
||||
### Frontend:
|
||||
- `admin/src/views/consumer/prescription/order_list.vue`
|
||||
- Added `getDictData` import
|
||||
- Added `servicePackageOptions` state
|
||||
- Added `loadServicePackageOptions()` function
|
||||
- Updated `editForm.service_package` to array type
|
||||
- Updated edit form UI to multi-select
|
||||
- Added data loading/saving conversion logic
|
||||
- Added `formatServicePackage()` display function
|
||||
- Updated detail display
|
||||
|
||||
### Backend:
|
||||
- `/adminapi/config/dict` endpoint (should already exist)
|
||||
- Dictionary data for `type=server_order`
|
||||
|
||||
## Summary
|
||||
|
||||
✅ Changed from text input to multi-select dropdown
|
||||
✅ Loads options from dictionary API
|
||||
✅ Supports multiple selection with tag display
|
||||
✅ Collapse tags for better UI when many items selected
|
||||
✅ Converts between array (frontend) and string (backend)
|
||||
✅ Displays formatted names in detail view
|
||||
✅ Backward compatible with existing string data
|
||||
✅ Filters out disabled options
|
||||
✅ Clearable and searchable
|
||||
|
||||
The service package field now provides a better user experience with predefined options and multi-selection capability!
|
||||
@@ -0,0 +1,3 @@
|
||||
-- 添加每贴出包数字段到处方表
|
||||
ALTER TABLE `zyt_tcm_prescription`
|
||||
ADD COLUMN `bags_per_dose` TINYINT UNSIGNED NULL DEFAULT 1 COMMENT '每贴出包数(饮片专用,1-9包)' AFTER `need_decoction`;
|
||||
@@ -122,7 +122,7 @@
|
||||
<div class="info-section">
|
||||
<el-row :gutter="16">
|
||||
|
||||
<el-col :span="12">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="处方类型" prop="prescription_type">
|
||||
<el-select v-model="formData.prescription_type" placeholder="请选择处方类型" class="!w-full">
|
||||
<el-option label="浓缩水丸" value="浓缩水丸" />
|
||||
@@ -137,14 +137,14 @@
|
||||
</el-col>
|
||||
|
||||
<!-- 用量字段 -->
|
||||
<el-col :span="12">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="用量" prop="dosage_amount">
|
||||
<!-- 浓缩水丸:1-10g 下拉选择 -->
|
||||
<el-select
|
||||
v-if="formData.prescription_type === '浓缩水丸'"
|
||||
v-model="formData.dosage_amount"
|
||||
placeholder="请选择用量"
|
||||
class="!w-32"
|
||||
class="!w-full"
|
||||
>
|
||||
<el-option :label="1 + 'g'" :value="1" />
|
||||
<el-option :label="2 + 'g'" :value="2" />
|
||||
@@ -162,11 +162,13 @@
|
||||
v-else-if="formData.prescription_type === '饮片'"
|
||||
v-model="formData.dosage_amount"
|
||||
placeholder="请选择用量"
|
||||
class="!w-32"
|
||||
class="!w-full"
|
||||
>
|
||||
<el-option label="50ml" :value="50" />
|
||||
<el-option label="100ml" :value="100" />
|
||||
<el-option label="120ml" :value="120" />
|
||||
<el-option label="150ml" :value="150" />
|
||||
<el-option label="180ml" :value="180" />
|
||||
<el-option label="200ml" :value="200" />
|
||||
<el-option label="250ml" :value="250" />
|
||||
</el-select>
|
||||
@@ -176,14 +178,31 @@
|
||||
v-model="formData.dosage_amount"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
class="!w-32"
|
||||
class="!w-full"
|
||||
/>
|
||||
<span v-if="formData.prescription_type !== '浓缩水丸' && formData.prescription_type !== '饮片'" class="ml-2">{{ formData.dosage_unit }}</span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 饮片每贴出包数 -->
|
||||
<el-col v-if="formData.prescription_type === '饮片'" :span="8">
|
||||
<el-form-item label="每贴出包数" prop="bags_per_dose">
|
||||
<el-select v-model="formData.bags_per_dose" placeholder="请选择" class="!w-full">
|
||||
<el-option label="1包" :value="1" />
|
||||
<el-option label="2包" :value="2" />
|
||||
<el-option label="3包" :value="3" />
|
||||
<el-option label="4包" :value="4" />
|
||||
<el-option label="5包" :value="5" />
|
||||
<el-option label="6包" :value="6" />
|
||||
<el-option label="7包" :value="7" />
|
||||
<el-option label="8包" :value="8" />
|
||||
<el-option label="9包" :value="9" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 饮片代煎选项 -->
|
||||
<el-col v-if="formData.prescription_type === '饮片'" :span="12">
|
||||
<el-col v-if="formData.prescription_type === '饮片'" :span="8">
|
||||
<el-form-item label="是否代煎" prop="need_decoction">
|
||||
<el-radio-group v-model="formData.need_decoction">
|
||||
<el-radio :label="true">代煎</el-radio>
|
||||
@@ -191,6 +210,7 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="8">
|
||||
<el-form-item label="服用天数" prop="usage_days">
|
||||
<el-input-number v-model="formData.usage_days" :min="1" :max="365" class="!w-full" />
|
||||
@@ -198,7 +218,7 @@
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="每天几次" prop="times_per_day">
|
||||
<el-input-number v-model="formData.times_per_day" :min="1" :max="10" class="!w-full" placeholder="每天服用次数" />
|
||||
<el-input-number v-model="formData.times_per_day" :min="1" :max="6" class="!w-full" placeholder="每天服用次数" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<!-- <el-col :span="8">
|
||||
@@ -641,6 +661,7 @@ interface PrescriptionForm {
|
||||
dosage_amount: number | undefined // 用量数值
|
||||
dosage_unit: string // 用量单位 (g 或 ml)
|
||||
need_decoction: boolean // 是否代煎(仅饮片)
|
||||
bags_per_dose: number // 每贴出包数(仅饮片,1-9包)
|
||||
patient_name: string
|
||||
gender: number
|
||||
gender_desc: string
|
||||
@@ -721,6 +742,7 @@ const formData = reactive<PrescriptionForm>({
|
||||
dosage_amount: 1,
|
||||
dosage_unit: 'g',
|
||||
need_decoction: false,
|
||||
bags_per_dose: 1,
|
||||
patient_name: '',
|
||||
gender: 0,
|
||||
gender_desc: '女',
|
||||
@@ -1129,14 +1151,17 @@ watch(() => formData.prescription_type, (newType) => {
|
||||
formData.dosage_unit = 'g'
|
||||
formData.dosage_amount = 1
|
||||
formData.need_decoction = false
|
||||
formData.bags_per_dose = 1
|
||||
} else if (newType === '饮片') {
|
||||
formData.dosage_unit = 'ml'
|
||||
formData.dosage_amount = 50
|
||||
formData.bags_per_dose = 1
|
||||
// need_decoction 保持用户选择
|
||||
} else {
|
||||
formData.dosage_unit = 'g'
|
||||
formData.dosage_amount = undefined
|
||||
formData.need_decoction = false
|
||||
formData.bags_per_dose = 1
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1190,6 +1215,7 @@ const handleSave = async () => {
|
||||
dosage_amount: formData.dosage_amount,
|
||||
dosage_unit: formData.dosage_unit,
|
||||
need_decoction: formData.need_decoction,
|
||||
bags_per_dose: formData.bags_per_dose,
|
||||
usage_days: formData.usage_days,
|
||||
times_per_day: formData.times_per_day,
|
||||
usage_instruction: formData.usage_instruction,
|
||||
|
||||
@@ -605,11 +605,13 @@
|
||||
placeholder="请选择用量"
|
||||
class="w-full"
|
||||
>
|
||||
<el-option label="50ml" :value="50" />
|
||||
<el-option label="100ml" :value="100" />
|
||||
<el-option label="150ml" :value="150" />
|
||||
<el-option label="200ml" :value="200" />
|
||||
<el-option label="250ml" :value="250" />
|
||||
<el-option label="50ml" :value="50" />
|
||||
<el-option label="100ml" :value="100" />
|
||||
<el-option label="120ml" :value="120" />
|
||||
<el-option label="150ml" :value="150" />
|
||||
<el-option label="180ml" :value="180" />
|
||||
<el-option label="200ml" :value="200" />
|
||||
<el-option label="250ml" :value="250" />
|
||||
</el-select>
|
||||
<!-- 其他类型:自由输入 -->
|
||||
<el-input-number
|
||||
@@ -631,9 +633,27 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<!-- 饮片每贴出包数 -->
|
||||
<el-col v-if="editForm.prescription_type === '饮片'" :span="8">
|
||||
<el-form-item label="每贴出包数" prop="bags_per_dose">
|
||||
<el-select v-model="editForm.bags_per_dose" placeholder="请选择" class="w-full">
|
||||
<el-option label="1包" :value="1" />
|
||||
<el-option label="2包" :value="2" />
|
||||
<el-option label="3包" :value="3" />
|
||||
<el-option label="4包" :value="4" />
|
||||
<el-option label="5包" :value="5" />
|
||||
<el-option label="6包" :value="6" />
|
||||
<el-option label="7包" :value="7" />
|
||||
<el-option label="8包" :value="8" />
|
||||
<el-option label="9包" :value="9" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="8">
|
||||
<el-form-item label="每天几次" prop="times_per_day">
|
||||
<el-input-number v-model="editForm.times_per_day" :min="1" :max="10" class="!w-full" placeholder="每天服用次数" />
|
||||
<el-input-number v-model="editForm.times_per_day" :min="1" :max="6" class="!w-full" placeholder="每天服用次数" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
@@ -859,7 +879,7 @@
|
||||
<el-cascader
|
||||
v-model="createOrderForm.region"
|
||||
:options="regionOptions"
|
||||
:props="{ value: 'name', label: 'name', children: 'children', emitPath: false, checkStrictly: false }"
|
||||
:props="{ value: 'name', label: 'name', children: 'children', emitPath: true, checkStrictly: false }"
|
||||
placeholder="请选择省/市/区"
|
||||
clearable
|
||||
filterable
|
||||
@@ -1267,143 +1287,138 @@ const createOrderForm = reactive({
|
||||
})
|
||||
|
||||
// 省市区数据(简化版,实际项目中应该从 API 获取或使用完整的省市区数据)
|
||||
const regionOptions = ref([
|
||||
{
|
||||
name: '四川省',
|
||||
children: [
|
||||
const regionOptions = ref([])
|
||||
|
||||
// 转换省市区数据格式
|
||||
const transformRegionData = (data: any[]) => {
|
||||
return data.map((province: any) => ({
|
||||
name: province.province,
|
||||
code: province.code,
|
||||
children: province.citys?.map((city: any) => ({
|
||||
name: city.city,
|
||||
code: city.code,
|
||||
children: city.areas?.map((area: any) => ({
|
||||
name: area.area,
|
||||
code: area.code
|
||||
})) || []
|
||||
})) || []
|
||||
}))
|
||||
}
|
||||
|
||||
// 加载省市区数据
|
||||
const loadRegionData = async () => {
|
||||
try {
|
||||
console.log('开始加载省市区数据...')
|
||||
// 使用代理路径,需要在vite.config.ts中配置代理
|
||||
const response = await fetch('/api-proxy/area/ChinaCitys.json')
|
||||
console.log('响应状态:', response.status)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
console.log('加载的数据条数:', data?.length)
|
||||
|
||||
// 转换数据格式
|
||||
regionOptions.value = transformRegionData(data)
|
||||
console.log('regionOptions 已设置,条数:', regionOptions.value.length)
|
||||
} catch (error) {
|
||||
console.error('加载省市区数据失败:', error)
|
||||
// 如果加载失败,使用基本的省市数据作为后备
|
||||
regionOptions.value = [
|
||||
{
|
||||
name: '成都市',
|
||||
name: '四川省',
|
||||
children: [
|
||||
{ name: '锦江区' },
|
||||
{ name: '青羊区' },
|
||||
{ name: '金牛区' },
|
||||
{ name: '武侯区' },
|
||||
{ name: '成华区' },
|
||||
{ name: '龙泉驿区' },
|
||||
{ name: '青白江区' },
|
||||
{ name: '新都区' },
|
||||
{ name: '温江区' },
|
||||
{ name: '双流区' },
|
||||
{ name: '郫都区' },
|
||||
{ name: '新津区' },
|
||||
{ name: '都江堰市' },
|
||||
{ name: '彭州市' },
|
||||
{ name: '邛崃市' },
|
||||
{ name: '崇州市' },
|
||||
{ name: '简阳市' },
|
||||
{ name: '金堂县' },
|
||||
{ name: '大邑县' },
|
||||
{ name: '蒲江县' }
|
||||
{
|
||||
name: '成都市',
|
||||
children: [
|
||||
{ name: '锦江区' },
|
||||
{ name: '青羊区' },
|
||||
{ name: '金牛区' },
|
||||
{ name: '武侯区' },
|
||||
{ name: '成华区' },
|
||||
{ name: '龙泉驿区' },
|
||||
{ name: '青白江区' },
|
||||
{ name: '新都区' },
|
||||
{ name: '温江区' },
|
||||
{ name: '双流区' }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '绵阳市',
|
||||
children: [
|
||||
{ name: '涪城区' },
|
||||
{ name: '游仙区' },
|
||||
{ name: '安州区' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '绵阳市',
|
||||
children: [
|
||||
{ name: '涪城区' },
|
||||
{ name: '游仙区' },
|
||||
{ name: '安州区' },
|
||||
{ name: '江油市' },
|
||||
{ name: '三台县' },
|
||||
{ name: '盐亭县' },
|
||||
{ name: '梓潼县' },
|
||||
{ name: '北川羌族自治县' },
|
||||
{ name: '平武县' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '北京市',
|
||||
children: [
|
||||
{
|
||||
name: '北京市',
|
||||
children: [
|
||||
{ name: '东城区' },
|
||||
{ name: '西城区' },
|
||||
{ name: '朝阳区' },
|
||||
{ name: '丰台区' },
|
||||
{ name: '石景山区' },
|
||||
{ name: '海淀区' },
|
||||
{ name: '门头沟区' },
|
||||
{ name: '房山区' },
|
||||
{ name: '通州区' },
|
||||
{ name: '顺义区' },
|
||||
{ name: '昌平区' },
|
||||
{ name: '大兴区' },
|
||||
{ name: '怀柔区' },
|
||||
{ name: '平谷区' },
|
||||
{ name: '密云区' },
|
||||
{ name: '延庆区' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '上海市',
|
||||
children: [
|
||||
{
|
||||
name: '上海市',
|
||||
children: [
|
||||
{ name: '黄浦区' },
|
||||
{ name: '徐汇区' },
|
||||
{ name: '长宁区' },
|
||||
{ name: '静安区' },
|
||||
{ name: '普陀区' },
|
||||
{ name: '虹口区' },
|
||||
{ name: '杨浦区' },
|
||||
{ name: '闵行区' },
|
||||
{ name: '宝山区' },
|
||||
{ name: '嘉定区' },
|
||||
{ name: '浦东新区' },
|
||||
{ name: '金山区' },
|
||||
{ name: '松江区' },
|
||||
{ name: '青浦区' },
|
||||
{ name: '奉贤区' },
|
||||
{ name: '崇明区' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '广东省',
|
||||
children: [
|
||||
{
|
||||
name: '广州市',
|
||||
children: [
|
||||
{ name: '荔湾区' },
|
||||
{ name: '越秀区' },
|
||||
{ name: '海珠区' },
|
||||
{ name: '天河区' },
|
||||
{ name: '白云区' },
|
||||
{ name: '黄埔区' },
|
||||
{ name: '番禺区' },
|
||||
{ name: '花都区' },
|
||||
{ name: '南沙区' },
|
||||
{ name: '从化区' },
|
||||
{ name: '增城区' }
|
||||
{
|
||||
name: '北京市',
|
||||
children: [
|
||||
{ name: '东城区' },
|
||||
{ name: '西城区' },
|
||||
{ name: '朝阳区' },
|
||||
{ name: '丰台区' },
|
||||
{ name: '海淀区' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '深圳市',
|
||||
name: '上海市',
|
||||
children: [
|
||||
{ name: '罗湖区' },
|
||||
{ name: '福田区' },
|
||||
{ name: '南山区' },
|
||||
{ name: '宝安区' },
|
||||
{ name: '龙岗区' },
|
||||
{ name: '盐田区' },
|
||||
{ name: '龙华区' },
|
||||
{ name: '坪山区' },
|
||||
{ name: '光明区' }
|
||||
{
|
||||
name: '上海市',
|
||||
children: [
|
||||
{ name: '黄浦区' },
|
||||
{ name: '徐汇区' },
|
||||
{ name: '长宁区' },
|
||||
{ name: '静安区' },
|
||||
{ name: '普陀区' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '广东省',
|
||||
children: [
|
||||
{
|
||||
name: '广州市',
|
||||
children: [
|
||||
{ name: '荔湾区' },
|
||||
{ name: '越秀区' },
|
||||
{ name: '海珠区' },
|
||||
{ name: '天河区' },
|
||||
{ name: '白云区' }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: '深圳市',
|
||||
children: [
|
||||
{ name: '罗湖区' },
|
||||
{ name: '福田区' },
|
||||
{ name: '南山区' },
|
||||
{ name: '宝安区' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
console.log('使用后备数据,条数:', regionOptions.value.length)
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
// 处理省市区选择变化
|
||||
const handleRegionChange = (value: string[]) => {
|
||||
console.log('选择的省市区:', value)
|
||||
// value is an array like ['四川省', '成都市', '锦江区']
|
||||
// We'll extract these in the submit function
|
||||
}
|
||||
|
||||
const createOrderLinkedPaidTotal = computed(() => {
|
||||
@@ -1619,6 +1634,14 @@ async function submitCreateOrderFromPrescription() {
|
||||
amount: createOrderForm.amount,
|
||||
remark_extra: createOrderForm.remark_extra || ''
|
||||
}
|
||||
|
||||
// 添加省市区字段
|
||||
if (createOrderForm.region && createOrderForm.region.length >= 3) {
|
||||
payload.shipping_province = createOrderForm.region[0]
|
||||
payload.shipping_city = createOrderForm.region[1]
|
||||
payload.shipping_district = createOrderForm.region[2]
|
||||
}
|
||||
|
||||
if (createOrderForm.medication_days != null && createOrderForm.medication_days > 0) {
|
||||
payload.medication_days = createOrderForm.medication_days
|
||||
}
|
||||
@@ -1672,6 +1695,7 @@ const editForm = reactive({
|
||||
dosage_amount: 1 as number | undefined,
|
||||
dosage_unit: 'g',
|
||||
need_decoction: false,
|
||||
bags_per_dose: 1,
|
||||
patient_name: '',
|
||||
gender: 1,
|
||||
age: 0,
|
||||
@@ -1705,20 +1729,31 @@ const editForm = reactive({
|
||||
times_per_day: 2
|
||||
})
|
||||
|
||||
// 标志:是否正在加载数据(防止watch函数覆盖已加载的值)
|
||||
const isLoadingData = ref(false)
|
||||
|
||||
// 监听处方类型变化,自动调整用量单位和默认值
|
||||
watch(() => editForm.prescription_type, (newType) => {
|
||||
// 如果正在加载数据,不要自动重置值
|
||||
if (isLoadingData.value) {
|
||||
return
|
||||
}
|
||||
|
||||
if (newType === '浓缩水丸') {
|
||||
editForm.dosage_unit = 'g'
|
||||
editForm.dosage_amount = 1
|
||||
editForm.need_decoction = false
|
||||
editForm.bags_per_dose = 1
|
||||
} else if (newType === '饮片') {
|
||||
editForm.dosage_unit = 'ml'
|
||||
editForm.dosage_amount = 50
|
||||
editForm.bags_per_dose = 1
|
||||
// need_decoction 保持用户选择
|
||||
} else {
|
||||
editForm.dosage_unit = 'g'
|
||||
editForm.dosage_amount = undefined
|
||||
editForm.need_decoction = false
|
||||
editForm.bags_per_dose = 1
|
||||
}
|
||||
})
|
||||
|
||||
@@ -2062,6 +2097,8 @@ const handleAdd = () => {
|
||||
editingWasVoid.value = false
|
||||
editingWasRejected.value = false
|
||||
editMode.value = 'add'
|
||||
// 新建时允许watch自动设置默认值
|
||||
isLoadingData.value = false
|
||||
showEdit.value = true
|
||||
nextTick(() => {
|
||||
formRef.value?.clearValidate()
|
||||
@@ -2096,12 +2133,34 @@ const handleEdit = (row: any) => {
|
||||
editingWasVoid.value = Number(row.void_status) === 1
|
||||
editingWasRejected.value = Number(row.audit_status) === 2
|
||||
editMode.value = 'edit'
|
||||
|
||||
// 开始加载数据,暂时禁用watch自动重置
|
||||
isLoadingData.value = true
|
||||
|
||||
// 深拷贝数据到表单
|
||||
editForm.id = row.id
|
||||
editForm.prescription_type = row.prescription_type || '浓缩水丸'
|
||||
editForm.dosage_amount = row.dosage_amount ?? undefined
|
||||
editForm.dosage_unit = row.dosage_unit || ''
|
||||
|
||||
// 确保 dosage_amount 是数字类型,以匹配下拉框的 :value
|
||||
if (row.dosage_amount !== null && row.dosage_amount !== undefined && row.dosage_amount !== '') {
|
||||
editForm.dosage_amount = Number(row.dosage_amount)
|
||||
} else {
|
||||
editForm.dosage_amount = undefined
|
||||
}
|
||||
|
||||
// 如果数据库中没有 dosage_unit,根据 prescription_type 设置默认值
|
||||
if (row.dosage_unit) {
|
||||
editForm.dosage_unit = row.dosage_unit
|
||||
} else {
|
||||
if (editForm.prescription_type === '饮片') {
|
||||
editForm.dosage_unit = 'ml'
|
||||
} else {
|
||||
editForm.dosage_unit = 'g'
|
||||
}
|
||||
}
|
||||
|
||||
editForm.need_decoction = row.need_decoction === 1 || row.need_decoction === true
|
||||
editForm.bags_per_dose = row.bags_per_dose ? Number(row.bags_per_dose) : 1
|
||||
editForm.patient_name = row.patient_name || ''
|
||||
editForm.gender = row.gender ?? 1
|
||||
editForm.age = row.age ?? 0
|
||||
@@ -2132,6 +2191,13 @@ const handleEdit = (row: any) => {
|
||||
editForm.diagnosis_id = row.diagnosis_id ?? 0
|
||||
editForm.business_prescription_audit_rejected = Number(row.business_prescription_audit_rejected) === 1 ? 1 : 0
|
||||
editForm.business_prescription_audit_remark = String(row.business_prescription_audit_remark || '')
|
||||
|
||||
// 数据加载完成,重新启用watch
|
||||
// 使用 setTimeout 确保所有数据都已经渲染完成
|
||||
setTimeout(() => {
|
||||
isLoadingData.value = false
|
||||
}, 0)
|
||||
|
||||
showEdit.value = true
|
||||
nextTick(() => {
|
||||
formRef.value?.clearValidate()
|
||||
@@ -2195,6 +2261,7 @@ const handleDelete = async (id: number) => {
|
||||
|
||||
onMounted(async () => {
|
||||
await loadRoleOptions()
|
||||
await loadRegionData()
|
||||
try {
|
||||
const list = await getDoctors()
|
||||
doctorOptions.value = Array.isArray(list) ? list : []
|
||||
|
||||
@@ -592,14 +592,14 @@
|
||||
<el-descriptions-item label="收货人">{{ detailData.recipient_name }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="手机">{{ detailData.recipient_phone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="收货地址" :span="2">{{ detailData.shipping_address }}</el-descriptions-item>
|
||||
<el-descriptions-item label="收货地址" :span="2">{{ detailFullAddress }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="复诊">{{ detailData.is_follow_up ? '是' : '否' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="用药疗程">{{ detailData.medication_days ? detailData.medication_days + ' 天' : '—' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="上次医护">{{ detailData.prev_staff || '—' }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item label="服务渠道">{{ detailData.service_channel || '—' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="服务套餐">{{ detailData.service_package || '—' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="服务套餐">{{ formatServicePackage(detailData.service_package) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="费用类别">{{ feeTypeText(detailData.fee_type) }}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item v-if="detailData.internal_cost != null && detailData.internal_cost !== ''" label="内部成本">
|
||||
@@ -757,8 +757,10 @@
|
||||
<el-cascader
|
||||
v-model="editForm.region"
|
||||
:options="regionOptions"
|
||||
:props="{ emitPath: true }"
|
||||
placeholder="请选择省/市/区"
|
||||
clearable
|
||||
filterable
|
||||
class="w-full"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -840,7 +842,22 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="服务套餐">
|
||||
<el-input v-model="editForm.service_package" maxlength="100" />
|
||||
<el-select
|
||||
v-model="editForm.service_package"
|
||||
multiple
|
||||
collapse-tags
|
||||
collapse-tags-tooltip
|
||||
placeholder="请选择服务套餐"
|
||||
clearable
|
||||
class="w-full"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in servicePackageOptions"
|
||||
:key="item.value"
|
||||
:label="item.name"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -1451,6 +1468,7 @@ import {
|
||||
prescriptionOrderPreviewGancaoRecipel,
|
||||
prescriptionDetail
|
||||
} from '@/api/tcm'
|
||||
import { getDictData } from '@/api/app'
|
||||
import { usePaging } from '@/hooks/usePaging'
|
||||
import feedback from '@/utils/feedback'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
@@ -1472,90 +1490,99 @@ const canViewFinanceFields = () => {
|
||||
}
|
||||
|
||||
// 省市区数据
|
||||
const regionOptions = [
|
||||
{
|
||||
value: '四川省',
|
||||
label: '四川省',
|
||||
children: [
|
||||
const regionOptions = ref([])
|
||||
|
||||
// 服务套餐选项
|
||||
const servicePackageOptions = ref<Array<{ name: string; value: string }>>([])
|
||||
|
||||
// 转换省市区数据格式
|
||||
const transformRegionData = (data: any[]) => {
|
||||
return data.map((province: any) => ({
|
||||
value: province.province,
|
||||
label: province.province,
|
||||
code: province.code,
|
||||
children: province.citys?.map((city: any) => ({
|
||||
value: city.city,
|
||||
label: city.city,
|
||||
code: city.code,
|
||||
children: city.areas?.map((area: any) => ({
|
||||
value: area.area,
|
||||
label: area.area,
|
||||
code: area.code
|
||||
})) || []
|
||||
})) || []
|
||||
}))
|
||||
}
|
||||
|
||||
// 加载省市区数据
|
||||
const loadRegionData = async () => {
|
||||
try {
|
||||
console.log('开始加载省市区数据...')
|
||||
// 使用代理路径,需要在vite.config.ts中配置代理
|
||||
const response = await fetch('/api-proxy/area/ChinaCitys.json')
|
||||
console.log('响应状态:', response.status)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
console.log('加载的数据条数:', data?.length)
|
||||
|
||||
// 转换数据格式
|
||||
regionOptions.value = transformRegionData(data)
|
||||
console.log('regionOptions 已设置,条数:', regionOptions.value.length)
|
||||
} catch (error) {
|
||||
console.error('加载省市区数据失败:', error)
|
||||
// 如果加载失败,使用基本的省市数据作为后备
|
||||
regionOptions.value = [
|
||||
{
|
||||
value: '成都市',
|
||||
label: '成都市',
|
||||
value: '四川省',
|
||||
label: '四川省',
|
||||
children: [
|
||||
{ value: '武侯区', label: '武侯区' },
|
||||
{ value: '锦江区', label: '锦江区' },
|
||||
{ value: '青羊区', label: '青羊区' },
|
||||
{ value: '金牛区', label: '金牛区' },
|
||||
{ value: '成华区', label: '成华区' },
|
||||
{ value: '高新区', label: '高新区' }
|
||||
{
|
||||
value: '成都市',
|
||||
label: '成都市',
|
||||
children: [
|
||||
{ value: '武侯区', label: '武侯区' },
|
||||
{ value: '锦江区', label: '锦江区' },
|
||||
{ value: '青羊区', label: '青羊区' },
|
||||
{ value: '金牛区', label: '金牛区' },
|
||||
{ value: '成华区', label: '成华区' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
value: '绵阳市',
|
||||
label: '绵阳市',
|
||||
children: [
|
||||
{ value: '涪城区', label: '涪城区' },
|
||||
{ value: '游仙区', label: '游仙区' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
value: '北京市',
|
||||
label: '北京市',
|
||||
children: [
|
||||
{
|
||||
value: '北京市',
|
||||
label: '北京市',
|
||||
children: [
|
||||
{ value: '朝阳区', label: '朝阳区' },
|
||||
{ value: '海淀区', label: '海淀区' },
|
||||
{ value: '东城区', label: '东城区' },
|
||||
{ value: '西城区', label: '西城区' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
value: '上海市',
|
||||
label: '上海市',
|
||||
children: [
|
||||
{
|
||||
value: '上海市',
|
||||
label: '上海市',
|
||||
children: [
|
||||
{ value: '浦东新区', label: '浦东新区' },
|
||||
{ value: '黄浦区', label: '黄浦区' },
|
||||
{ value: '徐汇区', label: '徐汇区' },
|
||||
{ value: '静安区', label: '静安区' }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
value: '广东省',
|
||||
label: '广东省',
|
||||
children: [
|
||||
{
|
||||
value: '广州市',
|
||||
label: '广州市',
|
||||
children: [
|
||||
{ value: '天河区', label: '天河区' },
|
||||
{ value: '越秀区', label: '越秀区' },
|
||||
{ value: '海珠区', label: '海珠区' }
|
||||
]
|
||||
},
|
||||
{
|
||||
value: '深圳市',
|
||||
label: '深圳市',
|
||||
children: [
|
||||
{ value: '南山区', label: '南山区' },
|
||||
{ value: '福田区', label: '福田区' },
|
||||
{ value: '罗湖区', label: '罗湖区' }
|
||||
{
|
||||
value: '北京市',
|
||||
label: '北京市',
|
||||
children: [
|
||||
{ value: '朝阳区', label: '朝阳区' },
|
||||
{ value: '海淀区', label: '海淀区' },
|
||||
{ value: '东城区', label: '东城区' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 加载服务套餐选项
|
||||
const loadServicePackageOptions = async () => {
|
||||
try {
|
||||
const data = await getDictData({ type: 'server_order' })
|
||||
servicePackageOptions.value = (data?.server_order || []).filter((item: any) => item.status !== 0)
|
||||
console.log('服务套餐选项已加载:', servicePackageOptions.value.length)
|
||||
} catch (error) {
|
||||
console.error('加载服务套餐选项失败:', error)
|
||||
servicePackageOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
const queryParams = reactive({
|
||||
order_no: '',
|
||||
@@ -1602,6 +1629,28 @@ function feeTypeText(t: number | undefined) {
|
||||
return m[Number(t)] ?? '—'
|
||||
}
|
||||
|
||||
// 格式化服务套餐显示
|
||||
function formatServicePackage(value: any): string {
|
||||
if (!value) return '—'
|
||||
|
||||
let packages: string[] = []
|
||||
if (Array.isArray(value)) {
|
||||
packages = value
|
||||
} else if (typeof value === 'string') {
|
||||
packages = value.split(',').filter(v => v.trim() !== '')
|
||||
}
|
||||
|
||||
if (packages.length === 0) return '—'
|
||||
|
||||
// 将值转换为名称
|
||||
const names = packages.map(val => {
|
||||
const option = servicePackageOptions.value.find(opt => opt.value === val)
|
||||
return option ? option.name : val
|
||||
})
|
||||
|
||||
return names.join('、')
|
||||
}
|
||||
|
||||
function auditStatusText(s: number | undefined) {
|
||||
if (s === 1) return '已通过'
|
||||
if (s === 2) return '已驳回'
|
||||
@@ -1847,6 +1896,20 @@ const detailRxHerbs = computed(() => normalizeSlipHerbs(detailPrescription.value
|
||||
/** false=无权限;true/缺省兼容旧接口(旧版未下发该字段时仍展示药材) */
|
||||
const detailHerbsVisible = computed(() => detailData.value?.prescription_detail_herbs_visible !== false)
|
||||
|
||||
// 完整收货地址(省市区 + 详细地址)
|
||||
const detailFullAddress = computed(() => {
|
||||
const d = detailData.value
|
||||
if (!d) return '—'
|
||||
|
||||
const parts = []
|
||||
if (d.shipping_province) parts.push(d.shipping_province)
|
||||
if (d.shipping_city) parts.push(d.shipping_city)
|
||||
if (d.shipping_district) parts.push(d.shipping_district)
|
||||
if (d.shipping_address) parts.push(d.shipping_address)
|
||||
|
||||
return parts.length > 0 ? parts.join(' ') : '—'
|
||||
})
|
||||
|
||||
function consumerRxAuditText(s: number | undefined) {
|
||||
const n = Number(s)
|
||||
if (n === 1) return '已通过'
|
||||
@@ -2003,7 +2066,7 @@ const editForm = reactive({
|
||||
dose_unit: '剂',
|
||||
prev_staff: '',
|
||||
service_channel: '',
|
||||
service_package: '',
|
||||
service_package: [] as string[],
|
||||
express_company: 'auto',
|
||||
tracking_number: '',
|
||||
fee_type: 3,
|
||||
@@ -2123,7 +2186,18 @@ async function openEdit(row: { id: number }) {
|
||||
editForm.dose_unit = d.dose_unit || '剂'
|
||||
editForm.prev_staff = d.prev_staff || ''
|
||||
editForm.service_channel = d.service_channel || ''
|
||||
editForm.service_package = d.service_package || ''
|
||||
// 处理服务套餐:如果是字符串,转换为数组
|
||||
if (d.service_package) {
|
||||
if (Array.isArray(d.service_package)) {
|
||||
editForm.service_package = d.service_package
|
||||
} else if (typeof d.service_package === 'string') {
|
||||
editForm.service_package = d.service_package.split(',').filter(v => v.trim() !== '')
|
||||
} else {
|
||||
editForm.service_package = []
|
||||
}
|
||||
} else {
|
||||
editForm.service_package = []
|
||||
}
|
||||
editForm.express_company = String(d.express_company || 'auto') || 'auto'
|
||||
editForm.tracking_number = d.tracking_number || ''
|
||||
editForm.fee_type = Number(d.fee_type) || 3
|
||||
@@ -2172,7 +2246,7 @@ async function submitEdit() {
|
||||
is_follow_up: editForm.is_follow_up,
|
||||
prev_staff: editForm.prev_staff || '',
|
||||
service_channel: editForm.service_channel || '',
|
||||
service_package: editForm.service_package || '',
|
||||
service_package: Array.isArray(editForm.service_package) ? editForm.service_package.join(',') : '',
|
||||
express_company: editForm.express_company || 'auto',
|
||||
tracking_number: editForm.tracking_number || '',
|
||||
fee_type: editForm.fee_type,
|
||||
@@ -2325,10 +2399,10 @@ async function testGancaoPreview() {
|
||||
gancaoPreviewDrawerVisible.value = true
|
||||
feedback.msgSuccess('预下单测试成功')
|
||||
} else {
|
||||
feedback.msgError(d?.error || '预下单失败')
|
||||
// feedback.msgError(d?.error || '预下单失败')
|
||||
}
|
||||
} catch (e: any) {
|
||||
feedback.msgError(e?.message || '预下单失败')
|
||||
// feedback.msgError(e?.message || '预下单失败')
|
||||
} finally {
|
||||
gancaoPreviewLoading.value = false
|
||||
}
|
||||
@@ -2354,12 +2428,12 @@ async function testGancaoPreviewFromDetail() {
|
||||
if (d && d.success) {
|
||||
gancaoPreviewData.value = d
|
||||
gancaoPreviewDrawerVisible.value = true
|
||||
feedback.msgSuccess('预下单测试成功')
|
||||
//feedback.msgSuccess('预下单测试成功')
|
||||
} else {
|
||||
feedback.msgError(d?.error || '预下单失败')
|
||||
// feedback.msgError(d?.error || '预下单失败')
|
||||
}
|
||||
} catch (e: any) {
|
||||
feedback.msgError(e?.message || '预下单失败')
|
||||
// feedback.msgError(e?.message || '预下单失败')
|
||||
} finally {
|
||||
gancaoPreviewLoading.value = false
|
||||
}
|
||||
@@ -2396,7 +2470,9 @@ async function confirmSubmitGancaoRecipel(row: { id: number }) {
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
await loadRegionData()
|
||||
await loadServicePackageOptions()
|
||||
getLists()
|
||||
})
|
||||
|
||||
@@ -2434,6 +2510,9 @@ async function submitQuickTrack() {
|
||||
id: d.id,
|
||||
recipient_name: d.recipient_name || '',
|
||||
recipient_phone: d.recipient_phone || '',
|
||||
shipping_province: d.shipping_province || '',
|
||||
shipping_city: d.shipping_city || '',
|
||||
shipping_district: d.shipping_district || '',
|
||||
shipping_address: d.shipping_address || '',
|
||||
is_follow_up: d.is_follow_up ? 1 : 0,
|
||||
medication_days: (d.medication_days != null && String(d.medication_days).trim() !== '') ? d.medication_days : '',
|
||||
|
||||
@@ -41,7 +41,14 @@ export default defineConfig({
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
hmr: true,
|
||||
open: true
|
||||
open: true,
|
||||
proxy: {
|
||||
'/api-proxy': {
|
||||
target: 'https://admin.zhenyangtang.com.cn',
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/api-proxy/, '')
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
|
||||
@@ -230,6 +230,7 @@ class PrescriptionLogic
|
||||
'dosage_amount' => isset($params['dosage_amount']) ? (float)$params['dosage_amount'] : null,
|
||||
'dosage_unit' => $params['dosage_unit'] ?? '',
|
||||
'need_decoction' => (int)($params['need_decoction'] ?? 0),
|
||||
'bags_per_dose' => isset($params['bags_per_dose']) ? (int)$params['bags_per_dose'] : 1,
|
||||
'diagnosis_id' => (int)($params['diagnosis_id'] ?? 0),
|
||||
'appointment_id' => (int)($params['appointment_id'] ?? 0),
|
||||
'patient_id' => (int)($params['patient_id'] ?? 0),
|
||||
@@ -358,6 +359,7 @@ class PrescriptionLogic
|
||||
'dosage_amount' => isset($params['dosage_amount']) ? (float)$params['dosage_amount'] : $prescription->dosage_amount,
|
||||
'dosage_unit' => $params['dosage_unit'] ?? $prescription->dosage_unit,
|
||||
'need_decoction' => isset($params['need_decoction']) ? (int)$params['need_decoction'] : (int)($prescription->need_decoction ?? 0),
|
||||
'bags_per_dose' => isset($params['bags_per_dose']) ? (int)$params['bags_per_dose'] : (int)($prescription->bags_per_dose ?? 1),
|
||||
'patient_name' => $params['patient_name'] ?? $prescription->patient_name,
|
||||
'gender' => (int)($params['gender'] ?? $prescription->gender),
|
||||
'age' => (int)($params['age'] ?? $prescription->age),
|
||||
|
||||
@@ -477,6 +477,18 @@ class PrescriptionOrderLogic
|
||||
$order->creator_id = $adminId;
|
||||
$order->recipient_name = (string) $params['recipient_name'];
|
||||
$order->recipient_phone = (string) $params['recipient_phone'];
|
||||
|
||||
// 处理省市区字段
|
||||
if (isset($params['shipping_province'])) {
|
||||
$order->shipping_province = (string) $params['shipping_province'];
|
||||
}
|
||||
if (isset($params['shipping_city'])) {
|
||||
$order->shipping_city = (string) $params['shipping_city'];
|
||||
}
|
||||
if (isset($params['shipping_district'])) {
|
||||
$order->shipping_district = (string) $params['shipping_district'];
|
||||
}
|
||||
|
||||
$order->shipping_address = (string) $params['shipping_address'];
|
||||
$order->is_follow_up = (int) ($params['is_follow_up'] ?? 0) === 1 ? 1 : 0;
|
||||
$order->medication_days = $medDays > 0 ? $medDays : null;
|
||||
|
||||
@@ -28,10 +28,11 @@ class PrescriptionValidate extends BaseValidate
|
||||
public function sceneAdd()
|
||||
{
|
||||
return $this->only([
|
||||
'prescription_type', 'patient_name', 'gender', 'age',
|
||||
'prescription_type', 'dosage_amount', 'dosage_unit', 'need_decoction', 'bags_per_dose',
|
||||
'patient_name', 'gender', 'age',
|
||||
'visit_no', 'prescription_date', 'tongue', 'tongue_image', 'pulse',
|
||||
'pulse_condition', 'clinical_diagnosis', 'herbs', 'dose_count', 'dose_unit',
|
||||
'usage_days', 'usage_instruction', 'usage_time', 'usage_way', 'dietary_taboo',
|
||||
'usage_days', 'times_per_day', 'usage_instruction', 'usage_time', 'usage_way', 'dietary_taboo',
|
||||
'usage_notes', 'doctor_name', 'is_shared', 'visible_role_ids',
|
||||
'diagnosis_id', 'appointment_id', 'audit_status',
|
||||
]);
|
||||
@@ -40,10 +41,11 @@ class PrescriptionValidate extends BaseValidate
|
||||
public function sceneEdit()
|
||||
{
|
||||
return $this->only([
|
||||
'id', 'prescription_type', 'patient_name', 'gender', 'age',
|
||||
'id', 'prescription_type', 'dosage_amount', 'dosage_unit', 'need_decoction', 'bags_per_dose',
|
||||
'patient_name', 'gender', 'age',
|
||||
'visit_no', 'prescription_date', 'tongue', 'tongue_image', 'pulse',
|
||||
'pulse_condition', 'clinical_diagnosis', 'herbs', 'dose_count', 'dose_unit',
|
||||
'usage_days', 'usage_instruction', 'usage_time', 'usage_way', 'dietary_taboo',
|
||||
'usage_days', 'times_per_day', 'usage_instruction', 'usage_time', 'usage_way', 'dietary_taboo',
|
||||
'usage_notes', 'doctor_name', 'is_shared', 'visible_role_ids', 'diagnosis_id',
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -412,21 +412,50 @@ final class GancaoScmRecipelService
|
||||
'package' => 'igc_scm.ops.api.order',
|
||||
'class' => 'CTM_PREVIEW',
|
||||
];
|
||||
dd($rx);
|
||||
|
||||
if ($dfId === 101) {
|
||||
$isDecoct = (int) $c['default_is_decoct'] ? 1 : 0;
|
||||
|
||||
$base['df101ext'] = [
|
||||
'times_per_day' => max(1, min(10, (int) $c['default_times_per_day'])),
|
||||
'is_decoct' => $isDecoct,
|
||||
'num_per_pack' => max(1, min(9, (int) $c['default_num_per_pack'])),
|
||||
'is_special_writing' => 0,
|
||||
'dose' => max(50, min(250, (int) $c['default_dose_ml'])),
|
||||
'times_per_day' =>$rx['times_per_day'],
|
||||
'is_decoct' => $rx['need_decoction'],
|
||||
'num_per_pack' => $rx['bags_per_dose'],
|
||||
'is_special_writing' => $rx['bags_per_dose']==$rx['times_per_day']?0:1,
|
||||
'dose' => $rx['dosage_amount'],
|
||||
'usage_mode' => 'ORAL',
|
||||
'ds_type' => 1
|
||||
];
|
||||
|
||||
$base['doct_advice']=[
|
||||
'taboo'=>$rx['dietary_taboo'],
|
||||
'usage_time'=>$rx['usage_time'],
|
||||
'usage_brief'=>$rx['usage_instruction'],
|
||||
'others'=>$rx['usage_notes']
|
||||
];
|
||||
$base['express_type']="general";
|
||||
$base['cradle_store']="线上接诊";
|
||||
$base['app_order_no']=$order['order_no'];
|
||||
$base['express_to']=[
|
||||
'name'=>$order['recipient_name'],
|
||||
'phone'=>$order['recipient_phone'],
|
||||
'province'=>$order['shipping_province'],
|
||||
'city'=>$order['shipping_city'],
|
||||
'addr'=>$order['shipping_province'].$order['shipping_city'].$order['shipping_city'].$order['shipping_address']
|
||||
];
|
||||
$base['callback_url']= $c['callback_url'];
|
||||
$base['diagnosis']= $rx['clinical_diagnosis']||'无';
|
||||
$base['disease']= $rx['clinical_diagnosis']||'无';
|
||||
$base['doctor']=[
|
||||
'name'=>$rx['doctor_name'],
|
||||
'phone'=>''
|
||||
];
|
||||
|
||||
$base['patient']=[
|
||||
'name'=>$rx['patient_name'],
|
||||
'age'=>$rx['age'],
|
||||
'sex'=>$rx['gender_desc']=='男'?1:0
|
||||
];
|
||||
}
|
||||
if ($dfId === 102) {
|
||||
$isDecoct = (int) $c['default_is_decoct'] ? 1 : 0;
|
||||
$base['df102ext'] = [
|
||||
'times_per_day' =>$rx['times_per_day'],
|
||||
'take_days'=>$order['medication_days']?$order['medication_days']:$rx['usage_days'],
|
||||
|
||||
Reference in New Issue
Block a user