This commit is contained in:
Your Name
2026-04-16 11:13:26 +08:00
parent 3d9c5dd8f5
commit 8022229e71
16 changed files with 1703 additions and 231 deletions
+167
View File
@@ -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!
+191
View File
@@ -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!
+170
View File
@@ -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!
+72
View File
@@ -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. 选择城市后能看到区县列表
+153
View File
@@ -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.
+199
View File
@@ -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
+293
View File
@@ -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!
+3
View File
@@ -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"> <div class="info-section">
<el-row :gutter="16"> <el-row :gutter="16">
<el-col :span="12"> <el-col :span="8">
<el-form-item label="处方类型" prop="prescription_type"> <el-form-item label="处方类型" prop="prescription_type">
<el-select v-model="formData.prescription_type" placeholder="请选择处方类型" class="!w-full"> <el-select v-model="formData.prescription_type" placeholder="请选择处方类型" class="!w-full">
<el-option label="浓缩水丸" value="浓缩水丸" /> <el-option label="浓缩水丸" value="浓缩水丸" />
@@ -137,14 +137,14 @@
</el-col> </el-col>
<!-- 用量字段 --> <!-- 用量字段 -->
<el-col :span="12"> <el-col :span="8">
<el-form-item label="用量" prop="dosage_amount"> <el-form-item label="用量" prop="dosage_amount">
<!-- 浓缩水丸1-10g 下拉选择 --> <!-- 浓缩水丸1-10g 下拉选择 -->
<el-select <el-select
v-if="formData.prescription_type === '浓缩水丸'" v-if="formData.prescription_type === '浓缩水丸'"
v-model="formData.dosage_amount" v-model="formData.dosage_amount"
placeholder="请选择用量" placeholder="请选择用量"
class="!w-32" class="!w-full"
> >
<el-option :label="1 + 'g'" :value="1" /> <el-option :label="1 + 'g'" :value="1" />
<el-option :label="2 + 'g'" :value="2" /> <el-option :label="2 + 'g'" :value="2" />
@@ -162,11 +162,13 @@
v-else-if="formData.prescription_type === '饮片'" v-else-if="formData.prescription_type === '饮片'"
v-model="formData.dosage_amount" v-model="formData.dosage_amount"
placeholder="请选择用量" placeholder="请选择用量"
class="!w-32" class="!w-full"
> >
<el-option label="50ml" :value="50" /> <el-option label="50ml" :value="50" />
<el-option label="100ml" :value="100" /> <el-option label="100ml" :value="100" />
<el-option label="120ml" :value="120" />
<el-option label="150ml" :value="150" /> <el-option label="150ml" :value="150" />
<el-option label="180ml" :value="180" />
<el-option label="200ml" :value="200" /> <el-option label="200ml" :value="200" />
<el-option label="250ml" :value="250" /> <el-option label="250ml" :value="250" />
</el-select> </el-select>
@@ -176,14 +178,31 @@
v-model="formData.dosage_amount" v-model="formData.dosage_amount"
:min="0" :min="0"
:precision="2" :precision="2"
class="!w-32" class="!w-full"
/> />
<span v-if="formData.prescription_type !== '浓缩水丸' && formData.prescription_type !== '饮片'" class="ml-2">{{ formData.dosage_unit }}</span> <span v-if="formData.prescription_type !== '浓缩水丸' && formData.prescription_type !== '饮片'" class="ml-2">{{ formData.dosage_unit }}</span>
</el-form-item> </el-form-item>
</el-col> </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-form-item label="是否代煎" prop="need_decoction">
<el-radio-group v-model="formData.need_decoction"> <el-radio-group v-model="formData.need_decoction">
<el-radio :label="true">代煎</el-radio> <el-radio :label="true">代煎</el-radio>
@@ -191,6 +210,7 @@
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-form-item label="服用天数" prop="usage_days"> <el-form-item label="服用天数" prop="usage_days">
<el-input-number v-model="formData.usage_days" :min="1" :max="365" class="!w-full" /> <el-input-number v-model="formData.usage_days" :min="1" :max="365" class="!w-full" />
@@ -198,7 +218,7 @@
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<el-form-item label="每天几次" prop="times_per_day"> <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-form-item>
</el-col> </el-col>
<!-- <el-col :span="8"> <!-- <el-col :span="8">
@@ -641,6 +661,7 @@ interface PrescriptionForm {
dosage_amount: number | undefined // 用量数值 dosage_amount: number | undefined // 用量数值
dosage_unit: string // 用量单位 (g 或 ml) dosage_unit: string // 用量单位 (g 或 ml)
need_decoction: boolean // 是否代煎(仅饮片) need_decoction: boolean // 是否代煎(仅饮片)
bags_per_dose: number // 每贴出包数(仅饮片,1-9包)
patient_name: string patient_name: string
gender: number gender: number
gender_desc: string gender_desc: string
@@ -721,6 +742,7 @@ const formData = reactive<PrescriptionForm>({
dosage_amount: 1, dosage_amount: 1,
dosage_unit: 'g', dosage_unit: 'g',
need_decoction: false, need_decoction: false,
bags_per_dose: 1,
patient_name: '', patient_name: '',
gender: 0, gender: 0,
gender_desc: '女', gender_desc: '女',
@@ -1129,14 +1151,17 @@ watch(() => formData.prescription_type, (newType) => {
formData.dosage_unit = 'g' formData.dosage_unit = 'g'
formData.dosage_amount = 1 formData.dosage_amount = 1
formData.need_decoction = false formData.need_decoction = false
formData.bags_per_dose = 1
} else if (newType === '饮片') { } else if (newType === '饮片') {
formData.dosage_unit = 'ml' formData.dosage_unit = 'ml'
formData.dosage_amount = 50 formData.dosage_amount = 50
formData.bags_per_dose = 1
// need_decoction 保持用户选择 // need_decoction 保持用户选择
} else { } else {
formData.dosage_unit = 'g' formData.dosage_unit = 'g'
formData.dosage_amount = undefined formData.dosage_amount = undefined
formData.need_decoction = false formData.need_decoction = false
formData.bags_per_dose = 1
} }
}) })
@@ -1190,6 +1215,7 @@ const handleSave = async () => {
dosage_amount: formData.dosage_amount, dosage_amount: formData.dosage_amount,
dosage_unit: formData.dosage_unit, dosage_unit: formData.dosage_unit,
need_decoction: formData.need_decoction, need_decoction: formData.need_decoction,
bags_per_dose: formData.bags_per_dose,
usage_days: formData.usage_days, usage_days: formData.usage_days,
times_per_day: formData.times_per_day, times_per_day: formData.times_per_day,
usage_instruction: formData.usage_instruction, usage_instruction: formData.usage_instruction,
+194 -127
View File
@@ -605,11 +605,13 @@
placeholder="请选择用量" placeholder="请选择用量"
class="w-full" class="w-full"
> >
<el-option label="50ml" :value="50" /> <el-option label="50ml" :value="50" />
<el-option label="100ml" :value="100" /> <el-option label="100ml" :value="100" />
<el-option label="150ml" :value="150" /> <el-option label="120ml" :value="120" />
<el-option label="200ml" :value="200" /> <el-option label="150ml" :value="150" />
<el-option label="250ml" :value="250" /> <el-option label="180ml" :value="180" />
<el-option label="200ml" :value="200" />
<el-option label="250ml" :value="250" />
</el-select> </el-select>
<!-- 其他类型:自由输入 --> <!-- 其他类型:自由输入 -->
<el-input-number <el-input-number
@@ -631,9 +633,27 @@
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-col> </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-col :span="8">
<el-form-item label="每天几次" prop="times_per_day"> <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-form-item>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
@@ -859,7 +879,7 @@
<el-cascader <el-cascader
v-model="createOrderForm.region" v-model="createOrderForm.region"
:options="regionOptions" :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="请选择省/市/区" placeholder="请选择省/市/区"
clearable clearable
filterable filterable
@@ -1267,143 +1287,138 @@ const createOrderForm = reactive({
}) })
// 省市区数据(简化版,实际项目中应该从 API 获取或使用完整的省市区数据) // 省市区数据(简化版,实际项目中应该从 API 获取或使用完整的省市区数据)
const regionOptions = ref([ const regionOptions = ref([])
{
name: '四川省', // 转换省市区数据格式
children: [ 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: [ children: [
{ name: '锦江区' }, {
{ name: '青羊区' }, name: '成都市',
{ name: '金牛区' }, children: [
{ name: '武侯区' }, { name: '锦江区' },
{ name: '成华区' }, { name: '青羊区' },
{ name: '龙泉驿区' }, { 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: '绵阳市',
children: [
{ name: '涪城区' },
{ name: '游仙区' },
{ name: '安州区' },
{ name: '江油市' },
{ name: '三台县' },
{ name: '盐亭县' },
{ name: '梓潼县' },
{ name: '北川羌族自治县' },
{ name: '平武县' }
]
}
]
},
{
name: '北京市',
children: [
{ {
name: '北京市', name: '北京市',
children: [ children: [
{ name: '东城区' }, {
{ name: '西城区' }, name: '北京市',
{ name: '朝阳区' }, children: [
{ name: '丰台区' }, { name: '东城区' },
{ 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: '深圳市', name: '上海市',
children: [ children: [
{ name: '罗湖区' }, {
{ name: '福田区' }, name: '上海市',
{ name: '南山区' }, children: [
{ name: '宝安区' }, { name: '黄浦区' },
{ name: '龙岗区' }, { name: '徐汇区' },
{ name: '盐田区' }, { name: '长宁区' },
{ 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[]) => { const handleRegionChange = (value: string[]) => {
console.log('选择的省市区:', value) console.log('选择的省市区:', value)
// value is an array like ['四川省', '成都市', '锦江区']
// We'll extract these in the submit function
} }
const createOrderLinkedPaidTotal = computed(() => { const createOrderLinkedPaidTotal = computed(() => {
@@ -1619,6 +1634,14 @@ async function submitCreateOrderFromPrescription() {
amount: createOrderForm.amount, amount: createOrderForm.amount,
remark_extra: createOrderForm.remark_extra || '' 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) { if (createOrderForm.medication_days != null && createOrderForm.medication_days > 0) {
payload.medication_days = createOrderForm.medication_days payload.medication_days = createOrderForm.medication_days
} }
@@ -1672,6 +1695,7 @@ const editForm = reactive({
dosage_amount: 1 as number | undefined, dosage_amount: 1 as number | undefined,
dosage_unit: 'g', dosage_unit: 'g',
need_decoction: false, need_decoction: false,
bags_per_dose: 1,
patient_name: '', patient_name: '',
gender: 1, gender: 1,
age: 0, age: 0,
@@ -1705,20 +1729,31 @@ const editForm = reactive({
times_per_day: 2 times_per_day: 2
}) })
// 标志:是否正在加载数据(防止watch函数覆盖已加载的值)
const isLoadingData = ref(false)
// 监听处方类型变化,自动调整用量单位和默认值 // 监听处方类型变化,自动调整用量单位和默认值
watch(() => editForm.prescription_type, (newType) => { watch(() => editForm.prescription_type, (newType) => {
// 如果正在加载数据,不要自动重置值
if (isLoadingData.value) {
return
}
if (newType === '浓缩水丸') { if (newType === '浓缩水丸') {
editForm.dosage_unit = 'g' editForm.dosage_unit = 'g'
editForm.dosage_amount = 1 editForm.dosage_amount = 1
editForm.need_decoction = false editForm.need_decoction = false
editForm.bags_per_dose = 1
} else if (newType === '饮片') { } else if (newType === '饮片') {
editForm.dosage_unit = 'ml' editForm.dosage_unit = 'ml'
editForm.dosage_amount = 50 editForm.dosage_amount = 50
editForm.bags_per_dose = 1
// need_decoction 保持用户选择 // need_decoction 保持用户选择
} else { } else {
editForm.dosage_unit = 'g' editForm.dosage_unit = 'g'
editForm.dosage_amount = undefined editForm.dosage_amount = undefined
editForm.need_decoction = false editForm.need_decoction = false
editForm.bags_per_dose = 1
} }
}) })
@@ -2062,6 +2097,8 @@ const handleAdd = () => {
editingWasVoid.value = false editingWasVoid.value = false
editingWasRejected.value = false editingWasRejected.value = false
editMode.value = 'add' editMode.value = 'add'
// 新建时允许watch自动设置默认值
isLoadingData.value = false
showEdit.value = true showEdit.value = true
nextTick(() => { nextTick(() => {
formRef.value?.clearValidate() formRef.value?.clearValidate()
@@ -2096,12 +2133,34 @@ const handleEdit = (row: any) => {
editingWasVoid.value = Number(row.void_status) === 1 editingWasVoid.value = Number(row.void_status) === 1
editingWasRejected.value = Number(row.audit_status) === 2 editingWasRejected.value = Number(row.audit_status) === 2
editMode.value = 'edit' editMode.value = 'edit'
// 开始加载数据,暂时禁用watch自动重置
isLoadingData.value = true
// 深拷贝数据到表单 // 深拷贝数据到表单
editForm.id = row.id editForm.id = row.id
editForm.prescription_type = row.prescription_type || '浓缩水丸' 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.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.patient_name = row.patient_name || ''
editForm.gender = row.gender ?? 1 editForm.gender = row.gender ?? 1
editForm.age = row.age ?? 0 editForm.age = row.age ?? 0
@@ -2132,6 +2191,13 @@ const handleEdit = (row: any) => {
editForm.diagnosis_id = row.diagnosis_id ?? 0 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_rejected = Number(row.business_prescription_audit_rejected) === 1 ? 1 : 0
editForm.business_prescription_audit_remark = String(row.business_prescription_audit_remark || '') editForm.business_prescription_audit_remark = String(row.business_prescription_audit_remark || '')
// 数据加载完成,重新启用watch
// 使用 setTimeout 确保所有数据都已经渲染完成
setTimeout(() => {
isLoadingData.value = false
}, 0)
showEdit.value = true showEdit.value = true
nextTick(() => { nextTick(() => {
formRef.value?.clearValidate() formRef.value?.clearValidate()
@@ -2195,6 +2261,7 @@ const handleDelete = async (id: number) => {
onMounted(async () => { onMounted(async () => {
await loadRoleOptions() await loadRoleOptions()
await loadRegionData()
try { try {
const list = await getDoctors() const list = await getDoctors()
doctorOptions.value = Array.isArray(list) ? list : [] 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_name }}</el-descriptions-item>
<el-descriptions-item label="手机">{{ detailData.recipient_phone }}</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.is_follow_up ? '是' : '否' }}</el-descriptions-item>
<el-descriptions-item label="用药疗程">{{ detailData.medication_days ? detailData.medication_days + ' 天' : '—' }}</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.prev_staff || '—' }}</el-descriptions-item>
<el-descriptions-item label="服务渠道">{{ detailData.service_channel || '—' }}</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 label="费用类别">{{ feeTypeText(detailData.fee_type) }}</el-descriptions-item>
<el-descriptions-item v-if="detailData.internal_cost != null && detailData.internal_cost !== ''" label="内部成本"> <el-descriptions-item v-if="detailData.internal_cost != null && detailData.internal_cost !== ''" label="内部成本">
@@ -757,8 +757,10 @@
<el-cascader <el-cascader
v-model="editForm.region" v-model="editForm.region"
:options="regionOptions" :options="regionOptions"
:props="{ emitPath: true }"
placeholder="请选择省/市/区" placeholder="请选择省/市/区"
clearable clearable
filterable
class="w-full" class="w-full"
/> />
</el-form-item> </el-form-item>
@@ -840,7 +842,22 @@
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="服务套餐"> <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-form-item>
</el-col> </el-col>
</el-row> </el-row>
@@ -1451,6 +1468,7 @@ import {
prescriptionOrderPreviewGancaoRecipel, prescriptionOrderPreviewGancaoRecipel,
prescriptionDetail prescriptionDetail
} from '@/api/tcm' } from '@/api/tcm'
import { getDictData } from '@/api/app'
import { usePaging } from '@/hooks/usePaging' import { usePaging } from '@/hooks/usePaging'
import feedback from '@/utils/feedback' import feedback from '@/utils/feedback'
import { ElMessageBox } from 'element-plus' import { ElMessageBox } from 'element-plus'
@@ -1472,90 +1490,99 @@ const canViewFinanceFields = () => {
} }
// 省市区数据 // 省市区数据
const regionOptions = [ const regionOptions = ref([])
{
value: '四川省', // 服务套餐选项
label: '四川省', const servicePackageOptions = ref<Array<{ name: string; value: string }>>([])
children: [
// 转换省市区数据格式
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: '成都市', value: '四川省',
label: '成都市', label: '四川省',
children: [ children: [
{ value: '武侯区', label: '武侯区' }, {
{ value: '锦江区', label: '锦江区' }, value: '成都市',
{ value: '青羊区', label: '青羊区' }, label: '成都市',
{ value: '金牛区', label: '金牛区' }, children: [
{ value: '成华区', label: '成华区' }, { value: '武侯区', label: '武侯区' },
{ value: '高新区', label: '高新区' } { value: '锦江区', label: '锦江区' },
{ value: '青羊区', label: '青羊区' },
{ value: '金牛区', label: '金牛区' },
{ value: '成华区', label: '成华区' }
]
}
] ]
}, },
{
value: '绵阳市',
label: '绵阳市',
children: [
{ value: '涪城区', label: '涪城区' },
{ value: '游仙区', label: '游仙区' }
]
}
]
},
{
value: '北京市',
label: '北京市',
children: [
{ {
value: '北京市', value: '北京市',
label: '北京市', label: '北京市',
children: [ children: [
{ value: '朝阳区', label: '朝阳区' }, {
{ value: '海淀区', label: '海淀区' }, value: '北京市',
{ value: '东城区', label: '东城区' }, label: '北京市',
{ value: '西城区', label: '西城区' } children: [
] { 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: '罗湖区' }
] ]
} }
] ]
} }
] }
// 加载服务套餐选项
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({ const queryParams = reactive({
order_no: '', order_no: '',
@@ -1602,6 +1629,28 @@ function feeTypeText(t: number | undefined) {
return m[Number(t)] ?? '—' 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) { function auditStatusText(s: number | undefined) {
if (s === 1) return '已通过' if (s === 1) return '已通过'
if (s === 2) return '已驳回' if (s === 2) return '已驳回'
@@ -1847,6 +1896,20 @@ const detailRxHerbs = computed(() => normalizeSlipHerbs(detailPrescription.value
/** false=无权限;true/缺省兼容旧接口(旧版未下发该字段时仍展示药材) */ /** false=无权限;true/缺省兼容旧接口(旧版未下发该字段时仍展示药材) */
const detailHerbsVisible = computed(() => detailData.value?.prescription_detail_herbs_visible !== false) 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) { function consumerRxAuditText(s: number | undefined) {
const n = Number(s) const n = Number(s)
if (n === 1) return '已通过' if (n === 1) return '已通过'
@@ -2003,7 +2066,7 @@ const editForm = reactive({
dose_unit: '剂', dose_unit: '剂',
prev_staff: '', prev_staff: '',
service_channel: '', service_channel: '',
service_package: '', service_package: [] as string[],
express_company: 'auto', express_company: 'auto',
tracking_number: '', tracking_number: '',
fee_type: 3, fee_type: 3,
@@ -2123,7 +2186,18 @@ async function openEdit(row: { id: number }) {
editForm.dose_unit = d.dose_unit || '剂' editForm.dose_unit = d.dose_unit || '剂'
editForm.prev_staff = d.prev_staff || '' editForm.prev_staff = d.prev_staff || ''
editForm.service_channel = d.service_channel || '' 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.express_company = String(d.express_company || 'auto') || 'auto'
editForm.tracking_number = d.tracking_number || '' editForm.tracking_number = d.tracking_number || ''
editForm.fee_type = Number(d.fee_type) || 3 editForm.fee_type = Number(d.fee_type) || 3
@@ -2172,7 +2246,7 @@ async function submitEdit() {
is_follow_up: editForm.is_follow_up, is_follow_up: editForm.is_follow_up,
prev_staff: editForm.prev_staff || '', prev_staff: editForm.prev_staff || '',
service_channel: editForm.service_channel || '', 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', express_company: editForm.express_company || 'auto',
tracking_number: editForm.tracking_number || '', tracking_number: editForm.tracking_number || '',
fee_type: editForm.fee_type, fee_type: editForm.fee_type,
@@ -2325,10 +2399,10 @@ async function testGancaoPreview() {
gancaoPreviewDrawerVisible.value = true gancaoPreviewDrawerVisible.value = true
feedback.msgSuccess('预下单测试成功') feedback.msgSuccess('预下单测试成功')
} else { } else {
feedback.msgError(d?.error || '预下单失败') // feedback.msgError(d?.error || '预下单失败')
} }
} catch (e: any) { } catch (e: any) {
feedback.msgError(e?.message || '预下单失败') // feedback.msgError(e?.message || '预下单失败')
} finally { } finally {
gancaoPreviewLoading.value = false gancaoPreviewLoading.value = false
} }
@@ -2354,12 +2428,12 @@ async function testGancaoPreviewFromDetail() {
if (d && d.success) { if (d && d.success) {
gancaoPreviewData.value = d gancaoPreviewData.value = d
gancaoPreviewDrawerVisible.value = true gancaoPreviewDrawerVisible.value = true
feedback.msgSuccess('预下单测试成功') //feedback.msgSuccess('预下单测试成功')
} else { } else {
feedback.msgError(d?.error || '预下单失败') // feedback.msgError(d?.error || '预下单失败')
} }
} catch (e: any) { } catch (e: any) {
feedback.msgError(e?.message || '预下单失败') // feedback.msgError(e?.message || '预下单失败')
} finally { } finally {
gancaoPreviewLoading.value = false gancaoPreviewLoading.value = false
} }
@@ -2396,7 +2470,9 @@ async function confirmSubmitGancaoRecipel(row: { id: number }) {
} }
} }
onMounted(() => { onMounted(async () => {
await loadRegionData()
await loadServicePackageOptions()
getLists() getLists()
}) })
@@ -2434,6 +2510,9 @@ async function submitQuickTrack() {
id: d.id, id: d.id,
recipient_name: d.recipient_name || '', recipient_name: d.recipient_name || '',
recipient_phone: d.recipient_phone || '', 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 || '', shipping_address: d.shipping_address || '',
is_follow_up: d.is_follow_up ? 1 : 0, is_follow_up: d.is_follow_up ? 1 : 0,
medication_days: (d.medication_days != null && String(d.medication_days).trim() !== '') ? d.medication_days : '', medication_days: (d.medication_days != null && String(d.medication_days).trim() !== '') ? d.medication_days : '',
+8 -1
View File
@@ -41,7 +41,14 @@ export default defineConfig({
server: { server: {
host: '0.0.0.0', host: '0.0.0.0',
hmr: true, hmr: true,
open: true open: true,
proxy: {
'/api-proxy': {
target: 'https://admin.zhenyangtang.com.cn',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api-proxy/, '')
}
}
}, },
plugins: [ plugins: [
vue(), vue(),
@@ -230,6 +230,7 @@ class PrescriptionLogic
'dosage_amount' => isset($params['dosage_amount']) ? (float)$params['dosage_amount'] : null, 'dosage_amount' => isset($params['dosage_amount']) ? (float)$params['dosage_amount'] : null,
'dosage_unit' => $params['dosage_unit'] ?? '', 'dosage_unit' => $params['dosage_unit'] ?? '',
'need_decoction' => (int)($params['need_decoction'] ?? 0), '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), 'diagnosis_id' => (int)($params['diagnosis_id'] ?? 0),
'appointment_id' => (int)($params['appointment_id'] ?? 0), 'appointment_id' => (int)($params['appointment_id'] ?? 0),
'patient_id' => (int)($params['patient_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_amount' => isset($params['dosage_amount']) ? (float)$params['dosage_amount'] : $prescription->dosage_amount,
'dosage_unit' => $params['dosage_unit'] ?? $prescription->dosage_unit, 'dosage_unit' => $params['dosage_unit'] ?? $prescription->dosage_unit,
'need_decoction' => isset($params['need_decoction']) ? (int)$params['need_decoction'] : (int)($prescription->need_decoction ?? 0), '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, 'patient_name' => $params['patient_name'] ?? $prescription->patient_name,
'gender' => (int)($params['gender'] ?? $prescription->gender), 'gender' => (int)($params['gender'] ?? $prescription->gender),
'age' => (int)($params['age'] ?? $prescription->age), 'age' => (int)($params['age'] ?? $prescription->age),
@@ -477,6 +477,18 @@ class PrescriptionOrderLogic
$order->creator_id = $adminId; $order->creator_id = $adminId;
$order->recipient_name = (string) $params['recipient_name']; $order->recipient_name = (string) $params['recipient_name'];
$order->recipient_phone = (string) $params['recipient_phone']; $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->shipping_address = (string) $params['shipping_address'];
$order->is_follow_up = (int) ($params['is_follow_up'] ?? 0) === 1 ? 1 : 0; $order->is_follow_up = (int) ($params['is_follow_up'] ?? 0) === 1 ? 1 : 0;
$order->medication_days = $medDays > 0 ? $medDays : null; $order->medication_days = $medDays > 0 ? $medDays : null;
@@ -28,10 +28,11 @@ class PrescriptionValidate extends BaseValidate
public function sceneAdd() public function sceneAdd()
{ {
return $this->only([ 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', 'visit_no', 'prescription_date', 'tongue', 'tongue_image', 'pulse',
'pulse_condition', 'clinical_diagnosis', 'herbs', 'dose_count', 'dose_unit', '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', 'usage_notes', 'doctor_name', 'is_shared', 'visible_role_ids',
'diagnosis_id', 'appointment_id', 'audit_status', 'diagnosis_id', 'appointment_id', 'audit_status',
]); ]);
@@ -40,10 +41,11 @@ class PrescriptionValidate extends BaseValidate
public function sceneEdit() public function sceneEdit()
{ {
return $this->only([ 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', 'visit_no', 'prescription_date', 'tongue', 'tongue_image', 'pulse',
'pulse_condition', 'clinical_diagnosis', 'herbs', 'dose_count', 'dose_unit', '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', 'usage_notes', 'doctor_name', 'is_shared', 'visible_role_ids', 'diagnosis_id',
]); ]);
} }
@@ -412,21 +412,50 @@ final class GancaoScmRecipelService
'package' => 'igc_scm.ops.api.order', 'package' => 'igc_scm.ops.api.order',
'class' => 'CTM_PREVIEW', 'class' => 'CTM_PREVIEW',
]; ];
dd($rx);
if ($dfId === 101) { if ($dfId === 101) {
$isDecoct = (int) $c['default_is_decoct'] ? 1 : 0;
$base['df101ext'] = [ $base['df101ext'] = [
'times_per_day' => max(1, min(10, (int) $c['default_times_per_day'])), 'times_per_day' =>$rx['times_per_day'],
'is_decoct' => $isDecoct, 'is_decoct' => $rx['need_decoction'],
'num_per_pack' => max(1, min(9, (int) $c['default_num_per_pack'])), 'num_per_pack' => $rx['bags_per_dose'],
'is_special_writing' => 0, 'is_special_writing' => $rx['bags_per_dose']==$rx['times_per_day']?0:1,
'dose' => max(50, min(250, (int) $c['default_dose_ml'])), 'dose' => $rx['dosage_amount'],
'usage_mode' => 'ORAL', 'usage_mode' => 'ORAL',
'ds_type' => 1 '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) { if ($dfId === 102) {
$isDecoct = (int) $c['default_is_decoct'] ? 1 : 0;
$base['df102ext'] = [ $base['df102ext'] = [
'times_per_day' =>$rx['times_per_day'], 'times_per_day' =>$rx['times_per_day'],
'take_days'=>$order['medication_days']?$order['medication_days']:$rx['usage_days'], 'take_days'=>$order['medication_days']?$order['medication_days']:$rx['usage_days'],