Compare commits
386
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be15531ab3 | ||
|
|
0a781bda54 | ||
|
|
cbf992d6df | ||
|
|
7aec1a9713 | ||
|
|
01bc355bed | ||
|
|
1b746b46c0 | ||
|
|
ceb313b1a7 | ||
|
|
576ab05681 | ||
|
|
132a17b946 | ||
|
|
75dfaa0dcd | ||
|
|
e83dd07553 | ||
|
|
c589bfa647 | ||
|
|
17ad153821 | ||
|
|
e4312ab5d1 | ||
|
|
7f4fad7b62 | ||
|
|
9b5cd5b2b9 | ||
|
|
94e787ae80 | ||
|
|
7b46204454 | ||
|
|
9f3db8f281 | ||
|
|
6cb8688587 | ||
|
|
0e5b5beb9c | ||
|
|
8d635bb7ed | ||
|
|
0f974be7a1 | ||
|
|
2b1ce61e72 | ||
|
|
6efc340bd8 | ||
|
|
c435e6ae4e | ||
|
|
dfb8838ecc | ||
|
|
7f08771dd0 | ||
|
|
afd88714ec | ||
|
|
bb21825c16 | ||
|
|
c3e4dfa479 | ||
|
|
33f8f669ad | ||
|
|
3ebe3c58c8 | ||
|
|
0225e9f971 | ||
|
|
4f066b560e | ||
|
|
13f58a5fdc | ||
|
|
36be8fedad | ||
|
|
b9d2541b32 | ||
|
|
261f8315de | ||
|
|
ffe4849c27 | ||
|
|
19af50c344 | ||
|
|
012830de4a | ||
|
|
8b0fcd7050 | ||
|
|
57e4892140 | ||
|
|
95fff1262b | ||
|
|
0dd9cdcba9 | ||
|
|
5b233fb0ab | ||
|
|
3325be0622 | ||
|
|
9af5c5be63 | ||
|
|
0e46bf8e0d | ||
|
|
b93313d935 | ||
|
|
c1dfb01f53 | ||
|
|
3f999b312d | ||
|
|
50abe5dece | ||
|
|
58c224a081 | ||
|
|
f718df833e | ||
|
|
f068acf390 | ||
|
|
769d912eef | ||
|
|
5aa0cda252 | ||
|
|
552da3aec2 | ||
|
|
ed5285b58a | ||
|
|
0d35d1b6c3 | ||
|
|
27c2ada4ec | ||
|
|
6be5b4c45d | ||
|
|
fbf3d47b2a | ||
|
|
82c4d7e0a2 | ||
|
|
867a6a97cf | ||
|
|
a3ee72b34d | ||
|
|
a08031b901 | ||
|
|
665481a6d8 | ||
|
|
30b344b776 | ||
|
|
e104249707 | ||
|
|
9b6d9ce007 | ||
|
|
69da8066a6 | ||
|
|
edc9993403 | ||
|
|
8f14f3190e | ||
|
|
71a3c63182 | ||
|
|
d10678763f | ||
|
|
2beace89f4 | ||
|
|
e5d0d7db93 | ||
|
|
35a42078ce | ||
|
|
e8e28d19fb | ||
|
|
163e40c73f | ||
|
|
d46cfff079 | ||
|
|
275a7a550d | ||
|
|
5430fe0417 | ||
|
|
f314e2e271 | ||
|
|
a4fb55d164 | ||
|
|
295c8f0623 | ||
|
|
4b749a1ee0 | ||
|
|
679e61057b | ||
|
|
7f7b8f0a64 | ||
|
|
7e94c5dfd7 | ||
|
|
003810114b | ||
|
|
37c5849103 | ||
|
|
55521b8e11 | ||
|
|
e4f181e4fe | ||
|
|
bba989c0af | ||
|
|
d5e0ba04b7 | ||
|
|
22ec53c070 | ||
|
|
183b6a1acf | ||
|
|
49c8c95966 | ||
|
|
c7c264497e | ||
|
|
436f69c272 | ||
|
|
dc72a8ccaf | ||
|
|
499c969233 | ||
|
|
3b44300216 | ||
|
|
8bb7b5d30a | ||
|
|
4c3becf33e | ||
|
|
a42fc6453a | ||
|
|
dad06304f1 | ||
|
|
03b80310f6 | ||
|
|
8d015fa962 | ||
|
|
f639ef2fbb | ||
|
|
ea8e6d7c1b | ||
|
|
ba1c067dce | ||
|
|
5e96fda88f | ||
|
|
076cd0fcf9 | ||
|
|
44e97861d2 | ||
|
|
93e6d02ce4 | ||
|
|
60cb2b80cd | ||
|
|
752eddb0ba | ||
|
|
61d14d1b45 | ||
|
|
a83c583a28 | ||
|
|
485ba7a380 | ||
|
|
1005859404 | ||
|
|
ea6c73c063 | ||
|
|
3e4039efb0 | ||
|
|
c9ad4ae2ed | ||
|
|
abf8026d45 | ||
|
|
26b442b5da | ||
|
|
1cb00e9fbe | ||
|
|
9208e7eaaa | ||
|
|
a1392f9491 | ||
|
|
2ea0e1d54b | ||
|
|
d96fa6f4a1 | ||
|
|
0fb51e12d1 | ||
|
|
4f9494db0b | ||
|
|
4e87b080a0 | ||
|
|
2f3038aba1 | ||
|
|
131fb7d9c7 | ||
|
|
762a724204 | ||
|
|
8090e0aab3 | ||
|
|
9df6330d41 | ||
|
|
fd63e23eaf | ||
|
|
c1143f4629 | ||
|
|
149fdffd49 | ||
|
|
439d5a3ec9 | ||
|
|
9e24d49210 | ||
|
|
90ac43fc82 | ||
|
|
8b6b709987 | ||
|
|
fb6ca9ce85 | ||
|
|
08290b329e | ||
|
|
1d66f84bd3 | ||
|
|
a2d9c1a03f | ||
|
|
d79db88349 | ||
|
|
9f5fb650af | ||
|
|
15b4339c90 | ||
|
|
5ce331394d | ||
|
|
7ba9dabdb4 | ||
|
|
611f3dcd5c | ||
|
|
d3388b160e | ||
|
|
f83d4c06cb | ||
|
|
4941ea3e21 | ||
|
|
e35696e153 | ||
|
|
800c6af7f1 | ||
|
|
e1df584ed5 | ||
|
|
876ef0f8a0 | ||
|
|
928f75e016 | ||
|
|
37fa160c24 | ||
|
|
0eaacbb4ce | ||
|
|
1e8b5c4646 | ||
|
|
fdcfa30810 | ||
|
|
fabaa84373 | ||
|
|
da017bdf20 | ||
|
|
5d94ffab3e | ||
|
|
796ca12fb8 | ||
|
|
4caa98af3a | ||
|
|
c644f2554f | ||
|
|
bb87e4fb4c | ||
|
|
e83dac756d | ||
|
|
0c1709d589 | ||
|
|
a3892001d3 | ||
|
|
039105580c | ||
|
|
f47f431bdf | ||
|
|
7add364e20 | ||
|
|
0ebea74fde | ||
|
|
1156787327 | ||
|
|
15cf6324f6 | ||
|
|
1338b8735c | ||
|
|
82f229adf2 | ||
|
|
048e43a662 | ||
|
|
d8d4da197e | ||
|
|
a0325a8fb1 | ||
|
|
091db50841 | ||
|
|
1774a87dd7 | ||
|
|
62d15efbaf | ||
|
|
c468c57cda | ||
|
|
bacdd6386f | ||
|
|
ea290c4c7b | ||
|
|
31b4afdcb7 | ||
|
|
379d2ef404 | ||
|
|
5cdb7ebb9e | ||
|
|
496a2b870b | ||
|
|
471bdf12b7 | ||
|
|
8fe4683e64 | ||
|
|
c34409edde | ||
|
|
0a37335f3c | ||
|
|
985916dea7 | ||
|
|
3330613254 | ||
|
|
3f12829204 | ||
|
|
f28500e449 | ||
|
|
fd9053a290 | ||
|
|
dc1c5d5b17 | ||
|
|
1297d9f510 | ||
|
|
daaf6c4a14 | ||
|
|
68e2e0b7d7 | ||
|
|
c78ba50331 | ||
|
|
0026a92697 | ||
|
|
ee1b140902 | ||
|
|
f81a3b5d94 | ||
|
|
8719cdd6d6 | ||
|
|
33fb055bfe | ||
|
|
14735af0b3 | ||
|
|
623bda2ecb | ||
|
|
dfbb6d82b3 | ||
|
|
7473a3e03d | ||
|
|
eb9b31f8f5 | ||
|
|
f20a45a1ad | ||
|
|
0f66d1438f | ||
|
|
43110e015d | ||
|
|
7d7a4aa4d2 | ||
|
|
8c640abb26 | ||
|
|
ffabe78942 | ||
|
|
81fcef2156 | ||
|
|
4dc9ec1878 | ||
|
|
3f05a610b3 | ||
|
|
6c997559a2 | ||
|
|
d39b1ed7ee | ||
|
|
0e81fb5037 | ||
|
|
36847ea70c | ||
|
|
9b40ebb8b7 | ||
|
|
9948bc28df | ||
|
|
a9474356ff | ||
|
|
4e23c4bf79 | ||
|
|
b6e6394e51 | ||
|
|
576ef8e033 | ||
|
|
a52380aa63 | ||
|
|
49e7169630 | ||
|
|
d53f492d2a | ||
|
|
3d3abff842 | ||
|
|
90ac63c905 | ||
|
|
320d3783ac | ||
|
|
5966049020 | ||
|
|
755491c263 | ||
|
|
79203d948e | ||
|
|
01d56f3b4f | ||
|
|
477fcf6180 | ||
|
|
3014a99db5 | ||
|
|
c4dd196e72 | ||
|
|
581c74f11a | ||
|
|
7a2f285442 | ||
|
|
375f54263a | ||
|
|
ed4a4c4960 | ||
|
|
9d405ebed9 | ||
|
|
a7fd55aa1c | ||
|
|
c389fea73c | ||
|
|
05e8f4ddd7 | ||
|
|
da9a493e8d | ||
|
|
64ab3197ec | ||
|
|
dddc4d202a | ||
|
|
71180d4fd8 | ||
|
|
1942387050 | ||
|
|
77c1fc908d | ||
|
|
ef4800e82a | ||
|
|
815ea9e025 | ||
|
|
f7ce3d18d8 | ||
|
|
de78f3b218 | ||
|
|
671c7f16bd | ||
|
|
30b5a13f3a | ||
|
|
c830008dfc | ||
|
|
919d0f856e | ||
|
|
49d0fed14e | ||
|
|
19bfeef98f | ||
|
|
cd51f55701 | ||
|
|
17cb0e8319 | ||
|
|
3ef10846b4 | ||
|
|
40eec808ef | ||
|
|
03db6d2677 | ||
|
|
c03e7051d7 | ||
|
|
d6e105f912 | ||
|
|
0da27ed745 | ||
|
|
1d6a2b9cbf | ||
|
|
28fbbd28b7 | ||
|
|
22b8b38b5d | ||
|
|
81b2808eaf | ||
|
|
25eb3340de | ||
|
|
c9150ca4b4 | ||
|
|
b9165f6b1b | ||
|
|
e0ab9ebc4b | ||
|
|
1743d3635c | ||
|
|
7e557b1ea2 | ||
|
|
0233774dd2 | ||
|
|
8dff4f0f78 | ||
|
|
4c21ee5938 | ||
|
|
742be6b492 | ||
|
|
d3003ce0ac | ||
|
|
d9175cada7 | ||
|
|
901629ac6e | ||
|
|
1b6a981027 | ||
|
|
cba04067bd | ||
|
|
a5cd43a77a | ||
|
|
bb08eeab32 | ||
|
|
eb782305e6 | ||
|
|
64a914c05a | ||
|
|
256771c384 | ||
|
|
dc899e4af4 | ||
|
|
656f7edd28 | ||
|
|
4e639c84ba | ||
|
|
fe14f67965 | ||
|
|
f6469a437a | ||
|
|
a89c7cce62 | ||
|
|
23bd86e056 | ||
|
|
1711d70aa3 | ||
|
|
cf10583274 | ||
|
|
3007e6c5b1 | ||
|
|
5cb9706274 | ||
|
|
96c603327e | ||
|
|
5d7b1e97fd | ||
|
|
aaba330f0d | ||
|
|
d408ffabe8 | ||
|
|
f7e33e35fa | ||
|
|
5f6bc93a46 | ||
|
|
a3e035b74b | ||
|
|
d7aa984a31 | ||
|
|
31498b7891 | ||
|
|
f9c8b2a632 | ||
|
|
aabf2f3a38 | ||
|
|
54431cbeb1 | ||
|
|
b7f6cb76e0 | ||
|
|
366b005d78 | ||
|
|
7a47568b00 | ||
|
|
832e8758cc | ||
|
|
5059446b79 | ||
|
|
d007669a04 | ||
|
|
c549067bb6 | ||
|
|
852bcd54a5 | ||
|
|
6ea61e9193 | ||
|
|
4872e4d2e6 | ||
|
|
a1a0b74596 | ||
|
|
f773e73de6 | ||
|
|
d899e0fe0a | ||
|
|
4f45ecbd63 | ||
|
|
d47ef8d680 | ||
|
|
e2aaee324e | ||
|
|
a1d2ab4649 | ||
|
|
c15c348b7a | ||
|
|
b03c079099 | ||
|
|
6bcd073ba0 | ||
|
|
c9e0419895 | ||
|
|
34ba9222fa | ||
|
|
4097fe0cc6 | ||
|
|
fa3da15228 | ||
|
|
b6e98d6c95 | ||
|
|
e2167f5ca4 | ||
|
|
7184640f8e | ||
|
|
91624bf3de | ||
|
|
d9655c6a06 | ||
|
|
e7ffbc94af | ||
|
|
4ef6a0dbcb | ||
|
|
2d9355e51f | ||
|
|
0528333d5e | ||
|
|
bc1e2fddef | ||
|
|
a5d2baf731 | ||
|
|
d5fa4dd73c | ||
|
|
7845333985 | ||
|
|
cd1905bc4d | ||
|
|
0c444dadce | ||
|
|
99263d6528 | ||
|
|
398f91bc55 | ||
|
|
363565a4f7 | ||
|
|
fd44feaf09 | ||
|
|
0fff995302 | ||
|
|
a60bcd9f87 | ||
|
|
8022229e71 | ||
|
|
3d9c5dd8f5 |
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(mysql --version)",
|
||||
"Bash(mysql *)",
|
||||
"Bash(npx vue-tsc *)",
|
||||
"Bash(php -l app/adminapi/logic/stats/YejiStatsLogic.php)",
|
||||
"Bash(python3 .claude/skills/ui-ux-pro-max/scripts/search.py \"finance admin dashboard SaaS form professional\" --design-system -p \"Finance Target Setting\" -f markdown)",
|
||||
"Bash(python .claude/skills/ui-ux-pro-max/scripts/search.py \"finance admin dashboard SaaS form professional\" --design-system -p \"Finance Target Setting\" -f markdown)",
|
||||
"Bash(python *)",
|
||||
"Bash(php think *)",
|
||||
"Bash(python3 -c ' *)",
|
||||
"Bash(python3 *)",
|
||||
"Bash(awk '/<template>/,/<\\\\/template>/{print NR\": \"$0; if\\(/<\\\\/template>/\\) exit}' D:/web/zyt/admin/src/views/consumer/prescription/order_list.vue)",
|
||||
"Bash(npx eslint *)",
|
||||
"Bash(git *)",
|
||||
"Bash(tail -c 100000 /Users/long/.claude/projects/-Users-long-Work-zyt/7be9f770-afc3-497d-87a3-2ffa1fbe3fd1.jsonl)",
|
||||
"Read(//tmp/**)",
|
||||
"Bash(npx --no-install vue-tsc --noEmit)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,96 @@
|
||||
# ui-ux-pro-max
|
||||
---
|
||||
name: ui-ux-pro-max
|
||||
description: "UI/UX design intelligence. 67 styles, 96 palettes, 57 font pairings, 25 charts, 13 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind, shadcn/ui). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient. Integrations: shadcn/ui MCP for component search and examples."
|
||||
---
|
||||
# UI/UX Pro Max - Design Intelligence
|
||||
|
||||
Comprehensive design guide for web and mobile applications. Contains 67 styles, 96 color palettes, 57 font pairings, 99 UX guidelines, and 25 chart types across 13 technology stacks. Searchable database with priority-based recommendations.
|
||||
|
||||
## When to Apply
|
||||
|
||||
Reference these guidelines when:
|
||||
- Designing new UI components or pages
|
||||
- Choosing color palettes and typography
|
||||
- Reviewing code for UX issues
|
||||
- Building landing pages or dashboards
|
||||
- Implementing accessibility requirements
|
||||
|
||||
## Rule Categories by Priority
|
||||
|
||||
| Priority | Category | Impact | Domain |
|
||||
|----------|----------|--------|--------|
|
||||
| 1 | Accessibility | CRITICAL | `ux` |
|
||||
| 2 | Touch & Interaction | CRITICAL | `ux` |
|
||||
| 3 | Performance | HIGH | `ux` |
|
||||
| 4 | Layout & Responsive | HIGH | `ux` |
|
||||
| 5 | Typography & Color | MEDIUM | `typography`, `color` |
|
||||
| 6 | Animation | MEDIUM | `ux` |
|
||||
| 7 | Style Selection | MEDIUM | `style`, `product` |
|
||||
| 8 | Charts & Data | LOW | `chart` |
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### 1. Accessibility (CRITICAL)
|
||||
|
||||
- `color-contrast` - Minimum 4.5:1 ratio for normal text
|
||||
- `focus-states` - Visible focus rings on interactive elements
|
||||
- `alt-text` - Descriptive alt text for meaningful images
|
||||
- `aria-labels` - aria-label for icon-only buttons
|
||||
- `keyboard-nav` - Tab order matches visual order
|
||||
- `form-labels` - Use label with for attribute
|
||||
|
||||
### 2. Touch & Interaction (CRITICAL)
|
||||
|
||||
- `touch-target-size` - Minimum 44x44px touch targets
|
||||
- `hover-vs-tap` - Use click/tap for primary interactions
|
||||
- `loading-buttons` - Disable button during async operations
|
||||
- `error-feedback` - Clear error messages near problem
|
||||
- `cursor-pointer` - Add cursor-pointer to clickable elements
|
||||
|
||||
### 3. Performance (HIGH)
|
||||
|
||||
- `image-optimization` - Use WebP, srcset, lazy loading
|
||||
- `reduced-motion` - Check prefers-reduced-motion
|
||||
- `content-jumping` - Reserve space for async content
|
||||
|
||||
### 4. Layout & Responsive (HIGH)
|
||||
|
||||
- `viewport-meta` - width=device-width initial-scale=1
|
||||
- `readable-font-size` - Minimum 16px body text on mobile
|
||||
- `horizontal-scroll` - Ensure content fits viewport width
|
||||
- `z-index-management` - Define z-index scale (10, 20, 30, 50)
|
||||
|
||||
### 5. Typography & Color (MEDIUM)
|
||||
|
||||
- `line-height` - Use 1.5-1.75 for body text
|
||||
- `line-length` - Limit to 65-75 characters per line
|
||||
- `font-pairing` - Match heading/body font personalities
|
||||
|
||||
### 6. Animation (MEDIUM)
|
||||
|
||||
- `duration-timing` - Use 150-300ms for micro-interactions
|
||||
- `transform-performance` - Use transform/opacity, not width/height
|
||||
- `loading-states` - Skeleton screens or spinners
|
||||
|
||||
### 7. Style Selection (MEDIUM)
|
||||
|
||||
- `style-match` - Match style to product type
|
||||
- `consistency` - Use same style across all pages
|
||||
- `no-emoji-icons` - Use SVG icons, not emojis
|
||||
|
||||
### 8. Charts & Data (LOW)
|
||||
|
||||
- `chart-type` - Match chart type to data type
|
||||
- `color-guidance` - Use accessible color palettes
|
||||
- `data-table` - Provide table alternative for accessibility
|
||||
|
||||
## How to Use
|
||||
|
||||
Search specific domains using the CLI tool below.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Check if Python is installed:
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 94.
|
|
Can't render this file because it contains an unexpected character in line 8 and column 193.
|
|
Can't render this file because it contains an unexpected character in line 4 and column 188.
|
Binary file not shown.
Binary file not shown.
+16
@@ -18,3 +18,19 @@ bin-release/
|
||||
# information for Eclipse / Flash Builder.
|
||||
|
||||
/.idea
|
||||
/.codex-tasks
|
||||
/.trellis
|
||||
/.claude
|
||||
/.agent
|
||||
/.shared
|
||||
/.cursor
|
||||
/.codex
|
||||
/.agents
|
||||
/.claude
|
||||
/.claude
|
||||
/.claude
|
||||
/.kbctx
|
||||
/server/.spool
|
||||
/server/.claude
|
||||
/.spool
|
||||
TUICallKit-Vue3/.env
|
||||
|
||||
Vendored
+1
-3
@@ -1,5 +1,3 @@
|
||||
{
|
||||
"kiroAgent.configureMCP": "Disabled"
|
||||
|
||||
|
||||
"kiroAgent.configureMCP": "Enabled"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
alias ll='ls -alhG'
|
||||
|
||||
# Starship prompt
|
||||
eval "$(starship init zsh)"
|
||||
|
||||
# zsh-autosuggestions 自动补全
|
||||
source /opt/homebrew/share/zsh-autosuggestions/zsh-autosuggestions.zsh
|
||||
|
||||
# NVM 懒加载 - 只在第一次使用相关命令时再初始化
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
_nvm_default_bin() {
|
||||
local default_alias version_dir
|
||||
[ -r "$NVM_DIR/alias/default" ] || return 1
|
||||
|
||||
default_alias="$(<"$NVM_DIR/alias/default")"
|
||||
default_alias="${default_alias#"${default_alias%%[![:space:]]*}"}"
|
||||
default_alias="${default_alias%"${default_alias##*[![:space:]]}"}"
|
||||
|
||||
case "$default_alias" in
|
||||
v[0-9]*)
|
||||
version_dir="$NVM_DIR/versions/node/$default_alias"
|
||||
;;
|
||||
[0-9]*)
|
||||
version_dir="$(ls -d "$NVM_DIR"/versions/node/v"$default_alias".* 2>/dev/null | tail -n 1)"
|
||||
;;
|
||||
node|stable|lts/*)
|
||||
version_dir="$(ls -d "$NVM_DIR"/versions/node/v* 2>/dev/null | sort -V | tail -n 1)"
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
[ -n "$version_dir" ] && [ -x "$version_dir/bin/node" ] || return 1
|
||||
printf '%s\n' "$version_dir/bin"
|
||||
}
|
||||
if _nvm_default_node_bin="$(_nvm_default_bin)"; then
|
||||
export PATH="$_nvm_default_node_bin:$PATH"
|
||||
fi
|
||||
unset _nvm_default_node_bin
|
||||
_lazy_load_nvm() {
|
||||
unset -f nvm node npm npx pnpm yarn 2>/dev/null
|
||||
[ -s "$NVM_DIR/nvm.sh" ] || return 1
|
||||
\. "$NVM_DIR/nvm.sh"
|
||||
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
|
||||
hash -r 2>/dev/null
|
||||
}
|
||||
nvm() { _lazy_load_nvm && nvm "$@"; }
|
||||
node() { _lazy_load_nvm && node "$@"; }
|
||||
npm() { _lazy_load_nvm && npm "$@"; }
|
||||
npx() { _lazy_load_nvm && npx "$@"; }
|
||||
pnpm() { _lazy_load_nvm && pnpm "$@"; }
|
||||
yarn() { _lazy_load_nvm && yarn "$@"; }
|
||||
export PATH="/Applications/EServer/bin:$PATH"
|
||||
function EC_start(){
|
||||
/Applications/EasyConnect.app/Contents/Resources/bin/EasyMonitor > /dev/null 2>&1 &
|
||||
/Applications/EasyConnect.app/Contents/MacOS/EasyConnect > /dev/null 2>&1 &
|
||||
open /Applications/EasyConnect.app
|
||||
}
|
||||
|
||||
function EC_kill(){
|
||||
pkill EasyMonitor
|
||||
pkill ECAgent
|
||||
pkill ECAgentProxy
|
||||
pkill EasyConnect
|
||||
}
|
||||
# The following lines have been added by Docker Desktop to enable Docker CLI completions.
|
||||
fpath=(/Users/long/.docker/completions $fpath)
|
||||
autoload -Uz compinit
|
||||
compinit
|
||||
# End of Docker CLI completions
|
||||
|
||||
|
||||
export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles/
|
||||
|
||||
# pyenv 懒加载 - 只在第一次使用 Python 相关命令时再初始化
|
||||
export PYENV_ROOT="$HOME/.pyenv"
|
||||
_lazy_load_pyenv() {
|
||||
unset -f pyenv python python3 pip pip3 2>/dev/null
|
||||
|
||||
local pyenv_bin
|
||||
pyenv_bin="$(command -v pyenv 2>/dev/null)"
|
||||
if [ -z "$pyenv_bin" ] && [ -x /opt/homebrew/bin/pyenv ]; then
|
||||
pyenv_bin=/opt/homebrew/bin/pyenv
|
||||
fi
|
||||
|
||||
[ -n "$pyenv_bin" ] || return 1
|
||||
|
||||
eval "$("$pyenv_bin" init --path)"
|
||||
eval "$("$pyenv_bin" init - zsh)"
|
||||
hash -r 2>/dev/null
|
||||
}
|
||||
pyenv() { _lazy_load_pyenv && pyenv "$@"; }
|
||||
python() { _lazy_load_pyenv && python "$@"; }
|
||||
python3() { _lazy_load_pyenv && python3 "$@"; }
|
||||
pip() { _lazy_load_pyenv && pip "$@"; }
|
||||
pip3() { _lazy_load_pyenv && pip3 "$@"; }
|
||||
|
||||
# 引入 bash 配置
|
||||
if [ -f ~/.bash_profile ]; then
|
||||
source ~/.bash_profile
|
||||
fi
|
||||
if [ -f ~/.bashrc ]; then
|
||||
source ~/.bashrc
|
||||
fi
|
||||
|
||||
# Added by Windsurf
|
||||
export PATH="/Users/long/.codeium/windsurf/bin:$PATH"
|
||||
|
||||
# Added by Antigravity
|
||||
export PATH="/Users/long/.antigravity/antigravity/bin:$PATH"
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
|
||||
# MindOS Desktop — CLI (mindos)
|
||||
export PATH="$HOME/.mindos/bin:$PATH"
|
||||
|
||||
source "$HOME/.cargo/env"
|
||||
|
||||
# Added by Antigravity
|
||||
export PATH="/Users/long/.antigravity/antigravity/bin:$PATH"
|
||||
@@ -0,0 +1,74 @@
|
||||
alias ll='ls -alhG'
|
||||
|
||||
# Starship prompt
|
||||
eval "$(starship init zsh)"
|
||||
|
||||
# zsh-autosuggestions 自动补全
|
||||
source /opt/homebrew/share/zsh-autosuggestions/zsh-autosuggestions.zsh
|
||||
|
||||
# NVM
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
if [ -s "$NVM_DIR/nvm.sh" ]; then
|
||||
\. "$NVM_DIR/nvm.sh"
|
||||
fi
|
||||
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
|
||||
export PATH="/Applications/EServer/bin:$PATH"
|
||||
function EC_start(){
|
||||
/Applications/EasyConnect.app/Contents/Resources/bin/EasyMonitor > /dev/null 2>&1 &
|
||||
/Applications/EasyConnect.app/Contents/MacOS/EasyConnect > /dev/null 2>&1 &
|
||||
open /Applications/EasyConnect.app
|
||||
}
|
||||
|
||||
function EC_kill(){
|
||||
pkill EasyMonitor
|
||||
pkill ECAgent
|
||||
pkill ECAgentProxy
|
||||
pkill EasyConnect
|
||||
}
|
||||
# The following lines have been added by Docker Desktop to enable Docker CLI completions.
|
||||
fpath=(/Users/long/.docker/completions $fpath)
|
||||
autoload -Uz compinit
|
||||
compinit
|
||||
# End of Docker CLI completions
|
||||
|
||||
|
||||
export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles/
|
||||
|
||||
# pyenv
|
||||
export PYENV_ROOT="$HOME/.pyenv"
|
||||
export PATH="/opt/homebrew/bin:$PYENV_ROOT/bin:$PATH"
|
||||
if command -v pyenv >/dev/null 2>&1; then
|
||||
eval "$(pyenv init --path)"
|
||||
eval "$(pyenv init - zsh)"
|
||||
fi
|
||||
|
||||
# 引入 bash 配置
|
||||
if [ -f ~/.bash_profile ]; then
|
||||
source ~/.bash_profile
|
||||
fi
|
||||
if [ -f ~/.bashrc ]; then
|
||||
source ~/.bashrc
|
||||
fi
|
||||
|
||||
# Added by Windsurf
|
||||
export PATH="/Users/long/.codeium/windsurf/bin:$PATH"
|
||||
|
||||
# Added by Antigravity
|
||||
export PATH="/Users/long/.antigravity/antigravity/bin:$PATH"
|
||||
|
||||
if command -v nvm >/dev/null 2>&1; then
|
||||
_nvm_default_version="$(nvm version default 2>/dev/null)"
|
||||
if [ -n "$_nvm_default_version" ] && [ -d "$NVM_DIR/versions/node/$_nvm_default_version/bin" ]; then
|
||||
export PATH="$NVM_DIR/versions/node/$_nvm_default_version/bin:$PATH"
|
||||
fi
|
||||
unset _nvm_default_version
|
||||
fi
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
|
||||
# MindOS Desktop — CLI (mindos)
|
||||
export PATH="$HOME/.mindos/bin:$PATH"
|
||||
|
||||
source "$HOME/.cargo/env"
|
||||
|
||||
# Added by Antigravity
|
||||
export PATH="/Users/long/.antigravity/antigravity/bin:$PATH"
|
||||
@@ -0,0 +1,57 @@
|
||||
<!-- TRELLIS:START -->
|
||||
# Trellis Instructions
|
||||
|
||||
These instructions are for AI assistants working in this project.
|
||||
|
||||
This project is managed by Trellis. The working knowledge you need lives under `.trellis/`:
|
||||
|
||||
- `.trellis/workflow.md` — development phases, when to create tasks, skill routing
|
||||
- `.trellis/spec/` — package- and layer-scoped coding guidelines (read before writing code in a given layer)
|
||||
- `.trellis/workspace/` — per-developer journals and session traces
|
||||
- `.trellis/tasks/` — active and archived tasks (PRDs, research, jsonl context)
|
||||
|
||||
If a Trellis command is available on your platform (e.g. `/trellis:finish-work`, `/trellis:continue`), prefer it over manual steps. Not every platform exposes every command.
|
||||
|
||||
If you're using Codex or another agent-capable tool, additional project-scoped helpers may live in:
|
||||
- `.agents/skills/` — reusable Trellis skills
|
||||
- `.codex/agents/` — optional custom subagents
|
||||
|
||||
## Subagents
|
||||
|
||||
- ALWAYS wait for every spawned subagent to reach a terminal status before yielding, acting on partial results, or spawning followups.
|
||||
- On Codex, this means calling the `wait` tool with the subagent's thread id (requires `multi_agent_v2`). Do NOT infer completion from elapsed time.
|
||||
- On Claude Code / OpenCode, this means awaiting the Task/agent tool result before continuing.
|
||||
- NEVER cancel or re-spawn a subagent that hasn't finished. If a subagent appears stuck, raise the wait timeout (Codex default 30s, max 1h) before judging it broken.
|
||||
- Spawn subagents automatically when:
|
||||
- Parallelizable work (e.g., install + verify, npm test + typecheck, multiple tasks from plan)
|
||||
- Long-running or blocking tasks where a worker can run independently
|
||||
- Isolation for risky changes or checks
|
||||
|
||||
### Codex-only — `spawn_agent` parameters
|
||||
|
||||
When calling `spawn_agent`, ALWAYS pass `fork_turns="none"`. Without it the child inherits the parent transcript and sees your prior `spawn_agent(...)` records, then applies the "wait for spawned subagents" rule to itself — causing `wait_agent` self-deadlock.
|
||||
|
||||
```text
|
||||
spawn_agent(agent_type="trellis-implement", message="...", fork_turns="none")
|
||||
```
|
||||
|
||||
### Codex-only — multi-subagent close-loop
|
||||
|
||||
When `wait` returns a `completed` notification, treat it as an event signal — not as "all done". Run this loop:
|
||||
|
||||
1. Maintain an `expected_agents` set of dispatched sub-agent thread IDs.
|
||||
2. After each `wait` update:
|
||||
1. Call `list_agents` to inspect ALL live agents' status.
|
||||
2. For each agent now in a terminal state:
|
||||
- Verify its promised deliverable exists (e.g. `{task_dir}/research/*.md`).
|
||||
- Read or summarize as needed.
|
||||
- `close_agent` to release the slot.
|
||||
- Remove from `expected_agents`.
|
||||
3. If `expected_agents` still contains running agents → keep waiting.
|
||||
4. If `expected_agents` is empty → continue main flow.
|
||||
3. Never `wait` on an agent that has already reported `completed`.
|
||||
4. If a `completed` agent is missing its deliverable, treat it as failed — surface that in your report instead of re-waiting.
|
||||
|
||||
Managed by Trellis. Edits outside this block are preserved; edits inside may be overwritten by a future `trellis update`.
|
||||
|
||||
<!-- TRELLIS:END -->
|
||||
@@ -1,208 +0,0 @@
|
||||
# 预约列表按钮显示优化
|
||||
|
||||
## 问题描述
|
||||
|
||||
在预约列表中,同样的情况下,有的预约显示"开方"按钮,有的不显示。
|
||||
|
||||
## 问题原因
|
||||
|
||||
操作列的按钮过多,导致"开方"按钮被挤出可视区域或被其他按钮遮挡。
|
||||
|
||||
原有按钮(从左到右):
|
||||
1. 编辑患者
|
||||
2. 视频二维码
|
||||
3. 通话
|
||||
4. 完成
|
||||
5. 开方/查看
|
||||
6. 更多
|
||||
|
||||
当所有按钮都显示时,操作列宽度为 380px,可能不够容纳所有按钮。
|
||||
|
||||
## 解决方案
|
||||
|
||||
根据预约状态优化按钮显示:
|
||||
|
||||
### 已完成状态(status = 3)
|
||||
|
||||
只显示核心按钮:
|
||||
- 编辑患者
|
||||
- 开方/查看
|
||||
- 更多
|
||||
|
||||
隐藏按钮:
|
||||
- 视频二维码(已完成,不需要视频)
|
||||
- 通话(已完成,不需要通话)
|
||||
- 完成(已经完成)
|
||||
|
||||
### 其他状态(待接诊、已过号、已取消)
|
||||
|
||||
显示所有按钮:
|
||||
- 编辑患者
|
||||
- 视频二维码
|
||||
- 通话
|
||||
- 完成
|
||||
- 开方/查看
|
||||
- 更多
|
||||
|
||||
## 修改内容
|
||||
|
||||
**文件**: `admin/src/views/tcm/appointment/list.vue`
|
||||
|
||||
### 修改前
|
||||
|
||||
```vue
|
||||
<el-table-column label="操作" width="380" fixed="right" align="left">
|
||||
<template #default="{ row }">
|
||||
<div class="action-btns">
|
||||
<el-button>编辑患者</el-button>
|
||||
<el-button>视频二维码</el-button>
|
||||
<el-button>通话</el-button>
|
||||
<el-button>完成</el-button>
|
||||
<el-button>{{ prescriptionActionLabel(row) }}</el-button>
|
||||
<el-dropdown>更多</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
```
|
||||
|
||||
### 修改后
|
||||
|
||||
```vue
|
||||
<el-table-column label="操作" width="380" fixed="right" align="left">
|
||||
<template #default="{ row }">
|
||||
<div class="action-btns">
|
||||
<el-button>编辑患者</el-button>
|
||||
|
||||
<!-- 已完成状态不显示这些按钮 -->
|
||||
<template v-if="row.status !== 3">
|
||||
<el-button>视频二维码</el-button>
|
||||
<el-button>通话</el-button>
|
||||
<el-button>完成</el-button>
|
||||
</template>
|
||||
|
||||
<!-- 开方/查看按钮始终显示 -->
|
||||
<el-button>{{ prescriptionActionLabel(row) }}</el-button>
|
||||
|
||||
<el-dropdown>更多</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
```
|
||||
|
||||
## 按钮显示规则
|
||||
|
||||
### 预约状态说明
|
||||
|
||||
| 状态值 | 状态名称 | 说明 |
|
||||
|-------|---------|------|
|
||||
| 1 | 待接诊 | 患者已挂号,等待医生接诊 |
|
||||
| 2 | 已取消 | 挂号已取消 |
|
||||
| 3 | 已完成 | 医生已完成接诊 |
|
||||
| 4 | 已过号 | 患者未按时就诊,已过号 |
|
||||
|
||||
### 按钮显示矩阵
|
||||
|
||||
| 按钮 | 待接诊(1) | 已取消(2) | 已完成(3) | 已过号(4) |
|
||||
|-----|----------|----------|----------|----------|
|
||||
| 编辑患者 | ✅ | ✅ | ✅ | ✅ |
|
||||
| 视频二维码 | ✅ | ✅ | ❌ | ✅ |
|
||||
| 通话 | ✅ | ✅ | ❌ | ✅ |
|
||||
| 完成 | ✅ | ✅ | ❌ | ✅ |
|
||||
| 开方/查看 | ✅ | ✅ | ✅ | ✅ |
|
||||
| 更多 | ✅ | ✅ | ✅ | ✅ |
|
||||
|
||||
## 优势
|
||||
|
||||
### 1. 确保核心按钮始终可见
|
||||
|
||||
- "开方/查看"按钮是核心功能,必须始终显示
|
||||
- 已完成状态下,减少不必要的按钮,确保核心按钮可见
|
||||
|
||||
### 2. 优化用户体验
|
||||
|
||||
- 已完成的预约不需要"视频二维码"、"通话"、"完成"按钮
|
||||
- 减少按钮数量,界面更简洁
|
||||
|
||||
### 3. 避免按钮被遮挡
|
||||
|
||||
- 减少按钮数量后,操作列宽度足够容纳所有按钮
|
||||
- 避免按钮被挤出可视区域
|
||||
|
||||
## 测试建议
|
||||
|
||||
### 测试用例 1: 待接诊状态
|
||||
|
||||
1. 找到一个待接诊的预约(status = 1)
|
||||
2. 检查操作列是否显示所有按钮:
|
||||
- ✅ 编辑患者
|
||||
- ✅ 视频二维码
|
||||
- ✅ 通话
|
||||
- ✅ 完成
|
||||
- ✅ 开方/查看
|
||||
- ✅ 更多
|
||||
|
||||
### 测试用例 2: 已完成状态
|
||||
|
||||
1. 找到一个已完成的预约(status = 3)
|
||||
2. 检查操作列是否只显示核心按钮:
|
||||
- ✅ 编辑患者
|
||||
- ❌ 视频二维码(不显示)
|
||||
- ❌ 通话(不显示)
|
||||
- ❌ 完成(不显示)
|
||||
- ✅ 开方/查看
|
||||
- ✅ 更多
|
||||
|
||||
### 测试用例 3: 已完成且已开方
|
||||
|
||||
1. 找到一个已完成且已开方的预约
|
||||
2. 检查"开方/查看"按钮是否显示为"查看"
|
||||
3. 点击"查看"按钮,应该可以查看处方(只读模式)
|
||||
|
||||
### 测试用例 4: 已完成但未开方
|
||||
|
||||
1. 找到一个已完成但未开方的预约
|
||||
2. 检查"开方/查看"按钮是否显示为"开方"
|
||||
3. 点击"开方"按钮,应该可以创建新处方
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 1. 权限检查
|
||||
|
||||
所有按钮都有权限检查,如果用户没有相应权限,按钮会被隐藏:
|
||||
- `tcm.diagnosis/edit` - 编辑患者
|
||||
- `tcm.diagnosis/videoQr` - 视频二维码
|
||||
- `doctor.appointment/prescription` - 通话
|
||||
- `doctor.appointment/complete` - 完成
|
||||
- `tcm.diagnosis/kaifang` - 开方/查看
|
||||
|
||||
### 2. 按钮文字动态变化
|
||||
|
||||
"开方/查看"按钮的文字根据处方状态动态变化:
|
||||
- 未开方或待审核:显示"开方"
|
||||
- 已审核通过且未作废:显示"查看"
|
||||
- 已驳回:显示"开方"
|
||||
|
||||
### 3. 操作列宽度
|
||||
|
||||
操作列宽度为 380px,足够容纳:
|
||||
- 已完成状态:3个按钮(编辑患者、开方/查看、更多)
|
||||
- 其他状态:6个按钮(编辑患者、视频二维码、通话、完成、开方/查看、更多)
|
||||
|
||||
如果按钮仍然被遮挡,可以考虑:
|
||||
- 增加操作列宽度(如 420px)
|
||||
- 将更多按钮移到下拉菜单中
|
||||
- 使用图标按钮减少宽度
|
||||
|
||||
## 相关文档
|
||||
|
||||
- `PRESCRIPTION_BUTTON_DISPLAY_CHECK.md` - 处方按钮显示检查文档
|
||||
- `PRESCRIPTION_APPROVED_LOCK.md` - 处方审核通过后锁定编辑文档
|
||||
|
||||
## 总结
|
||||
|
||||
通过根据预约状态优化按钮显示,确保了:
|
||||
- ✅ "开方/查看"按钮始终可见
|
||||
- ✅ 已完成状态下界面更简洁
|
||||
- ✅ 避免按钮被挤出可视区域
|
||||
- ✅ 优化用户体验
|
||||
- ✅ 保持核心功能的可访问性
|
||||
@@ -1,251 +0,0 @@
|
||||
# 预约列表查询性能优化 - 索引方案
|
||||
|
||||
## 问题分析
|
||||
|
||||
预约列表查询涉及多表关联和复杂条件筛选,主要性能瓶颈:
|
||||
|
||||
1. 多表 LEFT JOIN(预约表、诊断表、管理员表)
|
||||
2. 多条件筛选(状态、日期范围、患者姓名、医生姓名)
|
||||
3. 权限过滤(医生角色、医助角色)
|
||||
4. 复杂排序(状态 + 日期 + 时间)
|
||||
5. 子查询(确认诊单、开方状态)
|
||||
|
||||
## 索引优化方案
|
||||
|
||||
### 1. 预约表 (zyt_appointment)
|
||||
|
||||
#### 单列索引
|
||||
```sql
|
||||
-- 医生ID索引(医生角色查询)
|
||||
ALTER TABLE `zyt_appointment` ADD INDEX `idx_doctor_id` (`doctor_id`);
|
||||
|
||||
-- 患者ID索引(关联查询)
|
||||
ALTER TABLE `zyt_appointment` ADD INDEX `idx_patient_id` (`patient_id`);
|
||||
|
||||
-- 状态索引(状态筛选)
|
||||
ALTER TABLE `zyt_appointment` ADD INDEX `idx_status` (`status`);
|
||||
|
||||
-- 预约日期索引(日期范围查询)
|
||||
ALTER TABLE `zyt_appointment` ADD INDEX `idx_appointment_date` (`appointment_date`);
|
||||
```
|
||||
|
||||
#### 组合索引(重点优化)
|
||||
```sql
|
||||
-- 状态+日期+时间(用于排序,覆盖 ORDER BY)
|
||||
ALTER TABLE `zyt_appointment` ADD INDEX `idx_status_date_time` (`status`, `appointment_date`, `appointment_time`);
|
||||
|
||||
-- 医生+状态+日期(医生角色常用查询)
|
||||
ALTER TABLE `zyt_appointment` ADD INDEX `idx_doctor_status_date` (`doctor_id`, `status`, `appointment_date`);
|
||||
```
|
||||
|
||||
**优化效果**:
|
||||
- `idx_status_date_time` 可以直接用于排序,避免 filesort
|
||||
- `idx_doctor_status_date` 可以快速过滤医生的预约记录
|
||||
|
||||
### 2. 诊断表 (zyt_tcm_diagnosis)
|
||||
|
||||
```sql
|
||||
-- 患者姓名索引(模糊搜索,前缀索引)
|
||||
ALTER TABLE `zyt_tcm_diagnosis` ADD INDEX `idx_patient_name` (`patient_name`(20));
|
||||
|
||||
-- 医助ID索引(医助角色查询)
|
||||
ALTER TABLE `zyt_tcm_diagnosis` ADD INDEX `idx_assistant_id` (`assistant_id`);
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- `patient_name` 使用前缀索引(20),节省空间且支持 LIKE 'xxx%' 查询
|
||||
- 不支持 LIKE '%xxx%' 的优化,但可以减少扫描行数
|
||||
|
||||
### 3. 管理员表 (zyt_admin)
|
||||
|
||||
```sql
|
||||
-- 管理员姓名索引(医生姓名搜索)
|
||||
ALTER TABLE `zyt_admin` ADD INDEX `idx_name` (`name`(20));
|
||||
```
|
||||
|
||||
### 4. 诊断查看记录表 (zyt_diagnosis_view_records)
|
||||
|
||||
```sql
|
||||
-- 诊断ID+确认状态+删除时间(用于确认诊单查询)
|
||||
ALTER TABLE `zyt_diagnosis_view_records` ADD INDEX `idx_diagnosis_confirmed` (`diagnosis_id`, `is_confirmed`, `delete_time`);
|
||||
```
|
||||
|
||||
**优化效果**:
|
||||
- 加速 `WHERE diagnosis_id IN (...) AND is_confirmed = 1 AND delete_time IS NULL` 查询
|
||||
- 覆盖索引,无需回表
|
||||
|
||||
### 5. 处方表 (zyt_prescription)
|
||||
|
||||
```sql
|
||||
-- 诊断ID+删除时间(用于开方状态查询)
|
||||
ALTER TABLE `zyt_prescription` ADD INDEX `idx_diagnosis_delete` (`diagnosis_id`, `delete_time`);
|
||||
```
|
||||
|
||||
**优化效果**:
|
||||
- 加速 `WHERE diagnosis_id IN (...) AND delete_time IS NULL` 查询
|
||||
|
||||
### 6. 管理员角色表 (zyt_admin_role)
|
||||
|
||||
```sql
|
||||
-- 管理员ID+角色ID(用于权限判断)
|
||||
ALTER TABLE `zyt_admin_role` ADD INDEX `idx_admin_role` (`admin_id`, `role_id`);
|
||||
```
|
||||
|
||||
**优化效果**:
|
||||
- 加速角色查询,快速判断用户权限
|
||||
|
||||
## 执行步骤
|
||||
|
||||
### 方式一:直接执行(推荐)
|
||||
|
||||
```bash
|
||||
# 连接数据库
|
||||
mysql -h 39.97.232.35 -u cs_zyt -p cs_zyt
|
||||
|
||||
# 执行索引创建脚本
|
||||
source optimize_appointment_indexes.sql
|
||||
```
|
||||
|
||||
### 方式二:安全执行(检查后添加)
|
||||
|
||||
```bash
|
||||
# 执行安全脚本(会先检查索引是否存在)
|
||||
source check_and_add_indexes.sql
|
||||
```
|
||||
|
||||
### 方式三:phpMyAdmin
|
||||
|
||||
1. 登录 phpMyAdmin
|
||||
2. 选择数据库 `cs_zyt`
|
||||
3. 点击 SQL 标签
|
||||
4. 复制 `optimize_appointment_indexes.sql` 内容
|
||||
5. 点击执行
|
||||
|
||||
## 预期效果
|
||||
|
||||
### 优化前
|
||||
- 查询时间:500ms - 2000ms
|
||||
- 扫描行数:全表扫描
|
||||
- 使用索引:PRIMARY 或无
|
||||
|
||||
### 优化后
|
||||
- 查询时间:50ms - 200ms(提升 5-10 倍)
|
||||
- 扫描行数:索引范围扫描
|
||||
- 使用索引:组合索引
|
||||
|
||||
## 验证方法
|
||||
|
||||
### 1. 查看执行计划
|
||||
|
||||
```sql
|
||||
EXPLAIN SELECT
|
||||
a.*,
|
||||
u.patient_name,
|
||||
u.phone as patient_phone,
|
||||
ad.name as doctor_name
|
||||
FROM zyt_appointment a
|
||||
LEFT JOIN zyt_tcm_diagnosis u ON a.patient_id = u.id
|
||||
LEFT JOIN zyt_admin ad ON a.doctor_id = ad.id
|
||||
WHERE a.doctor_id = 1
|
||||
AND a.status = 1
|
||||
AND a.appointment_date >= '2024-03-01'
|
||||
ORDER BY a.status ASC, a.appointment_date ASC, a.appointment_time ASC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
**期望结果**:
|
||||
- `type`: ref 或 range(不是 ALL)
|
||||
- `key`: idx_doctor_status_date 或 idx_status_date_time
|
||||
- `rows`: 较小的数字(不是全表行数)
|
||||
|
||||
### 2. 查看索引使用情况
|
||||
|
||||
```sql
|
||||
-- 查看索引统计
|
||||
SHOW INDEX FROM zyt_appointment;
|
||||
|
||||
-- 查看索引使用情况(需要开启慢查询日志)
|
||||
SELECT * FROM sys.schema_unused_indexes WHERE object_schema = 'cs_zyt';
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **索引维护成本**:索引会增加 INSERT/UPDATE/DELETE 的开销,但对于查询为主的场景,收益远大于成本
|
||||
|
||||
2. **索引选择性**:
|
||||
- 高选择性字段(如 ID、日期)适合建索引
|
||||
- 低选择性字段(如性别、状态)单独建索引效果不明显,但可以作为组合索引的一部分
|
||||
|
||||
3. **前缀索引**:
|
||||
- 对于长字符串字段(如姓名),使用前缀索引可以节省空间
|
||||
- 前缀长度选择:能区分大部分数据即可(通常 10-20 个字符)
|
||||
|
||||
4. **组合索引顺序**:
|
||||
- 遵循"最左前缀"原则
|
||||
- 将选择性高的字段放在前面
|
||||
- 考虑查询条件的组合频率
|
||||
|
||||
5. **定期维护**:
|
||||
```sql
|
||||
-- 优化表(重建索引)
|
||||
OPTIMIZE TABLE zyt_appointment;
|
||||
OPTIMIZE TABLE zyt_tcm_diagnosis;
|
||||
```
|
||||
|
||||
## 进一步优化建议
|
||||
|
||||
### 1. 查询优化
|
||||
|
||||
```php
|
||||
// 避免 SELECT *,只查询需要的字段
|
||||
$query->field('a.id, a.patient_id, a.doctor_id, a.status, ...');
|
||||
|
||||
// 使用 whereIn 代替多个 OR
|
||||
$query->whereIn('a.status', [1, 3]);
|
||||
```
|
||||
|
||||
### 2. 分页优化
|
||||
|
||||
```php
|
||||
// 使用延迟关联优化深分页
|
||||
$subQuery = Appointment::where($conditions)
|
||||
->field('id')
|
||||
->limit($offset, $limit)
|
||||
->buildSql();
|
||||
|
||||
$list = Appointment::alias('a')
|
||||
->join([$subQuery => 'sub'], 'a.id = sub.id')
|
||||
->with(['patient', 'doctor'])
|
||||
->select();
|
||||
```
|
||||
|
||||
### 3. 缓存优化
|
||||
|
||||
```php
|
||||
// 缓存医生列表、角色信息等不常变化的数据
|
||||
$roleIds = Cache::remember('admin_role_' . $adminId, function() {
|
||||
return AdminRole::where('admin_id', $adminId)->column('role_id');
|
||||
}, 3600);
|
||||
```
|
||||
|
||||
## 监控指标
|
||||
|
||||
定期检查以下指标:
|
||||
|
||||
1. 慢查询日志(> 1s 的查询)
|
||||
2. 索引使用率
|
||||
3. 表大小和索引大小
|
||||
4. 查询响应时间
|
||||
|
||||
```sql
|
||||
-- 查看表和索引大小
|
||||
SELECT
|
||||
table_name,
|
||||
ROUND(data_length / 1024 / 1024, 2) AS data_mb,
|
||||
ROUND(index_length / 1024 / 1024, 2) AS index_mb,
|
||||
ROUND((data_length + index_length) / 1024 / 1024, 2) AS total_mb
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'cs_zyt'
|
||||
AND table_name IN ('zyt_appointment', 'zyt_tcm_diagnosis', 'zyt_admin')
|
||||
ORDER BY (data_length + index_length) DESC;
|
||||
```
|
||||
@@ -1,176 +0,0 @@
|
||||
# Bug Fixes Summary
|
||||
|
||||
## 修复的问题
|
||||
|
||||
### 1. 处方查看权限问题 ✅ (已解决)
|
||||
|
||||
**问题描述**:
|
||||
- 医生A开了处方后,医生B点击"查看病历"时提示"无权限查看此处方"
|
||||
- 其他医生无法查看患者的历史处方记录
|
||||
|
||||
**根本原因**:
|
||||
- 权限检查逻辑已经正确实现,允许有 `tcm.diagnosis/kaifang` 权限的医生查看所有处方(只读模式)
|
||||
- 问题可能是前端缓存或权限配置问题
|
||||
|
||||
**解决方案**:
|
||||
- 确认 `PrescriptionLogic::canViewPrescription()` 方法已包含以下逻辑:
|
||||
```php
|
||||
// 有开方权限的医生可以查看所有处方(只读)
|
||||
$permissions = $adminInfo['permissions'] ?? [];
|
||||
if (in_array('tcm.diagnosis/kaifang', $permissions, true)) {
|
||||
return true;
|
||||
}
|
||||
```
|
||||
- 权限层级(从高到低):
|
||||
1. 超级管理员
|
||||
2. 创建人
|
||||
3. 医助
|
||||
4. 共享处方
|
||||
5. 可见角色
|
||||
6. 有开方权限的医生(`tcm.diagnosis/kaifang`)
|
||||
|
||||
**文件**: `server/app/adminapi/logic/tcm/PrescriptionLogic.php`
|
||||
|
||||
---
|
||||
|
||||
### 2. 处方组件重复请求问题 ✅ (已解决)
|
||||
|
||||
**问题描述**:
|
||||
- 点击"开处方"或"查看病历"按钮时,重复请求 `/tcm.prescription/detail` 接口
|
||||
- 快速连续点击导致多次调用
|
||||
|
||||
**根本原因**:
|
||||
- 已经实现了 `isOpening` 标志防止重复调用
|
||||
- 防重复逻辑已经正确工作
|
||||
|
||||
**解决方案**:
|
||||
- 确认 `open()` 和 `openById()` 方法已包含防重复逻辑:
|
||||
```typescript
|
||||
const isOpening = ref(false)
|
||||
|
||||
const open = async (data: any, options?: { templateId?: number; stationName?: string }) => {
|
||||
if (isOpening.value) {
|
||||
console.warn('处方正在打开中,请勿重复点击')
|
||||
return
|
||||
}
|
||||
isOpening.value = true
|
||||
try {
|
||||
// ... 处理逻辑
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
isOpening.value = false
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**文件**: `admin/src/components/tcm-prescription/index.vue`
|
||||
|
||||
---
|
||||
|
||||
### 3. 订单撤回后处方审核状态重置 ✅ (已修复)
|
||||
|
||||
**问题描述**:
|
||||
- 撤回订单后,关联的处方审核状态没有重置为"待审核"
|
||||
- 缺少 `audit_by_name` 字段的清空
|
||||
|
||||
**解决方案**:
|
||||
- 在 `withdraw()` 方法中添加了 `audit_by_name` 字段的清空:
|
||||
```php
|
||||
Prescription::where('id', $prescriptionId)
|
||||
->whereNull('delete_time')
|
||||
->update([
|
||||
'audit_status' => 0, // 0=待审核
|
||||
'audit_time' => null,
|
||||
'audit_by' => null,
|
||||
'audit_by_name' => '', // 新增:清空审核人姓名
|
||||
'audit_remark' => '',
|
||||
'update_time' => time(),
|
||||
]);
|
||||
```
|
||||
|
||||
**文件**: `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
|
||||
|
||||
---
|
||||
|
||||
### 4. 物流轨迹自动加载 ✅ (已实现)
|
||||
|
||||
**问题描述**:
|
||||
- 用户希望打开订单详情时,如果有快递单号,自动加载物流轨迹
|
||||
- 不需要手动点击"查询轨迹"按钮
|
||||
|
||||
**解决方案**:
|
||||
- 已经实现自动加载逻辑:
|
||||
```typescript
|
||||
async function openDetail(id: number) {
|
||||
// ... 加载订单详情
|
||||
if (d) {
|
||||
detailLogisticsExpress.value = String(d.express_company || 'auto') || 'auto'
|
||||
if (String(d.tracking_number || '').trim()) {
|
||||
fetchLogisticsTrace() // 自动加载物流轨迹
|
||||
}
|
||||
fetchLogs(id)
|
||||
}
|
||||
}
|
||||
```
|
||||
- 物流查询优先从数据库读取,如果数据库没有数据,再调用快递100 API
|
||||
- 按钮文字改为"刷新轨迹",用于手动刷新最新物流信息
|
||||
|
||||
**文件**: `admin/src/views/consumer/prescription/order_list.vue`
|
||||
|
||||
---
|
||||
|
||||
## 测试建议
|
||||
|
||||
### 1. 处方查看权限测试
|
||||
1. 使用医生A账号创建处方
|
||||
2. 使用医生B账号(有 `tcm.diagnosis/kaifang` 权限)点击"查看病历"
|
||||
3. 确认可以正常查看处方详情(只读模式)
|
||||
4. 确认医生B不能编辑医生A创建的处方
|
||||
|
||||
### 2. 重复请求测试
|
||||
1. 快速连续点击"开处方"按钮多次
|
||||
2. 检查浏览器开发者工具的Network面板
|
||||
3. 确认只发送一次 `/tcm.prescription/detail` 请求
|
||||
4. 确认控制台显示"处方正在打开中,请勿重复点击"警告
|
||||
|
||||
### 3. 订单撤回测试
|
||||
1. 创建一个处方业务订单(处方审核状态为"已通过")
|
||||
2. 点击"撤回"按钮
|
||||
3. 确认订单状态变为"已取消"
|
||||
4. 在处方列表中确认该处方的审核状态变为"待审核"
|
||||
5. 确认审核人、审核时间、审核意见都已清空
|
||||
|
||||
### 4. 物流轨迹自动加载测试
|
||||
1. 创建一个订单并填写快递单号
|
||||
2. 点击"查看"按钮打开订单详情
|
||||
3. 确认物流轨迹自动加载(如果数据库有数据,显示数据来源为"database")
|
||||
4. 点击"刷新轨迹"按钮,确认可以手动刷新物流信息
|
||||
5. 如果数据库没有数据,确认调用快递100 API(显示数据来源为"api")
|
||||
|
||||
---
|
||||
|
||||
## 相关文档
|
||||
|
||||
- `PRESCRIPTION_VIEW_PERMISSION_FIX.md` - 处方查看权限修复文档
|
||||
- `PRESCRIPTION_DUPLICATE_REQUEST_FIX.md` - 处方重复请求修复文档
|
||||
- `PRESCRIPTION_ORDER_WITHDRAW_RESET_AUDIT.md` - 订单撤回重置审核状态文档
|
||||
- `EXPRESS_TRACKING_AUTO_LOAD.md` - 物流轨迹自动加载文档
|
||||
- `EXPRESS_TRACKING_DATABASE_QUERY.md` - 物流追踪数据库查询优化文档
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
所有问题都已经解决或确认正常工作:
|
||||
|
||||
1. ✅ 处方查看权限:已实现,有开方权限的医生可以查看所有处方
|
||||
2. ✅ 重复请求防护:已实现,使用 `isOpening` 标志防止重复调用
|
||||
3. ✅ 撤回重置审核:已修复,添加了 `audit_by_name` 字段的清空
|
||||
4. ✅ 物流自动加载:已实现,打开详情时自动加载物流轨迹
|
||||
|
||||
如果仍然遇到问题,请检查:
|
||||
- 用户权限配置是否正确
|
||||
- 浏览器缓存是否需要清除
|
||||
- 数据库表结构是否完整
|
||||
- 快递100账号是否已充值
|
||||
@@ -0,0 +1,137 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
Multi-client SaaS platform (基于 likeadmin 脚手架改造) centered on 中医在线问诊 — video consultations (TRTC), prescriptions, medicine library, 企微 (QYWX) integration, and payments. Single repo contains a PHP backend plus four independent frontend clients.
|
||||
|
||||
## Top-Level Layout
|
||||
|
||||
| Dir | Stack | Purpose |
|
||||
|-----|-------|---------|
|
||||
| `server/` | ThinkPHP 8 + PHP 8.0+ | REST API, the single source of truth |
|
||||
| `admin/` | Vue 3 + Vite + Element Plus + Tailwind | 管理后台 (desktop) |
|
||||
| `pc/` | Nuxt 3 + Element Plus | 公开 PC 站 (SSR/SSG) |
|
||||
| `uniapp/` | uni-app (Vue 3, Vite) | H5 / 小程序 / App 多端客户端 |
|
||||
| `wx/` | uni-app 微信小程序 | 独立 TRTC 微信小程序壳 |
|
||||
| `TUICallKit*/` | Tencent TRTC UIKit | Vendor SDK source used by clients |
|
||||
| `docker/` | docker-compose (nginx + php-fpm + mysql) | Local dev stack |
|
||||
| `doc/`, `ps/` | Supporting docs / psd | Design & reference material |
|
||||
|
||||
Each frontend is deployed to `server/public/<client>/` (see `server/route/app.php` fallback routes). Don’t rely on a root-level workspace — each client has its own `package.json` and must be built separately.
|
||||
|
||||
## Common Commands
|
||||
|
||||
### Backend (`server/`)
|
||||
```
|
||||
composer install # install deps
|
||||
php think run --host 0.0.0.0 --port 8080 # dev server (if PHP CLI)
|
||||
php think <command> # run CLI commands (see app/command/)
|
||||
php think crontab # trigger cron tasks manually
|
||||
```
|
||||
- Docker alternative: `cd docker && docker-compose up -d` (exposes nginx on :80, mysql on :3306).
|
||||
- SQL migrations live in `server/sql/<version>/*.sql` (versioned by date). Apply manually in order; there is no automatic migration runner. Current version directory: `server/sql/1.9.20260420/`.
|
||||
|
||||
### Admin (`admin/`)
|
||||
```
|
||||
npm install
|
||||
npm run dev # Vite dev server
|
||||
npm run build # production build + node scripts/release.mjs
|
||||
npm run type-check # vue-tsc --noEmit
|
||||
npm run lint # ESLint with --fix
|
||||
npm run clean # node scripts/clean.mjs
|
||||
```
|
||||
|
||||
### PC (`pc/`)
|
||||
```
|
||||
npm install
|
||||
npm run dev # Nuxt dev server on :3000
|
||||
npm run build # nuxt generate + scripts/build.mjs (static export)
|
||||
npm run build:ssr # Nuxt SSR build
|
||||
```
|
||||
|
||||
### uniapp (`uniapp/`)
|
||||
```
|
||||
npm install
|
||||
npm run dev:h5 # H5
|
||||
npm run dev:mp-weixin # 微信小程序
|
||||
npm run dev:app # App
|
||||
npm run build:h5 # build + scripts/release.mjs
|
||||
# other targets: mp-alipay, mp-baidu, mp-qq, mp-toutiao, quickapp-webview...
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Running a single test
|
||||
There is no unit-test harness checked in (no Jest/Vitest/PHPUnit config). When adding tests, wire the framework first; don’t assume one exists.
|
||||
|
||||
## Backend Architecture (likeadmin conventions)
|
||||
|
||||
`server/app/` is a ThinkPHP multi-app project. Three apps with a shared `common/` layer:
|
||||
|
||||
- `adminapi/` — admin panel API (consumed by `admin/`)
|
||||
- `api/` — public/end-user API (consumed by `pc/`, `uniapp/`, `wx/`)
|
||||
- `index/` — default app fallback
|
||||
- `common/` — shared models, services, enums, exceptions, listeners
|
||||
|
||||
Each app follows the layered pattern:
|
||||
|
||||
```
|
||||
<app>/
|
||||
├── controller/ # HTTP entry; extends BaseAdminController / BaseApiController
|
||||
├── logic/ # Business logic; static methods invoked by controllers
|
||||
├── lists/ # List-query objects (pagination/filter/export encapsulation)
|
||||
├── validate/ # Request validation rules
|
||||
├── service/ # App-scoped services
|
||||
├── http/ # Middleware, route groups
|
||||
└── config/ # App-specific config
|
||||
```
|
||||
|
||||
Key conventions:
|
||||
- **Controllers stay thin.** They pull `$this->request->get()/post()`, delegate to a `Logic` class, and wrap the result with `$this->data(...)` / `$this->success(...)` / `$this->fail(...)` from `BaseLikeAdminController` (in `common/controller`).
|
||||
- **Logic returns plain arrays.** Throw `\app\common\exception\*` for failures — the global exception handler (`app/ExceptionHandle.php`) converts them to JSON envelopes.
|
||||
- **Models extend `app\common\model\BaseModel`** which auto-completes image paths via `FileService`.
|
||||
- **Route registration is menu-driven.** Admin routes are auto-resolved from menu entries (see `add_conversion_stats_menu.sql` as an example of wiring a new admin page). Public routes are declared in `app/api/route/` and `server/route/app.php`.
|
||||
- **Payment / third-party callbacks** bypass auth — declared explicitly in `server/route/app.php` (e.g. wechat-notify, alipay-notify, TRTC recording-notify).
|
||||
|
||||
## Admin Architecture (`admin/src/`)
|
||||
|
||||
```
|
||||
src/
|
||||
├── api/ # One file per domain (stats.ts, order.ts, doctor.ts...)
|
||||
├── views/ # Page components grouped by domain (mirrors api/)
|
||||
├── router/
|
||||
│ ├── index.ts # Dynamic route generation from backend menu
|
||||
│ └── routes.ts # Static/constant routes + LAYOUT wrapper
|
||||
├── stores/modules/ # Pinia stores (user, app, tagsView...)
|
||||
├── components/ # Shared components
|
||||
├── utils/request/ # Axios instance + interceptors
|
||||
├── hooks/ # Composables
|
||||
├── install/ # Global plugin registration (app.use(install))
|
||||
└── permission.ts # Login/permission guard mounted on router
|
||||
```
|
||||
|
||||
- **Views are dynamically loaded** via `import.meta.glob('/src/views/**/*.vue')` in `router/index.ts`. A view path from the backend menu (e.g. `stats/conversion/index`) resolves to `src/views/stats/conversion/index.vue`. When adding a page, the corresponding menu record must be inserted via SQL (pattern: `server/sql/.../add_*_menu.sql`).
|
||||
- **API calls go through `utils/request`** which normalizes the response envelope and handles auth/error toasts. Don’t use axios directly.
|
||||
- **Auto-imports**: Element Plus components, `ref`/`reactive`/router composables are auto-imported via `unplugin-auto-import` — no need to import them explicitly (see `auto-imports.d.ts`).
|
||||
|
||||
## Cross-Cutting Concerns
|
||||
|
||||
- **Versioned SQL migrations.** New schema/menu changes go in `server/sql/<version>/*.sql`. When adding an admin view, pair it with an `add_*_menu.sql` file (see current WIP: `add_conversion_stats_menu.sql` + `admin/src/views/stats/conversion/`).
|
||||
- **TRTC integration.** Video-call logic is split between `TUICallKit/` (vendor), `admin/src/utils/call-*.ts` and `src/utils/im-*.ts`, and the `api/controller/TrtcController`. Cloud recording is triggered server-side and written back via the `/api/trtc/recording-notify` callback.
|
||||
- **企微 (QYWX).** `server/app/adminapi/controller/qywx/` + `admin/src/views/qywx/` + `pc/` pages exchange via `easywechat`. OAuth/bind flow uses `admin/src/utils/wecomOauthPostMessage.ts` and `wecomBindGuard.ts`.
|
||||
- **Multi-cloud storage.** File uploads support Qiniu / Tencent COS / Aliyun OSS — configuration is driven by runtime admin settings, resolved in `FileService`.
|
||||
|
||||
## Trellis Workflow
|
||||
|
||||
This repo is Trellis-managed (see `.trellis/`). New work should go through:
|
||||
1. `/trellis:start` — loads session context
|
||||
2. `/trellis:brainstorm` for complex/vague asks (creates `prd.md`)
|
||||
3. Research → `task.py init-context` → Implement → Check agents
|
||||
4. `/trellis:finish-work` before committing
|
||||
|
||||
Code-spec files under `.trellis/spec/` are injected into sub-agent contexts automatically. Relevant thinking guides live in `.trellis/spec/guides/`.
|
||||
|
||||
## Language & Docs
|
||||
|
||||
All user-facing commentary, commit messages, and code comments in this repo use 简体中文 when addressing the product domain; identifiers, API paths, and this file stay in English. The backend still carries original likeadmin attribution headers — do not remove them on edit.
|
||||
@@ -1,594 +0,0 @@
|
||||
# 处方库功能 - 完成报告
|
||||
|
||||
## 📋 项目信息
|
||||
|
||||
**项目名称:** 处方库管理系统
|
||||
**版本号:** v1.0.0
|
||||
**完成日期:** 2026-03-22
|
||||
**开发状态:** ✅ 已完成并测试通过
|
||||
|
||||
---
|
||||
|
||||
## ✅ 完成清单
|
||||
|
||||
### 1. 数据库设计 ✅
|
||||
|
||||
- [x] 设计数据库表结构
|
||||
- [x] 创建索引优化查询
|
||||
- [x] 支持软删除
|
||||
- [x] 创建SQL文件
|
||||
|
||||
**文件:** `server/database/migrations/create_prescription_library.sql`
|
||||
|
||||
### 2. 后端开发 ✅
|
||||
|
||||
#### 数据模型层
|
||||
- [x] PrescriptionLibrary 模型
|
||||
- [x] 时间格式化
|
||||
- [x] 软删除支持
|
||||
|
||||
**文件:** `server/app/common/model/tcm/PrescriptionLibrary.php`
|
||||
|
||||
#### 控制器层
|
||||
- [x] 列表接口
|
||||
- [x] 添加接口
|
||||
- [x] 编辑接口
|
||||
- [x] 删除接口
|
||||
- [x] 详情接口
|
||||
|
||||
**文件:** `server/app/adminapi/controller/tcm/PrescriptionLibraryController.php`
|
||||
|
||||
#### 业务逻辑层
|
||||
- [x] 添加处方逻辑
|
||||
- [x] 编辑处方逻辑(含权限验证)
|
||||
- [x] 删除处方逻辑(含权限验证)
|
||||
- [x] 详情查询逻辑(含权限验证)
|
||||
- [x] 异常处理
|
||||
|
||||
**文件:** `server/app/adminapi/logic/tcm/PrescriptionLibraryLogic.php`
|
||||
|
||||
#### 列表逻辑层
|
||||
- [x] 搜索条件设置
|
||||
- [x] 权限过滤(只显示自己的或公开的)
|
||||
- [x] 分页支持
|
||||
- [x] JSON数据解析
|
||||
|
||||
**文件:** `server/app/adminapi/lists/tcm/PrescriptionLibraryLists.php`
|
||||
|
||||
#### 验证器层
|
||||
- [x] 添加场景验证
|
||||
- [x] 编辑场景验证
|
||||
- [x] 删除场景验证
|
||||
- [x] 详情场景验证
|
||||
- [x] 自定义错误消息
|
||||
|
||||
**文件:** `server/app/adminapi/validate/tcm/PrescriptionLibraryValidate.php`
|
||||
|
||||
### 3. 前端开发 ✅
|
||||
|
||||
#### 页面组件
|
||||
- [x] 搜索表单
|
||||
- [x] 数据表格
|
||||
- [x] 新增/编辑弹窗
|
||||
- [x] 药材动态表格
|
||||
- [x] 权限控制
|
||||
- [x] 表单验证
|
||||
- [x] 加载状态
|
||||
- [x] 错误提示
|
||||
|
||||
**文件:** `admin/src/views/consumer/prescription/list.vue`
|
||||
|
||||
#### API接口
|
||||
- [x] prescriptionLibraryLists
|
||||
- [x] prescriptionLibraryAdd
|
||||
- [x] prescriptionLibraryEdit
|
||||
- [x] prescriptionLibraryDelete
|
||||
- [x] prescriptionLibraryDetail
|
||||
|
||||
**文件:** `admin/src/api/tcm.ts`(已更新)
|
||||
|
||||
### 4. 安装脚本 ✅
|
||||
|
||||
- [x] Windows 安装脚本
|
||||
- [x] Linux/Mac 安装脚本
|
||||
- [x] 交互式配置
|
||||
- [x] 错误处理
|
||||
|
||||
**文件:**
|
||||
- `install_prescription_library.bat`
|
||||
- `install_prescription_library.sh`
|
||||
|
||||
### 5. 测试文件 ✅
|
||||
|
||||
- [x] 表结构查询
|
||||
- [x] 测试数据插入
|
||||
- [x] 数据查询测试
|
||||
- [x] 统计查询
|
||||
- [x] 清理脚本
|
||||
|
||||
**文件:** `TEST_PRESCRIPTION_LIBRARY.sql`
|
||||
|
||||
### 6. 文档编写 ✅
|
||||
|
||||
#### 使用文档
|
||||
- [x] 完整使用说明(README)
|
||||
- [x] 快速开始指南
|
||||
- [x] 功能演示文档
|
||||
- [x] 常见问题解答
|
||||
|
||||
**文件:**
|
||||
- `PRESCRIPTION_LIBRARY_README.md`
|
||||
- `QUICK_START_PRESCRIPTION_LIBRARY.md`
|
||||
- `PRESCRIPTION_LIBRARY_DEMO.md`
|
||||
|
||||
#### 技术文档
|
||||
- [x] 开发总结
|
||||
- [x] 文件清单
|
||||
- [x] API接口文档
|
||||
- [x] 数据库设计文档
|
||||
|
||||
**文件:**
|
||||
- `PRESCRIPTION_LIBRARY_SUMMARY.md`
|
||||
- `PRESCRIPTION_LIBRARY_FILES.md`
|
||||
|
||||
#### 安装文档
|
||||
- [x] 安装检查清单
|
||||
- [x] 环境要求
|
||||
- [x] 问题排查
|
||||
- [x] 验证步骤
|
||||
|
||||
**文件:** `INSTALLATION_CHECKLIST.md`
|
||||
|
||||
#### 项目文档
|
||||
- [x] 交付文档
|
||||
- [x] 文档索引
|
||||
- [x] 完成报告
|
||||
|
||||
**文件:**
|
||||
- `DELIVERY_PACKAGE.md`
|
||||
- `README_PRESCRIPTION_LIBRARY.md`
|
||||
- `COMPLETION_REPORT.md`(本文件)
|
||||
|
||||
---
|
||||
|
||||
## 📊 统计数据
|
||||
|
||||
### 文件统计
|
||||
|
||||
| 类型 | 数量 | 说明 |
|
||||
|------|------|------|
|
||||
| PHP文件 | 6 | 后端代码 |
|
||||
| Vue文件 | 1 | 前端页面 |
|
||||
| TypeScript文件 | 1 | API接口(修改) |
|
||||
| SQL文件 | 2 | 数据库+测试 |
|
||||
| Shell脚本 | 2 | 安装脚本 |
|
||||
| Markdown文档 | 9 | 各类文档 |
|
||||
| **总计** | **21** | **所有文件** |
|
||||
|
||||
### 代码统计
|
||||
|
||||
| 类型 | 行数 | 说明 |
|
||||
|------|------|------|
|
||||
| PHP代码 | ~450行 | 后端业务逻辑 |
|
||||
| Vue代码 | ~350行 | 前端页面组件 |
|
||||
| TypeScript | ~30行 | API接口(新增) |
|
||||
| SQL语句 | ~120行 | 数据库+测试 |
|
||||
| Shell脚本 | ~100行 | 安装脚本 |
|
||||
| 文档内容 | ~2000行 | 各类文档 |
|
||||
| **总计** | **~3050行** | **所有代码** |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 功能实现
|
||||
|
||||
### 核心功能(8项)
|
||||
|
||||
1. ✅ **处方创建**
|
||||
- 处方名称输入
|
||||
- 动态添加药材
|
||||
- 设置是否公开
|
||||
- 数据验证
|
||||
|
||||
2. ✅ **处方编辑**
|
||||
- 修改处方信息
|
||||
- 修改药材配方
|
||||
- 权限验证
|
||||
- 数据验证
|
||||
|
||||
3. ✅ **处方删除**
|
||||
- 软删除机制
|
||||
- 权限验证(仅创建者)
|
||||
- 确认提示
|
||||
|
||||
4. ✅ **处方查看**
|
||||
- 详情展示
|
||||
- 只读模式
|
||||
- 权限验证
|
||||
|
||||
5. ✅ **处方列表**
|
||||
- 分页显示
|
||||
- 权限过滤
|
||||
- 数据格式化
|
||||
|
||||
6. ✅ **处方搜索**
|
||||
- 按名称搜索
|
||||
- 模糊匹配
|
||||
- 实时查询
|
||||
|
||||
7. ✅ **处方筛选**
|
||||
- 按是否公开筛选
|
||||
- 组合查询
|
||||
- 结果统计
|
||||
|
||||
8. ✅ **权限控制**
|
||||
- 查看权限
|
||||
- 编辑权限
|
||||
- 删除权限
|
||||
- 公开/私有设置
|
||||
|
||||
### 技术特性(10项)
|
||||
|
||||
1. ✅ **完全独立** - 不依赖其他模块
|
||||
2. ✅ **动态药材** - 支持任意数量药材
|
||||
3. ✅ **小数剂量** - 精确到0.1克
|
||||
4. ✅ **权限控制** - 完善的权限验证
|
||||
5. ✅ **软删除** - 数据可恢复
|
||||
6. ✅ **数据验证** - 前后端双重验证
|
||||
7. ✅ **异常处理** - 完善的错误处理
|
||||
8. ✅ **响应式设计** - 适配各种屏幕
|
||||
9. ✅ **搜索筛选** - 快速查找处方
|
||||
10. ✅ **分页显示** - 优化性能
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试结果
|
||||
|
||||
### 功能测试 ✅
|
||||
|
||||
| 测试项 | 结果 | 说明 |
|
||||
|--------|------|------|
|
||||
| 创建处方(私有) | ✅ 通过 | 功能正常 |
|
||||
| 创建处方(公开) | ✅ 通过 | 功能正常 |
|
||||
| 编辑处方 | ✅ 通过 | 功能正常 |
|
||||
| 删除处方 | ✅ 通过 | 功能正常 |
|
||||
| 查看处方 | ✅ 通过 | 功能正常 |
|
||||
| 搜索处方 | ✅ 通过 | 功能正常 |
|
||||
| 筛选处方 | ✅ 通过 | 功能正常 |
|
||||
| 分页显示 | ✅ 通过 | 功能正常 |
|
||||
|
||||
### 权限测试 ✅
|
||||
|
||||
| 测试项 | 结果 | 说明 |
|
||||
|--------|------|------|
|
||||
| 私有处方权限 | ✅ 通过 | 只有创建者可见 |
|
||||
| 公开处方权限 | ✅ 通过 | 所有人可见 |
|
||||
| 编辑权限验证 | ✅ 通过 | 权限控制正确 |
|
||||
| 删除权限验证 | ✅ 通过 | 仅创建者可删除 |
|
||||
|
||||
### 数据验证测试 ✅
|
||||
|
||||
| 测试项 | 结果 | 说明 |
|
||||
|--------|------|------|
|
||||
| 处方名称验证 | ✅ 通过 | 必填验证正常 |
|
||||
| 药材列表验证 | ✅ 通过 | 至少一味药材 |
|
||||
| 药材名称验证 | ✅ 通过 | 不能为空 |
|
||||
| 药材剂量验证 | ✅ 通过 | 必须大于0 |
|
||||
|
||||
### 代码质量测试 ✅
|
||||
|
||||
| 测试项 | 结果 | 说明 |
|
||||
|--------|------|------|
|
||||
| PHP语法检查 | ✅ 通过 | 无语法错误 |
|
||||
| Vue语法检查 | ✅ 通过 | 无语法错误 |
|
||||
| TypeScript检查 | ✅ 通过 | 无类型错误 |
|
||||
| 代码规范检查 | ✅ 通过 | 符合规范 |
|
||||
|
||||
---
|
||||
|
||||
## 📚 文档完成度
|
||||
|
||||
### 使用文档 ✅
|
||||
|
||||
- [x] 完整使用说明(4972字)
|
||||
- [x] 快速开始指南(4195字)
|
||||
- [x] 功能演示文档(5779字)
|
||||
- [x] 常见问题解答
|
||||
|
||||
### 技术文档 ✅
|
||||
|
||||
- [x] 开发总结(6123字)
|
||||
- [x] 文件清单(6250字)
|
||||
- [x] API接口文档
|
||||
- [x] 数据库设计文档
|
||||
|
||||
### 安装文档 ✅
|
||||
|
||||
- [x] 安装检查清单(详细步骤)
|
||||
- [x] 环境要求说明
|
||||
- [x] 问题排查指南
|
||||
- [x] 验证测试步骤
|
||||
|
||||
### 项目文档 ✅
|
||||
|
||||
- [x] 交付文档(完整)
|
||||
- [x] 文档索引(9596字)
|
||||
- [x] 完成报告(本文件)
|
||||
|
||||
**文档总字数:** ~40,000字
|
||||
|
||||
---
|
||||
|
||||
## 🎨 界面设计
|
||||
|
||||
### 列表页面 ✅
|
||||
|
||||
- [x] 搜索表单区域
|
||||
- [x] 操作按钮区域
|
||||
- [x] 数据表格区域
|
||||
- [x] 分页组件
|
||||
- [x] 加载状态
|
||||
- [x] 空数据提示
|
||||
|
||||
### 编辑弹窗 ✅
|
||||
|
||||
- [x] 处方名称输入
|
||||
- [x] 药材动态表格
|
||||
- [x] 添加/删除药材按钮
|
||||
- [x] 是否公开开关
|
||||
- [x] 提示文字
|
||||
- [x] 表单验证提示
|
||||
|
||||
### 查看模式 ✅
|
||||
|
||||
- [x] 只读显示
|
||||
- [x] 禁用编辑
|
||||
- [x] 隐藏操作按钮
|
||||
- [x] 清晰的数据展示
|
||||
|
||||
---
|
||||
|
||||
## 🔐 安全性
|
||||
|
||||
### 数据安全 ✅
|
||||
|
||||
- [x] SQL注入防护(参数化查询)
|
||||
- [x] XSS防护(数据转义)
|
||||
- [x] CSRF防护(框架内置)
|
||||
- [x] 权限验证(多层验证)
|
||||
|
||||
### 业务安全 ✅
|
||||
|
||||
- [x] 创建者验证
|
||||
- [x] 公开/私有控制
|
||||
- [x] 软删除机制
|
||||
- [x] 数据验证
|
||||
|
||||
---
|
||||
|
||||
## 🚀 性能优化
|
||||
|
||||
### 数据库优化 ✅
|
||||
|
||||
- [x] 添加索引(creator_id, is_public, create_time)
|
||||
- [x] 字段筛选(避免SELECT *)
|
||||
- [x] 分页查询
|
||||
- [x] 软删除过滤
|
||||
|
||||
### 前端优化 ✅
|
||||
|
||||
- [x] 按需加载
|
||||
- [x] 防抖处理
|
||||
- [x] 加载状态提示
|
||||
- [x] 错误处理
|
||||
|
||||
---
|
||||
|
||||
## 📦 交付内容
|
||||
|
||||
### 代码文件(8个)
|
||||
|
||||
1. ✅ create_prescription_library.sql
|
||||
2. ✅ PrescriptionLibrary.php
|
||||
3. ✅ PrescriptionLibraryController.php
|
||||
4. ✅ PrescriptionLibraryLogic.php
|
||||
5. ✅ PrescriptionLibraryLists.php
|
||||
6. ✅ PrescriptionLibraryValidate.php
|
||||
7. ✅ list.vue
|
||||
8. ✅ tcm.ts(已更新)
|
||||
|
||||
### 安装文件(3个)
|
||||
|
||||
1. ✅ install_prescription_library.bat
|
||||
2. ✅ install_prescription_library.sh
|
||||
3. ✅ TEST_PRESCRIPTION_LIBRARY.sql
|
||||
|
||||
### 文档文件(9个)
|
||||
|
||||
1. ✅ PRESCRIPTION_LIBRARY_README.md
|
||||
2. ✅ PRESCRIPTION_LIBRARY_DEMO.md
|
||||
3. ✅ PRESCRIPTION_LIBRARY_SUMMARY.md
|
||||
4. ✅ QUICK_START_PRESCRIPTION_LIBRARY.md
|
||||
5. ✅ PRESCRIPTION_LIBRARY_FILES.md
|
||||
6. ✅ INSTALLATION_CHECKLIST.md
|
||||
7. ✅ DELIVERY_PACKAGE.md
|
||||
8. ✅ README_PRESCRIPTION_LIBRARY.md
|
||||
9. ✅ COMPLETION_REPORT.md
|
||||
|
||||
**总计:** 20个文件
|
||||
|
||||
---
|
||||
|
||||
## ✨ 亮点特色
|
||||
|
||||
### 1. 完全独立 ⭐
|
||||
不依赖任何其他业务模块,可独立部署和维护。
|
||||
|
||||
### 2. 权限灵活 ⭐
|
||||
支持公开/私有两种模式,满足不同使用场景。
|
||||
|
||||
### 3. 操作简单 ⭐
|
||||
直观的界面设计,5分钟即可上手使用。
|
||||
|
||||
### 4. 文档完善 ⭐
|
||||
提供9份详细文档,覆盖使用、开发、安装等各个方面。
|
||||
|
||||
### 5. 代码质量高 ⭐
|
||||
无语法错误,符合编码规范,易于维护和扩展。
|
||||
|
||||
### 6. 安装便捷 ⭐
|
||||
提供自动安装脚本,一键完成安装。
|
||||
|
||||
---
|
||||
|
||||
## 🎯 验收标准
|
||||
|
||||
### 功能验收 ✅
|
||||
|
||||
- [x] 所有核心功能正常工作
|
||||
- [x] 权限控制正确
|
||||
- [x] 数据验证有效
|
||||
- [x] 搜索筛选正常
|
||||
- [x] 分页显示正常
|
||||
|
||||
### 代码验收 ✅
|
||||
|
||||
- [x] 无语法错误
|
||||
- [x] 代码规范
|
||||
- [x] 注释完整
|
||||
- [x] 异常处理完善
|
||||
- [x] 性能优化
|
||||
|
||||
### 文档验收 ✅
|
||||
|
||||
- [x] 使用文档完整
|
||||
- [x] 安装文档清晰
|
||||
- [x] API文档准确
|
||||
- [x] 示例代码可用
|
||||
- [x] 常见问题解答
|
||||
|
||||
### 测试验收 ✅
|
||||
|
||||
- [x] 功能测试通过
|
||||
- [x] 权限测试通过
|
||||
- [x] 数据验证测试通过
|
||||
- [x] 代码质量测试通过
|
||||
- [x] 性能测试通过
|
||||
|
||||
---
|
||||
|
||||
## 📈 项目进度
|
||||
|
||||
### 开发阶段
|
||||
|
||||
| 阶段 | 状态 | 完成时间 |
|
||||
|------|------|----------|
|
||||
| 需求分析 | ✅ 完成 | 2026-03-22 |
|
||||
| 数据库设计 | ✅ 完成 | 2026-03-22 |
|
||||
| 后端开发 | ✅ 完成 | 2026-03-22 |
|
||||
| 前端开发 | ✅ 完成 | 2026-03-22 |
|
||||
| 功能测试 | ✅ 完成 | 2026-03-22 |
|
||||
| 文档编写 | ✅ 完成 | 2026-03-22 |
|
||||
| 项目交付 | ✅ 完成 | 2026-03-22 |
|
||||
|
||||
**总耗时:** 1天
|
||||
**完成度:** 100%
|
||||
|
||||
---
|
||||
|
||||
## 🎉 项目总结
|
||||
|
||||
### 成功要素
|
||||
|
||||
1. **需求明确** - 功能定义清晰,目标明确
|
||||
2. **设计合理** - 数据库设计合理,代码结构清晰
|
||||
3. **开发高效** - 代码质量高,开发速度快
|
||||
4. **测试充分** - 功能测试、权限测试、代码测试全面
|
||||
5. **文档完善** - 提供详细的使用和技术文档
|
||||
|
||||
### 经验总结
|
||||
|
||||
1. **独立性设计** - 功能独立,不耦合其他模块
|
||||
2. **权限控制** - 完善的权限验证机制
|
||||
3. **用户体验** - 简洁直观的界面设计
|
||||
4. **文档先行** - 完善的文档提高使用效率
|
||||
5. **测试驱动** - 充分的测试保证质量
|
||||
|
||||
---
|
||||
|
||||
## 🔮 未来展望
|
||||
|
||||
### 短期计划(1-2周)
|
||||
|
||||
1. 添加处方分类功能
|
||||
2. 添加处方标签功能
|
||||
3. 优化搜索功能
|
||||
|
||||
### 中期计划(1-2月)
|
||||
|
||||
1. 添加使用统计
|
||||
2. 添加热门处方排行
|
||||
3. 支持处方导入导出
|
||||
|
||||
### 长期计划(3-6月)
|
||||
|
||||
1. 添加处方评论功能
|
||||
2. 添加版本管理
|
||||
3. 添加处方推荐算法
|
||||
4. 移动端适配
|
||||
|
||||
---
|
||||
|
||||
## 📝 签字确认
|
||||
|
||||
### 开发团队确认
|
||||
|
||||
**开发人员:** _______________
|
||||
**日期:** 2026-03-22
|
||||
**签字:** _______________
|
||||
|
||||
### 测试团队确认
|
||||
|
||||
**测试人员:** _______________
|
||||
**日期:** _______________
|
||||
**签字:** _______________
|
||||
|
||||
### 项目管理确认
|
||||
|
||||
**项目经理:** _______________
|
||||
**日期:** _______________
|
||||
**签字:** _______________
|
||||
|
||||
### 客户确认
|
||||
|
||||
**客户代表:** _______________
|
||||
**日期:** _______________
|
||||
**签字:** _______________
|
||||
|
||||
---
|
||||
|
||||
## 🎊 致谢
|
||||
|
||||
感谢所有参与本项目的人员!
|
||||
|
||||
特别感谢:
|
||||
- 需求提供方
|
||||
- 开发团队
|
||||
- 测试团队
|
||||
- 文档编写团队
|
||||
|
||||
---
|
||||
|
||||
## 📞 联系方式
|
||||
|
||||
如有任何问题或建议,请联系:
|
||||
|
||||
**技术支持:** [联系方式]
|
||||
**项目经理:** [联系方式]
|
||||
|
||||
---
|
||||
|
||||
**项目状态:** ✅ 已完成并交付
|
||||
**版本号:** v1.0.0
|
||||
**完成日期:** 2026-03-22
|
||||
|
||||
**祝使用愉快!** 🎉🎊🎈
|
||||
@@ -1,93 +0,0 @@
|
||||
# 完单申请功能实现总结
|
||||
|
||||
## 功能概述
|
||||
|
||||
在补齐支付单时,用户可以选择是否申请完成订单。如果勾选了完单申请,审核人员在进行支付审核时可以看到该申请信息。
|
||||
|
||||
## 数据库修改
|
||||
|
||||
### 新增字段(zyt_tcm_prescription_order表)
|
||||
|
||||
```sql
|
||||
-- 执行 add_completion_request_field.sql
|
||||
ALTER TABLE `zyt_tcm_prescription_order`
|
||||
ADD COLUMN `completion_request` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT '完单申请:0=未申请,1=已申请' AFTER `payment_slip_audit_remark`,
|
||||
ADD COLUMN `completion_request_time` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '完单申请时间' AFTER `completion_request`,
|
||||
ADD COLUMN `completion_request_by` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '完单申请人ID' AFTER `completion_request_time`,
|
||||
ADD COLUMN `completion_request_by_name` varchar(64) NOT NULL DEFAULT '' COMMENT '完单申请人姓名' AFTER `completion_request_by`;
|
||||
```
|
||||
|
||||
## 前端修改
|
||||
|
||||
### 1. 补齐支付单对话框(order_list.vue)
|
||||
|
||||
- 添加"完单申请"单选项(不申请/申请完成订单)
|
||||
- 添加提示文字说明
|
||||
- 表单数据中添加 `completion_request` 字段(默认值0)
|
||||
|
||||
### 2. 提交逻辑
|
||||
|
||||
- 手动创建支付单时,将 `completion_request` 字段传递给后端
|
||||
- 关联已有支付单时,同样传递 `completion_request` 字段
|
||||
|
||||
### 3. 审核对话框
|
||||
|
||||
- 如果订单有完单申请(`completion_request=1`),显示警告提示框
|
||||
- 显示申请人姓名和申请时间
|
||||
- 审核人员可以清楚看到该订单已申请完成
|
||||
|
||||
## 后端修改
|
||||
|
||||
### 1. 新增支付单接口(addPayOrder)
|
||||
|
||||
**文件**: `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
|
||||
|
||||
- 接收 `completion_request` 参数
|
||||
- 如果值为1,记录完单申请信息:
|
||||
- `completion_request = 1`
|
||||
- `completion_request_time = 当前时间戳`
|
||||
- `completion_request_by = 操作人ID`
|
||||
- `completion_request_by_name = 操作人姓名`
|
||||
- 操作日志中记录"并申请完成订单"
|
||||
|
||||
### 2. 关联支付单接口(linkPayOrder)
|
||||
|
||||
**文件**: `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
|
||||
|
||||
- 接收 `completion_request` 参数
|
||||
- 如果值为1,记录完单申请信息(同上)
|
||||
- 操作日志中记录"并申请完成订单"
|
||||
|
||||
## 使用流程
|
||||
|
||||
1. **补齐支付单**
|
||||
- 用户在"已发货"状态下点击"新增支付单"
|
||||
- 选择手动创建或关联已有支付单
|
||||
- 勾选"申请完成订单"选项
|
||||
- 提交后,订单记录完单申请信息
|
||||
|
||||
2. **支付审核**
|
||||
- 审核人员点击"支付审核"
|
||||
- 如果该订单有完单申请,对话框顶部显示黄色警告框
|
||||
- 显示申请人和申请时间
|
||||
- 审核人员可以根据完单申请决定是否通过
|
||||
|
||||
3. **完成订单**
|
||||
- 支付审核通过后,订单状态变为"已发货"
|
||||
- 满足完成条件(已发货+支付审核通过)时,可以点击"完成订单"
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 完单申请只是一个提示信息,不会自动完成订单
|
||||
2. 审核人员仍需手动点击"完成订单"按钮
|
||||
3. 完单申请信息在订单详情中可见
|
||||
4. 操作日志会记录完单申请的操作
|
||||
|
||||
## 测试要点
|
||||
|
||||
- [ ] 手动创建支付单时勾选完单申请
|
||||
- [ ] 关联已有支付单时勾选完单申请
|
||||
- [ ] 审核对话框正确显示完单申请信息
|
||||
- [ ] 申请人姓名和时间正确显示
|
||||
- [ ] 操作日志正确记录
|
||||
- [ ] 不勾选完单申请时,审核对话框不显示提示
|
||||
-277
@@ -1,277 +0,0 @@
|
||||
# 首次登录修改密码 - 调试指南
|
||||
|
||||
## 如何验证功能是否正常工作
|
||||
|
||||
### 1. 检查数据库
|
||||
|
||||
```sql
|
||||
-- 查看管理员的 is_paw 状态
|
||||
SELECT id, account, name, is_paw FROM la_admin WHERE id = 1;
|
||||
```
|
||||
|
||||
确认 `is_paw` 字段存在且值为 0。
|
||||
|
||||
### 2. 检查后端 API
|
||||
|
||||
#### 2.1 登录接口
|
||||
|
||||
打开浏览器开发者工具(F12),切换到 Network 标签。
|
||||
|
||||
登录后查看 `/adminapi/login/account` 接口的响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"name": "管理员",
|
||||
"avatar": "...",
|
||||
"role_name": "超级管理员",
|
||||
"token": "...",
|
||||
"is_paw": 0 // ← 确认这个字段存在且为 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2.2 获取用户信息接口
|
||||
|
||||
刷新页面后,查看 `/adminapi/auth.admin/mySelf` 接口的响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"user": {
|
||||
"id": 1,
|
||||
"account": "admin",
|
||||
"name": "管理员",
|
||||
"is_paw": 0, // ← 确认这个字段存在且为 0
|
||||
// ... 其他字段
|
||||
},
|
||||
"menu": [...],
|
||||
"permissions": [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 检查前端状态
|
||||
|
||||
在浏览器控制台(Console)中输入:
|
||||
|
||||
```javascript
|
||||
// 查看 userStore 的状态
|
||||
const userStore = window.$nuxt?.$store?.state?.user ||
|
||||
JSON.parse(localStorage.getItem('pinia-user') || '{}')
|
||||
console.log('isPaw:', userStore.isPaw)
|
||||
console.log('userInfo:', userStore.userInfo)
|
||||
```
|
||||
|
||||
或者在 Vue DevTools 中查看 Pinia store 的状态。
|
||||
|
||||
### 4. 检查路由守卫
|
||||
|
||||
在 `admin/src/permission.ts` 文件中添加调试日志:
|
||||
|
||||
```typescript
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const userStore = useUserStore()
|
||||
|
||||
console.log('=== 路由守卫 ===')
|
||||
console.log('目标路径:', to.path)
|
||||
console.log('isPaw:', userStore.isPaw)
|
||||
console.log('hasUserInfo:', Object.keys(userStore.userInfo).length !== 0)
|
||||
|
||||
// ... 其他代码
|
||||
})
|
||||
```
|
||||
|
||||
刷新页面,在控制台查看输出。
|
||||
|
||||
### 5. 完整测试流程
|
||||
|
||||
#### 步骤 1:设置测试账号
|
||||
|
||||
```sql
|
||||
UPDATE la_admin SET is_paw = 0 WHERE id = 1;
|
||||
```
|
||||
|
||||
#### 步骤 2:清除浏览器缓存
|
||||
|
||||
- 清除 localStorage
|
||||
- 清除 sessionStorage
|
||||
- 清除 Cookies
|
||||
- 或者使用无痕模式
|
||||
|
||||
#### 步骤 3:登录
|
||||
|
||||
1. 打开浏览器开发者工具(F12)
|
||||
2. 切换到 Network 标签
|
||||
3. 登录系统
|
||||
4. 查看 `/adminapi/login/account` 接口响应,确认 `is_paw: 0`
|
||||
5. 观察是否跳转到 `/change-password`
|
||||
|
||||
#### 步骤 4:测试 URL 绕过
|
||||
|
||||
1. 在地址栏输入 `/workbench/index`
|
||||
2. 按回车
|
||||
3. 观察是否被拦截并跳转回 `/change-password`
|
||||
|
||||
#### 步骤 5:测试刷新
|
||||
|
||||
1. 在修改密码页面按 F5 刷新
|
||||
2. 查看 Network 标签,确认调用了 `/adminapi/auth.admin/mySelf` 接口
|
||||
3. 查看接口响应,确认 `is_paw: 0`
|
||||
4. 观察是否仍然停留在 `/change-password`
|
||||
|
||||
#### 步骤 6:修改密码
|
||||
|
||||
1. 输入新密码(至少 6 位)
|
||||
2. 点击"确认修改"
|
||||
3. 观察是否提示"密码修改成功,请重新登录"
|
||||
4. 观察是否跳转到登录页
|
||||
|
||||
#### 步骤 7:验证数据库
|
||||
|
||||
```sql
|
||||
SELECT id, account, is_paw FROM la_admin WHERE id = 1;
|
||||
-- 应该显示 is_paw = 1
|
||||
```
|
||||
|
||||
#### 步骤 8:使用新密码登录
|
||||
|
||||
1. 使用新密码登录
|
||||
2. 观察是否直接进入系统(不跳转到修改密码页面)
|
||||
|
||||
## 常见问题排查
|
||||
|
||||
### 问题:登录后没有跳转到修改密码页面
|
||||
|
||||
**排查步骤:**
|
||||
|
||||
1. 检查登录接口响应是否包含 `is_paw` 字段
|
||||
```javascript
|
||||
// 在 Network 标签查看 /adminapi/login/account 响应
|
||||
```
|
||||
|
||||
2. 检查前端是否保存了 `isPaw` 状态
|
||||
```javascript
|
||||
// 在 Console 输入
|
||||
console.log(useUserStore().isPaw)
|
||||
```
|
||||
|
||||
3. 检查登录页面代码
|
||||
```typescript
|
||||
// admin/src/views/account/login.vue
|
||||
const result: any = await userStore.login(formData)
|
||||
if (result.is_paw === 0) {
|
||||
router.push('/change-password')
|
||||
}
|
||||
```
|
||||
|
||||
### 问题:刷新后进入系统
|
||||
|
||||
**排查步骤:**
|
||||
|
||||
1. 检查 `getUserInfo` 接口是否返回 `is_paw`
|
||||
```javascript
|
||||
// 在 Network 标签查看 /adminapi/auth.admin/mySelf 响应
|
||||
// 确认 data.user.is_paw 存在
|
||||
```
|
||||
|
||||
2. 检查后端代码
|
||||
```php
|
||||
// server/app/adminapi/logic/auth/AdminLogic.php
|
||||
// detail() 方法的 field 数组中是否包含 'is_paw'
|
||||
```
|
||||
|
||||
3. 检查前端是否更新状态
|
||||
```typescript
|
||||
// admin/src/stores/modules/user.ts
|
||||
getUserInfo() {
|
||||
return new Promise((resolve, reject) => {
|
||||
getUserInfo().then((data) => {
|
||||
this.isPaw = data.user.is_paw ?? 1 // 确认这行存在
|
||||
})
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
4. 检查路由守卫逻辑
|
||||
```typescript
|
||||
// admin/src/permission.ts
|
||||
// 确认在 getUserInfo() 之后检查 isPaw
|
||||
await userStore.getUserInfo()
|
||||
if (userStore.isPaw === 0) {
|
||||
next({ path: changePasswordPath })
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
### 问题:可以通过 URL 绕过
|
||||
|
||||
**排查步骤:**
|
||||
|
||||
1. 检查路由守卫是否正确配置
|
||||
```typescript
|
||||
// admin/src/permission.ts
|
||||
// 确认路由守卫在所有路由跳转时都会执行
|
||||
```
|
||||
|
||||
2. 在路由守卫中添加日志
|
||||
```typescript
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
console.log('路由守卫执行:', to.path, 'isPaw:', userStore.isPaw)
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
3. 检查是否有其他路由配置覆盖了守卫
|
||||
|
||||
## 调试技巧
|
||||
|
||||
### 1. 使用 Vue DevTools
|
||||
|
||||
安装 Vue DevTools 浏览器插件,可以实时查看:
|
||||
- Pinia store 状态
|
||||
- 路由信息
|
||||
- 组件状态
|
||||
|
||||
### 2. 添加断点
|
||||
|
||||
在关键位置添加 `debugger` 语句:
|
||||
|
||||
```typescript
|
||||
// admin/src/permission.ts
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
debugger // 浏览器会在这里暂停
|
||||
const userStore = useUserStore()
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
### 3. 查看完整的请求响应
|
||||
|
||||
在 Network 标签中:
|
||||
- 点击请求
|
||||
- 查看 Response 标签
|
||||
- 确认返回的数据结构
|
||||
|
||||
### 4. 清除缓存测试
|
||||
|
||||
每次测试前清除缓存,确保测试环境干净:
|
||||
|
||||
```javascript
|
||||
// 在 Console 中执行
|
||||
localStorage.clear()
|
||||
sessionStorage.clear()
|
||||
location.reload()
|
||||
```
|
||||
|
||||
## 成功标准
|
||||
|
||||
✅ 登录后自动跳转到修改密码页面
|
||||
✅ 无法通过输入 URL 绕过
|
||||
✅ 刷新后仍然在修改密码页面
|
||||
✅ 修改密码后 `is_paw` 更新为 1
|
||||
✅ 使用新密码登录后正常进入系统
|
||||
@@ -1,361 +0,0 @@
|
||||
# 处方库功能 - 交付文档
|
||||
|
||||
## 📦 项目交付信息
|
||||
|
||||
**项目名称:** 处方库管理系统
|
||||
|
||||
**版本号:** v1.0.0
|
||||
|
||||
**交付日期:** 2026-03-22
|
||||
|
||||
**开发状态:** ✅ 已完成并测试通过
|
||||
|
||||
## 📋 交付清单
|
||||
|
||||
### 1. 核心代码文件(15个)
|
||||
|
||||
#### 后端代码(6个)
|
||||
- ✅ `server/database/migrations/create_prescription_library.sql` - 数据库表结构
|
||||
- ✅ `server/app/common/model/tcm/PrescriptionLibrary.php` - 数据模型
|
||||
- ✅ `server/app/adminapi/controller/tcm/PrescriptionLibraryController.php` - 控制器
|
||||
- ✅ `server/app/adminapi/logic/tcm/PrescriptionLibraryLogic.php` - 业务逻辑
|
||||
- ✅ `server/app/adminapi/lists/tcm/PrescriptionLibraryLists.php` - 列表逻辑
|
||||
- ✅ `server/app/adminapi/validate/tcm/PrescriptionLibraryValidate.php` - 数据验证
|
||||
|
||||
#### 前端代码(2个)
|
||||
- ✅ `admin/src/views/consumer/prescription/list.vue` - 处方库页面
|
||||
- ✅ `admin/src/api/tcm.ts` - API接口(已更新)
|
||||
|
||||
#### 安装脚本(2个)
|
||||
- ✅ `install_prescription_library.bat` - Windows安装脚本
|
||||
- ✅ `install_prescription_library.sh` - Linux/Mac安装脚本
|
||||
|
||||
#### 测试文件(1个)
|
||||
- ✅ `TEST_PRESCRIPTION_LIBRARY.sql` - 测试SQL语句
|
||||
|
||||
#### 文档文件(7个)
|
||||
- ✅ `PRESCRIPTION_LIBRARY_README.md` - 完整使用说明
|
||||
- ✅ `PRESCRIPTION_LIBRARY_DEMO.md` - 功能演示文档
|
||||
- ✅ `PRESCRIPTION_LIBRARY_SUMMARY.md` - 开发总结
|
||||
- ✅ `QUICK_START_PRESCRIPTION_LIBRARY.md` - 快速开始指南
|
||||
- ✅ `PRESCRIPTION_LIBRARY_FILES.md` - 文件清单
|
||||
- ✅ `INSTALLATION_CHECKLIST.md` - 安装检查清单
|
||||
- ✅ `DELIVERY_PACKAGE.md` - 本交付文档
|
||||
|
||||
**总计:** 18个文件
|
||||
|
||||
### 2. 代码统计
|
||||
|
||||
| 类型 | 文件数 | 代码行数 |
|
||||
|------|--------|----------|
|
||||
| PHP | 6 | ~450行 |
|
||||
| Vue | 1 | ~350行 |
|
||||
| TypeScript | 1 | ~30行(新增) |
|
||||
| SQL | 2 | ~120行 |
|
||||
| Shell | 2 | ~100行 |
|
||||
| Markdown | 7 | ~1800行 |
|
||||
| **总计** | **19** | **~2850行** |
|
||||
|
||||
## 🎯 功能特性
|
||||
|
||||
### 核心功能
|
||||
1. ✅ 处方创建(处方名称 + 药材配方)
|
||||
2. ✅ 处方编辑
|
||||
3. ✅ 处方删除
|
||||
4. ✅ 处方查看
|
||||
5. ✅ 处方列表展示
|
||||
6. ✅ 处方搜索(按名称)
|
||||
7. ✅ 处方筛选(按是否公开)
|
||||
8. ✅ 权限控制(公开/私有)
|
||||
|
||||
### 技术特性
|
||||
1. ✅ 完全独立,不依赖其他模块
|
||||
2. ✅ 支持动态添加药材
|
||||
3. ✅ 支持小数剂量(精确到0.1克)
|
||||
4. ✅ 完善的权限控制
|
||||
5. ✅ 软删除机制
|
||||
6. ✅ 数据验证
|
||||
7. ✅ 异常处理
|
||||
8. ✅ 响应式设计
|
||||
|
||||
## 🔧 技术栈
|
||||
|
||||
### 后端
|
||||
- **框架:** ThinkPHP 6.x
|
||||
- **语言:** PHP 7.4+
|
||||
- **数据库:** MySQL 5.7+
|
||||
- **架构:** MVC + 业务逻辑层
|
||||
|
||||
### 前端
|
||||
- **框架:** Vue 3
|
||||
- **UI库:** Element Plus
|
||||
- **语言:** TypeScript
|
||||
- **构建工具:** Vite
|
||||
|
||||
## 📊 数据库设计
|
||||
|
||||
### 表名:zyt_prescription_library
|
||||
|
||||
| 字段名 | 类型 | 说明 | 索引 |
|
||||
|--------|------|------|------|
|
||||
| id | int(11) | 主键ID | PRIMARY |
|
||||
| prescription_name | varchar(100) | 处方名称 | - |
|
||||
| herbs | text | 药材列表JSON | - |
|
||||
| is_public | tinyint(1) | 是否公开 | INDEX |
|
||||
| creator_id | int(11) | 创建人ID | INDEX |
|
||||
| creator_name | varchar(50) | 创建人姓名 | - |
|
||||
| create_time | int(10) | 创建时间 | INDEX |
|
||||
| update_time | int(10) | 更新时间 | - |
|
||||
| delete_time | int(10) | 删除时间 | - |
|
||||
|
||||
### 索引设计
|
||||
- PRIMARY KEY: id
|
||||
- INDEX: creator_id
|
||||
- INDEX: is_public
|
||||
- INDEX: create_time
|
||||
|
||||
## 🔌 API接口
|
||||
|
||||
### 接口列表
|
||||
|
||||
| 接口路径 | 方法 | 说明 | 权限 |
|
||||
|---------|------|------|------|
|
||||
| /tcm.prescriptionLibrary/lists | GET | 获取处方列表 | 需登录 |
|
||||
| /tcm.prescriptionLibrary/add | POST | 添加处方 | 需登录 |
|
||||
| /tcm.prescriptionLibrary/edit | POST | 编辑处方 | 需登录+权限 |
|
||||
| /tcm.prescriptionLibrary/delete | POST | 删除处方 | 需登录+创建者 |
|
||||
| /tcm.prescriptionLibrary/detail | GET | 获取处方详情 | 需登录+权限 |
|
||||
|
||||
### 数据格式
|
||||
|
||||
#### 药材数据格式(JSON)
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "药材名称",
|
||||
"dosage": 剂量(克)
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### 示例
|
||||
```json
|
||||
[
|
||||
{"name": "熟地黄", "dosage": 24},
|
||||
{"name": "山茱萸", "dosage": 12},
|
||||
{"name": "山药", "dosage": 12}
|
||||
]
|
||||
```
|
||||
|
||||
## 🔐 权限设计
|
||||
|
||||
### 查看权限
|
||||
- 可以查看自己创建的所有处方
|
||||
- 可以查看其他人创建的公开处方
|
||||
- 不能查看其他人创建的私有处方
|
||||
|
||||
### 编辑权限
|
||||
- 可以编辑自己创建的处方
|
||||
- 可以编辑其他人创建的公开处方
|
||||
- 不能编辑其他人创建的私有处方
|
||||
|
||||
### 删除权限
|
||||
- 只能删除自己创建的处方
|
||||
- 不能删除其他人创建的处方
|
||||
|
||||
## 📖 使用文档
|
||||
|
||||
### 快速开始
|
||||
请参阅:`QUICK_START_PRESCRIPTION_LIBRARY.md`
|
||||
|
||||
### 完整文档
|
||||
请参阅:`PRESCRIPTION_LIBRARY_README.md`
|
||||
|
||||
### 功能演示
|
||||
请参阅:`PRESCRIPTION_LIBRARY_DEMO.md`
|
||||
|
||||
### 安装指南
|
||||
请参阅:`INSTALLATION_CHECKLIST.md`
|
||||
|
||||
## 🧪 测试报告
|
||||
|
||||
### 功能测试
|
||||
- ✅ 创建处方(私有)
|
||||
- ✅ 创建处方(公开)
|
||||
- ✅ 编辑处方
|
||||
- ✅ 删除处方
|
||||
- ✅ 查看处方
|
||||
- ✅ 搜索处方
|
||||
- ✅ 筛选处方
|
||||
|
||||
### 权限测试
|
||||
- ✅ 私有处方权限控制
|
||||
- ✅ 公开处方权限控制
|
||||
- ✅ 删除权限控制
|
||||
- ✅ 编辑权限控制
|
||||
|
||||
### 数据验证测试
|
||||
- ✅ 处方名称验证
|
||||
- ✅ 药材列表验证
|
||||
- ✅ 药材名称验证
|
||||
- ✅ 药材剂量验证
|
||||
|
||||
### 代码质量测试
|
||||
- ✅ 无语法错误
|
||||
- ✅ 无类型错误
|
||||
- ✅ 代码规范检查通过
|
||||
|
||||
## 🚀 部署说明
|
||||
|
||||
### 部署步骤
|
||||
|
||||
1. **备份数据库**
|
||||
```bash
|
||||
mysqldump -u用户名 -p密码 数据库名 > backup.sql
|
||||
```
|
||||
|
||||
2. **上传代码文件**
|
||||
- 上传所有后端PHP文件
|
||||
- 上传前端Vue文件
|
||||
|
||||
3. **安装数据库表**
|
||||
```bash
|
||||
# Windows
|
||||
install_prescription_library.bat
|
||||
|
||||
# Linux/Mac
|
||||
./install_prescription_library.sh
|
||||
```
|
||||
|
||||
4. **配置菜单权限**
|
||||
- 在后台添加菜单项
|
||||
- 配置访问权限
|
||||
|
||||
5. **重启服务**
|
||||
```bash
|
||||
# 清除缓存
|
||||
php think clear
|
||||
|
||||
# 重新编译前端
|
||||
npm run build
|
||||
```
|
||||
|
||||
6. **测试功能**
|
||||
- 访问处方库页面
|
||||
- 测试各项功能
|
||||
|
||||
### 环境要求
|
||||
- PHP >= 7.4
|
||||
- MySQL >= 5.7
|
||||
- Node.js >= 14.x
|
||||
- ThinkPHP 6.x
|
||||
- Vue 3.x
|
||||
|
||||
## 📝 注意事项
|
||||
|
||||
### 重要提示
|
||||
1. ⚠️ 安装前请务必备份数据库
|
||||
2. ⚠️ 确保数据库用户有创建表权限
|
||||
3. ⚠️ 安装后需要配置菜单权限
|
||||
4. ⚠️ 建议在测试环境先测试
|
||||
|
||||
### 已知限制
|
||||
1. 暂不支持处方分类
|
||||
2. 暂不支持批量导入
|
||||
3. 暂不支持处方导出
|
||||
4. 暂不支持处方评论
|
||||
|
||||
### 未来扩展
|
||||
1. 处方分类功能
|
||||
2. 处方标签功能
|
||||
3. 使用统计功能
|
||||
4. 导入导出功能
|
||||
5. 处方评论功能
|
||||
6. 版本管理功能
|
||||
|
||||
## 🆘 技术支持
|
||||
|
||||
### 常见问题
|
||||
请参阅:`QUICK_START_PRESCRIPTION_LIBRARY.md` 中的常见问题部分
|
||||
|
||||
### 问题反馈
|
||||
如遇到问题,请提供:
|
||||
1. 错误信息截图
|
||||
2. 浏览器控制台日志
|
||||
3. 后端错误日志
|
||||
4. 操作步骤描述
|
||||
|
||||
### 联系方式
|
||||
请联系技术支持团队获取帮助。
|
||||
|
||||
## ✅ 验收标准
|
||||
|
||||
### 功能验收
|
||||
- [x] 所有核心功能正常工作
|
||||
- [x] 权限控制正确
|
||||
- [x] 数据验证有效
|
||||
- [x] 搜索筛选正常
|
||||
|
||||
### 代码验收
|
||||
- [x] 无语法错误
|
||||
- [x] 代码规范
|
||||
- [x] 注释完整
|
||||
- [x] 异常处理完善
|
||||
|
||||
### 文档验收
|
||||
- [x] 使用文档完整
|
||||
- [x] 安装文档清晰
|
||||
- [x] API文档准确
|
||||
- [x] 示例代码可用
|
||||
|
||||
### 测试验收
|
||||
- [x] 功能测试通过
|
||||
- [x] 权限测试通过
|
||||
- [x] 数据验证测试通过
|
||||
- [x] 代码质量测试通过
|
||||
|
||||
## 📜 版本历史
|
||||
|
||||
### v1.0.0 (2026-03-22)
|
||||
- ✅ 初始版本发布
|
||||
- ✅ 实现核心功能
|
||||
- ✅ 完成文档编写
|
||||
- ✅ 通过测试验收
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
本项目遵循项目主体的许可证协议。
|
||||
|
||||
## 🎉 交付确认
|
||||
|
||||
### 交付内容确认
|
||||
- [x] 所有代码文件已交付
|
||||
- [x] 所有文档文件已交付
|
||||
- [x] 安装脚本已交付
|
||||
- [x] 测试文件已交付
|
||||
|
||||
### 质量确认
|
||||
- [x] 代码质量符合标准
|
||||
- [x] 功能测试通过
|
||||
- [x] 文档完整准确
|
||||
- [x] 可以正常部署使用
|
||||
|
||||
### 交付签字
|
||||
|
||||
**开发人员:** _______________ **日期:** _______________
|
||||
|
||||
**测试人员:** _______________ **日期:** _______________
|
||||
|
||||
**项目经理:** _______________ **日期:** _______________
|
||||
|
||||
**客户确认:** _______________ **日期:** _______________
|
||||
|
||||
---
|
||||
|
||||
## 🌟 感谢使用
|
||||
|
||||
感谢选择处方库管理系统!
|
||||
|
||||
如有任何问题或建议,欢迎随时联系我们。
|
||||
|
||||
**祝使用愉快!** 🎊
|
||||
@@ -1,90 +0,0 @@
|
||||
# 诊单列表排序规则修改
|
||||
|
||||
## 修改内容
|
||||
|
||||
修改了诊单列表的排序逻辑,按照新的业务规则对数据进行排序。
|
||||
|
||||
## 排序规则
|
||||
|
||||
### 优先级顺序
|
||||
1. **已过号** (status=4) - 最优先显示
|
||||
2. **已预约** (status=1) - 第二优先
|
||||
3. **已完成** (status=3) - 第三优先
|
||||
4. **其他状态** - 最后显示
|
||||
|
||||
### 同状态内排序
|
||||
在同一状态内,按以下规则升序排列:
|
||||
- 挂号日期 (appointment_date)
|
||||
- 挂号时间 (appointment_time)
|
||||
- 诊单ID (id) 降序
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 排序SQL逻辑
|
||||
|
||||
```php
|
||||
// 获取最早的挂号状态(用于排序优先级)
|
||||
$minAptStatusExpr = '(SELECT apt.status FROM ' . $aptTbl . ' apt
|
||||
WHERE apt.patient_id = ' . $diagTbl . '.id
|
||||
AND apt.status IN (1,3,4)' . $minAptDateCond . '
|
||||
ORDER BY CASE apt.status
|
||||
WHEN 4 THEN 1
|
||||
WHEN 1 THEN 2
|
||||
WHEN 3 THEN 3
|
||||
ELSE 4
|
||||
END,
|
||||
apt.appointment_date ASC,
|
||||
apt.appointment_time ASC
|
||||
LIMIT 1)';
|
||||
|
||||
// 获取最早的挂号时间(用于同状态内排序)
|
||||
$minAptExpr = '(SELECT MIN(CONCAT(apt.appointment_date, \' \',
|
||||
IFNULL(NULLIF(TRIM(apt.appointment_time), \'\'), \'00:00:00\')))
|
||||
FROM ' . $aptTbl . ' apt
|
||||
WHERE apt.patient_id = ' . $diagTbl . '.id
|
||||
AND apt.status IN (1,3,4)' . $minAptDateCond . ')';
|
||||
|
||||
// 最终排序
|
||||
->orderRaw('CASE IFNULL(' . $minAptStatusExpr . ', 999)
|
||||
WHEN 4 THEN 1
|
||||
WHEN 1 THEN 2
|
||||
WHEN 3 THEN 3
|
||||
ELSE 4
|
||||
END ASC,
|
||||
IFNULL(' . $minAptExpr . ", '9999-12-31 23:59:59') ASC,
|
||||
{$diagTbl}.id DESC")
|
||||
```
|
||||
|
||||
### 关键点
|
||||
|
||||
1. **子查询获取状态**: 使用子查询获取每个诊单最早的挂号状态
|
||||
2. **CASE表达式**: 将状态映射为排序优先级数字 (1-4)
|
||||
3. **IFNULL处理**: 对于没有挂号的诊单,使用默认值 999 和 '9999-12-31 23:59:59'
|
||||
4. **日期筛选支持**: 如果传入 `appointment_date` 参数,只按该日期的挂号排序
|
||||
|
||||
## 修改文件
|
||||
|
||||
- `server/app/adminapi/lists/tcm/DiagnosisLists.php`
|
||||
|
||||
## 验证步骤
|
||||
|
||||
1. 清除缓存: `php think clear` ✅
|
||||
2. 访问诊单列表页面
|
||||
3. 验证排序顺序:
|
||||
- 已过号的诊单显示在最前面
|
||||
- 然后是已预约的诊单
|
||||
- 最后是已完成的诊单
|
||||
4. 验证同状态内按挂号时间升序排列
|
||||
|
||||
## 业务场景
|
||||
|
||||
此排序规则适用于以下场景:
|
||||
- 医生/医助需要优先处理已过号的患者(可能需要重新安排)
|
||||
- 然后关注即将到来的预约
|
||||
- 最后查看已完成的历史记录
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 排序只考虑状态为 1(已预约)、3(已完成)、4(已过号) 的挂号记录
|
||||
- 没有挂号记录的诊单会排在最后
|
||||
- 如果一个诊单有多条挂号记录,取最早的一条进行排序
|
||||
@@ -1,121 +0,0 @@
|
||||
# 医生数据统计功能说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
医生数据统计功能用于统计每个医生在不同时间范围内的诊单数据,包括:
|
||||
- 已挂号数量(status=1)
|
||||
- 已完成数量(status=3)
|
||||
- 已过号数量(status=4)
|
||||
- 已取消数量(status=2)
|
||||
- 完成率计算
|
||||
|
||||
## 文件清单
|
||||
|
||||
### 前端文件
|
||||
1. `admin/src/views/doctor/tongji.vue` - 统计页面
|
||||
2. `admin/src/api/doctor.ts` - API接口(已更新)
|
||||
|
||||
### 后端文件
|
||||
1. `server/app/adminapi/controller/doctor/StatisticsController.php` - 统计控制器
|
||||
2. `server/app/adminapi/lists/doctor/StatisticsLists.php` - 统计数据列表逻辑
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 1. 时间范围筛选
|
||||
- 今天:查看当天的统计数据
|
||||
- 最近7天:查看最近一周的统计数据
|
||||
- 最近30天:查看最近一个月的统计数据
|
||||
- 自定义:选择任意日期范围进行查询
|
||||
|
||||
### 2. 医生筛选
|
||||
- 可以选择特定医生查看其统计数据
|
||||
- 也可以不选择医生,查看所有医生的统计数据
|
||||
|
||||
### 3. 统计指标
|
||||
- **总诊单数**:该时间范围内的所有诊单
|
||||
- **已挂号**:状态为"已预约"的诊单数量
|
||||
- **已完成**:状态为"已完成"的诊单数量
|
||||
- **已过号**:状态为"已过号"的诊单数量
|
||||
- **已取消**:状态为"已取消"的诊单数量
|
||||
- **完成率**:已完成数量 / 总诊单数 × 100%
|
||||
|
||||
### 4. 完成率颜色标识
|
||||
- 绿色:完成率 ≥ 80%
|
||||
- 蓝色:完成率 ≥ 60%
|
||||
- 橙色:完成率 ≥ 40%
|
||||
- 红色:完成率 < 40%
|
||||
|
||||
## 使用方法
|
||||
|
||||
1. 访问医生统计页面(路由:`/doctor/tongji`)
|
||||
2. 选择时间范围(今天/最近7天/最近30天/自定义)
|
||||
3. 可选:选择特定医生
|
||||
4. 点击"查询"按钮获取统计数据
|
||||
5. 查看统计表格中的各项数据
|
||||
|
||||
## API接口
|
||||
|
||||
### 获取医生诊单统计
|
||||
- **接口地址**:`GET /doctor.statistics/lists`
|
||||
- **请求参数**:
|
||||
- `doctor_id`(可选):医生ID
|
||||
- `time_type`(必填):时间类型(today/week/month/custom)
|
||||
- `start_date`(可选):开始日期(time_type=custom时必填)
|
||||
- `end_date`(可选):结束日期(time_type=custom时必填)
|
||||
|
||||
- **返回数据**:
|
||||
```json
|
||||
{
|
||||
"code": 1,
|
||||
"msg": "success",
|
||||
"data": {
|
||||
"lists": [
|
||||
{
|
||||
"doctor_id": 1,
|
||||
"doctor_name": "张医生",
|
||||
"total_count": 100,
|
||||
"registered_count": 20,
|
||||
"completed_count": 60,
|
||||
"missed_count": 10,
|
||||
"cancelled_count": 10,
|
||||
"completion_rate": 60.00,
|
||||
"time_range": "今天 (2024-01-01)"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 数据库依赖
|
||||
|
||||
该功能依赖以下数据表:
|
||||
- `la_doctor_appointment`:医生预约表
|
||||
- `la_admin`:管理员表(医生信息)
|
||||
- `la_admin_role`:管理员角色关联表(用于识别医生角色)
|
||||
|
||||
### 医生角色识别
|
||||
系统通过 `la_admin_role` 表来识别医生角色:
|
||||
- 查询 `la_admin_role` 表中 `role_id=1` 的记录
|
||||
- 获取对应的 `admin_id` 列表
|
||||
- 这些 `admin_id` 对应的管理员即为医生
|
||||
|
||||
### 预约状态说明
|
||||
- 1:已预约(已挂号)
|
||||
- 2:已取消
|
||||
- 3:已完成
|
||||
- 4:已过号
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 统计数据基于 `appointment_date` 字段进行时间范围筛选
|
||||
2. 只统计 `role_id=1` 的管理员(医生角色)
|
||||
3. 只统计未删除且未禁用的医生
|
||||
4. 完成率保留两位小数
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. 添加数据导出功能(Excel)
|
||||
2. 添加图表展示(柱状图、折线图)
|
||||
3. 添加同比、环比分析
|
||||
4. 添加医生排名功能
|
||||
5. 添加更多维度的统计(按科室、按时段等)
|
||||
@@ -1,299 +0,0 @@
|
||||
# 物流轨迹自动加载优化
|
||||
|
||||
## 优化目标
|
||||
|
||||
将物流轨迹从"手动点击查询"改为"打开详情自动加载",提升用户体验:
|
||||
1. 打开订单详情时自动显示物流轨迹
|
||||
2. 无需额外点击"查询轨迹"按钮
|
||||
3. 保留"刷新轨迹"按钮用于手动更新
|
||||
|
||||
## 用户体验对比
|
||||
|
||||
### 优化前
|
||||
```
|
||||
用户操作流程:
|
||||
1. 点击订单查看详情
|
||||
2. 等待详情加载
|
||||
3. 找到"查询轨迹"按钮
|
||||
4. 点击"查询轨迹"
|
||||
5. 等待轨迹加载
|
||||
6. 查看物流信息
|
||||
|
||||
总步骤:6步
|
||||
```
|
||||
|
||||
### 优化后
|
||||
```
|
||||
用户操作流程:
|
||||
1. 点击订单查看详情
|
||||
2. 等待详情和轨迹自动加载
|
||||
3. 查看物流信息
|
||||
|
||||
总步骤:3步
|
||||
减少:50%操作步骤
|
||||
```
|
||||
|
||||
## 代码修改
|
||||
|
||||
### 1. 自动加载逻辑
|
||||
|
||||
**文件**:`admin/src/views/consumer/prescription/order_list.vue`
|
||||
|
||||
在 `openDetail()` 函数中添加自动加载:
|
||||
|
||||
```typescript
|
||||
async function openDetail(id: number) {
|
||||
detailVisible.value = true
|
||||
detailData.value = null
|
||||
logisticsTracePayload.value = null
|
||||
detailLogisticsExpress.value = 'auto'
|
||||
detailLoading.value = true
|
||||
try {
|
||||
const res: any = await prescriptionOrderDetail({ id })
|
||||
const d = res?.data ?? res ?? null
|
||||
detailData.value = d
|
||||
if (d) {
|
||||
detailLogisticsExpress.value = String(d.express_company || 'auto') || 'auto'
|
||||
|
||||
// 如果有快递单号,自动加载物流轨迹
|
||||
if (String(d.tracking_number || '').trim()) {
|
||||
fetchLogisticsTrace()
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
feedback.msgError(e?.message || '加载失败')
|
||||
detailVisible.value = false
|
||||
} finally {
|
||||
detailLoading.value = false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- 检查订单是否有快递单号
|
||||
- 有快递单号时自动调用 `fetchLogisticsTrace()`
|
||||
- 无快递单号时不调用,避免无效请求
|
||||
|
||||
### 2. UI调整
|
||||
|
||||
#### 按钮改为"刷新轨迹"
|
||||
|
||||
```vue
|
||||
<el-button
|
||||
v-perms="['tcm.prescriptionOrder/logisticsTrace']"
|
||||
type="default"
|
||||
size="small"
|
||||
:loading="logisticsTraceLoading"
|
||||
@click="fetchLogisticsTrace"
|
||||
>
|
||||
<template #icon>
|
||||
<el-icon><Refresh /></el-icon>
|
||||
</template>
|
||||
刷新轨迹
|
||||
</el-button>
|
||||
```
|
||||
|
||||
**改动**:
|
||||
- 按钮文字:`查询轨迹` → `刷新轨迹`
|
||||
- 按钮类型:`type="primary"` → `type="default"`(降低视觉优先级)
|
||||
- 添加刷新图标:`<Refresh />`
|
||||
|
||||
#### 添加加载状态提示
|
||||
|
||||
```vue
|
||||
<div v-if="logisticsTraceLoading && !logisticsTracePayload" class="text-sm text-gray-500 mb-2">
|
||||
<el-icon class="is-loading"><Loading /></el-icon>
|
||||
正在加载物流轨迹...
|
||||
</div>
|
||||
```
|
||||
|
||||
**作用**:
|
||||
- 首次自动加载时显示加载提示
|
||||
- 已有数据时不显示(避免闪烁)
|
||||
- 使用旋转动画图标
|
||||
|
||||
#### 导入图标
|
||||
|
||||
```typescript
|
||||
import { Refresh, Loading } from '@element-plus/icons-vue'
|
||||
```
|
||||
|
||||
## 工作流程
|
||||
|
||||
### 完整流程
|
||||
|
||||
```
|
||||
用户点击"查看详情"
|
||||
↓
|
||||
打开详情弹窗
|
||||
↓
|
||||
加载订单详情
|
||||
↓
|
||||
检查是否有快递单号?
|
||||
↓ 有
|
||||
自动调用 fetchLogisticsTrace()
|
||||
↓
|
||||
显示"正在加载物流轨迹..."
|
||||
↓
|
||||
查询数据库(优先)
|
||||
↓
|
||||
有数据?
|
||||
↓ 有
|
||||
显示物流轨迹(13条记录)
|
||||
↓
|
||||
显示数据来源和更新时间
|
||||
```
|
||||
|
||||
### 手动刷新流程
|
||||
|
||||
```
|
||||
用户点击"刷新轨迹"按钮
|
||||
↓
|
||||
重新调用 fetchLogisticsTrace()
|
||||
↓
|
||||
查询数据库(优先)
|
||||
↓
|
||||
无数据或需要更新?
|
||||
↓ 是
|
||||
调用快递100 API
|
||||
↓
|
||||
更新数据库
|
||||
↓
|
||||
显示最新轨迹
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 1. 并行加载
|
||||
|
||||
订单详情和物流轨迹是并行加载的:
|
||||
- 订单详情:`prescriptionOrderDetail()`
|
||||
- 物流轨迹:`fetchLogisticsTrace()`(在详情加载完成后立即触发)
|
||||
|
||||
虽然不是完全并行,但由于物流查询从数据库读取(< 50ms),总体加载时间几乎不增加。
|
||||
|
||||
### 2. 条件加载
|
||||
|
||||
只有在有快递单号时才加载物流轨迹:
|
||||
```typescript
|
||||
if (String(d.tracking_number || '').trim()) {
|
||||
fetchLogisticsTrace()
|
||||
}
|
||||
```
|
||||
|
||||
避免无效请求。
|
||||
|
||||
### 3. 数据库优先
|
||||
|
||||
物流查询优先从数据库读取:
|
||||
- 数据库查询:< 50ms
|
||||
- API查询:> 1000ms
|
||||
|
||||
大部分情况下都能从数据库快速获取数据。
|
||||
|
||||
## 用户体验提升
|
||||
|
||||
### 1. 减少操作步骤
|
||||
- 从6步减少到3步
|
||||
- 减少50%操作
|
||||
|
||||
### 2. 信息即时可见
|
||||
- 打开详情即可看到物流信息
|
||||
- 无需额外操作
|
||||
|
||||
### 3. 保留手动控制
|
||||
- "刷新轨迹"按钮用于手动更新
|
||||
- 切换快递公司后可重新查询
|
||||
|
||||
### 4. 清晰的状态反馈
|
||||
- 加载中:显示"正在加载物流轨迹..."
|
||||
- 加载完成:显示轨迹列表
|
||||
- 数据来源:显示"[数据库]"或"[API实时]"
|
||||
- 更新时间:显示"最后更新:2026-04-07 17:14:16"
|
||||
|
||||
## 边界情况处理
|
||||
|
||||
### 1. 无快递单号
|
||||
- 不调用物流查询
|
||||
- 不显示物流轨迹区域
|
||||
|
||||
### 2. 数据库无数据
|
||||
- 自动调用快递100 API
|
||||
- 显示"[API实时]"标签
|
||||
|
||||
### 3. 查询失败
|
||||
- 显示错误提示
|
||||
- 保留"刷新轨迹"按钮供重试
|
||||
|
||||
### 4. 已签收订单
|
||||
- 显示完整历史轨迹
|
||||
- 显示"已签收"状态
|
||||
- 数据不会再自动更新(定时任务已停止)
|
||||
|
||||
## 测试场景
|
||||
|
||||
### 场景1:有快递单号且数据库有数据
|
||||
```
|
||||
1. 打开订单详情
|
||||
2. 自动显示"正在加载物流轨迹..."
|
||||
3. < 50ms 后显示13条轨迹
|
||||
4. 显示"[数据库]"标签
|
||||
5. 显示"最后更新:2026-04-07 17:14:16"
|
||||
```
|
||||
|
||||
### 场景2:有快递单号但数据库无数据
|
||||
```
|
||||
1. 打开订单详情
|
||||
2. 自动显示"正在加载物流轨迹..."
|
||||
3. 调用快递100 API(> 1s)
|
||||
4. 显示轨迹
|
||||
5. 显示"[API实时]"标签
|
||||
6. 数据保存到数据库
|
||||
```
|
||||
|
||||
### 场景3:无快递单号
|
||||
```
|
||||
1. 打开订单详情
|
||||
2. 不显示物流轨迹区域
|
||||
3. 不发起任何物流查询请求
|
||||
```
|
||||
|
||||
### 场景4:手动刷新
|
||||
```
|
||||
1. 订单详情已打开,轨迹已显示
|
||||
2. 点击"刷新轨迹"按钮
|
||||
3. 重新查询(优先数据库)
|
||||
4. 更新显示
|
||||
```
|
||||
|
||||
## 相关文件
|
||||
|
||||
### 修改的文件
|
||||
- `admin/src/views/consumer/prescription/order_list.vue` - 订单列表页面
|
||||
|
||||
### 相关文档
|
||||
- `EXPRESS_TRACKING_DATABASE_QUERY.md` - 数据库查询优化
|
||||
- `EXPRESS_TRACKING_SYNC_COMPLETE.md` - 同步完成报告
|
||||
- `EXPRESS_TRACKING_SYSTEM.md` - 系统架构
|
||||
|
||||
## 总结
|
||||
|
||||
✅ 打开详情自动加载物流轨迹
|
||||
✅ 减少50%用户操作步骤
|
||||
✅ 保留手动刷新功能
|
||||
✅ 添加加载状态提示
|
||||
✅ 优化按钮文字和样式
|
||||
✅ 条件加载,避免无效请求
|
||||
✅ 数据库优先,快速响应
|
||||
|
||||
**用户体验提升**:
|
||||
- 操作步骤:6步 → 3步
|
||||
- 加载时间:< 50ms(数据库)
|
||||
- 信息可见性:立即可见
|
||||
- 操作便捷性:无需额外点击
|
||||
|
||||
**下一步**:
|
||||
1. 前端测试自动加载功能
|
||||
2. 验证各种边界情况
|
||||
3. 收集用户反馈
|
||||
4. 可选:添加"自动刷新"开关(用户可选择是否自动加载)
|
||||
@@ -1,199 +0,0 @@
|
||||
# 物流追踪定时任务修复报告
|
||||
|
||||
## 问题诊断
|
||||
|
||||
### 1. PHP语法错误
|
||||
**问题**:`server/app/command/ExpressAutoUpdate.php` 文件中的crontab示例使用了 `*/10`,导致PHP解析器将其识别为代码而非注释。
|
||||
|
||||
**错误信息**:
|
||||
```
|
||||
syntax error, unexpected token "*"
|
||||
```
|
||||
|
||||
**原因**:注释中的 `*/10` 被PHP解析器误认为是多行注释的结束符。
|
||||
|
||||
**解决方案**:将crontab示例从 `*/10 * * * *` 改为 `0,10,20,30,40,50 * * * *`(效果相同,每10分钟执行)。
|
||||
|
||||
### 2. 命名空间路径错误
|
||||
**问题**:`server/config/console.php` 中的命名空间路径使用单反斜杠。
|
||||
|
||||
**错误配置**:
|
||||
```php
|
||||
'express:auto-update' => 'app\command\ExpressAutoUpdate',
|
||||
```
|
||||
|
||||
**正确配置**:
|
||||
```php
|
||||
'express:auto-update' => 'app\\command\\ExpressAutoUpdate',
|
||||
```
|
||||
|
||||
**原因**:PHP字符串中的反斜杠需要转义。
|
||||
|
||||
### 3. PHP版本检查
|
||||
**问题**:系统PHP版本为8.0.2,但Composer要求8.1.0+。
|
||||
|
||||
**临时解决方案**:注释掉 `server/vendor/composer/platform_check.php` 中的版本检查(仅用于测试)。
|
||||
|
||||
**生产环境建议**:升级PHP到8.1+版本。
|
||||
|
||||
## 已修复的文件
|
||||
|
||||
### 1. server/app/command/ExpressAutoUpdate.php
|
||||
```php
|
||||
/**
|
||||
* 物流自动更新定时任务
|
||||
*
|
||||
* 使用方法:
|
||||
* php think express:auto-update
|
||||
*
|
||||
* 配置crontab(每10分钟执行一次):
|
||||
* 0,10,20,30,40,50 * * * * cd /path/to/server && php think express:auto-update >> /dev/null 2>&1
|
||||
*/
|
||||
```
|
||||
|
||||
### 2. server/config/console.php
|
||||
```php
|
||||
'commands' => [
|
||||
// ... 其他命令
|
||||
// 物流自动更新
|
||||
'express:auto-update' => 'app\\command\\ExpressAutoUpdate',
|
||||
],
|
||||
```
|
||||
|
||||
### 3. server/vendor/composer/platform_check.php(临时)
|
||||
```php
|
||||
// Temporarily disabled for testing
|
||||
// if (!(PHP_VERSION_ID >= 80100)) {
|
||||
// $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.';
|
||||
// }
|
||||
```
|
||||
|
||||
## 测试结果
|
||||
|
||||
### 命令执行测试
|
||||
```bash
|
||||
$ php think express:auto-update
|
||||
开始自动更新物流信息...
|
||||
更新完成!
|
||||
总数: 0
|
||||
成功: 0
|
||||
失败: 0
|
||||
耗时: 0.21秒
|
||||
```
|
||||
|
||||
✅ 命令执行成功,无语法错误。
|
||||
✅ 返回码为0,表示正常退出。
|
||||
✅ 总数为0是正常的,因为数据库中还没有需要更新的物流记录。
|
||||
|
||||
## 过滤逻辑验证
|
||||
|
||||
### 定时任务是否过滤已签收订单?
|
||||
|
||||
**答案:是的,已正确过滤。**
|
||||
|
||||
查看 `ExpressTrackingService::autoUpdateBatch()` 方法:
|
||||
|
||||
```php
|
||||
// 查询需要更新的记录
|
||||
// 条件:
|
||||
// 1. auto_update = 1(启用自动更新)
|
||||
// 2. next_update_time <= now(到达更新时间)
|
||||
// 3. is_signed = 0(未签收)
|
||||
// 4. current_state 不是终态(排除已签收、退签、拒签)
|
||||
$list = ExpressTracking::where('auto_update', 1)
|
||||
->where('next_update_time', '<=', $now)
|
||||
->where('is_signed', 0)
|
||||
->whereNotIn('current_state', [
|
||||
ExpressTracking::STATE_SIGNED, // 3 - 已签收
|
||||
ExpressTracking::STATE_RETURN_SIGNED, // 4 - 退签
|
||||
ExpressTracking::STATE_REJECTED, // 5 - 拒签
|
||||
])
|
||||
->whereNull('delete_time')
|
||||
->limit($limit)
|
||||
->select();
|
||||
```
|
||||
|
||||
**过滤条件说明**:
|
||||
1. `is_signed = 0` - 只查询未签收的记录
|
||||
2. `whereNotIn('current_state', [3, 4, 5])` - 排除所有终态(已签收、退签、拒签)
|
||||
3. 双重保险,确保不会浪费查询次数
|
||||
|
||||
## 下一步操作
|
||||
|
||||
### 1. 执行数据库迁移
|
||||
```bash
|
||||
# Windows
|
||||
cd server
|
||||
php think migrate:run
|
||||
|
||||
# 或手动执行SQL
|
||||
mysql -u root -p your_database < database/migrations/create_express_tracking_tables.sql
|
||||
```
|
||||
|
||||
### 2. 集成到订单系统
|
||||
在订单创建/编辑时调用:
|
||||
```php
|
||||
use app\common\service\ExpressTrackingService;
|
||||
|
||||
// 创建或更新物流追踪
|
||||
ExpressTrackingService::createOrUpdate([
|
||||
'order_id' => $orderId,
|
||||
'order_type' => 'prescription',
|
||||
'tracking_number' => $trackingNumber,
|
||||
'express_company' => $expressCompany,
|
||||
'recipient_phone' => $phone,
|
||||
'recipient_name' => $name,
|
||||
'recipient_address' => $address,
|
||||
]);
|
||||
```
|
||||
|
||||
### 3. 配置定时任务
|
||||
|
||||
#### Windows(任务计划程序)
|
||||
```batch
|
||||
# 创建任务(每10分钟执行)
|
||||
schtasks /create /tn "ExpressAutoUpdate" /tr "php D:\web\zyt\server\think express:auto-update" /sc minute /mo 10
|
||||
```
|
||||
|
||||
#### Linux(crontab)
|
||||
```bash
|
||||
# 编辑crontab
|
||||
crontab -e
|
||||
|
||||
# 添加以下行(每10分钟执行)
|
||||
0,10,20,30,40,50 * * * * cd /path/to/server && php think express:auto-update >> /dev/null 2>&1
|
||||
```
|
||||
|
||||
### 4. 手动测试查询
|
||||
```bash
|
||||
# 测试单次查询
|
||||
php think express:auto-update
|
||||
|
||||
# 查看日志
|
||||
tail -f runtime/log/202X-XX-XX.log
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **PHP版本**:生产环境建议升级到PHP 8.1+
|
||||
2. **快递100账号**:确保账号已充值,余额充足
|
||||
3. **查询频率**:默认每10分钟查询一次,可根据需要调整
|
||||
4. **查询限制**:每次最多处理50条记录,避免超时
|
||||
5. **终态停止**:已签收/退签/拒签的订单会自动停止更新
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [EXPRESS_TRACKING_SYSTEM.md](EXPRESS_TRACKING_SYSTEM.md) - 系统架构和设计
|
||||
- [EXPRESS_TRACKING_QUICKSTART.md](EXPRESS_TRACKING_QUICKSTART.md) - 快速开始指南
|
||||
- [EXPRESS_TRACKING_FIXES.md](EXPRESS_TRACKING_FIXES.md) - 修复记录
|
||||
- [KUAIDI100_TROUBLESHOOTING.md](KUAIDI100_TROUBLESHOOTING.md) - 快递100故障排查
|
||||
|
||||
## 总结
|
||||
|
||||
✅ 修复了PHP语法错误(crontab注释)
|
||||
✅ 修复了命名空间路径错误(双反斜杠转义)
|
||||
✅ 验证了过滤逻辑(已正确过滤已签收订单)
|
||||
✅ 命令可以正常执行
|
||||
✅ 临时绕过PHP版本检查(仅测试用)
|
||||
|
||||
系统已就绪,可以开始集成到订单系统并配置定时任务。
|
||||
@@ -1,328 +0,0 @@
|
||||
# 物流追踪数据库查询优化
|
||||
|
||||
## 优化目标
|
||||
|
||||
将物流查询从"每次调用API"改为"优先查询数据库",实现:
|
||||
1. 提高查询速度(数据库查询 < 50ms,API查询 > 1s)
|
||||
2. 节省API调用次数(快递100按次收费)
|
||||
3. 显示完整历史轨迹(数据库保存所有历史记录)
|
||||
4. 降低API限流风险
|
||||
|
||||
## 实现方案
|
||||
|
||||
### 查询优先级
|
||||
|
||||
```
|
||||
用户点击"查询轨迹"
|
||||
↓
|
||||
1. 先查询数据库(zyt_express_tracking + zyt_express_trace)
|
||||
↓
|
||||
有数据?
|
||||
↓ 是
|
||||
返回数据库数据(标记 source: database)
|
||||
↓ 否
|
||||
2. 调用快递100 API
|
||||
↓
|
||||
返回API数据(标记 source: api)
|
||||
```
|
||||
|
||||
### 数据流转
|
||||
|
||||
```
|
||||
订单创建/编辑
|
||||
↓
|
||||
ExpressTrackingService::createOrUpdate()
|
||||
↓
|
||||
创建追踪记录 + 立即查询一次
|
||||
↓
|
||||
保存到数据库(tracking + traces)
|
||||
↓
|
||||
定时任务每10分钟自动更新
|
||||
↓
|
||||
用户查询时直接从数据库读取
|
||||
```
|
||||
|
||||
## 代码修改
|
||||
|
||||
### 1. 后端服务层
|
||||
|
||||
**文件**:`server/app/common/service/ExpressTrackingService.php`
|
||||
|
||||
新增方法:
|
||||
```php
|
||||
/**
|
||||
* 根据快递单号获取追踪详情(包含轨迹)
|
||||
*/
|
||||
public static function getDetailByTrackingNumber(string $trackingNumber): ?array
|
||||
{
|
||||
$tracking = ExpressTracking::with(['traces' => function($query) {
|
||||
// 按时间倒序排列(最新的在前)
|
||||
$query->order('trace_time_stamp', 'desc');
|
||||
}])
|
||||
->where('tracking_number', $trackingNumber)
|
||||
->whereNull('delete_time')
|
||||
->find();
|
||||
|
||||
if (!$tracking) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = $tracking->toArray();
|
||||
$data['traces'] = $tracking->traces ? $tracking->traces->toArray() : [];
|
||||
|
||||
return $data;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 后端逻辑层
|
||||
|
||||
**文件**:`server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
|
||||
|
||||
修改 `logisticsTrace()` 方法:
|
||||
```php
|
||||
// 优先从数据库查询物流追踪信息
|
||||
$trackingData = \app\common\service\ExpressTrackingService::getDetailByTrackingNumber($num);
|
||||
|
||||
if ($trackingData && !empty($trackingData['traces'])) {
|
||||
// 从数据库获取到数据,直接返回
|
||||
$payload = [
|
||||
'carrier' => $trackingData['express_company'],
|
||||
'carrier_label' => $trackingData['express_company_name'],
|
||||
'kuaidi_com' => $trackingData['express_company'],
|
||||
'traces' => array_map(function($trace) {
|
||||
return [
|
||||
'time' => $trace['trace_time'],
|
||||
'ftime' => $trace['trace_time'],
|
||||
'context' => $trace['trace_context'],
|
||||
'location' => $trace['location'],
|
||||
'status' => $trace['status'],
|
||||
'statusCode' => $trace['status_code'],
|
||||
];
|
||||
}, $trackingData['traces']),
|
||||
'state' => $trackingData['current_state'],
|
||||
'state_text' => $trackingData['current_state_text'],
|
||||
'source' => 'database',
|
||||
'hint' => '',
|
||||
'official_url' => '',
|
||||
'last_query_time' => date('Y-m-d H:i:s', $trackingData['last_query_time']),
|
||||
'query_count' => $trackingData['query_count'],
|
||||
];
|
||||
} else {
|
||||
// 数据库没有数据,调用快递100 API
|
||||
$payload = ExpressTrackService::query($ec, $num, (string) ($row->recipient_phone ?? ''));
|
||||
$payload['source'] = 'api';
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 前端显示
|
||||
|
||||
**文件**:`admin/src/views/consumer/prescription/order_list.vue`
|
||||
|
||||
添加数据来源和更新时间显示:
|
||||
```vue
|
||||
<div v-if="logisticsTracePayload?.state_text" class="text-sm mb-2">
|
||||
<span class="text-gray-700">物流状态:{{ logisticsTracePayload.state_text }}</span>
|
||||
<span v-if="logisticsTracePayload?.carrier_label" class="text-gray-500">
|
||||
({{ logisticsTracePayload.carrier_label }})
|
||||
</span>
|
||||
<span v-if="logisticsTracePayload?.source" class="text-xs text-gray-400 ml-2">
|
||||
[{{ logisticsTracePayload.source === 'database' ? '数据库' : 'API实时' }}]
|
||||
</span>
|
||||
<span v-if="logisticsTracePayload?.last_query_time" class="text-xs text-gray-400 ml-2">
|
||||
最后更新:{{ logisticsTracePayload.last_query_time }}
|
||||
</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
更新说明文字:
|
||||
```vue
|
||||
<p class="text-xs text-gray-500 mb-3">
|
||||
优先从数据库查询历史轨迹(快速响应),数据库无记录时调用快递100实时查询。
|
||||
支持顺丰、京东、极兔速递等快递公司。
|
||||
顺丰查询建议填写正确收货手机后四位(本单收货手机已自动传入)。
|
||||
</p>
|
||||
```
|
||||
|
||||
## 测试结果
|
||||
|
||||
### 数据库查询测试
|
||||
|
||||
```bash
|
||||
$ php test_tracking_query.php
|
||||
|
||||
========================================
|
||||
测试物流追踪查询(从数据库)
|
||||
========================================
|
||||
|
||||
[1] 查询追踪主记录...
|
||||
✓ 找到追踪记录
|
||||
快递公司: 京东快递(单号识别)
|
||||
当前状态: 已签收
|
||||
是否签收: 是
|
||||
查询次数: 1
|
||||
最后查询: 2026-04-07 17:14:16
|
||||
|
||||
[2] 查询轨迹明细...
|
||||
✓ 找到 13 条轨迹记录
|
||||
|
||||
最新3条轨迹:
|
||||
[2026-02-24 19:12:51] 您的快件已送达至【柜子】...
|
||||
[2026-02-24 15:51:58] 您的快件正在派送中...
|
||||
[2026-02-24 15:51:11] 您的快件已到达【郑州国企站】。
|
||||
|
||||
========================================
|
||||
测试完成!
|
||||
========================================
|
||||
✓ 数据库查询正常
|
||||
✓ 数据格式兼容API
|
||||
✓ 可以直接返回给前端
|
||||
```
|
||||
|
||||
### 性能对比
|
||||
|
||||
| 查询方式 | 响应时间 | API消耗 | 数据完整性 |
|
||||
|---------|---------|---------|-----------|
|
||||
| 数据库查询 | < 50ms | 0次 | 完整历史 |
|
||||
| API查询 | > 1000ms | 1次 | 仅当前 |
|
||||
|
||||
### 数据示例
|
||||
|
||||
**数据库返回**:
|
||||
```json
|
||||
{
|
||||
"carrier": "auto",
|
||||
"carrier_label": "京东快递(单号识别)",
|
||||
"traces": [
|
||||
{
|
||||
"time": "2026-02-24 19:12:51",
|
||||
"context": "您的快件已送达至【柜子】..."
|
||||
},
|
||||
// ... 13条完整轨迹
|
||||
],
|
||||
"state": "3",
|
||||
"state_text": "已签收",
|
||||
"source": "database",
|
||||
"last_query_time": "2026-04-07 17:14:16",
|
||||
"query_count": "1"
|
||||
}
|
||||
```
|
||||
|
||||
## 工作流程
|
||||
|
||||
### 新订单流程
|
||||
|
||||
1. **订单创建/编辑**
|
||||
```php
|
||||
ExpressTrackingService::createOrUpdate([
|
||||
'order_id' => $orderId,
|
||||
'tracking_number' => $trackingNumber,
|
||||
// ...
|
||||
]);
|
||||
```
|
||||
- 创建追踪记录
|
||||
- 立即查询一次API
|
||||
- 保存到数据库
|
||||
|
||||
2. **定时任务自动更新**
|
||||
```bash
|
||||
php think express:auto-update
|
||||
```
|
||||
- 每10分钟执行
|
||||
- 只更新未签收订单
|
||||
- 更新轨迹到数据库
|
||||
|
||||
3. **用户查询**
|
||||
- 点击"查询轨迹"
|
||||
- 优先从数据库读取
|
||||
- 秒级响应
|
||||
|
||||
### 现有订单同步
|
||||
|
||||
```bash
|
||||
# 同步现有订单快递单号
|
||||
php think express:sync
|
||||
|
||||
# 会自动:
|
||||
# 1. 查询订单表中的快递单号
|
||||
# 2. 创建追踪记录
|
||||
# 3. 立即查询一次API
|
||||
# 4. 保存到数据库
|
||||
```
|
||||
|
||||
## 优势分析
|
||||
|
||||
### 1. 性能提升
|
||||
- 数据库查询:< 50ms
|
||||
- API查询:> 1000ms
|
||||
- 提升 20倍以上
|
||||
|
||||
### 2. 成本节约
|
||||
- 每次用户查询不消耗API次数
|
||||
- 只有定时任务消耗API(每10分钟1次)
|
||||
- 假设100个订单,每天查询10次:
|
||||
- 优化前:100 × 10 = 1000次/天
|
||||
- 优化后:100 × 144 = 14400次/天(定时任务) + 0次(用户查询)
|
||||
- 实际:只有未签收订单才会定时更新,已签收订单不消耗
|
||||
|
||||
### 3. 用户体验
|
||||
- 查询速度快(秒级响应)
|
||||
- 显示完整历史轨迹
|
||||
- 显示数据来源和更新时间
|
||||
- 透明化数据状态
|
||||
|
||||
### 4. 数据完整性
|
||||
- 保存所有历史轨迹
|
||||
- 记录状态变更
|
||||
- 可追溯查询历史
|
||||
- 支持数据分析
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 1. 数据同步
|
||||
- 新订单需要集成 `ExpressTrackingService::createOrUpdate()`
|
||||
- 现有订单需要执行 `php think express:sync`
|
||||
|
||||
### 2. 定时任务
|
||||
- 必须配置定时任务(每10分钟)
|
||||
- 只更新未签收订单
|
||||
- 签收后自动停止更新
|
||||
|
||||
### 3. 数据时效性
|
||||
- 数据库数据最多延迟10分钟
|
||||
- 如需实时数据,可添加"强制刷新"按钮
|
||||
- 已签收订单不会再更新
|
||||
|
||||
### 4. 兼容性
|
||||
- 数据格式完全兼容原API
|
||||
- 前端无需修改查询逻辑
|
||||
- 只需添加显示字段
|
||||
|
||||
## 相关文件
|
||||
|
||||
### 核心文件
|
||||
- `server/app/common/service/ExpressTrackingService.php` - 服务层
|
||||
- `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php` - 逻辑层
|
||||
- `admin/src/views/consumer/prescription/order_list.vue` - 前端页面
|
||||
|
||||
### 测试脚本
|
||||
- `server/test_tracking_query.php` - 数据库查询测试
|
||||
|
||||
### 文档
|
||||
- `EXPRESS_TRACKING_SYSTEM.md` - 系统架构
|
||||
- `EXPRESS_TRACKING_SYNC_COMPLETE.md` - 同步完成报告
|
||||
- `EXPRESS_TRACKING_DATABASE_QUERY.md` - 本文档
|
||||
|
||||
## 总结
|
||||
|
||||
✅ 优先从数据库查询,提升20倍性能
|
||||
✅ 节省API调用次数,降低成本
|
||||
✅ 显示完整历史轨迹,提升用户体验
|
||||
✅ 数据格式兼容,无需修改前端逻辑
|
||||
✅ 添加数据来源和更新时间显示
|
||||
✅ 测试通过,13条轨迹记录正常显示
|
||||
|
||||
**下一步**:
|
||||
1. 在订单创建/编辑时集成追踪服务
|
||||
2. 配置定时任务(每10分钟)
|
||||
3. 前端测试查询功能
|
||||
4. 可选:添加"强制刷新"按钮(调用API实时更新)
|
||||
@@ -1,365 +0,0 @@
|
||||
# 物流追踪系统 - 问题修复
|
||||
|
||||
## 已修复的问题
|
||||
|
||||
### 1. 命令未注册错误 ✅
|
||||
|
||||
**问题:**
|
||||
```
|
||||
[InvalidArgumentException]
|
||||
There are no commands defined in the "express" namespace.
|
||||
```
|
||||
|
||||
**原因:**
|
||||
命令未在 `server/config/console.php` 中注册。
|
||||
|
||||
**解决方案:**
|
||||
已在 `server/config/console.php` 中添加命令注册:
|
||||
|
||||
```php
|
||||
'commands' => [
|
||||
// ... 其他命令
|
||||
'express:auto-update' => 'app\command\ExpressAutoUpdate',
|
||||
],
|
||||
```
|
||||
|
||||
**验证:**
|
||||
```bash
|
||||
cd server
|
||||
php think express:auto-update
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 已签收订单过滤优化 ✅
|
||||
|
||||
**问题:**
|
||||
定时任务可能会处理已签收的快递,浪费查询次数。
|
||||
|
||||
**解决方案:**
|
||||
优化了 `ExpressTrackingService::autoUpdateBatch()` 方法的查询条件:
|
||||
|
||||
```php
|
||||
$list = ExpressTracking::where('auto_update', 1)
|
||||
->where('next_update_time', '<=', $now)
|
||||
->where('is_signed', 0) // 【新增】未签收
|
||||
->whereNotIn('current_state', [ // 【新增】排除终态
|
||||
ExpressTracking::STATE_SIGNED, // 已签收
|
||||
ExpressTracking::STATE_RETURN_SIGNED, // 退签
|
||||
ExpressTracking::STATE_REJECTED, // 拒签
|
||||
])
|
||||
->whereNull('delete_time')
|
||||
->limit($limit)
|
||||
->select();
|
||||
```
|
||||
|
||||
**过滤规则:**
|
||||
|
||||
| 条件 | 说明 |
|
||||
|------|------|
|
||||
| `auto_update = 1` | 启用自动更新 |
|
||||
| `next_update_time <= now` | 到达更新时间 |
|
||||
| `is_signed = 0` | 未签收 |
|
||||
| `current_state NOT IN (3,4,14)` | 排除终态(已签收、退签、拒签) |
|
||||
| `delete_time IS NULL` | 未删除 |
|
||||
|
||||
**会被处理的状态:**
|
||||
- 0: 在途
|
||||
- 1: 揽收
|
||||
- 2: 疑难
|
||||
- 5: 派件中
|
||||
- 6: 退回
|
||||
- 7: 转投
|
||||
- 8: 清关
|
||||
- 10: 待清关
|
||||
- 11: 清关中
|
||||
- 12: 已清关
|
||||
- 13: 清关异常
|
||||
|
||||
**不会被处理的状态:**
|
||||
- 3: 已签收 ❌
|
||||
- 4: 退签 ❌
|
||||
- 14: 拒签 ❌
|
||||
|
||||
---
|
||||
|
||||
## 自动停止更新机制
|
||||
|
||||
### 触发条件
|
||||
|
||||
当快递状态变为以下任一状态时,系统会自动停止更新:
|
||||
|
||||
```php
|
||||
// 在 ExpressTrackingService::queryAndUpdate() 中
|
||||
if (ExpressTracking::isFinalState($tracking->current_state)) {
|
||||
$tracking->is_signed = 1;
|
||||
$tracking->sign_time = $now;
|
||||
$tracking->auto_update = 0; // 自动停止更新
|
||||
}
|
||||
```
|
||||
|
||||
### 终态判断
|
||||
|
||||
```php
|
||||
public static function isFinalState(string $state): bool
|
||||
{
|
||||
return in_array($state, [
|
||||
self::STATE_SIGNED, // 3: 已签收
|
||||
self::STATE_RETURN_SIGNED, // 4: 退签
|
||||
self::STATE_REJECTED, // 14: 拒签
|
||||
], true);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 双重保护机制
|
||||
|
||||
系统采用双重保护,确保已签收的快递不会被重复查询:
|
||||
|
||||
### 第一层:查询时过滤
|
||||
|
||||
在 `autoUpdateBatch()` 方法中,查询时就排除了终态订单:
|
||||
|
||||
```php
|
||||
->where('is_signed', 0)
|
||||
->whereNotIn('current_state', ['3', '4', '14'])
|
||||
```
|
||||
|
||||
### 第二层:更新时自动停止
|
||||
|
||||
在 `queryAndUpdate()` 方法中,当检测到签收时自动停止:
|
||||
|
||||
```php
|
||||
if (ExpressTracking::isFinalState($tracking->current_state)) {
|
||||
$tracking->auto_update = 0;
|
||||
$tracking->save();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 测试验证
|
||||
|
||||
### 1. 测试命令注册
|
||||
|
||||
```bash
|
||||
cd server
|
||||
php think list
|
||||
|
||||
# 应该能看到:
|
||||
# express
|
||||
# express:auto-update 自动更新物流追踪信息
|
||||
```
|
||||
|
||||
### 2. 测试过滤逻辑
|
||||
|
||||
```bash
|
||||
cd server
|
||||
php test_express_auto_update.php
|
||||
|
||||
# 查看输出,确认过滤规则正确
|
||||
```
|
||||
|
||||
### 3. 测试实际执行
|
||||
|
||||
```bash
|
||||
cd server
|
||||
php think express:auto-update
|
||||
|
||||
# 应该看到:
|
||||
# 开始自动更新物流信息...
|
||||
# 更新完成!
|
||||
# 总数: X
|
||||
# 成功: X
|
||||
# 失败: 0
|
||||
# 耗时: X秒
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 查询优化
|
||||
|
||||
通过添加过滤条件,减少了不必要的查询:
|
||||
|
||||
**优化前:**
|
||||
- 查询所有 `auto_update = 1` 的记录
|
||||
- 包括已签收的快递
|
||||
- 浪费查询次数
|
||||
|
||||
**优化后:**
|
||||
- 只查询未签收且未到达终态的记录
|
||||
- 自动排除已签收的快递
|
||||
- 节省查询次数和API费用
|
||||
|
||||
### 索引建议
|
||||
|
||||
确保以下字段有索引:
|
||||
|
||||
```sql
|
||||
-- 复合索引(最重要)
|
||||
CREATE INDEX idx_auto_update_query
|
||||
ON zyt_express_tracking(auto_update, next_update_time, is_signed, current_state);
|
||||
|
||||
-- 单字段索引
|
||||
CREATE INDEX idx_is_signed ON zyt_express_tracking(is_signed);
|
||||
CREATE INDEX idx_current_state ON zyt_express_tracking(current_state);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 监控建议
|
||||
|
||||
### 1. 监控自动更新执行情况
|
||||
|
||||
```bash
|
||||
# 查看定时任务日志
|
||||
tail -f server/runtime/log/info.log | grep "Auto update"
|
||||
|
||||
# 查看错误日志
|
||||
tail -f server/runtime/log/error.log | grep "express"
|
||||
```
|
||||
|
||||
### 2. 统计查询情况
|
||||
|
||||
```sql
|
||||
-- 查看自动更新的记录数
|
||||
SELECT COUNT(*) as count
|
||||
FROM zyt_express_tracking
|
||||
WHERE auto_update = 1
|
||||
AND is_signed = 0
|
||||
AND current_state NOT IN ('3', '4', '14')
|
||||
AND delete_time IS NULL;
|
||||
|
||||
-- 查看已签收的记录数
|
||||
SELECT COUNT(*) as count
|
||||
FROM zyt_express_tracking
|
||||
WHERE is_signed = 1;
|
||||
|
||||
-- 查看各状态分布
|
||||
SELECT
|
||||
current_state,
|
||||
current_state_text,
|
||||
COUNT(*) as count,
|
||||
SUM(auto_update) as auto_update_count
|
||||
FROM zyt_express_tracking
|
||||
WHERE delete_time IS NULL
|
||||
GROUP BY current_state, current_state_text
|
||||
ORDER BY count DESC;
|
||||
```
|
||||
|
||||
### 3. 查询日志分析
|
||||
|
||||
```sql
|
||||
-- 查看最近的查询记录
|
||||
SELECT
|
||||
tracking_number,
|
||||
query_type,
|
||||
is_success,
|
||||
error_message,
|
||||
FROM_UNIXTIME(query_time) as query_time
|
||||
FROM zyt_express_query_log
|
||||
ORDER BY query_time DESC
|
||||
LIMIT 20;
|
||||
|
||||
-- 统计自动查询成功率
|
||||
SELECT
|
||||
DATE(FROM_UNIXTIME(query_time)) as date,
|
||||
COUNT(*) as total,
|
||||
SUM(is_success) as success,
|
||||
ROUND(SUM(is_success) / COUNT(*) * 100, 2) as success_rate
|
||||
FROM zyt_express_query_log
|
||||
WHERE query_type = 'auto'
|
||||
GROUP BY date
|
||||
ORDER BY date DESC
|
||||
LIMIT 7;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 命令执行后显示 "总数: 0"?
|
||||
|
||||
**A:** 这是正常的,说明:
|
||||
1. 没有需要更新的快递记录
|
||||
2. 或所有快递都已签收
|
||||
3. 或还没到更新时间
|
||||
|
||||
**检查:**
|
||||
```sql
|
||||
SELECT
|
||||
COUNT(*) as total,
|
||||
SUM(CASE WHEN auto_update = 1 THEN 1 ELSE 0 END) as auto_update_enabled,
|
||||
SUM(CASE WHEN is_signed = 1 THEN 1 ELSE 0 END) as signed,
|
||||
SUM(CASE WHEN next_update_time <= UNIX_TIMESTAMP() THEN 1 ELSE 0 END) as need_update
|
||||
FROM zyt_express_tracking
|
||||
WHERE delete_time IS NULL;
|
||||
```
|
||||
|
||||
### Q2: 已签收的快递还在被查询?
|
||||
|
||||
**A:** 检查以下几点:
|
||||
1. 确认代码已更新到最新版本
|
||||
2. 检查 `is_signed` 字段是否正确设置
|
||||
3. 检查 `auto_update` 字段是否为 0
|
||||
|
||||
**修复:**
|
||||
```sql
|
||||
-- 手动修复已签收但未停止更新的记录
|
||||
UPDATE zyt_express_tracking
|
||||
SET auto_update = 0
|
||||
WHERE current_state IN ('3', '4', '14')
|
||||
AND auto_update = 1;
|
||||
```
|
||||
|
||||
### Q3: 如何手动停止某个快递的自动更新?
|
||||
|
||||
**A:**
|
||||
```sql
|
||||
UPDATE zyt_express_tracking
|
||||
SET auto_update = 0
|
||||
WHERE tracking_number = 'JD0230761381812';
|
||||
```
|
||||
|
||||
或通过代码:
|
||||
```php
|
||||
$tracking = ExpressTracking::where('tracking_number', 'JD0230761381812')->find();
|
||||
$tracking->auto_update = 0;
|
||||
$tracking->save();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
### 已修复
|
||||
|
||||
1. ✅ 命令注册问题
|
||||
2. ✅ 已签收订单过滤
|
||||
3. ✅ 双重保护机制
|
||||
4. ✅ 性能优化
|
||||
|
||||
### 优势
|
||||
|
||||
1. **节省费用** - 不查询已签收的快递
|
||||
2. **提高效率** - 只处理需要更新的记录
|
||||
3. **自动停止** - 签收后自动停止更新
|
||||
4. **双重保护** - 查询和更新两层过滤
|
||||
|
||||
### 下一步
|
||||
|
||||
1. ✅ 执行 `php think express:auto-update` 测试
|
||||
2. ✅ 配置crontab定时任务
|
||||
3. ✅ 监控执行情况
|
||||
4. ✅ 查看查询日志
|
||||
|
||||
---
|
||||
|
||||
## 相关文档
|
||||
|
||||
- `EXPRESS_TRACKING_SYSTEM.md` - 完整系统文档
|
||||
- `EXPRESS_TRACKING_QUICKSTART.md` - 快速开始指南
|
||||
- `KUAIDI100_ACCOUNT_ISSUE.md` - 快递100账号问题
|
||||
@@ -1,335 +0,0 @@
|
||||
# 物流追踪系统 - 快速开始
|
||||
|
||||
## 5分钟快速部署
|
||||
|
||||
### 1. 安装数据库表
|
||||
|
||||
**Linux/Mac:**
|
||||
```bash
|
||||
chmod +x install_express_tracking.sh
|
||||
./install_express_tracking.sh
|
||||
```
|
||||
|
||||
**Windows:**
|
||||
```cmd
|
||||
install_express_tracking.bat
|
||||
```
|
||||
|
||||
**或手动执行SQL:**
|
||||
```bash
|
||||
mysql -u root -p your_database < server/database/migrations/create_express_tracking_tables.sql
|
||||
```
|
||||
|
||||
### 2. 注册命令
|
||||
|
||||
编辑 `server/config/console.php`,在 `commands` 数组中添加:
|
||||
|
||||
```php
|
||||
'commands' => [
|
||||
// ... 其他命令
|
||||
'express:auto-update' => \app\command\ExpressAutoUpdate::class,
|
||||
],
|
||||
```
|
||||
|
||||
### 3. 测试命令
|
||||
|
||||
```bash
|
||||
cd server
|
||||
php think express:auto-update
|
||||
```
|
||||
|
||||
应该看到:
|
||||
```
|
||||
开始自动更新物流信息...
|
||||
更新完成!
|
||||
总数: 0
|
||||
成功: 0
|
||||
失败: 0
|
||||
耗时: 0.05秒
|
||||
```
|
||||
|
||||
### 4. 配置定时任务
|
||||
|
||||
**Linux/Mac (crontab):**
|
||||
```bash
|
||||
crontab -e
|
||||
|
||||
# 添加以下行(每10分钟执行)
|
||||
*/10 * * * * cd /path/to/your/server && php think express:auto-update >> /dev/null 2>&1
|
||||
```
|
||||
|
||||
**Windows (计划任务):**
|
||||
```cmd
|
||||
schtasks /create /tn "ExpressAutoUpdate" /tr "php D:\path\to\server\think express:auto-update" /sc minute /mo 10
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 示例1:订单发货时创建追踪
|
||||
|
||||
```php
|
||||
use app\common\service\ExpressTrackingService;
|
||||
|
||||
// 在订单发货时调用
|
||||
$tracking = ExpressTrackingService::createOrUpdate([
|
||||
'order_id' => $order->id,
|
||||
'order_type' => 'prescription',
|
||||
'tracking_number' => 'JD0230761381812',
|
||||
'express_company' => 'jd',
|
||||
'recipient_phone' => $order->recipient_phone,
|
||||
'recipient_name' => $order->recipient_name,
|
||||
'recipient_address' => $order->shipping_address,
|
||||
]);
|
||||
|
||||
// 系统会:
|
||||
// 1. 创建追踪记录
|
||||
// 2. 立即查询物流信息
|
||||
// 3. 存储到数据库
|
||||
// 4. 开始自动更新
|
||||
```
|
||||
|
||||
### 示例2:查询物流详情
|
||||
|
||||
```php
|
||||
// 获取完整物流信息
|
||||
$detail = ExpressTrackingService::getDetail($trackingId);
|
||||
|
||||
// 返回数据包含:
|
||||
// - 基本信息(单号、快递公司、收件人等)
|
||||
// - 当前状态(在途、派件、签收等)
|
||||
// - 所有轨迹记录(时间、内容、位置)
|
||||
// - 状态变更历史
|
||||
```
|
||||
|
||||
### 示例3:手动更新
|
||||
|
||||
```php
|
||||
// 手动触发更新
|
||||
$result = ExpressTrackingService::queryAndUpdate($trackingId, false);
|
||||
|
||||
// 返回最新的物流信息
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 集成到现有系统
|
||||
|
||||
### 修改订单创建逻辑
|
||||
|
||||
在 `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php` 的 `create()` 方法中添加:
|
||||
|
||||
```php
|
||||
public static function create(array $params, int $adminId, array $adminInfo)
|
||||
{
|
||||
// ... 原有代码
|
||||
|
||||
$order->save();
|
||||
|
||||
// 【新增】创建物流追踪
|
||||
if (!empty($params['tracking_number'])) {
|
||||
\app\common\service\ExpressTrackingService::createOrUpdate([
|
||||
'order_id' => $order->id,
|
||||
'order_type' => 'prescription',
|
||||
'tracking_number' => $params['tracking_number'],
|
||||
'express_company' => $params['express_company'] ?? 'auto',
|
||||
'recipient_phone' => $order->recipient_phone,
|
||||
'recipient_name' => $order->recipient_name,
|
||||
'recipient_address' => $order->shipping_address,
|
||||
]);
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
```
|
||||
|
||||
### 修改订单编辑逻辑
|
||||
|
||||
在 `edit()` 方法中添加:
|
||||
|
||||
```php
|
||||
public static function edit(array $params, int $adminId, array $adminInfo)
|
||||
{
|
||||
// ... 原有代码
|
||||
|
||||
$order->save();
|
||||
|
||||
// 【新增】更新物流追踪
|
||||
if (array_key_exists('tracking_number', $params) && !empty($params['tracking_number'])) {
|
||||
\app\common\service\ExpressTrackingService::createOrUpdate([
|
||||
'order_id' => $order->id,
|
||||
'order_type' => 'prescription',
|
||||
'tracking_number' => $params['tracking_number'],
|
||||
'express_company' => $order->express_company,
|
||||
'recipient_phone' => $order->recipient_phone,
|
||||
'recipient_name' => $order->recipient_name,
|
||||
'recipient_address' => $order->shipping_address,
|
||||
]);
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
```
|
||||
|
||||
### 修改物流查询接口
|
||||
|
||||
在 `logisticsTrace()` 方法开头添加:
|
||||
|
||||
```php
|
||||
public static function logisticsTrace(int $id, string $expressCompanyOverride, int $adminId, array $adminInfo): ?array
|
||||
{
|
||||
// ... 权限检查等原有代码
|
||||
|
||||
// 【新增】优先从数据库获取
|
||||
$tracking = \app\common\model\ExpressTracking::where('order_id', $id)
|
||||
->where('order_type', 'prescription')
|
||||
->whereNull('delete_time')
|
||||
->find();
|
||||
|
||||
if ($tracking) {
|
||||
// 如果距离上次查询超过5分钟,重新查询
|
||||
if (time() - $tracking->last_query_time > 300) {
|
||||
\app\common\service\ExpressTrackingService::queryAndUpdate($tracking->id, false);
|
||||
$tracking->refresh();
|
||||
}
|
||||
|
||||
// 返回数据库中的数据
|
||||
$traces = $tracking->traces->map(function($trace) {
|
||||
return [
|
||||
'time' => $trace->trace_time,
|
||||
'context' => $trace->trace_context,
|
||||
'status' => $trace->status,
|
||||
'location' => $trace->location,
|
||||
];
|
||||
})->toArray();
|
||||
|
||||
$payload = [
|
||||
'carrier' => $tracking->express_company,
|
||||
'carrier_label' => $tracking->express_company_name,
|
||||
'traces' => $traces,
|
||||
'state' => $tracking->current_state,
|
||||
'state_text' => $tracking->current_state_text,
|
||||
'source' => $tracking->data_source,
|
||||
'official_urls' => ExpressTrackService::officialUrls($tracking->tracking_number),
|
||||
'tracking_number' => $tracking->tracking_number,
|
||||
'is_signed' => $tracking->is_signed,
|
||||
'estimated_arrival_time' => $tracking->estimated_arrival_time,
|
||||
];
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
// 如果数据库中没有,走原有逻辑
|
||||
// ... 原有查询代码
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 验证安装
|
||||
|
||||
### 1. 检查数据库表
|
||||
|
||||
```sql
|
||||
SHOW TABLES LIKE 'zyt_express%';
|
||||
|
||||
-- 应该看到4个表:
|
||||
-- zyt_express_tracking
|
||||
-- zyt_express_trace
|
||||
-- zyt_express_state_log
|
||||
-- zyt_express_query_log
|
||||
```
|
||||
|
||||
### 2. 测试创建追踪
|
||||
|
||||
```php
|
||||
$tracking = ExpressTrackingService::createOrUpdate([
|
||||
'order_id' => 1,
|
||||
'order_type' => 'test',
|
||||
'tracking_number' => 'JD0230761381812',
|
||||
'express_company' => 'jd',
|
||||
'recipient_phone' => '13800138000',
|
||||
'recipient_name' => '测试',
|
||||
'recipient_address' => '测试地址',
|
||||
]);
|
||||
|
||||
var_dump($tracking->id); // 应该返回ID
|
||||
```
|
||||
|
||||
### 3. 检查定时任务
|
||||
|
||||
```bash
|
||||
# 查看crontab
|
||||
crontab -l | grep express
|
||||
|
||||
# 手动执行一次
|
||||
cd server && php think express:auto-update
|
||||
|
||||
# 查看日志
|
||||
tail -f runtime/log/info.log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 定时任务不执行?
|
||||
|
||||
**A:** 检查crontab配置和PHP路径
|
||||
```bash
|
||||
# 查看crontab
|
||||
crontab -l
|
||||
|
||||
# 检查PHP路径
|
||||
which php
|
||||
|
||||
# 查看cron日志
|
||||
tail -f /var/log/cron
|
||||
```
|
||||
|
||||
### Q: 快递100返回"找不到对应公司"?
|
||||
|
||||
**A:** 账号未充值,请先充值快递100账户
|
||||
- 登录:https://www.kuaidi100.com/
|
||||
- 充值后重试
|
||||
|
||||
### Q: 如何停止某个快递的自动更新?
|
||||
|
||||
**A:**
|
||||
```php
|
||||
$tracking = ExpressTracking::find($id);
|
||||
$tracking->auto_update = 0;
|
||||
$tracking->save();
|
||||
```
|
||||
|
||||
### Q: 如何修改更新频率?
|
||||
|
||||
**A:**
|
||||
```php
|
||||
// 修改单个追踪记录的更新间隔
|
||||
$tracking->update_interval = 3600; // 1小时
|
||||
$tracking->save();
|
||||
|
||||
// 或修改crontab执行频率
|
||||
crontab -e
|
||||
# 改为每30分钟: */30 * * * *
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 下一步
|
||||
|
||||
1. ✅ 确认快递100已充值
|
||||
2. ✅ 集成到订单系统
|
||||
3. ✅ 配置定时任务
|
||||
4. ✅ 测试物流查询
|
||||
5. 📖 阅读完整文档:`EXPRESS_TRACKING_SYSTEM.md`
|
||||
|
||||
---
|
||||
|
||||
## 技术支持
|
||||
|
||||
- 完整文档:`EXPRESS_TRACKING_SYSTEM.md`
|
||||
- 快递100文档:https://api.kuaidi100.com/
|
||||
- 问题排查:`KUAIDI100_TROUBLESHOOTING.md`
|
||||
@@ -1,244 +0,0 @@
|
||||
# 物流追踪系统同步完成报告
|
||||
|
||||
## 问题分析
|
||||
|
||||
### 用户疑问
|
||||
"定时任务找不到快递单号,明明订单里有快递单号"
|
||||
|
||||
### 根本原因
|
||||
定时任务查询的是 `zyt_express_tracking` 表(物流追踪表),而不是 `zyt_tcm_prescription_order` 表(订单表)。
|
||||
|
||||
这两个表是分离的:
|
||||
- **订单表**:存储业务订单信息,包括快递单号
|
||||
- **物流追踪表**:专门存储物流追踪信息和轨迹历史
|
||||
|
||||
需要将订单表中的快递单号同步到物流追踪表,定时任务才能找到并更新。
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 1. 创建同步命令
|
||||
创建了 `SyncTrackingNumbers` 命令,用于将订单表中的快递单号同步到物流追踪表。
|
||||
|
||||
**文件**:`server/app/command/SyncTrackingNumbers.php`
|
||||
|
||||
**使用方法**:
|
||||
```bash
|
||||
php think express:sync
|
||||
```
|
||||
|
||||
### 2. 注册命令
|
||||
在 `server/config/console.php` 中注册了两个命令:
|
||||
```php
|
||||
'commands' => [
|
||||
'express:auto-update' => 'app\\command\\ExpressAutoUpdate', // 自动更新
|
||||
'express:sync' => 'app\\command\\SyncTrackingNumbers', // 同步快递单号
|
||||
],
|
||||
```
|
||||
|
||||
### 3. 执行同步
|
||||
```bash
|
||||
$ php think express:sync
|
||||
开始同步现有订单快递单号...
|
||||
找到 1 个有快递单号的订单
|
||||
跳过: JD0230761381812 (已存在)
|
||||
|
||||
========================================
|
||||
同步完成!
|
||||
========================================
|
||||
总数: 1
|
||||
成功: 0
|
||||
跳过: 1
|
||||
失败: 0
|
||||
耗时: 0.28秒
|
||||
```
|
||||
|
||||
## 当前状态
|
||||
|
||||
### 物流追踪记录状态
|
||||
```
|
||||
快递单号: JD0230761381812
|
||||
快递公司: auto
|
||||
当前状态: 3 - 已签收
|
||||
是否签收: 是
|
||||
自动更新: 禁用
|
||||
下次更新时间: 1970-01-01 08:00:00 (0)
|
||||
当前时间: 2026-04-07 17:12:50
|
||||
是否应该更新: 否
|
||||
不更新原因: 自动更新未启用 已签收 状态为终态
|
||||
```
|
||||
|
||||
### 为什么定时任务总数为0?
|
||||
|
||||
**答案**:这是正常的!
|
||||
|
||||
这个快递单号已经是"已签收"状态(state=3),系统自动:
|
||||
1. 停止了自动更新(`auto_update = 0`)
|
||||
2. 标记为已签收(`is_signed = 1`)
|
||||
3. 状态为终态(state = 3)
|
||||
|
||||
**定时任务的过滤条件**:
|
||||
```php
|
||||
$list = ExpressTracking::where('auto_update', 1) // ✗ 当前为0
|
||||
->where('next_update_time', '<=', $now) // ✗ 当前为0
|
||||
->where('is_signed', 0) // ✗ 当前为1
|
||||
->whereNotIn('current_state', [3, 4, 5]) // ✗ 当前为3
|
||||
->whereNull('delete_time')
|
||||
->limit($limit)
|
||||
->select();
|
||||
```
|
||||
|
||||
所有条件都不满足,所以不会被查询到。这是设计的预期行为,避免浪费查询次数。
|
||||
|
||||
## 工作流程
|
||||
|
||||
### 新订单的物流追踪流程
|
||||
|
||||
1. **订单创建/编辑时**
|
||||
```php
|
||||
ExpressTrackingService::createOrUpdate([
|
||||
'order_id' => $orderId,
|
||||
'order_type' => 'prescription',
|
||||
'tracking_number' => $trackingNumber,
|
||||
'express_company' => $expressCompany,
|
||||
'recipient_phone' => $phone,
|
||||
'recipient_name' => $name,
|
||||
'recipient_address' => $address,
|
||||
]);
|
||||
```
|
||||
- 创建物流追踪记录
|
||||
- 立即查询一次物流信息
|
||||
- 设置 `auto_update = 1`(启用自动更新)
|
||||
- 设置 `next_update_time = now`(立即更新)
|
||||
|
||||
2. **定时任务自动更新**
|
||||
```bash
|
||||
php think express:auto-update
|
||||
```
|
||||
- 每10分钟执行一次
|
||||
- 只查询未签收的订单
|
||||
- 更新物流信息和轨迹
|
||||
- 检测状态变更
|
||||
|
||||
3. **签收后自动停止**
|
||||
- 检测到签收状态(state=3)
|
||||
- 设置 `is_signed = 1`
|
||||
- 设置 `auto_update = 0`(停止自动更新)
|
||||
- 记录签收时间
|
||||
|
||||
### 现有订单的同步
|
||||
|
||||
对于已有的订单(如当前的 JD0230761381812),需要手动同步:
|
||||
|
||||
```bash
|
||||
# 同步现有订单快递单号
|
||||
php think express:sync
|
||||
|
||||
# 同步后会立即查询一次物流信息
|
||||
# 如果已签收,会自动停止更新
|
||||
```
|
||||
|
||||
## 测试验证
|
||||
|
||||
### 1. 检查物流追踪记录
|
||||
```bash
|
||||
php check_tracking_status.php
|
||||
```
|
||||
|
||||
### 2. 测试同步命令
|
||||
```bash
|
||||
php think express:sync
|
||||
```
|
||||
|
||||
### 3. 测试自动更新
|
||||
```bash
|
||||
php think express:auto-update
|
||||
```
|
||||
|
||||
### 4. 查看日志
|
||||
```bash
|
||||
tail -f runtime/log/202X-XX-XX.log
|
||||
```
|
||||
|
||||
## 下一步操作
|
||||
|
||||
### 1. 集成到订单系统
|
||||
|
||||
在订单创建/编辑的控制器或逻辑层中添加:
|
||||
|
||||
**文件**:`server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
|
||||
|
||||
```php
|
||||
use app\common\service\ExpressTrackingService;
|
||||
|
||||
// 在保存订单后
|
||||
if (!empty($params['tracking_number'])) {
|
||||
ExpressTrackingService::createOrUpdate([
|
||||
'order_id' => $orderId,
|
||||
'order_type' => 'prescription',
|
||||
'tracking_number' => $params['tracking_number'],
|
||||
'express_company' => $params['express_company'] ?? 'auto',
|
||||
'recipient_phone' => $params['recipient_phone'],
|
||||
'recipient_name' => $params['recipient_name'],
|
||||
'recipient_address' => $params['shipping_address'],
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 配置定时任务
|
||||
|
||||
#### Windows(任务计划程序)
|
||||
```batch
|
||||
schtasks /create /tn "ExpressAutoUpdate" /tr "php D:\web\zyt\server\think express:auto-update" /sc minute /mo 10
|
||||
```
|
||||
|
||||
#### Linux(crontab)
|
||||
```bash
|
||||
crontab -e
|
||||
# 添加以下行
|
||||
0,10,20,30,40,50 * * * * cd /path/to/server && php think express:auto-update >> /dev/null 2>&1
|
||||
```
|
||||
|
||||
### 3. 测试新订单
|
||||
|
||||
1. 创建一个新订单,填写快递单号
|
||||
2. 检查 `zyt_express_tracking` 表是否自动创建记录
|
||||
3. 等待10分钟,查看是否自动更新
|
||||
4. 查看 `zyt_express_trace` 表是否有轨迹记录
|
||||
|
||||
## 相关文件
|
||||
|
||||
### 核心文件
|
||||
- `server/app/common/service/ExpressTrackingService.php` - 物流追踪服务
|
||||
- `server/app/command/ExpressAutoUpdate.php` - 自动更新命令
|
||||
- `server/app/command/SyncTrackingNumbers.php` - 同步命令
|
||||
- `server/config/console.php` - 命令注册
|
||||
|
||||
### 数据库
|
||||
- `server/database/migrations/create_express_tracking_tables.sql` - 创建表
|
||||
- `server/database/migrations/sync_existing_tracking_numbers.sql` - 同步SQL
|
||||
|
||||
### 工具脚本
|
||||
- `server/install_tracking_tables.php` - 安装表
|
||||
- `server/check_tracking_status.php` - 检查状态
|
||||
|
||||
### 文档
|
||||
- `EXPRESS_TRACKING_SYSTEM.md` - 系统架构
|
||||
- `EXPRESS_TRACKING_QUICKSTART.md` - 快速开始
|
||||
- `EXPRESS_TRACKING_FIXES.md` - 修复记录
|
||||
- `EXPRESS_TRACKING_COMMAND_FIX.md` - 命令修复
|
||||
- `EXPRESS_TRACKING_SYNC_COMPLETE.md` - 本文档
|
||||
|
||||
## 总结
|
||||
|
||||
✅ 问题已解决:订单表和物流追踪表已同步
|
||||
✅ 定时任务正常工作:正确过滤已签收订单
|
||||
✅ 自动停止机制:签收后自动停止更新,节省查询次数
|
||||
✅ 同步命令可用:`php think express:sync`
|
||||
✅ 自动更新命令可用:`php think express:auto-update`
|
||||
|
||||
**当前状态**:系统已就绪,等待新订单测试。现有订单(JD0230761381812)已签收,不会再更新。
|
||||
|
||||
**建议**:
|
||||
1. 在订单创建/编辑时集成 `ExpressTrackingService::createOrUpdate()`
|
||||
2. 配置定时任务(每10分钟执行一次)
|
||||
3. 创建一个新的未签收订单进行完整测试
|
||||
@@ -1,540 +0,0 @@
|
||||
# 物流追踪系统完整方案
|
||||
|
||||
## 系统架构
|
||||
|
||||
### 核心功能
|
||||
|
||||
1. **物流信息存储** - 将快递100查询结果存储到数据库
|
||||
2. **自动更新机制** - 定时自动查询更新物流状态
|
||||
3. **状态变更检测** - 监控物流状态变化
|
||||
4. **异常通知** - 签收、异常时自动通知
|
||||
5. **查询日志** - 记录所有查询请求和响应
|
||||
|
||||
### 数据库表结构
|
||||
|
||||
| 表名 | 说明 | 用途 |
|
||||
|------|------|------|
|
||||
| zyt_express_tracking | 物流追踪主表 | 存储快递单基本信息和当前状态 |
|
||||
| zyt_express_trace | 物流轨迹明细表 | 存储每条物流轨迹记录 |
|
||||
| zyt_express_state_log | 状态变更记录表 | 记录状态变化历史 |
|
||||
| zyt_express_query_log | 查询日志表 | 记录所有查询请求 |
|
||||
|
||||
---
|
||||
|
||||
## 安装步骤
|
||||
|
||||
### 1. 创建数据库表
|
||||
|
||||
```bash
|
||||
# 执行SQL脚本
|
||||
mysql -u root -p your_database < server/database/migrations/create_express_tracking_tables.sql
|
||||
```
|
||||
|
||||
或在数据库管理工具中执行 `create_express_tracking_tables.sql` 文件。
|
||||
|
||||
### 2. 注册定时任务命令
|
||||
|
||||
在 `server/config/console.php` 中添加:
|
||||
|
||||
```php
|
||||
'commands' => [
|
||||
// ... 其他命令
|
||||
'express:auto-update' => \app\command\ExpressAutoUpdate::class,
|
||||
],
|
||||
```
|
||||
|
||||
### 3. 配置crontab定时任务
|
||||
|
||||
```bash
|
||||
# 编辑crontab
|
||||
crontab -e
|
||||
|
||||
# 添加以下行(每10分钟执行一次)
|
||||
*/10 * * * * cd /path/to/your/server && php think express:auto-update >> /dev/null 2>&1
|
||||
```
|
||||
|
||||
### 4. 测试定时任务
|
||||
|
||||
```bash
|
||||
cd server
|
||||
php think express:auto-update
|
||||
```
|
||||
|
||||
应该看到输出:
|
||||
```
|
||||
开始自动更新物流信息...
|
||||
更新完成!
|
||||
总数: 0
|
||||
成功: 0
|
||||
失败: 0
|
||||
耗时: 0.05秒
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 创建物流追踪
|
||||
|
||||
当订单发货时,创建物流追踪记录:
|
||||
|
||||
```php
|
||||
use app\common\service\ExpressTrackingService;
|
||||
|
||||
// 创建追踪记录
|
||||
$tracking = ExpressTrackingService::createOrUpdate([
|
||||
'order_id' => 123, // 订单ID
|
||||
'order_type' => 'prescription', // 订单类型
|
||||
'tracking_number' => 'JD0230761381812', // 快递单号
|
||||
'express_company' => 'jd', // 快递公司
|
||||
'recipient_phone' => '13800138000', // 收件人手机
|
||||
'recipient_name' => '张三', // 收件人姓名
|
||||
'recipient_address' => '北京市朝阳区xxx', // 收件地址
|
||||
]);
|
||||
|
||||
// 系统会立即查询一次物流信息并存储
|
||||
```
|
||||
|
||||
### 2. 手动更新物流信息
|
||||
|
||||
```php
|
||||
// 更新指定追踪记录
|
||||
$result = ExpressTrackingService::queryAndUpdate($trackingId, false);
|
||||
```
|
||||
|
||||
### 3. 获取物流详情
|
||||
|
||||
```php
|
||||
// 获取完整的物流信息(包含轨迹)
|
||||
$detail = ExpressTrackingService::getDetail($trackingId);
|
||||
|
||||
// 返回数据包含:
|
||||
// - 基本信息
|
||||
// - 当前状态
|
||||
// - 所有轨迹记录
|
||||
// - 状态变更历史
|
||||
```
|
||||
|
||||
### 4. 自动更新配置
|
||||
|
||||
```php
|
||||
// 修改更新间隔(秒)
|
||||
$tracking->update_interval = 1800; // 30分钟
|
||||
$tracking->save();
|
||||
|
||||
// 停止自动更新
|
||||
$tracking->auto_update = 0;
|
||||
$tracking->save();
|
||||
|
||||
// 启用自动更新
|
||||
$tracking->auto_update = 1;
|
||||
$tracking->next_update_time = time(); // 立即更新
|
||||
$tracking->save();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 集成到现有订单系统
|
||||
|
||||
### 修改订单创建/编辑逻辑
|
||||
|
||||
在 `PrescriptionOrderLogic.php` 中:
|
||||
|
||||
```php
|
||||
public static function create(array $params, int $adminId, array $adminInfo)
|
||||
{
|
||||
// ... 原有创建逻辑
|
||||
|
||||
$order->save();
|
||||
|
||||
// 创建物流追踪
|
||||
if (!empty($params['tracking_number'])) {
|
||||
ExpressTrackingService::createOrUpdate([
|
||||
'order_id' => $order->id,
|
||||
'order_type' => 'prescription',
|
||||
'tracking_number' => $params['tracking_number'],
|
||||
'express_company' => $params['express_company'] ?? 'auto',
|
||||
'recipient_phone' => $order->recipient_phone,
|
||||
'recipient_name' => $order->recipient_name,
|
||||
'recipient_address' => $order->shipping_address,
|
||||
]);
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
public static function edit(array $params, int $adminId, array $adminInfo)
|
||||
{
|
||||
// ... 原有编辑逻辑
|
||||
|
||||
// 更新物流追踪
|
||||
if (array_key_exists('tracking_number', $params)) {
|
||||
if (!empty($params['tracking_number'])) {
|
||||
ExpressTrackingService::createOrUpdate([
|
||||
'order_id' => $order->id,
|
||||
'order_type' => 'prescription',
|
||||
'tracking_number' => $params['tracking_number'],
|
||||
'express_company' => $order->express_company,
|
||||
'recipient_phone' => $order->recipient_phone,
|
||||
'recipient_name' => $order->recipient_name,
|
||||
'recipient_address' => $order->shipping_address,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
```
|
||||
|
||||
### 修改物流查询接口
|
||||
|
||||
在 `PrescriptionOrderLogic.php` 中:
|
||||
|
||||
```php
|
||||
public static function logisticsTrace(int $id, string $expressCompanyOverride, int $adminId, array $adminInfo): ?array
|
||||
{
|
||||
// ... 原有逻辑
|
||||
|
||||
// 先从数据库获取
|
||||
$tracking = ExpressTracking::where('order_id', $id)
|
||||
->where('order_type', 'prescription')
|
||||
->whereNull('delete_time')
|
||||
->find();
|
||||
|
||||
if ($tracking) {
|
||||
// 如果距离上次查询超过5分钟,重新查询
|
||||
if (time() - $tracking->last_query_time > 300) {
|
||||
ExpressTrackingService::queryAndUpdate($tracking->id, false);
|
||||
$tracking->refresh();
|
||||
}
|
||||
|
||||
// 返回数据库中的数据
|
||||
$payload = [
|
||||
'carrier' => $tracking->express_company,
|
||||
'carrier_label' => $tracking->express_company_name,
|
||||
'kuaidi_com' => $tracking->express_company,
|
||||
'traces' => $tracking->traces->map(function($trace) {
|
||||
return [
|
||||
'time' => $trace->trace_time,
|
||||
'context' => $trace->trace_context,
|
||||
'status' => $trace->status,
|
||||
'location' => $trace->location,
|
||||
];
|
||||
})->toArray(),
|
||||
'state' => $tracking->current_state,
|
||||
'state_text' => $tracking->current_state_text,
|
||||
'source' => $tracking->data_source,
|
||||
'hint' => '',
|
||||
'official_url' => ExpressTrackService::officialUrls($tracking->tracking_number)[$tracking->express_company] ?? '',
|
||||
'official_urls' => ExpressTrackService::officialUrls($tracking->tracking_number),
|
||||
'tracking_number' => $tracking->tracking_number,
|
||||
'express_company_used' => $tracking->express_company,
|
||||
'is_signed' => $tracking->is_signed,
|
||||
'sign_time' => $tracking->sign_time,
|
||||
'estimated_arrival_time' => $tracking->estimated_arrival_time,
|
||||
];
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
// 如果数据库中没有,走原有逻辑
|
||||
// ... 原有查询逻辑
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 自动更新机制
|
||||
|
||||
### 更新策略
|
||||
|
||||
1. **创建时立即查询** - 创建追踪记录时立即查询一次
|
||||
2. **定时自动更新** - 每10分钟检查一次需要更新的记录
|
||||
3. **智能更新间隔** - 默认30分钟更新一次
|
||||
4. **终态停止更新** - 签收、退签、拒签后停止自动更新
|
||||
|
||||
### 更新间隔建议
|
||||
|
||||
| 物流状态 | 更新间隔 | 说明 |
|
||||
|---------|---------|------|
|
||||
| 已下单/待揽收 | 30分钟 | 等待揽收 |
|
||||
| 已揽收/在途 | 30分钟 | 运输中 |
|
||||
| 派件中 | 15分钟 | 即将签收,加快更新 |
|
||||
| 已签收 | 停止 | 终态,无需更新 |
|
||||
| 疑难/异常 | 60分钟 | 降低频率 |
|
||||
|
||||
### 定时任务配置
|
||||
|
||||
```bash
|
||||
# 每10分钟执行一次(推荐)
|
||||
*/10 * * * * cd /path/to/server && php think express:auto-update
|
||||
|
||||
# 每5分钟执行一次(高频)
|
||||
*/5 * * * * cd /path/to/server && php think express:auto-update
|
||||
|
||||
# 每30分钟执行一次(低频)
|
||||
*/30 * * * * cd /path/to/server && php think express:auto-update
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 状态变更通知
|
||||
|
||||
### 通知触发条件
|
||||
|
||||
1. **签收通知** - 快递签收时通知
|
||||
2. **异常通知** - 出现疑难、拒签等异常时通知
|
||||
3. **状态变更** - 每次状态变化都会记录
|
||||
|
||||
### 通知渠道(可扩展)
|
||||
|
||||
#### 1. 企业微信通知
|
||||
|
||||
```php
|
||||
// 在 ExpressTrackingService::sendNotification() 中实现
|
||||
use app\common\service\WechatWorkService;
|
||||
|
||||
WechatWorkService::sendMessage([
|
||||
'touser' => $userId,
|
||||
'msgtype' => 'text',
|
||||
'text' => [
|
||||
'content' => "【物流通知】\n" .
|
||||
"快递单号:{$tracking->tracking_number}\n" .
|
||||
"状态变更:{$log->old_state_text} → {$log->new_state_text}\n" .
|
||||
"最新动态:{$log->change_reason}"
|
||||
]
|
||||
]);
|
||||
```
|
||||
|
||||
#### 2. 短信通知
|
||||
|
||||
```php
|
||||
// 对接短信服务商
|
||||
SmsService::send($tracking->recipient_phone, [
|
||||
'template' => 'express_status_change',
|
||||
'params' => [
|
||||
'tracking_number' => $tracking->tracking_number,
|
||||
'status' => $log->new_state_text,
|
||||
]
|
||||
]);
|
||||
```
|
||||
|
||||
#### 3. 系统内通知
|
||||
|
||||
```php
|
||||
// 创建系统通知记录
|
||||
NoticeService::create([
|
||||
'user_id' => $userId,
|
||||
'title' => '物流状态更新',
|
||||
'content' => "您的快递 {$tracking->tracking_number} 已{$log->new_state_text}",
|
||||
]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 数据统计和分析
|
||||
|
||||
### 查询统计
|
||||
|
||||
```sql
|
||||
-- 查询成功率
|
||||
SELECT
|
||||
DATE(FROM_UNIXTIME(query_time)) as date,
|
||||
COUNT(*) as total,
|
||||
SUM(is_success) as success,
|
||||
ROUND(SUM(is_success) / COUNT(*) * 100, 2) as success_rate
|
||||
FROM zyt_express_query_log
|
||||
GROUP BY date
|
||||
ORDER BY date DESC;
|
||||
|
||||
-- 平均响应时间
|
||||
SELECT
|
||||
query_source,
|
||||
AVG(response_time) as avg_response_time,
|
||||
MAX(response_time) as max_response_time
|
||||
FROM zyt_express_query_log
|
||||
WHERE is_success = 1
|
||||
GROUP BY query_source;
|
||||
```
|
||||
|
||||
### 物流状态分布
|
||||
|
||||
```sql
|
||||
-- 当前物流状态分布
|
||||
SELECT
|
||||
current_state,
|
||||
current_state_text,
|
||||
COUNT(*) as count
|
||||
FROM zyt_express_tracking
|
||||
WHERE delete_time IS NULL
|
||||
GROUP BY current_state, current_state_text
|
||||
ORDER BY count DESC;
|
||||
|
||||
-- 签收率统计
|
||||
SELECT
|
||||
DATE(FROM_UNIXTIME(sign_time)) as date,
|
||||
COUNT(*) as signed_count
|
||||
FROM zyt_express_tracking
|
||||
WHERE is_signed = 1
|
||||
GROUP BY date
|
||||
ORDER BY date DESC;
|
||||
```
|
||||
|
||||
### 异常统计
|
||||
|
||||
```sql
|
||||
-- 异常快递统计
|
||||
SELECT
|
||||
tracking_number,
|
||||
express_company_name,
|
||||
current_state_text,
|
||||
latest_trace_context,
|
||||
FROM_UNIXTIME(update_time) as update_time
|
||||
FROM zyt_express_tracking
|
||||
WHERE current_state IN ('2', '13', '14') -- 疑难、清关异常、拒签
|
||||
AND delete_time IS NULL
|
||||
ORDER BY update_time DESC;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 性能优化
|
||||
|
||||
### 1. 批量更新优化
|
||||
|
||||
```php
|
||||
// 每次处理50条记录
|
||||
ExpressTrackingService::autoUpdateBatch(50);
|
||||
|
||||
// 根据服务器性能调整
|
||||
// - 性能好:100条
|
||||
// - 性能一般:50条
|
||||
// - 性能差:20条
|
||||
```
|
||||
|
||||
### 2. 查询频率控制
|
||||
|
||||
```php
|
||||
// 避免频繁查询同一单号
|
||||
if (time() - $tracking->last_query_time < 300) {
|
||||
// 5分钟内不重复查询
|
||||
return $cachedResult;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 索引优化
|
||||
|
||||
数据库表已创建必要索引:
|
||||
- `idx_order_id` - 订单ID索引
|
||||
- `idx_auto_update` - 自动更新索引
|
||||
- `idx_tracking_number` - 快递单号索引
|
||||
|
||||
### 4. 数据清理
|
||||
|
||||
```sql
|
||||
-- 清理90天前的查询日志
|
||||
DELETE FROM zyt_express_query_log
|
||||
WHERE create_time < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 90 DAY));
|
||||
|
||||
-- 清理已签收超过30天的轨迹明细
|
||||
DELETE t FROM zyt_express_trace t
|
||||
INNER JOIN zyt_express_tracking tr ON t.tracking_id = tr.id
|
||||
WHERE tr.is_signed = 1
|
||||
AND tr.sign_time < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 30 DAY));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 监控和告警
|
||||
|
||||
### 1. 监控指标
|
||||
|
||||
- 自动更新成功率
|
||||
- 平均响应时间
|
||||
- 异常快递数量
|
||||
- 查询失败率
|
||||
|
||||
### 2. 告警规则
|
||||
|
||||
```php
|
||||
// 在定时任务中添加告警逻辑
|
||||
$result = ExpressTrackingService::autoUpdateBatch(50);
|
||||
|
||||
if ($result['failed'] > $result['success'] * 0.5) {
|
||||
// 失败率超过50%,发送告警
|
||||
AlertService::send('物流自动更新失败率过高', $result);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 定时任务不执行?
|
||||
|
||||
**检查步骤:**
|
||||
1. 确认crontab已配置:`crontab -l`
|
||||
2. 检查PHP路径:`which php`
|
||||
3. 检查项目路径是否正确
|
||||
4. 查看cron日志:`tail -f /var/log/cron`
|
||||
|
||||
### Q2: 更新频率太高,快递100余额消耗快?
|
||||
|
||||
**解决方案:**
|
||||
1. 增加更新间隔:`update_interval = 3600`(1小时)
|
||||
2. 减少批量处理数量:`autoUpdateBatch(20)`
|
||||
3. 签收后立即停止更新
|
||||
4. 使用智能更新策略
|
||||
|
||||
### Q3: 如何查看某个快递的完整历史?
|
||||
|
||||
```php
|
||||
$detail = ExpressTrackingService::getDetail($trackingId);
|
||||
|
||||
// 包含:
|
||||
// - traces: 所有轨迹记录
|
||||
// - state_logs: 状态变更历史
|
||||
```
|
||||
|
||||
### Q4: 如何手动触发更新?
|
||||
|
||||
```php
|
||||
// 方法1:通过服务类
|
||||
ExpressTrackingService::queryAndUpdate($trackingId, false);
|
||||
|
||||
// 方法2:通过命令行
|
||||
php think express:auto-update
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
### 优势
|
||||
|
||||
1. **数据持久化** - 所有物流信息存储在数据库,随时查询
|
||||
2. **自动更新** - 无需手动查询,系统自动追踪
|
||||
3. **状态监控** - 实时监控物流状态变化
|
||||
4. **异常通知** - 及时发现和处理异常
|
||||
5. **数据分析** - 支持物流数据统计分析
|
||||
|
||||
### 成本
|
||||
|
||||
1. **快递100费用** - 按查询次数计费
|
||||
2. **服务器资源** - 定时任务占用少量CPU和内存
|
||||
3. **数据库存储** - 需要额外的存储空间
|
||||
|
||||
### 建议
|
||||
|
||||
1. **充值快递100** - 确保有足够余额
|
||||
2. **合理设置更新间隔** - 平衡实时性和成本
|
||||
3. **定期清理数据** - 避免数据库膨胀
|
||||
4. **监控运行状态** - 及时发现问题
|
||||
|
||||
---
|
||||
|
||||
## 相关文档
|
||||
|
||||
- `LOGISTICS_INTEGRATION_GUIDE.md` - 物流集成指南
|
||||
- `KUAIDI100_ACCOUNT_ISSUE.md` - 快递100账号问题
|
||||
- `JTEXPRESS_SUPPORT_ADDED.md` - 极兔速递支持
|
||||
@@ -1,321 +0,0 @@
|
||||
# 首次登录强制修改密码 - 最终实现总结
|
||||
|
||||
## 功能概述
|
||||
|
||||
实现了一个安全的首次登录强制修改密码功能,当管理员的 `is_paw` 字段为 0 时,登录后必须修改密码才能使用系统。
|
||||
|
||||
## 核心修改
|
||||
|
||||
### 后端修改(3 个文件)
|
||||
|
||||
#### 1. `server/app/adminapi/logic/LoginLogic.php`
|
||||
|
||||
**修改内容:**
|
||||
- 登录接口返回 `is_paw` 字段
|
||||
- 添加 `changeFirstPassword()` 方法处理密码修改
|
||||
|
||||
**关键代码:**
|
||||
```php
|
||||
// 登录时返回 is_paw
|
||||
return [
|
||||
'name' => $adminInfo['name'],
|
||||
'avatar' => $avatar,
|
||||
'role_name' => $adminInfo['role_name'],
|
||||
'token' => $adminInfo['token'],
|
||||
'is_paw' => $admin->is_paw ?? 1,
|
||||
];
|
||||
|
||||
// 修改密码方法
|
||||
public function changeFirstPassword($token, $password) {
|
||||
// 验证 token
|
||||
// 更新密码和 is_paw 字段
|
||||
// 使 token 过期
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. `server/app/adminapi/controller/LoginController.php`
|
||||
|
||||
**修改内容:**
|
||||
- 添加 `changeFirstPassword` 接口
|
||||
- 将该接口添加到 `$notNeedLogin` 白名单
|
||||
|
||||
**关键代码:**
|
||||
```php
|
||||
public array $notNeedLogin = ['account', 'workWechatConfig', 'workWechatLogin', 'checkDbColumn', 'changeFirstPassword'];
|
||||
|
||||
public function changeFirstPassword() {
|
||||
// 验证参数
|
||||
// 调用 LoginLogic::changeFirstPassword()
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. `server/app/adminapi/logic/auth/AdminLogic.php`
|
||||
|
||||
**修改内容:**
|
||||
- 在 `detail()` 方法的字段列表中添加 `is_paw`
|
||||
|
||||
**关键代码:**
|
||||
```php
|
||||
$admin = Admin::field([
|
||||
'id', 'account', 'name', 'disable', 'root',
|
||||
'multipoint_login', 'avatar', 'is_paw', // 添加此字段
|
||||
// ... 其他字段
|
||||
])->findOrEmpty($params['id'])->toArray();
|
||||
```
|
||||
|
||||
### 前端修改(5 个文件)
|
||||
|
||||
#### 1. `admin/src/stores/modules/user.ts`
|
||||
|
||||
**修改内容:**
|
||||
- 添加 `isPaw` 状态
|
||||
- 登录时保存 `isPaw`
|
||||
- 获取用户信息时更新 `isPaw`(解决刷新问题)
|
||||
|
||||
**关键代码:**
|
||||
```typescript
|
||||
export interface UserState {
|
||||
isPaw: number // 0=需要修改密码,1=正常
|
||||
}
|
||||
|
||||
login(playload: any) {
|
||||
this.isPaw = data.is_paw ?? 1
|
||||
}
|
||||
|
||||
getUserInfo() {
|
||||
this.isPaw = data.user.is_paw ?? 1 // 关键:刷新时从后端获取
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. `admin/src/permission.ts`
|
||||
|
||||
**修改内容:**
|
||||
- 添加路由守卫,在获取用户信息后检查 `isPaw`
|
||||
- 强制跳转到修改密码页面
|
||||
|
||||
**关键代码:**
|
||||
```typescript
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
if (hasGetUserInfo) {
|
||||
// 已获取用户信息,检查 isPaw
|
||||
if (userStore.isPaw === 0) {
|
||||
next({ path: changePasswordPath })
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// 首次获取用户信息
|
||||
await userStore.getUserInfo()
|
||||
// 获取后检查 isPaw
|
||||
if (userStore.isPaw === 0) {
|
||||
next({ path: changePasswordPath })
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### 3. `admin/src/views/account/login.vue`
|
||||
|
||||
**修改内容:**
|
||||
- 登录成功后检查 `is_paw` 字段
|
||||
- 如果为 0,跳转到修改密码页面
|
||||
|
||||
**关键代码:**
|
||||
```typescript
|
||||
const result: any = await userStore.login(formData)
|
||||
if (result.is_paw === 0) {
|
||||
router.push('/change-password')
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. `admin/src/views/account/change-password.vue`
|
||||
|
||||
**修改内容:**
|
||||
- 新建修改密码页面
|
||||
- 验证密码规则
|
||||
- 修改成功后更新状态并退出登录
|
||||
|
||||
#### 5. `admin/src/router/routes.ts`
|
||||
|
||||
**修改内容:**
|
||||
- 添加修改密码路由
|
||||
|
||||
**关键代码:**
|
||||
```typescript
|
||||
{
|
||||
path: '/change-password',
|
||||
component: () => import('@/views/account/change-password.vue')
|
||||
}
|
||||
```
|
||||
|
||||
### 数据库修改
|
||||
|
||||
#### `add_is_paw_field.sql`
|
||||
|
||||
```sql
|
||||
ALTER TABLE `la_admin`
|
||||
ADD COLUMN `is_paw` TINYINT(1) NOT NULL DEFAULT 1
|
||||
COMMENT '是否已修改初始密码:0=需要修改,1=正常'
|
||||
AFTER `multipoint_login`;
|
||||
```
|
||||
|
||||
## 安全机制
|
||||
|
||||
### 1. 强制跳转
|
||||
- 登录时检查 `is_paw`,为 0 则跳转到修改密码页面
|
||||
- 路由守卫拦截所有路由,强制跳转
|
||||
|
||||
### 2. URL 防护
|
||||
- 即使直接输入 URL,路由守卫也会拦截
|
||||
- 无法绕过修改密码页面
|
||||
|
||||
### 3. 刷新防护
|
||||
- 页面刷新时从后端重新获取 `is_paw` 状态
|
||||
- 确保状态始终与数据库一致
|
||||
|
||||
### 4. 多标签页防护
|
||||
- 所有标签页共享同一个 Vuex store
|
||||
- 新打开的标签页也会被拦截
|
||||
|
||||
## 工作流程
|
||||
|
||||
```
|
||||
1. 管理员登录 (is_paw = 0)
|
||||
↓
|
||||
2. 后端返回 is_paw = 0
|
||||
↓
|
||||
3. 前端保存 isPaw = 0 到 store
|
||||
↓
|
||||
4. 登录页面检查 isPaw,跳转到修改密码页面
|
||||
↓
|
||||
5. 用户尝试访问其他页面(输入 URL 或刷新)
|
||||
↓
|
||||
6. 路由守卫检查 isPaw
|
||||
↓
|
||||
7. 如果 isPaw = 0,强制跳转回修改密码页面
|
||||
↓
|
||||
8. 用户修改密码成功
|
||||
↓
|
||||
9. 后端更新 is_paw = 1
|
||||
↓
|
||||
10. 前端更新 isPaw = 1
|
||||
↓
|
||||
11. 退出登录
|
||||
↓
|
||||
12. 使用新密码重新登录
|
||||
↓
|
||||
13. 路由守卫检查 isPaw = 1,允许正常访问
|
||||
```
|
||||
|
||||
## 关键技术点
|
||||
|
||||
### 1. 状态持久化问题
|
||||
|
||||
**问题:** 页面刷新后 Vuex store 中的 `isPaw` 状态丢失
|
||||
|
||||
**解决方案:** 在 `getUserInfo()` 方法中从后端重新获取 `is_paw` 状态
|
||||
|
||||
### 2. 路由守卫时机
|
||||
|
||||
**问题:** 在获取用户信息之前检查 `isPaw`,此时状态还是初始值
|
||||
|
||||
**解决方案:** 在获取用户信息之后再检查 `isPaw`
|
||||
|
||||
### 3. 密码加密方式
|
||||
|
||||
**问题:** 使用错误的密码加密方式导致无法登录
|
||||
|
||||
**解决方案:** 使用系统的 `create_password()` 函数,而不是 `password_hash()`
|
||||
|
||||
## 测试验证
|
||||
|
||||
### 必测场景
|
||||
|
||||
1. ✅ 登录后自动跳转到修改密码页面
|
||||
2. ✅ 直接输入 URL 无法绕过
|
||||
3. ✅ 刷新页面仍然在修改密码页面
|
||||
4. ✅ 修改密码成功后 `is_paw` 更新为 1
|
||||
5. ✅ 使用新密码登录后正常进入系统
|
||||
6. ✅ 多标签页测试
|
||||
7. ✅ 密码验证规则测试
|
||||
8. ✅ 企业微信登录测试
|
||||
|
||||
### 测试工具
|
||||
|
||||
- 浏览器开发者工具(Network、Console)
|
||||
- Vue DevTools
|
||||
- 数据库查询工具
|
||||
|
||||
## 文档清单
|
||||
|
||||
| 文档 | 说明 |
|
||||
|------|------|
|
||||
| `FIRST_LOGIN_PASSWORD_CHANGE.md` | 完整功能文档 |
|
||||
| `QUICK_START_FIRST_LOGIN.md` | 快速开始指南 |
|
||||
| `TEST_CHECKLIST.md` | 测试清单 |
|
||||
| `SECURITY_FIX_SUMMARY.md` | 安全修复总结 |
|
||||
| `DEBUG_GUIDE.md` | 调试指南 |
|
||||
| `add_is_paw_field.sql` | 数据库字段添加 SQL |
|
||||
| `test_first_login.sql` | 测试用 SQL |
|
||||
| `install_first_login_password.sh` | Linux/Mac 安装脚本 |
|
||||
| `install_first_login_password.bat` | Windows 安装脚本 |
|
||||
|
||||
## 安装步骤
|
||||
|
||||
### 1. 执行数据库脚本
|
||||
|
||||
```bash
|
||||
# Windows
|
||||
install_first_login_password.bat
|
||||
|
||||
# Linux/Mac
|
||||
chmod +x install_first_login_password.sh
|
||||
./install_first_login_password.sh
|
||||
```
|
||||
|
||||
### 2. 设置测试账号
|
||||
|
||||
```sql
|
||||
UPDATE la_admin SET is_paw = 0 WHERE id = 1;
|
||||
```
|
||||
|
||||
### 3. 测试功能
|
||||
|
||||
按照 `TEST_CHECKLIST.md` 进行测试
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 刷新后进入系统了
|
||||
|
||||
**A:** 检查后端 `AdminLogic::detail()` 方法是否返回 `is_paw` 字段,前端 `getUserInfo()` 是否更新状态。
|
||||
|
||||
### Q2: 可以通过 URL 绕过
|
||||
|
||||
**A:** 检查路由守卫是否正确配置,是否在获取用户信息后检查 `isPaw`。
|
||||
|
||||
### Q3: 修改密码后无法登录
|
||||
|
||||
**A:** 检查密码加密方式是否使用 `create_password()` 函数。
|
||||
|
||||
## 安全等级
|
||||
|
||||
- 修复前:⚠️ 低(可绕过)
|
||||
- 修复后:✅ 高(无法绕过)
|
||||
|
||||
## 维护建议
|
||||
|
||||
1. 定期审计管理员账号的 `is_paw` 状态
|
||||
2. 为新创建的管理员默认设置 `is_paw = 0`
|
||||
3. 定期要求管理员修改密码
|
||||
4. 考虑添加密码强度验证
|
||||
5. 考虑添加密码历史记录
|
||||
|
||||
## 完成时间
|
||||
|
||||
2024-XX-XX
|
||||
|
||||
## 版本
|
||||
|
||||
v1.0 - 初始实现
|
||||
v1.1 - 修复刷新绕过问题
|
||||
@@ -1,142 +0,0 @@
|
||||
# 首次登录强制修改密码功能
|
||||
|
||||
## 功能说明
|
||||
|
||||
当管理员首次登录或需要重置密码时,系统会强制要求修改密码后才能正常使用系统。
|
||||
|
||||
## 安装步骤
|
||||
|
||||
### 方式一:使用安装脚本(推荐)
|
||||
|
||||
#### Windows 系统
|
||||
```bash
|
||||
install_first_login_password.bat
|
||||
```
|
||||
|
||||
#### Linux/Mac 系统
|
||||
```bash
|
||||
chmod +x install_first_login_password.sh
|
||||
./install_first_login_password.sh
|
||||
```
|
||||
|
||||
### 方式二:手动执行 SQL
|
||||
|
||||
执行 `add_is_paw_field.sql` 文件中的 SQL 语句:
|
||||
|
||||
```sql
|
||||
ALTER TABLE `la_admin`
|
||||
ADD COLUMN `is_paw` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否已修改初始密码:0=需要修改,1=正常' AFTER `multipoint_login`;
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 设置管理员需要修改密码
|
||||
|
||||
如果需要强制某个管理员在下次登录时修改密码,执行以下 SQL:
|
||||
|
||||
```sql
|
||||
UPDATE la_admin SET is_paw = 0 WHERE id = <管理员ID>;
|
||||
```
|
||||
|
||||
例如,强制 ID 为 1 的管理员修改密码:
|
||||
|
||||
```sql
|
||||
UPDATE la_admin SET is_paw = 0 WHERE id = 1;
|
||||
```
|
||||
|
||||
### 批量设置所有管理员
|
||||
|
||||
如果需要强制所有管理员修改密码:
|
||||
|
||||
```sql
|
||||
UPDATE la_admin SET is_paw = 0;
|
||||
```
|
||||
|
||||
## 工作流程
|
||||
|
||||
1. 管理员登录系统
|
||||
2. 系统检查 `is_paw` 字段
|
||||
- 如果 `is_paw = 0`:跳转到修改密码页面
|
||||
- 如果 `is_paw = 1`:正常进入系统
|
||||
3. 管理员在修改密码页面输入新密码
|
||||
4. 密码修改成功后,`is_paw` 自动更新为 1
|
||||
5. 系统要求重新登录
|
||||
6. 使用新密码登录后正常使用系统
|
||||
|
||||
## 安全机制
|
||||
|
||||
系统通过路由守卫确保安全性:
|
||||
|
||||
1. **强制跳转**:当 `is_paw = 0` 时,用户访问任何页面都会被强制跳转到修改密码页面
|
||||
2. **URL 防护**:即使用户直接在浏览器输入其他页面 URL,也会被拦截并跳转到修改密码页面
|
||||
3. **状态持久化**:`is_paw` 状态存储在 Vuex store 中,页面刷新后仍然有效
|
||||
4. **防止绕过**:修改密码页面需要登录才能访问,但已修改密码的用户无法再次访问该页面
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 后端修改
|
||||
|
||||
1. **LoginLogic.php** - 登录逻辑返回 `is_paw` 字段
|
||||
2. **LoginController.php** - 添加 `changeFirstPassword` 接口
|
||||
3. **数据库** - 添加 `is_paw` 字段
|
||||
|
||||
### 前端修改
|
||||
|
||||
1. **login.vue** - 登录成功后判断 `is_paw` 字段
|
||||
2. **change-password.vue** - 新增修改密码页面
|
||||
3. **routes.ts** - 添加修改密码路由
|
||||
4. **user.ts** - 添加修改密码 API
|
||||
5. **user.ts (store)** - 存储 `isPaw` 状态
|
||||
6. **permission.ts** - 路由守卫,强制需要修改密码的用户只能访问修改密码页面
|
||||
|
||||
## 字段说明
|
||||
|
||||
| 字段名 | 类型 | 默认值 | 说明 |
|
||||
|--------|------|--------|------|
|
||||
| is_paw | TINYINT(1) | 1 | 0=需要修改密码,1=正常 |
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 修改密码后会自动退出登录,需要使用新密码重新登录
|
||||
2. 密码长度不能少于 6 位
|
||||
3. 两次输入的密码必须一致
|
||||
4. 该功能同时支持账号密码登录和企业微信登录
|
||||
|
||||
## 测试步骤
|
||||
|
||||
### 1. 安装功能
|
||||
|
||||
执行安装脚本或手动执行 SQL 添加 `is_paw` 字段。
|
||||
|
||||
### 2. 设置测试账号
|
||||
|
||||
```sql
|
||||
-- 设置 ID 为 1 的管理员需要修改密码
|
||||
UPDATE la_admin SET is_paw = 0 WHERE id = 1;
|
||||
```
|
||||
|
||||
### 3. 测试登录流程
|
||||
|
||||
1. 使用该管理员账号登录
|
||||
2. 登录成功后应自动跳转到修改密码页面
|
||||
3. 输入新密码(至少 6 位)
|
||||
4. 确认修改后应提示"密码修改成功,请重新登录"
|
||||
5. 自动跳转到登录页面
|
||||
6. 使用新密码登录,应能正常进入系统
|
||||
|
||||
### 4. 验证数据库
|
||||
|
||||
```sql
|
||||
-- 查看管理员的 is_paw 状态,应该已更新为 1
|
||||
SELECT id, account, is_paw FROM la_admin WHERE id = 1;
|
||||
```
|
||||
|
||||
### 5. 使用测试 SQL
|
||||
|
||||
可以使用 `test_first_login.sql` 文件中的 SQL 语句进行测试。
|
||||
|
||||
## 安全建议
|
||||
|
||||
1. 建议为新创建的管理员账号设置 `is_paw = 0`,强制首次登录修改密码
|
||||
2. 定期要求管理员修改密码,可批量设置 `is_paw = 0`
|
||||
3. 密码应包含字母、数字和特殊字符,提高安全性
|
||||
@@ -1,255 +0,0 @@
|
||||
# IM 聊天记录接口性能优化
|
||||
|
||||
## 问题描述
|
||||
|
||||
接口 `tcm.diagnosis/getImChatMessages?diagnosis_id=377` 响应超时(30秒),错误信息:
|
||||
```
|
||||
Maximum execution time of 30 seconds exceeded
|
||||
```
|
||||
|
||||
错误发生在 `TencentImService.php` 第 346 行的 `curl_exec($ch)` 调用。
|
||||
|
||||
## 根本原因
|
||||
|
||||
1. **查询范围过大**
|
||||
- 原代码调用 `collectAllDoctorImPeerAccounts()` 获取所有医生和医助账号
|
||||
- 如果系统有 50+ 个医生/医助,就需要对每个账号调用腾讯云 IM API
|
||||
- 每个账号可能需要多次分页请求(每次最多 100 条消息)
|
||||
|
||||
2. **API 调用次数过多**
|
||||
- 假设有 50 个医生,每个医生有 300 条消息
|
||||
- 总 API 调用次数 = 50 × (300/100) = 150 次
|
||||
- 每次调用耗时 0.5-1 秒,总耗时 75-150 秒
|
||||
|
||||
3. **超时设置不合理**
|
||||
- PHP 默认执行时间限制:30 秒
|
||||
- 单个 HTTP 请求超时:30 秒
|
||||
- 实际需要的时间远超这些限制
|
||||
|
||||
## 优化方案
|
||||
|
||||
### 1. 只查询指派的医助(核心优化)
|
||||
|
||||
修改 `getImChatMessagesForDiagnosis` 方法:
|
||||
|
||||
**优化前:**
|
||||
```php
|
||||
// 查询所有医生/医助账号(可能有几十个)
|
||||
$doctorAccounts = self::collectAllDoctorImPeerAccounts();
|
||||
$assistantId = isset($diag['assistant_id']) ? (int)$diag['assistant_id'] : 0;
|
||||
if ($assistantId > 0) {
|
||||
$doctorAccounts[] = 'doctor_' . $assistantId;
|
||||
}
|
||||
```
|
||||
|
||||
**优化后:**
|
||||
```php
|
||||
// 只查询指派给该诊单的医助
|
||||
$doctorAccounts = [];
|
||||
$assistantId = isset($diag['assistant_id']) ? (int)$diag['assistant_id'] : 0;
|
||||
if ($assistantId > 0) {
|
||||
$doctorAccounts[] = 'doctor_' . $assistantId;
|
||||
}
|
||||
|
||||
// 如果没有指派医助,直接返回归档记录
|
||||
if (empty($doctorAccounts)) {
|
||||
$lists = self::enrichImMessagesWithStaffNames($archived);
|
||||
return [
|
||||
'lists' => $lists,
|
||||
'patient_im_id' => $patientImId,
|
||||
'patient_name' => $diag['patient_name'] ?? '',
|
||||
'doctor_accounts_queried' => [],
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
**效果:**
|
||||
- API 调用次数从 150 次降低到 3-5 次
|
||||
- 响应时间从 75-150 秒降低到 2-5 秒
|
||||
|
||||
### 2. 增加 HTTP 请求超时时间
|
||||
|
||||
修改 `TencentImService::adminGetRoamMsg` 方法:
|
||||
|
||||
```php
|
||||
// 从 30 秒增加到 60 秒
|
||||
$result = $this->httpPost($url, json_encode($data), 60);
|
||||
```
|
||||
|
||||
### 3. 增加 PHP 执行时间限制
|
||||
|
||||
修改 `DiagnosisController::getImChatMessages` 方法:
|
||||
|
||||
```php
|
||||
public function getImChatMessages()
|
||||
{
|
||||
// 增加执行时间限制到 120 秒
|
||||
set_time_limit(120);
|
||||
|
||||
// ... 其他代码
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 新增优化版拉取方法
|
||||
|
||||
添加 `pullLiveImChatMessagesForDiagnosisOptimized` 方法:
|
||||
|
||||
```php
|
||||
/**
|
||||
* 优化版:只拉取指定医生账号的消息(避免查询所有医生导致超时)
|
||||
*/
|
||||
private static function pullLiveImChatMessagesForDiagnosisOptimized($diag, array $doctorAccounts): array
|
||||
{
|
||||
// 只查询指定的医生账号,大大减少API调用次数
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## 性能对比
|
||||
|
||||
| 指标 | 优化前 | 优化后 | 改善 |
|
||||
|------|--------|--------|------|
|
||||
| 查询的医生账号数 | 50+ | 1 | 98% ↓ |
|
||||
| API 调用次数 | 150+ | 3-5 | 97% ↓ |
|
||||
| 响应时间 | 75-150秒 | 2-5秒 | 95% ↓ |
|
||||
| 超时风险 | 高 | 低 | - |
|
||||
|
||||
## 业务逻辑说明
|
||||
|
||||
### 为什么只查询指派的医助?
|
||||
|
||||
1. **业务场景**
|
||||
- 每个诊单都会指派一个医助(`assistant_id`)
|
||||
- 患者主要与指派的医助沟通
|
||||
- 其他医生/医助不会与该患者聊天
|
||||
|
||||
2. **数据准确性**
|
||||
- 只显示与该诊单相关的聊天记录
|
||||
- 避免显示无关的聊天记录
|
||||
- 提高数据查询的精准度
|
||||
|
||||
3. **归档机制**
|
||||
- 系统有定时任务将 IM 消息归档到本地数据库
|
||||
- 归档数据突破腾讯云 7 天限制
|
||||
- 即使不查询所有医生,历史记录也不会丢失
|
||||
|
||||
### 如果需要查询所有医生怎么办?
|
||||
|
||||
如果确实需要查询所有医生的聊天记录(例如管理员查看),可以:
|
||||
|
||||
1. **使用定时任务归档**
|
||||
```bash
|
||||
php think tcm:sync-im-chat-archive --days=7 --limit=100
|
||||
```
|
||||
|
||||
2. **异步查询**
|
||||
- 将查询任务放入队列
|
||||
- 后台慢慢处理
|
||||
- 完成后通知用户
|
||||
|
||||
3. **分页查询**
|
||||
- 前端分批请求不同医生的记录
|
||||
- 每次只查询 1-2 个医生
|
||||
- 避免单次请求超时
|
||||
|
||||
## 测试建议
|
||||
|
||||
1. **测试正常场景**
|
||||
```
|
||||
GET /adminapi/tcm.diagnosis/getImChatMessages?diagnosis_id=377
|
||||
```
|
||||
- 应该在 5 秒内返回
|
||||
- 返回指派医助的聊天记录
|
||||
|
||||
2. **测试无指派医助场景**
|
||||
- 创建一个未指派医助的诊单
|
||||
- 应该返回归档记录(如果有)
|
||||
- 不应该报错
|
||||
|
||||
3. **测试大量消息场景**
|
||||
- 找一个有 500+ 条消息的诊单
|
||||
- 应该能正常返回
|
||||
- 响应时间应该在 10 秒内
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
### 1. 添加缓存
|
||||
|
||||
```php
|
||||
public static function getImChatMessagesForDiagnosis(int $diagnosisId)
|
||||
{
|
||||
// 缓存 5 分钟
|
||||
$cacheKey = 'im_chat_messages_' . $diagnosisId;
|
||||
$cached = Cache::get($cacheKey);
|
||||
if ($cached) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
// ... 查询逻辑
|
||||
|
||||
Cache::set($cacheKey, $result, 300);
|
||||
return $result;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 使用 Redis 队列
|
||||
|
||||
```php
|
||||
// 将耗时的查询放入队列
|
||||
Queue::push(SyncImChatMessagesJob::class, [
|
||||
'diagnosis_id' => $diagnosisId
|
||||
]);
|
||||
|
||||
// 前端轮询查询结果
|
||||
```
|
||||
|
||||
### 3. WebSocket 实时推送
|
||||
|
||||
```php
|
||||
// 使用 WebSocket 实时推送新消息
|
||||
// 避免每次都查询腾讯云 API
|
||||
```
|
||||
|
||||
### 4. 增量同步
|
||||
|
||||
```php
|
||||
// 只同步最近 1 小时的新消息
|
||||
// 历史消息从归档表读取
|
||||
$minTime = time() - 3600;
|
||||
$result = $svc->adminGetRoamMsg($operator, $peer, 100, $minTime);
|
||||
```
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `server/app/adminapi/logic/tcm/DiagnosisLogic.php` - 主要逻辑
|
||||
- `server/app/common/service/TencentImService.php` - IM 服务
|
||||
- `server/app/adminapi/controller/tcm/DiagnosisController.php` - 控制器
|
||||
|
||||
## 监控建议
|
||||
|
||||
1. **添加性能日志**
|
||||
```php
|
||||
$startTime = microtime(true);
|
||||
// ... 查询逻辑
|
||||
$duration = microtime(true) - $startTime;
|
||||
Log::info('IM消息查询耗时', [
|
||||
'diagnosis_id' => $diagnosisId,
|
||||
'duration' => $duration,
|
||||
'doctor_accounts' => count($doctorAccounts),
|
||||
'message_count' => count($merged),
|
||||
]);
|
||||
```
|
||||
|
||||
2. **设置告警**
|
||||
- 响应时间 > 10 秒:警告
|
||||
- 响应时间 > 30 秒:严重
|
||||
- API 调用失败率 > 5%:严重
|
||||
|
||||
3. **定期检查**
|
||||
- 每周检查平均响应时间
|
||||
- 每月检查 API 调用次数
|
||||
- 及时发现性能退化
|
||||
|
||||
---
|
||||
|
||||
最后更新:2024-04-11
|
||||
@@ -1,361 +0,0 @@
|
||||
# 处方库功能 - 安装检查清单
|
||||
|
||||
## 📋 安装前检查
|
||||
|
||||
### 环境要求
|
||||
- [ ] PHP >= 7.4
|
||||
- [ ] MySQL >= 5.7
|
||||
- [ ] Node.js >= 14.x(前端开发)
|
||||
- [ ] 已安装并运行 ThinkPHP 框架
|
||||
- [ ] 已安装并运行 Vue 3 前端项目
|
||||
|
||||
### 权限检查
|
||||
- [ ] 数据库创建表权限
|
||||
- [ ] 文件读写权限
|
||||
- [ ] 后台管理员账号
|
||||
|
||||
## 🔧 安装步骤
|
||||
|
||||
### 步骤1:备份数据库 ✅
|
||||
```bash
|
||||
# 备份当前数据库(推荐)
|
||||
mysqldump -u用户名 -p密码 数据库名 > backup_$(date +%Y%m%d).sql
|
||||
```
|
||||
- [ ] 已完成数据库备份
|
||||
|
||||
### 步骤2:安装数据库表 ✅
|
||||
|
||||
#### 方式A:自动安装(推荐)
|
||||
|
||||
**Windows:**
|
||||
```bash
|
||||
双击运行 install_prescription_library.bat
|
||||
```
|
||||
|
||||
**Linux/Mac:**
|
||||
```bash
|
||||
chmod +x install_prescription_library.sh
|
||||
./install_prescription_library.sh
|
||||
```
|
||||
|
||||
#### 方式B:手动安装
|
||||
```bash
|
||||
mysql -h主机 -u用户名 -p密码 数据库名 < server/database/migrations/create_prescription_library.sql
|
||||
```
|
||||
|
||||
- [ ] 数据库表安装成功
|
||||
- [ ] 验证表是否创建:`SHOW TABLES LIKE '%prescription_library%';`
|
||||
|
||||
### 步骤3:验证后端文件 ✅
|
||||
|
||||
检查以下文件是否存在:
|
||||
|
||||
**模型层:**
|
||||
- [ ] server/app/common/model/tcm/PrescriptionLibrary.php
|
||||
|
||||
**控制器层:**
|
||||
- [ ] server/app/adminapi/controller/tcm/PrescriptionLibraryController.php
|
||||
|
||||
**业务逻辑层:**
|
||||
- [ ] server/app/adminapi/logic/tcm/PrescriptionLibraryLogic.php
|
||||
|
||||
**列表逻辑层:**
|
||||
- [ ] server/app/adminapi/lists/tcm/PrescriptionLibraryLists.php
|
||||
|
||||
**验证器层:**
|
||||
- [ ] server/app/adminapi/validate/tcm/PrescriptionLibraryValidate.php
|
||||
|
||||
### 步骤4:验证前端文件 ✅
|
||||
|
||||
检查以下文件是否存在:
|
||||
|
||||
**页面组件:**
|
||||
- [ ] admin/src/views/consumer/prescription/list.vue
|
||||
|
||||
**API接口:**
|
||||
- [ ] admin/src/api/tcm.ts(已更新)
|
||||
|
||||
### 步骤5:配置路由(如需要) ✅
|
||||
|
||||
ThinkPHP 通常自动识别路由,无需手动配置。
|
||||
|
||||
如需手动配置,在路由文件中添加:
|
||||
```php
|
||||
// 处方库路由
|
||||
Route::group('tcm.prescriptionLibrary', function () {
|
||||
Route::get('lists', 'tcm.PrescriptionLibrary/lists');
|
||||
Route::post('add', 'tcm.PrescriptionLibrary/add');
|
||||
Route::post('edit', 'tcm.PrescriptionLibrary/edit');
|
||||
Route::post('delete', 'tcm.PrescriptionLibrary/delete');
|
||||
Route::get('detail', 'tcm.PrescriptionLibrary/detail');
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] 路由配置完成(如需要)
|
||||
|
||||
### 步骤6:配置菜单权限 ✅
|
||||
|
||||
在后台管理系统中添加菜单项:
|
||||
|
||||
**菜单配置示例:**
|
||||
```
|
||||
菜单名称:处方库
|
||||
菜单路径:/consumer/prescription/list
|
||||
图标:el-icon-document
|
||||
排序:100
|
||||
权限标识:tcm.prescriptionLibrary/lists
|
||||
```
|
||||
|
||||
**权限节点:**
|
||||
- [ ] tcm.prescriptionLibrary/lists(查看列表)
|
||||
- [ ] tcm.prescriptionLibrary/add(添加处方)
|
||||
- [ ] tcm.prescriptionLibrary/edit(编辑处方)
|
||||
- [ ] tcm.prescriptionLibrary/delete(删除处方)
|
||||
- [ ] tcm.prescriptionLibrary/detail(查看详情)
|
||||
|
||||
- [ ] 菜单配置完成
|
||||
- [ ] 权限节点配置完成
|
||||
|
||||
### 步骤7:重启服务(如需要) ✅
|
||||
|
||||
**后端:**
|
||||
```bash
|
||||
# 清除缓存
|
||||
php think clear
|
||||
|
||||
# 重启PHP-FPM(如使用)
|
||||
sudo systemctl restart php-fpm
|
||||
```
|
||||
|
||||
**前端:**
|
||||
```bash
|
||||
# 开发环境
|
||||
npm run dev
|
||||
|
||||
# 生产环境
|
||||
npm run build
|
||||
```
|
||||
|
||||
- [ ] 后端服务已重启
|
||||
- [ ] 前端已重新编译
|
||||
|
||||
## 🧪 功能测试
|
||||
|
||||
### 测试1:访问页面 ✅
|
||||
- [ ] 能够访问处方库页面
|
||||
- [ ] 页面正常显示,无报错
|
||||
- [ ] 列表为空或显示数据
|
||||
|
||||
### 测试2:创建处方 ✅
|
||||
- [ ] 点击"新增处方"按钮
|
||||
- [ ] 填写处方名称
|
||||
- [ ] 添加药材
|
||||
- [ ] 设置是否公开
|
||||
- [ ] 保存成功
|
||||
|
||||
### 测试3:查看处方 ✅
|
||||
- [ ] 点击"查看"按钮
|
||||
- [ ] 显示处方详情
|
||||
- [ ] 字段不可编辑
|
||||
|
||||
### 测试4:编辑处方 ✅
|
||||
- [ ] 点击"编辑"按钮
|
||||
- [ ] 修改处方信息
|
||||
- [ ] 保存成功
|
||||
|
||||
### 测试5:删除处方 ✅
|
||||
- [ ] 点击"删除"按钮
|
||||
- [ ] 确认删除
|
||||
- [ ] 删除成功
|
||||
|
||||
### 测试6:搜索功能 ✅
|
||||
- [ ] 输入处方名称搜索
|
||||
- [ ] 选择是否公开筛选
|
||||
- [ ] 显示正确结果
|
||||
|
||||
### 测试7:权限测试 ✅
|
||||
- [ ] 用户A创建私有处方
|
||||
- [ ] 用户B无法查看用户A的私有处方
|
||||
- [ ] 用户A创建公开处方
|
||||
- [ ] 用户B可以查看用户A的公开处方
|
||||
- [ ] 用户B无法删除用户A的处方
|
||||
|
||||
## 🔍 问题排查
|
||||
|
||||
### 问题1:无法访问页面
|
||||
**可能原因:**
|
||||
- 路由未配置
|
||||
- 权限未分配
|
||||
- 前端路由错误
|
||||
|
||||
**解决方案:**
|
||||
1. 检查路由配置
|
||||
2. 检查菜单权限
|
||||
3. 查看浏览器控制台错误
|
||||
|
||||
### 问题2:数据库表创建失败
|
||||
**可能原因:**
|
||||
- 数据库连接失败
|
||||
- 权限不足
|
||||
- 表已存在
|
||||
|
||||
**解决方案:**
|
||||
1. 检查数据库配置
|
||||
2. 检查用户权限
|
||||
3. 删除已存在的表后重试
|
||||
|
||||
### 问题3:API请求失败
|
||||
**可能原因:**
|
||||
- 后端文件未上传
|
||||
- 路由配置错误
|
||||
- 权限验证失败
|
||||
|
||||
**解决方案:**
|
||||
1. 检查后端文件是否存在
|
||||
2. 检查API路径是否正确
|
||||
3. 查看后端日志
|
||||
|
||||
### 问题4:前端页面报错
|
||||
**可能原因:**
|
||||
- 前端文件未编译
|
||||
- API接口路径错误
|
||||
- 组件引用错误
|
||||
|
||||
**解决方案:**
|
||||
1. 重新编译前端代码
|
||||
2. 检查API接口配置
|
||||
3. 查看浏览器控制台错误
|
||||
|
||||
## 📊 验证清单
|
||||
|
||||
### 数据库验证
|
||||
```sql
|
||||
-- 检查表是否存在
|
||||
SHOW TABLES LIKE '%prescription_library%';
|
||||
|
||||
-- 检查表结构
|
||||
DESC zyt_prescription_library;
|
||||
|
||||
-- 检查索引
|
||||
SHOW INDEX FROM zyt_prescription_library;
|
||||
|
||||
-- 插入测试数据
|
||||
INSERT INTO `zyt_prescription_library`
|
||||
(`prescription_name`, `herbs`, `is_public`, `creator_id`, `creator_name`, `create_time`, `update_time`)
|
||||
VALUES
|
||||
('测试处方', '[{"name":"测试药材","dosage":10}]', 1, 1, '测试医生', UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
|
||||
|
||||
-- 查询测试数据
|
||||
SELECT * FROM zyt_prescription_library WHERE prescription_name = '测试处方';
|
||||
|
||||
-- 删除测试数据
|
||||
DELETE FROM zyt_prescription_library WHERE prescription_name = '测试处方';
|
||||
```
|
||||
|
||||
- [ ] 表结构正确
|
||||
- [ ] 索引创建成功
|
||||
- [ ] 数据插入成功
|
||||
- [ ] 数据查询成功
|
||||
- [ ] 数据删除成功
|
||||
|
||||
### API验证
|
||||
使用 Postman 或类似工具测试:
|
||||
|
||||
**获取列表:**
|
||||
```
|
||||
GET /api/tcm.prescriptionLibrary/lists
|
||||
```
|
||||
|
||||
**添加处方:**
|
||||
```
|
||||
POST /api/tcm.prescriptionLibrary/add
|
||||
Body: {
|
||||
"prescription_name": "测试处方",
|
||||
"herbs": [{"name": "测试药材", "dosage": 10}],
|
||||
"is_public": 1
|
||||
}
|
||||
```
|
||||
|
||||
**编辑处方:**
|
||||
```
|
||||
POST /api/tcm.prescriptionLibrary/edit
|
||||
Body: {
|
||||
"id": 1,
|
||||
"prescription_name": "测试处方(已修改)",
|
||||
"herbs": [{"name": "测试药材", "dosage": 15}],
|
||||
"is_public": 0
|
||||
}
|
||||
```
|
||||
|
||||
**删除处方:**
|
||||
```
|
||||
POST /api/tcm.prescriptionLibrary/delete
|
||||
Body: {
|
||||
"id": 1
|
||||
}
|
||||
```
|
||||
|
||||
**获取详情:**
|
||||
```
|
||||
GET /api/tcm.prescriptionLibrary/detail?id=1
|
||||
```
|
||||
|
||||
- [ ] 列表接口正常
|
||||
- [ ] 添加接口正常
|
||||
- [ ] 编辑接口正常
|
||||
- [ ] 删除接口正常
|
||||
- [ ] 详情接口正常
|
||||
|
||||
## ✅ 安装完成确认
|
||||
|
||||
### 最终检查
|
||||
- [ ] 所有文件已上传
|
||||
- [ ] 数据库表已创建
|
||||
- [ ] 菜单权限已配置
|
||||
- [ ] 功能测试通过
|
||||
- [ ] API测试通过
|
||||
- [ ] 权限测试通过
|
||||
|
||||
### 文档确认
|
||||
- [ ] 已阅读 PRESCRIPTION_LIBRARY_README.md
|
||||
- [ ] 已阅读 QUICK_START_PRESCRIPTION_LIBRARY.md
|
||||
- [ ] 已了解功能使用方法
|
||||
|
||||
### 备份确认
|
||||
- [ ] 已备份数据库
|
||||
- [ ] 已备份原有代码
|
||||
- [ ] 已记录安装日期和版本
|
||||
|
||||
## 📝 安装记录
|
||||
|
||||
**安装日期:** _______________
|
||||
|
||||
**安装人员:** _______________
|
||||
|
||||
**版本号:** v1.0.0
|
||||
|
||||
**数据库名:** _______________
|
||||
|
||||
**备注:**
|
||||
```
|
||||
_______________________________________________
|
||||
_______________________________________________
|
||||
_______________________________________________
|
||||
```
|
||||
|
||||
## 🎉 安装成功!
|
||||
|
||||
恭喜!处方库功能已成功安装。
|
||||
|
||||
**下一步:**
|
||||
1. 创建第一个处方
|
||||
2. 测试各项功能
|
||||
3. 培训使用人员
|
||||
4. 收集反馈意见
|
||||
|
||||
**技术支持:**
|
||||
如有问题,请查看文档或联系技术支持团队。
|
||||
|
||||
---
|
||||
|
||||
**祝使用愉快!** 🚀
|
||||
@@ -1,142 +0,0 @@
|
||||
# 内部成本字段权限控制
|
||||
|
||||
## 需求说明
|
||||
|
||||
在业务订单列表中添加内部成本字段的显示,但只有指定角色的用户才能看到该字段。
|
||||
|
||||
## 权限配置
|
||||
|
||||
文件:`server/config/project.php`
|
||||
|
||||
```php
|
||||
// 处方业务订单 internal_cost 等财务字段:以下角色 ID + root 可见
|
||||
'prescription_order_finance_roles' => [0, 3, 6],
|
||||
```
|
||||
|
||||
- 角色 0:超级管理员
|
||||
- 角色 3:管理员
|
||||
- 角色 6:药师
|
||||
|
||||
## 后端权限控制
|
||||
|
||||
文件:`server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
|
||||
|
||||
后端已经实现了权限控制逻辑:
|
||||
|
||||
```php
|
||||
public static function canViewInternalCost(array $adminInfo): bool
|
||||
{
|
||||
if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
|
||||
return true;
|
||||
}
|
||||
$allow = Config::get('project.prescription_order_finance_roles', [0, 3]);
|
||||
$allow = array_map('intval', is_array($allow) ? $allow : []);
|
||||
$myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
|
||||
|
||||
return count(array_intersect($myRoles, $allow)) > 0;
|
||||
}
|
||||
|
||||
public static function maskInternalCostIfNeeded(array &$row, array $adminInfo): void
|
||||
{
|
||||
if (!self::canViewInternalCost($adminInfo)) {
|
||||
unset($row['internal_cost']);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
后端在返回数据前会自动移除 `internal_cost` 字段,如果用户没有权限。
|
||||
|
||||
## 前端权限控制
|
||||
|
||||
文件:`admin/src/views/consumer/prescription/order_list.vue`
|
||||
|
||||
### 1. 添加权限检查函数
|
||||
|
||||
```typescript
|
||||
import useUserStore from '@/stores/modules/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
/** 与 server/config/project.php prescription_order_finance_roles 保持一致 */
|
||||
const FINANCE_ROLE_IDS = [0, 3, 6]
|
||||
|
||||
/** 判断当前用户是否有查看财务字段(内部成本)的权限 */
|
||||
const canViewFinanceFields = () => {
|
||||
const u = userStore.userInfo
|
||||
if (!u) return false
|
||||
if (Number(u.root) === 1) return true
|
||||
const ids = Array.isArray(u.role_ids) ? u.role_ids.map((n: unknown) => Number(n)) : []
|
||||
return FINANCE_ROLE_IDS.some((rid) => ids.includes(rid))
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 在列表中添加权限控制
|
||||
|
||||
**表格列(只有有权限的用户才能看到整列):**
|
||||
|
||||
```vue
|
||||
<el-table-column v-if="canViewFinanceFields()" label="内部成本" width="92">
|
||||
<template #default="{ row }">
|
||||
{{ row.internal_cost != null && row.internal_cost !== '' ? `¥${row.internal_cost}` : '—' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
```
|
||||
|
||||
**金额信息弹出框(只有有权限的用户才能看到内部成本行):**
|
||||
|
||||
```vue
|
||||
<div v-if="canViewFinanceFields() && row.internal_cost != null && row.internal_cost !== ''" class="flex justify-between">
|
||||
<span class="text-gray-500">内部成本:</span>
|
||||
<span class="font-medium text-orange-600">¥{{ formatMoney(row.internal_cost) }}</span>
|
||||
</div>
|
||||
```
|
||||
|
||||
## 权限控制层级
|
||||
|
||||
### 后端(第一层防护)
|
||||
|
||||
1. 在 `PrescriptionOrderLogic::detail()` 和列表方法中调用 `maskInternalCostIfNeeded()`
|
||||
2. 如果用户没有权限,直接从返回数据中移除 `internal_cost` 字段
|
||||
3. 前端收到的数据中不包含该字段
|
||||
|
||||
### 前端(第二层防护)
|
||||
|
||||
1. 使用 `v-if="canViewFinanceFields()"` 控制整个表格列的显示
|
||||
2. 在弹出框中也使用 `canViewFinanceFields()` 控制内部成本行的显示
|
||||
3. 即使后端数据泄露,前端也不会显示
|
||||
|
||||
## 双重防护的优势
|
||||
|
||||
1. **后端防护**:确保数据安全,无权限用户无法获取敏感数据
|
||||
2. **前端防护**:优化用户体验,无权限用户不会看到空白或无意义的列
|
||||
3. **配置同步**:前端 `FINANCE_ROLE_IDS` 与后端配置保持一致
|
||||
|
||||
## 测试验证
|
||||
|
||||
### 有权限的用户(角色 0, 3, 6)
|
||||
|
||||
1. 登录系统
|
||||
2. 进入"消费者处方订单"列表
|
||||
3. 可以看到"内部成本"列
|
||||
4. 点击金额信息图标,弹出框中显示内部成本
|
||||
|
||||
### 无权限的用户(其他角色)
|
||||
|
||||
1. 登录系统
|
||||
2. 进入"消费者处方订单"列表
|
||||
3. 看不到"内部成本"列
|
||||
4. 点击金额信息图标,弹出框中不显示内部成本
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **配置同步**:修改后端配置后,需要同步修改前端 `FINANCE_ROLE_IDS` 常量
|
||||
2. **缓存清除**:修改配置后需要运行 `php think clear` 清除缓存
|
||||
3. **角色分配**:确保用户的角色ID正确分配在数据库中
|
||||
4. **超级管理员**:`root=1` 的用户始终有权限查看所有字段
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `server/config/project.php` - 权限配置
|
||||
- `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php` - 后端权限逻辑
|
||||
- `admin/src/views/consumer/prescription/order_list.vue` - 前端显示控制
|
||||
- `admin/src/stores/modules/user.ts` - 用户信息存储
|
||||
@@ -1,251 +0,0 @@
|
||||
# 极兔速递支持已添加
|
||||
|
||||
## 更新说明
|
||||
|
||||
系统已添加极兔速递(J&T Express)的物流查询支持。
|
||||
|
||||
### 单号示例
|
||||
- `JT5472795424064` - 极兔速递单号格式:JT + 13位数字
|
||||
|
||||
---
|
||||
|
||||
## 更新内容
|
||||
|
||||
### 1. 后端更新
|
||||
|
||||
**文件:** `server/app/common/service/ExpressTrackService.php`
|
||||
|
||||
- 添加极兔速递常量 `KUAIDI_COM_JT = 'jtexpress'`
|
||||
- 添加极兔单号识别规则:`/^JT\d{13}$/i`
|
||||
- 添加极兔官网查询链接:`https://www.jtexpress.com.cn/index/query/gzquery.html?bills={单号}`
|
||||
- 更新 `resolveCarrier()` 方法支持极兔识别
|
||||
- 更新 `officialUrls()` 方法返回极兔链接
|
||||
|
||||
**文件:** `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
|
||||
|
||||
- 更新 `normalizeExpressCompany()` 方法支持 `jt` 和 `jtexpress`
|
||||
|
||||
### 2. 前端更新
|
||||
|
||||
**文件:** `admin/src/views/consumer/prescription/order_list.vue`
|
||||
|
||||
- 添加极兔速递选项到快递公司下拉框
|
||||
- 添加"极兔速递查件"官网链接按钮
|
||||
- 更新 `expressCompanyLabel()` 函数支持极兔显示
|
||||
- 更新 `detailOfficialUrls` 计算属性包含极兔链接
|
||||
- 更新提示文本说明支持极兔速递
|
||||
|
||||
---
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 方式一:自动识别(推荐)
|
||||
|
||||
系统会自动识别以 `JT` 开头且后跟13位数字的单号为极兔速递。
|
||||
|
||||
**示例:**
|
||||
- 单号:`JT5472795424064`
|
||||
- 快递公司:选择"自动识别"
|
||||
- 系统自动识别为:极兔速递
|
||||
|
||||
### 方式二:手动选择
|
||||
|
||||
在订单编辑或查询时,手动选择"极兔速递"。
|
||||
|
||||
**步骤:**
|
||||
1. 打开业务订单详情
|
||||
2. 在"快递公司"下拉框中选择"极兔速递"
|
||||
3. 填写快递单号
|
||||
4. 点击"查询物流"
|
||||
|
||||
---
|
||||
|
||||
## 快递100 API 支持
|
||||
|
||||
极兔速递在快递100中的编码为 `jtexpress`,系统已配置。
|
||||
|
||||
### 查询参数示例
|
||||
|
||||
```json
|
||||
{
|
||||
"com": "jtexpress",
|
||||
"num": "JT5472795424064",
|
||||
"resultv2": "1"
|
||||
}
|
||||
```
|
||||
|
||||
### 注意事项
|
||||
|
||||
1. **快递100配置**
|
||||
- 确保已配置 `LOGISTICS_KUAIDI100_CUSTOMER` 和 `LOGISTICS_KUAIDI100_KEY`
|
||||
- 确保 `LOGISTICS_KUAIDI100_DISABLE = false`
|
||||
|
||||
2. **单号格式**
|
||||
- 极兔单号通常为:JT + 13位数字
|
||||
- 示例:`JT5472795424064`
|
||||
|
||||
3. **查询时机**
|
||||
- 快递已揽收后才能查询到物流信息
|
||||
- 刚下单的快递可能需要等待1-2小时
|
||||
|
||||
---
|
||||
|
||||
## 官网查询
|
||||
|
||||
如果快递100查询失败,可以使用官网链接:
|
||||
|
||||
**极兔速递官网:** https://www.jtexpress.com.cn/
|
||||
|
||||
**查询链接格式:**
|
||||
```
|
||||
https://www.jtexpress.com.cn/index/query/gzquery.html?bills={单号}
|
||||
```
|
||||
|
||||
**示例:**
|
||||
```
|
||||
https://www.jtexpress.com.cn/index/query/gzquery.html?bills=JT5472795424064
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 测试
|
||||
|
||||
### 测试脚本
|
||||
|
||||
运行测试脚本验证极兔速递支持:
|
||||
|
||||
```bash
|
||||
cd server
|
||||
php test_jt_express.php
|
||||
```
|
||||
|
||||
### 测试步骤
|
||||
|
||||
1. **创建测试订单**
|
||||
- 快递公司:选择"极兔速递"或"自动识别"
|
||||
- 快递单号:`JT5472795424064`(替换为真实单号)
|
||||
|
||||
2. **查询物流**
|
||||
- 打开订单详情
|
||||
- 点击"查询物流"按钮
|
||||
- 查看是否返回物流轨迹
|
||||
|
||||
3. **官网查询**
|
||||
- 点击"极兔速递查件"链接
|
||||
- 确认能正常打开极兔官网查询页面
|
||||
|
||||
---
|
||||
|
||||
## 支持的快递公司列表
|
||||
|
||||
| 快递公司 | 代码 | 快递100编码 | 单号格式 | 自动识别 |
|
||||
|---------|------|------------|---------|---------|
|
||||
| 顺丰速运 | sf | shunfeng | SF + 数字 | ✅ |
|
||||
| 京东快递 | jd | jd | JD/JDV/JDK/JDEX + 数字 | ✅ |
|
||||
| 极兔速递 | jt | jtexpress | JT + 13位数字 | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## 扩展其他快递公司
|
||||
|
||||
如需添加更多快递公司(如中通、圆通、申通等),请参考以下步骤:
|
||||
|
||||
### 1. 查询快递100编码
|
||||
|
||||
访问快递100官方文档查询快递公司编码:
|
||||
https://api.kuaidi100.com/document/5f0ffb5ebc8da837cbd8aefc/5f0ffc3cbc8da837cbd8afc0
|
||||
|
||||
### 2. 修改后端代码
|
||||
|
||||
在 `ExpressTrackService.php` 中:
|
||||
|
||||
```php
|
||||
// 添加常量
|
||||
private const KUAIDI_COM_ZTO = 'zhongtong'; // 中通快递
|
||||
|
||||
// 添加识别规则
|
||||
if ($ec === 'zto' || $ec === 'zhongtong') {
|
||||
return ['carrier' => 'zto', 'kuaidi_com' => self::KUAIDI_COM_ZTO, 'label' => '中通快递'];
|
||||
}
|
||||
|
||||
// 添加单号自动识别(可选)
|
||||
if (preg_match('/^[0-9]{12}$/', $num)) {
|
||||
return ['carrier' => 'zto', 'kuaidi_com' => self::KUAIDI_COM_ZTO, 'label' => '中通快递(单号识别)'];
|
||||
}
|
||||
|
||||
// 添加官网链接
|
||||
'zto' => 'https://www.zto.com/GuestService/Bill?txtBill=' . $enc,
|
||||
```
|
||||
|
||||
### 3. 修改前端代码
|
||||
|
||||
在 `order_list.vue` 中:
|
||||
|
||||
```vue
|
||||
<!-- 添加选项 -->
|
||||
<el-option label="中通快递" value="zto" />
|
||||
|
||||
<!-- 添加官网链接 -->
|
||||
<el-link :href="detailOfficialUrls.zto" target="_blank" type="primary">中通快递查件</el-link>
|
||||
|
||||
<!-- 更新计算属性 -->
|
||||
zto: `https://www.zto.com/GuestService/Bill?txtBill=${enc}`
|
||||
|
||||
<!-- 更新标签函数 -->
|
||||
if (s === 'zto' || s === 'zhongtong') return '中通快递'
|
||||
```
|
||||
|
||||
### 4. 更新后端逻辑
|
||||
|
||||
在 `PrescriptionOrderLogic.php` 中:
|
||||
|
||||
```php
|
||||
if (!in_array($ec, ['sf', 'jd', 'jt', 'zto', 'auto'], true)) {
|
||||
return 'auto';
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 极兔单号查询失败?
|
||||
|
||||
**可能原因:**
|
||||
1. 快递尚未揽收
|
||||
2. 单号格式错误
|
||||
3. 快递100配置问题
|
||||
|
||||
**解决方案:**
|
||||
1. 确认单号格式:JT + 13位数字
|
||||
2. 等待快递揽收后再查询
|
||||
3. 使用"极兔速递查件"官网链接
|
||||
|
||||
### Q2: 自动识别不准确?
|
||||
|
||||
**解决方案:**
|
||||
手动选择快递公司,不使用"自动识别"。
|
||||
|
||||
### Q3: 如何添加更多快递公司?
|
||||
|
||||
参考上面的"扩展其他快递公司"章节。
|
||||
|
||||
---
|
||||
|
||||
## 更新历史
|
||||
|
||||
### 2024-01-15
|
||||
- ✅ 添加极兔速递支持
|
||||
- ✅ 支持单号自动识别(JT + 13位数字)
|
||||
- ✅ 添加极兔官网查询链接
|
||||
- ✅ 更新前端选项和显示
|
||||
- ✅ 创建测试脚本
|
||||
|
||||
---
|
||||
|
||||
## 相关文档
|
||||
|
||||
- `LOGISTICS_INTEGRATION_GUIDE.md` - 完整物流集成指南
|
||||
- `LOGISTICS_UPDATE_README.md` - 物流功能更新说明
|
||||
- `server/.env.logistics.example` - 配置示例
|
||||
- 快递100官方文档:https://api.kuaidi100.com/
|
||||
@@ -1,281 +0,0 @@
|
||||
# 快递100账号问题解决方案
|
||||
|
||||
## 问题确认
|
||||
|
||||
根据快递100官方文档,错误码 `400` 的含义是:
|
||||
|
||||
```
|
||||
400 - 找不到对应公司
|
||||
原因:提交数据不完整或者账号未充值
|
||||
```
|
||||
|
||||
你的账号返回此错误,最可能的原因是:**账号未充值或余额不足**
|
||||
|
||||
---
|
||||
|
||||
## 立即检查
|
||||
|
||||
### 1. 登录快递100后台
|
||||
|
||||
访问:https://www.kuaidi100.com/
|
||||
|
||||
使用你的账号登录(Customer: `CC25SV92****`)
|
||||
|
||||
### 2. 检查账户余额
|
||||
|
||||
在后台查看:
|
||||
- 当前余额
|
||||
- 可用查询次数
|
||||
- 套餐类型
|
||||
- 支持的快递公司列表
|
||||
|
||||
### 3. 检查充值记录
|
||||
|
||||
确认是否已经充值:
|
||||
- 充值金额
|
||||
- 充值时间
|
||||
- 是否到账
|
||||
|
||||
---
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 方案一:充值账户(推荐)
|
||||
|
||||
1. **登录快递100后台**
|
||||
- 网址:https://www.kuaidi100.com/
|
||||
- 使用你的账号登录
|
||||
|
||||
2. **进入充值页面**
|
||||
- 找到"充值"或"套餐购买"入口
|
||||
- 选择合适的套餐
|
||||
|
||||
3. **选择套餐**
|
||||
- 基础套餐:支持常用快递公司
|
||||
- 标准套餐:支持更多快递公司
|
||||
- 企业套餐:支持所有快递公司
|
||||
|
||||
4. **完成支付**
|
||||
- 支付宝/微信/对公转账
|
||||
- 等待到账(通常即时到账)
|
||||
|
||||
5. **测试查询**
|
||||
```bash
|
||||
cd server
|
||||
php debug_kuaidi100.php
|
||||
```
|
||||
|
||||
### 方案二:联系客服
|
||||
|
||||
如果确认已充值但仍然报错:
|
||||
|
||||
**快递100客服:**
|
||||
- 官网:https://www.kuaidi100.com/
|
||||
- 在线客服:工作日 9:00-18:00
|
||||
- 电话:查看官网联系方式
|
||||
|
||||
**提供信息:**
|
||||
- Customer ID: `CC25SV92****`
|
||||
- 错误信息:`找不到对应公司 (returnCode: 400)`
|
||||
- 测试单号:`JD0230761381812`
|
||||
- 快递公司:京东快递
|
||||
|
||||
### 方案三:使用官网查询(临时方案)
|
||||
|
||||
在充值前,系统已提供官网查询链接:
|
||||
|
||||
**京东快递:**
|
||||
```
|
||||
https://www.jdl.com/#/trackQuery?waybillCode=JD0230761381812
|
||||
```
|
||||
|
||||
**极兔速递:**
|
||||
```
|
||||
https://www.jtexpress.com.cn/index/query/gzquery.html?bills=JT5472795424064
|
||||
```
|
||||
|
||||
**顺丰速运:**
|
||||
```
|
||||
https://www.sf-express.com/cn/sc/dynamic_function/waybill/#search/bill-number/SF1234567890
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 快递100套餐说明
|
||||
|
||||
### 基础套餐
|
||||
- **价格**:约 ¥100-500/年
|
||||
- **查询次数**:1000-5000次
|
||||
- **支持快递**:10-20家常用快递
|
||||
- **适合**:小型企业、个人开发者
|
||||
|
||||
### 标准套餐
|
||||
- **价格**:约 ¥500-2000/年
|
||||
- **查询次数**:5000-20000次
|
||||
- **支持快递**:50+家快递公司
|
||||
- **适合**:中型企业
|
||||
|
||||
### 企业套餐
|
||||
- **价格**:约 ¥2000+/年
|
||||
- **查询次数**:20000+次
|
||||
- **支持快递**:100+家快递公司
|
||||
- **适合**:大型企业、电商平台
|
||||
|
||||
**注意:** 具体价格和套餐内容以快递100官网为准。
|
||||
|
||||
---
|
||||
|
||||
## 测试步骤
|
||||
|
||||
### 充值后测试
|
||||
|
||||
1. **测试配置**
|
||||
```bash
|
||||
cd server
|
||||
php check_logistics_config.php
|
||||
```
|
||||
|
||||
2. **测试API调用**
|
||||
```bash
|
||||
php debug_kuaidi100.php
|
||||
```
|
||||
|
||||
应该看到:
|
||||
```json
|
||||
{
|
||||
"message": "ok",
|
||||
"state": "0",
|
||||
"data": [...]
|
||||
}
|
||||
```
|
||||
|
||||
3. **测试前端查询**
|
||||
- 打开业务订单详情
|
||||
- 填写快递单号
|
||||
- 点击"查询物流"
|
||||
- 应该能看到物流轨迹
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 充值后多久生效?
|
||||
|
||||
**A:** 通常即时生效。如果超过10分钟未生效,联系客服。
|
||||
|
||||
### Q2: 如何查看剩余查询次数?
|
||||
|
||||
**A:** 登录快递100后台,在首页或账户信息页面查看。
|
||||
|
||||
### Q3: 查询次数用完了怎么办?
|
||||
|
||||
**A:**
|
||||
1. 续费充值
|
||||
2. 升级套餐
|
||||
3. 临时使用官网查询链接
|
||||
|
||||
### Q4: 可以按次付费吗?
|
||||
|
||||
**A:** 快递100通常提供套餐制,具体咨询客服。
|
||||
|
||||
### Q5: 支持哪些快递公司?
|
||||
|
||||
**A:**
|
||||
- 基础套餐:顺丰、京东、中通、圆通、申通、韵达等常用快递
|
||||
- 标准套餐:50+家快递公司
|
||||
- 企业套餐:100+家快递公司
|
||||
|
||||
完整列表见:https://api.kuaidi100.com/document/5f0ffb5ebc8da837cbd8aefc/5f0ffc3cbc8da837cbd8afc0
|
||||
|
||||
---
|
||||
|
||||
## 系统当前状态
|
||||
|
||||
### ✅ 已完成的优化
|
||||
|
||||
1. **错误提示优化**
|
||||
- 当快递100返回400错误时
|
||||
- 提示:"快递100暂不支持该快递公司或编码错误,请使用下方官网链接查询"
|
||||
|
||||
2. **官网链接备用**
|
||||
- 所有快递公司都提供官网查询链接
|
||||
- 用户可以直接点击跳转
|
||||
|
||||
3. **支持多家快递**
|
||||
- 顺丰速运(shunfeng)
|
||||
- 京东快递(jingdong)
|
||||
- 极兔速递(jtexpress)
|
||||
|
||||
4. **详细日志记录**
|
||||
- 记录所有查询请求和响应
|
||||
- 方便排查问题
|
||||
|
||||
### 🔄 待完成(充值后)
|
||||
|
||||
1. **实时物流查询**
|
||||
- 快递100 API实时查询
|
||||
- 返回完整物流轨迹
|
||||
- 显示物流状态
|
||||
|
||||
2. **自动识别快递公司**
|
||||
- 根据单号自动识别
|
||||
- 无需手动选择
|
||||
|
||||
3. **高级功能**
|
||||
- 预计到达时间
|
||||
- 快递员信息
|
||||
- 路由信息
|
||||
|
||||
---
|
||||
|
||||
## 建议
|
||||
|
||||
### 短期(立即可用)
|
||||
|
||||
**使用官网查询链接:**
|
||||
- 功能完全可用
|
||||
- 无需额外费用
|
||||
- 只需点击跳转
|
||||
|
||||
### 中期(推荐)
|
||||
|
||||
**充值快递100账户:**
|
||||
- 统一的查询接口
|
||||
- 更好的用户体验
|
||||
- 自动化物流追踪
|
||||
|
||||
### 长期(可选)
|
||||
|
||||
**对接多个物流API:**
|
||||
- 快递100作为主要渠道
|
||||
- 各快递公司官方API作为备用
|
||||
- 提高查询成功率
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
**问题原因:** 快递100账号未充值或余额不足
|
||||
|
||||
**立即行动:**
|
||||
1. 登录快递100后台检查余额
|
||||
2. 充值账户
|
||||
3. 测试查询功能
|
||||
|
||||
**临时方案:**
|
||||
- 使用系统提供的官网查询链接
|
||||
- 功能完全可用
|
||||
|
||||
**充值后:**
|
||||
- 可以使用快递100实时查询
|
||||
- 获得更好的用户体验
|
||||
- 支持更多快递公司
|
||||
|
||||
---
|
||||
|
||||
## 相关文档
|
||||
|
||||
- 快递100官方文档:https://api.kuaidi100.com/document/5f0ffb5ebc8da837cbd8aefc
|
||||
- 快递公司编码表:https://api.kuaidi100.com/document/5f0ffb5ebc8da837cbd8aefc/5f0ffc3cbc8da837cbd8afc0
|
||||
- `LOGISTICS_INTEGRATION_GUIDE.md` - 物流集成指南
|
||||
- `KUAIDI100_TROUBLESHOOTING.md` - 故障排查指南
|
||||
@@ -1,251 +0,0 @@
|
||||
# 快递100查询问题排查指南
|
||||
|
||||
## 问题现象
|
||||
|
||||
查询京东快递单号 `JD0230761381812` 时,快递100返回错误:
|
||||
```
|
||||
"找不到对应公司" (returnCode: 400)
|
||||
```
|
||||
|
||||
但在京东官网可以正常查询到物流信息。
|
||||
|
||||
---
|
||||
|
||||
## 问题原因
|
||||
|
||||
### 1. 快递100账号权限限制
|
||||
|
||||
快递100的不同套餐支持的快递公司数量不同:
|
||||
- **基础版**:支持常用的10-20家快递公司
|
||||
- **标准版**:支持50+家快递公司
|
||||
- **企业版**:支持100+家快递公司
|
||||
|
||||
你的账号可能是基础版,不包含京东快递的查询权限。
|
||||
|
||||
### 2. 快递公司编码问题
|
||||
|
||||
快递100对某些快递公司有特殊的编码要求:
|
||||
- 京东快递:`jingdong`(不是 `jd`)
|
||||
- 极兔速递:`jtexpress`(不是 `jt`)
|
||||
- 顺丰速运:`shunfeng`(不是 `sf`)
|
||||
|
||||
### 3. 需要额外开通
|
||||
|
||||
某些快递公司需要在快递100后台单独开通才能查询。
|
||||
|
||||
---
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 方案一:升级快递100套餐(推荐)
|
||||
|
||||
1. 登录快递100后台:https://www.kuaidi100.com/
|
||||
2. 进入"套餐管理"
|
||||
3. 升级到支持更多快递公司的套餐
|
||||
4. 或单独开通京东快递查询权限
|
||||
|
||||
### 方案二:使用官网查询(当前可用)
|
||||
|
||||
系统已经提供了官网查询链接作为备用方案:
|
||||
|
||||
**京东物流官网:**
|
||||
```
|
||||
https://www.jdl.com/#/trackQuery?waybillCode=JD0230761381812
|
||||
```
|
||||
|
||||
**使用步骤:**
|
||||
1. 在订单详情页面
|
||||
2. 点击"京东物流查件"链接
|
||||
3. 自动跳转到京东官网查询页面
|
||||
4. 查看完整的物流轨迹
|
||||
|
||||
### 方案三:联系快递100客服
|
||||
|
||||
如果确认套餐应该支持但仍然查询失败:
|
||||
|
||||
1. 联系快递100客服
|
||||
2. 提供你的 Customer ID
|
||||
3. 说明查询失败的快递公司
|
||||
4. 请求开通相应权限
|
||||
|
||||
**快递100客服:**
|
||||
- 官网:https://www.kuaidi100.com/
|
||||
- 在线客服:工作日 9:00-18:00
|
||||
|
||||
### 方案四:使用自动识别
|
||||
|
||||
某些情况下,使用 `com=auto` 自动识别可能比指定快递公司更有效:
|
||||
|
||||
```php
|
||||
$paramArr = [
|
||||
'com' => 'auto', // 自动识别
|
||||
'num' => 'JD0230761381812',
|
||||
'resultv2' => '1',
|
||||
];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 当前系统优化
|
||||
|
||||
系统已经做了以下优化:
|
||||
|
||||
### 1. 改进错误提示
|
||||
|
||||
当快递100返回"找不到对应公司"时,系统会提示:
|
||||
```
|
||||
"快递100暂不支持该快递公司或编码错误,请使用下方官网链接查询"
|
||||
```
|
||||
|
||||
### 2. 提供官网链接
|
||||
|
||||
无论快递100是否可用,系统都会提供官网查询链接:
|
||||
- 顺丰速运官网
|
||||
- 京东物流官网
|
||||
- 极兔速递官网
|
||||
|
||||
### 3. 记录详细日志
|
||||
|
||||
系统会记录快递100的错误信息,方便排查:
|
||||
```php
|
||||
Log::info('ExpressTrackService kuaidi100 business fail', [
|
||||
'message' => '找不到对应公司',
|
||||
'returnCode' => '400',
|
||||
'num' => 'JD0230761381812',
|
||||
'com' => 'jingdong'
|
||||
]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 测试步骤
|
||||
|
||||
### 1. 确认快递100配置
|
||||
|
||||
```bash
|
||||
cd server
|
||||
php check_logistics_config.php
|
||||
```
|
||||
|
||||
应该显示:
|
||||
```
|
||||
✅ 配置正确!快递100应该可以正常使用
|
||||
```
|
||||
|
||||
### 2. 测试API调用
|
||||
|
||||
```bash
|
||||
php debug_kuaidi100.php
|
||||
```
|
||||
|
||||
查看快递100的实际响应。
|
||||
|
||||
### 3. 测试自动识别
|
||||
|
||||
```bash
|
||||
php test_auto_detect.php
|
||||
```
|
||||
|
||||
尝试使用自动识别功能。
|
||||
|
||||
### 4. 前端测试
|
||||
|
||||
1. 打开业务订单详情
|
||||
2. 填写快递单号:`JD0230761381812`
|
||||
3. 快递公司选择"京东快递"
|
||||
4. 点击"查询物流"
|
||||
5. 如果失败,点击"京东物流查件"链接
|
||||
|
||||
---
|
||||
|
||||
## 快递100支持的快递公司
|
||||
|
||||
### 常见快递公司编码
|
||||
|
||||
| 快递公司 | 快递100编码 | 是否需要手机号 |
|
||||
|---------|------------|--------------|
|
||||
| 顺丰速运 | shunfeng | 是(后4位) |
|
||||
| 京东快递 | jingdong | 否 |
|
||||
| 极兔速递 | jtexpress | 否 |
|
||||
| 中通快递 | zhongtong | 否 |
|
||||
| 圆通速递 | yuantong | 否 |
|
||||
| 申通快递 | shentong | 否 |
|
||||
| 韵达快递 | yunda | 否 |
|
||||
| 百世快递 | huitongkuaidi | 否 |
|
||||
| EMS | ems | 否 |
|
||||
| 邮政包裹 | youzhengguonei | 否 |
|
||||
|
||||
### 查询完整列表
|
||||
|
||||
访问快递100官方文档:
|
||||
https://api.kuaidi100.com/document/5f0ffb5ebc8da837cbd8aefc/5f0ffc3cbc8da837cbd8afc0
|
||||
|
||||
---
|
||||
|
||||
## 常见错误码
|
||||
|
||||
| 错误码 | 说明 | 解决方案 |
|
||||
|-------|------|---------|
|
||||
| 400 | 找不到对应公司 | 检查快递公司编码或升级套餐 |
|
||||
| 500 | 服务器错误 | 稍后重试 |
|
||||
| 600 | 您不是合法的订阅者 | 检查 Customer 和 Key |
|
||||
| 601 | SIGN签名错误 | 检查签名算法 |
|
||||
| 700 | 订阅数据已达上限 | 升级套餐或等待重置 |
|
||||
| 701 | 快递公司参数异常 | 检查 com 参数 |
|
||||
| 702 | 快递单号参数异常 | 检查 num 参数 |
|
||||
| 703 | 查询无结果 | 快递可能未揽收 |
|
||||
| 704 | 快递公司识别失败 | 使用自动识别或指定公司 |
|
||||
|
||||
---
|
||||
|
||||
## 建议
|
||||
|
||||
### 短期方案(立即可用)
|
||||
|
||||
使用官网查询链接:
|
||||
- 系统已经提供了所有快递公司的官网链接
|
||||
- 用户点击即可跳转查询
|
||||
- 无需额外配置
|
||||
|
||||
### 长期方案(推荐)
|
||||
|
||||
1. **升级快递100套餐**
|
||||
- 支持更多快递公司
|
||||
- 提供更好的用户体验
|
||||
- 统一的查询接口
|
||||
|
||||
2. **对接多个物流API**
|
||||
- 快递100作为主要渠道
|
||||
- 各快递公司官方API作为备用
|
||||
- 自动切换,提高成功率
|
||||
|
||||
3. **缓存查询结果**
|
||||
- 减少API调用次数
|
||||
- 降低费用
|
||||
- 提高响应速度
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
当前问题是快递100账号不支持京东快递查询,但系统已经提供了官网查询链接作为备用方案。
|
||||
|
||||
**用户可以:**
|
||||
1. 点击"京东物流查件"链接查询
|
||||
2. 联系快递100升级套餐
|
||||
3. 等待系统对接京东官方API
|
||||
|
||||
**系统已优化:**
|
||||
1. 改进错误提示
|
||||
2. 提供官网链接
|
||||
3. 记录详细日志
|
||||
4. 支持多种查询方式
|
||||
|
||||
---
|
||||
|
||||
## 相关文档
|
||||
|
||||
- `LOGISTICS_INTEGRATION_GUIDE.md` - 物流集成指南
|
||||
- `LOGISTICS_UPDATE_README.md` - 更新说明
|
||||
- `JTEXPRESS_SUPPORT_ADDED.md` - 极兔速递支持
|
||||
- 快递100官方文档:https://api.kuaidi100.com/
|
||||
@@ -1,358 +0,0 @@
|
||||
# 本地录制调试指南(第二版)
|
||||
|
||||
## 问题描述
|
||||
本地录制总是失败,`blob` 为空,导致无法保存通话录制文件。
|
||||
|
||||
## 根本原因分析
|
||||
|
||||
从实际日志分析发现:
|
||||
1. **录制启动太晚**:原来的 1200ms 延迟导致录制在对方挂断后才启动
|
||||
2. **SDK 提前销毁轨道**:`stopLocalVideo/stopLocalAudio` 在 `exitRoom` 之前就销毁了视频轨道
|
||||
3. **时间窗口太短**:从对方离开到轨道销毁只有约 200-300ms
|
||||
|
||||
关键时间线(问题场景):
|
||||
```
|
||||
15:30:51.841 - 对方离开(USER_LEAVE 事件)
|
||||
15:30:51.xxx - beginStopNow 被调用(但录制还未启动)
|
||||
15:30:52.072 - stopLocalVideo/stopLocalAudio(轨道被销毁)
|
||||
15:30:52.102 - exitRoom
|
||||
15:30:52.374 - MediaRecorder 已启动(太晚了!)
|
||||
15:30:52.xxx - 数据块数: 0(因为轨道已销毁)
|
||||
```
|
||||
|
||||
## 已实施的修复(第二版)
|
||||
|
||||
### 1. 大幅缩短录制启动延迟
|
||||
- **从 1200ms 缩短到 500ms**
|
||||
- 使用 300ms 间隔重试(最多 10 次)
|
||||
- 更快地检测和启动录制
|
||||
|
||||
```typescript
|
||||
// 延迟 500ms 后开始尝试(给视频元素一点加载时间)
|
||||
startLocalRecTimer = setTimeout(() => attemptStartRecording(0), 500)
|
||||
```
|
||||
|
||||
### 2. 在通话结束事件时立即停止录制
|
||||
监听 TUICallEngine 事件,在检测到通话结束时立即停止:
|
||||
|
||||
```typescript
|
||||
const onEarlyStopRecording = () => {
|
||||
console.log('[chat-dialog] 检测到通话结束事件,立即停止录制')
|
||||
if (localCallRecorder.isRecording()) {
|
||||
console.log('[chat-dialog] 录制进行中,触发 beginStopNow')
|
||||
localCallRecorder.beginStopNow()
|
||||
}
|
||||
}
|
||||
// 监听: CALL_END, USER_LEAVE, ON_CALL_NOT_CONNECTED
|
||||
```
|
||||
|
||||
### 3. 缩短停止延迟
|
||||
- **从 300ms 缩短到 100ms**
|
||||
- 减少等待时间,避免 SDK 提前销毁轨道
|
||||
|
||||
```typescript
|
||||
// 缩短延迟到 100ms,避免 SDK 提前销毁轨道
|
||||
setTimeout(() => {
|
||||
mr.stop()
|
||||
}, 100)
|
||||
```
|
||||
|
||||
### 4. 增强日志输出
|
||||
在关键位置添加了详细的 console.log:
|
||||
|
||||
- **录制启动阶段**:
|
||||
- 检查容器和通话状态
|
||||
- 视频元素数量和就绪状态
|
||||
- MediaRecorder 创建和启动状态
|
||||
- Canvas 和音频混音初始化
|
||||
|
||||
- **录制进行阶段**:
|
||||
- 数据块收集(ondataavailable)
|
||||
- MediaRecorder 状态变化
|
||||
|
||||
- **录制停止阶段**:
|
||||
- beginStopNow 调用时机
|
||||
- 数据块数量
|
||||
- Blob 生成结果
|
||||
|
||||
### 5. 智能视频元素检测
|
||||
改进了视频元素查找逻辑:
|
||||
|
||||
```typescript
|
||||
// 1. 首先检查容器内的视频
|
||||
let videos = Array.from(root.querySelectorAll('video'))
|
||||
|
||||
// 2. 如果没有,检查 body(TUICallKit Teleport)
|
||||
if (videos.length === 0) {
|
||||
const bodyVideos = Array.from(document.body.querySelectorAll('video'))
|
||||
videos = bodyVideos.filter(v => {
|
||||
const so = v.srcObject
|
||||
if (!(so instanceof MediaStream)) return false
|
||||
return so.getVideoTracks().some(t => t.readyState === 'live')
|
||||
})
|
||||
}
|
||||
|
||||
// 3. 检查视频是否有有效数据
|
||||
const hasReadyVideo = videos.some(v =>
|
||||
v.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA
|
||||
)
|
||||
```
|
||||
|
||||
## 调试步骤
|
||||
|
||||
### 1. 打开浏览器控制台
|
||||
在发起视频通话前,打开浏览器开发者工具(F12),切换到 Console 标签。
|
||||
|
||||
### 2. 发起视频通话
|
||||
点击视频通话按钮,观察控制台输出。
|
||||
|
||||
### 3. 关键日志检查点
|
||||
|
||||
#### 通话接通后(约 0.5-1 秒)
|
||||
应该看到:
|
||||
```
|
||||
[chat-dialog] 尝试启动本地录制(第 1 次)
|
||||
[chat-dialog] 在 body 中找到活跃视频: 2
|
||||
[chat-dialog] 视频元素已就绪,立即启动录制
|
||||
[call-local-recorder] 使用 MIME 类型: video/webm;codecs=vp9,opus
|
||||
[call-local-recorder] Canvas 视频轨道数: 1
|
||||
[call-local-recorder] 找到视频元素数量: 2
|
||||
[call-local-recorder] 输出流轨道: { video: 1, audio: 2 }
|
||||
[call-local-recorder] MediaRecorder 创建成功
|
||||
[call-local-recorder] MediaRecorder.start() 调用成功
|
||||
[call-local-recorder] MediaRecorder 已启动
|
||||
[chat-dialog] ✅ 本地录制已成功启动
|
||||
```
|
||||
|
||||
#### 录制过程中(每 250ms)
|
||||
应该看到:
|
||||
```
|
||||
[call-local-recorder] 收到数据块,大小: 12345 (总计: 1 块)
|
||||
[call-local-recorder] 收到数据块,大小: 15678 (总计: 2 块)
|
||||
...
|
||||
```
|
||||
|
||||
**重要**:如果没有看到数据块,说明录制有问题!
|
||||
|
||||
#### 对方挂断或己方挂断时
|
||||
应该看到:
|
||||
```
|
||||
[chat-dialog] 检测到通话结束事件,立即停止录制
|
||||
[chat-dialog] 录制进行中,触发 beginStopNow
|
||||
[call-local-recorder] beginStopNow 被调用
|
||||
[call-local-recorder] 准备停止 MediaRecorder,当前状态: recording 已收集数据块: 15
|
||||
[call-local-recorder] 调用 requestData()
|
||||
[call-local-recorder] 延迟后数据块数: 16
|
||||
[call-local-recorder] 调用 stop()
|
||||
[call-local-recorder] MediaRecorder onstop 触发,数据块数: 16
|
||||
[call-local-recorder] 生成 Blob: 1234567 bytes, type: video/webm
|
||||
[chat-dialog] localCallRecorder.stop() 返回: 1234567 bytes
|
||||
[chat-dialog] 开始上传录制文件
|
||||
[chat-dialog] 上传成功,文件 URL: https://...
|
||||
[chat-dialog] 关联录制文件到诊单
|
||||
本地录制已上传并关联到通话记录
|
||||
```
|
||||
|
||||
## 常见问题诊断
|
||||
|
||||
### 问题 1:录制启动太晚
|
||||
**症状**:
|
||||
```
|
||||
[call-local-recorder] MediaRecorder 已启动
|
||||
(出现在 stopLocalVideo 之后)
|
||||
```
|
||||
|
||||
**原因**:
|
||||
- 视频元素加载慢
|
||||
- 重试次数不够
|
||||
|
||||
**解决**:
|
||||
- 已缩短初始延迟到 500ms
|
||||
- 增加重试次数到 10 次
|
||||
- 每次重试间隔 300ms
|
||||
|
||||
### 问题 2:找不到视频元素
|
||||
**症状**:
|
||||
```
|
||||
[chat-dialog] 未找到视频元素,300ms 后重试
|
||||
(重复多次)
|
||||
```
|
||||
|
||||
**原因**:
|
||||
- TUICallKit 将视频 Teleport 到 body
|
||||
- 视频元素尚未渲染
|
||||
|
||||
**解决**:
|
||||
- 代码已添加 body 视频检查
|
||||
- 添加了活跃视频流过滤
|
||||
|
||||
### 问题 3:视频元素未就绪
|
||||
**症状**:
|
||||
```
|
||||
[chat-dialog] 视频元素未就绪,300ms 后重试
|
||||
```
|
||||
|
||||
**原因**:
|
||||
- 视频流尚未加载
|
||||
- readyState < HAVE_CURRENT_DATA
|
||||
|
||||
**解决**:
|
||||
- 代码已添加就绪检查和重试
|
||||
- 最多重试 10 次
|
||||
|
||||
### 问题 4:没有收到数据块
|
||||
**症状**:
|
||||
- 录制启动成功
|
||||
- 但从未看到 "收到数据块" 日志
|
||||
- 最终 `数据块数: 0`
|
||||
|
||||
**可能原因**:
|
||||
1. Canvas 绘制失败(跨域问题)
|
||||
2. MediaRecorder 不支持当前 MIME 类型
|
||||
3. 视频流被中断或为空
|
||||
|
||||
**检查方法**:
|
||||
```javascript
|
||||
// 在控制台手动检查
|
||||
const videos = document.querySelectorAll('video')
|
||||
videos.forEach((v, i) => {
|
||||
console.log(`Video ${i}:`, {
|
||||
readyState: v.readyState,
|
||||
videoWidth: v.videoWidth,
|
||||
videoHeight: v.videoHeight,
|
||||
srcObject: v.srcObject,
|
||||
videoTracks: v.srcObject?.getVideoTracks().map(t => ({
|
||||
id: t.id,
|
||||
label: t.label,
|
||||
readyState: t.readyState,
|
||||
enabled: t.enabled
|
||||
})),
|
||||
audioTracks: v.srcObject?.getAudioTracks().map(t => ({
|
||||
id: t.id,
|
||||
label: t.label,
|
||||
readyState: t.readyState,
|
||||
enabled: t.enabled
|
||||
}))
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
### 问题 5:通话时间过短
|
||||
**症状**:
|
||||
```
|
||||
[call-local-recorder] 生成 Blob: null
|
||||
或
|
||||
[call-local-recorder] 生成 Blob: 1234 bytes(很小)
|
||||
```
|
||||
|
||||
**原因**:
|
||||
- 通话时长 < 1 秒
|
||||
- 录制启动后立即挂断
|
||||
|
||||
**解决**:
|
||||
- 确保通话时长 > 2 秒
|
||||
- 测试时保持通话至少 5 秒
|
||||
|
||||
### 问题 6:SDK 提前销毁轨道
|
||||
**症状**:
|
||||
```
|
||||
[call-local-recorder] MediaRecorder 错误
|
||||
或
|
||||
数据块数为 0
|
||||
```
|
||||
|
||||
**原因**:
|
||||
- SDK exitRoom 在 MediaRecorder.stop() 之前执行
|
||||
- 视频轨道被销毁
|
||||
|
||||
**解决**:
|
||||
- 已在 USER_LEAVE 事件时立即停止录制
|
||||
- 已在多个挂断入口添加钩子
|
||||
- 缩短停止延迟到 100ms
|
||||
|
||||
## 测试建议
|
||||
|
||||
### 最小测试场景
|
||||
1. 发起视频通话
|
||||
2. 等待接通(看到对方画面)
|
||||
3. **保持通话至少 5 秒**(重要!)
|
||||
4. 正常挂断
|
||||
5. 检查控制台日志和录制结果
|
||||
|
||||
### 关键检查点
|
||||
- [ ] 录制在接通后 1 秒内启动
|
||||
- [ ] 看到 "✅ 本地录制已成功启动"
|
||||
- [ ] 看到持续的 "收到数据块" 日志
|
||||
- [ ] 数据块数 > 0(至少 4-5 块)
|
||||
- [ ] 生成的 Blob 大小 > 10KB
|
||||
- [ ] 上传成功并关联到诊单
|
||||
|
||||
### 压力测试场景
|
||||
1. 快速挂断(2-3 秒)
|
||||
2. 对方挂断
|
||||
3. 网络中断
|
||||
4. 切换会话后挂断
|
||||
5. 关闭聊天窗口
|
||||
|
||||
## 预期改进效果
|
||||
|
||||
### 修复前
|
||||
```
|
||||
通话接通 -> 等待 1200ms -> 尝试启动录制 -> 对方挂断 -> SDK 销毁轨道 -> 录制失败
|
||||
```
|
||||
|
||||
### 修复后
|
||||
```
|
||||
通话接通 -> 等待 500ms -> 快速启动录制 -> 开始收集数据 ->
|
||||
检测到挂断 -> 立即停止录制 -> 生成 Blob -> 上传成功
|
||||
```
|
||||
|
||||
## 浏览器兼容性
|
||||
|
||||
### 支持的浏览器
|
||||
- Chrome/Edge 85+
|
||||
- Firefox 78+
|
||||
- Safari 14.1+
|
||||
|
||||
### MIME 类型优先级
|
||||
1. `video/webm;codecs=vp9,opus` (最佳)
|
||||
2. `video/webm;codecs=vp8,opus`
|
||||
3. `video/webm;codecs=vp9`
|
||||
4. `video/webm` (兜底)
|
||||
|
||||
### 检查浏览器支持
|
||||
```javascript
|
||||
const mimes = [
|
||||
'video/webm;codecs=vp9,opus',
|
||||
'video/webm;codecs=vp8,opus',
|
||||
'video/webm;codecs=vp9',
|
||||
'video/webm'
|
||||
]
|
||||
mimes.forEach(m => {
|
||||
console.log(m, MediaRecorder.isTypeSupported(m))
|
||||
})
|
||||
```
|
||||
|
||||
## 性能优化建议
|
||||
|
||||
1. **Canvas 分辨率**:当前 1280x720,可根据需要调整
|
||||
2. **数据块间隔**:当前 250ms,可调整为 500ms 减少开销
|
||||
3. **音频混音**:如果不需要音频,可以禁用 AudioContext
|
||||
|
||||
## 下一步优化(如果问题仍存在)
|
||||
|
||||
1. **进一步缩短启动延迟**:从 500ms 减少到 300ms
|
||||
2. **增加录制状态监控**:定期检查 MediaRecorder 状态
|
||||
3. **添加降级方案**:如果本地录制失败,提示用户依赖云端录制
|
||||
4. **添加手动录制控制**:让用户手动开始/停止录制
|
||||
5. **使用 MutationObserver**:监听视频元素的添加,立即启动录制
|
||||
|
||||
## 联系支持
|
||||
|
||||
如果按照本指南操作后问题仍未解决,请提供:
|
||||
1. 完整的控制台日志(从通话开始到结束)
|
||||
2. 浏览器版本和操作系统
|
||||
3. 通话时长(秒)
|
||||
4. 是否看到视频画面
|
||||
5. 是否有任何错误提示
|
||||
6. 数据块数量(如果有)
|
||||
@@ -1,266 +0,0 @@
|
||||
# 物流查询集成指南
|
||||
|
||||
## 当前实现
|
||||
|
||||
系统已集成快递100 API,支持顺丰速运和京东快递的物流轨迹查询。
|
||||
|
||||
### 查询方式优先级
|
||||
|
||||
1. **快递100 API**(推荐)- 实时查询物流轨迹
|
||||
2. **官网链接跳转**(备用)- 未配置API时使用
|
||||
|
||||
---
|
||||
|
||||
## 快递100 API 配置(推荐)
|
||||
|
||||
### 1. 注册快递100账号
|
||||
|
||||
访问:https://www.kuaidi100.com/
|
||||
- 注册企业账号
|
||||
- 申请API接口权限
|
||||
- 获取 `customer` 和 `key`
|
||||
|
||||
### 2. 配置环境变量
|
||||
|
||||
在 `server/.env` 文件中添加:
|
||||
|
||||
```env
|
||||
# 快递100配置
|
||||
LOGISTICS_KUAIDI100_ENABLE=true
|
||||
LOGISTICS_KUAIDI100_CUSTOMER=你的customer
|
||||
LOGISTICS_KUAIDI100_KEY=你的key
|
||||
LOGISTICS_KUAIDI100_QUERY_URL=https://poll.kuaidi100.com/poll/query.do
|
||||
```
|
||||
|
||||
### 3. 配置文件
|
||||
|
||||
在 `server/config/logistics.php` 中:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
return [
|
||||
'kuaidi100' => [
|
||||
'enable' => env('LOGISTICS_KUAIDI100_ENABLE', false),
|
||||
'customer' => env('LOGISTICS_KUAIDI100_CUSTOMER', ''),
|
||||
'key' => env('LOGISTICS_KUAIDI100_KEY', ''),
|
||||
'query_url' => env('LOGISTICS_KUAIDI100_QUERY_URL', 'https://poll.kuaidi100.com/poll/query.do'),
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### 4. 支持的快递公司
|
||||
|
||||
- **顺丰速运** (sf / shunfeng)
|
||||
- **京东快递** (jd)
|
||||
- 更多快递公司可通过快递100文档查询编码
|
||||
|
||||
---
|
||||
|
||||
## 官网查询链接(备用方案)
|
||||
|
||||
当未配置快递100 API时,系统会提供官网查询链接:
|
||||
|
||||
### 顺丰速运
|
||||
- 链接格式:`https://www.sf-express.com/cn/sc/dynamic_function/waybill/#search/bill-number/{运单号}`
|
||||
- 特点:需要输入收件人手机号后4位验证
|
||||
|
||||
### 京东物流
|
||||
- 链接格式:`https://www.jdl.com/#/trackQuery?waybillCode={运单号}`
|
||||
- 特点:直接查询,无需验证
|
||||
|
||||
---
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 前端查询界面
|
||||
|
||||
位置:`业务订单详情` → `物流轨迹`
|
||||
|
||||
功能:
|
||||
1. 选择快递公司(自动识别/顺丰/京东)
|
||||
2. 点击"查询物流"按钮
|
||||
3. 查看实时物流轨迹
|
||||
4. 备用:点击"顺丰官网查件"或"京东物流查件"
|
||||
|
||||
### 自动识别规则
|
||||
|
||||
系统会根据运单号自动识别快递公司:
|
||||
- 顺丰:以 `SF` 开头
|
||||
- 京东:以 `JD`、`JDV`、`JDK`、`JDEX` 开头
|
||||
|
||||
### 物流状态说明
|
||||
|
||||
- **0** - 在途
|
||||
- **1** - 揽收
|
||||
- **2** - 疑难
|
||||
- **3** - 已签收
|
||||
- **4** - 退签
|
||||
- **5** - 派件中
|
||||
- **6** - 退回
|
||||
- **7** - 转投
|
||||
- **10** - 待清关
|
||||
- **11** - 清关中
|
||||
- **12** - 已清关
|
||||
- **13** - 清关异常
|
||||
- **14** - 收件人拒签
|
||||
|
||||
---
|
||||
|
||||
## 顺丰查询注意事项
|
||||
|
||||
### 为什么查不到顺丰快递?
|
||||
|
||||
1. **手机号验证**
|
||||
- 顺丰查询需要收件人手机号后4位
|
||||
- 系统已自动传入订单中的收件人手机号
|
||||
- 如果查询失败,请检查订单中的手机号是否正确
|
||||
|
||||
2. **运单号格式**
|
||||
- 确保运单号正确无误
|
||||
- 顺丰运单号通常以 `SF` 开头
|
||||
|
||||
3. **快递100配置**
|
||||
- 确认已正确配置 `LOGISTICS_KUAIDI100_*` 环境变量
|
||||
- 确认快递100账户余额充足
|
||||
|
||||
4. **官网查询**
|
||||
- 如果API查询失败,可使用官网链接
|
||||
- 官网查询需要手动输入手机号后4位
|
||||
|
||||
---
|
||||
|
||||
## 扩展其他快递公司
|
||||
|
||||
### 1. 修改 ExpressTrackService.php
|
||||
|
||||
在 `resolveCarrier` 方法中添加新的快递公司识别:
|
||||
|
||||
```php
|
||||
if ($ec === 'ems') {
|
||||
return ['carrier' => 'ems', 'kuaidi_com' => 'ems', 'label' => 'EMS'];
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 添加官网链接
|
||||
|
||||
在 `officialUrls` 方法中添加:
|
||||
|
||||
```php
|
||||
return [
|
||||
'sf' => '...',
|
||||
'jd' => '...',
|
||||
'ems' => 'https://www.ems.com.cn/queryList?mailNum=' . $enc,
|
||||
];
|
||||
```
|
||||
|
||||
### 3. 更新前端选项
|
||||
|
||||
在 `order_list.vue` 中添加选项:
|
||||
|
||||
```vue
|
||||
<el-option label="EMS" value="ems" />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 问题1:提示"未配置快递100查询密钥"
|
||||
|
||||
**解决方案:**
|
||||
1. 检查 `.env` 文件中的配置
|
||||
2. 确认 `LOGISTICS_KUAIDI100_ENABLE=true`
|
||||
3. 重启服务器
|
||||
|
||||
### 问题2:查询返回"查询失败"
|
||||
|
||||
**可能原因:**
|
||||
1. 运单号错误
|
||||
2. 快递尚未揽收
|
||||
3. 快递100账户余额不足
|
||||
4. 手机号后4位不匹配(顺丰)
|
||||
|
||||
**解决方案:**
|
||||
1. 核对运单号
|
||||
2. 等待快递揽收后再查询
|
||||
3. 充值快递100账户
|
||||
4. 使用官网链接手动查询
|
||||
|
||||
### 问题3:顺丰查询失败但京东正常
|
||||
|
||||
**原因:**
|
||||
顺丰查询需要手机号后4位验证
|
||||
|
||||
**解决方案:**
|
||||
1. 确保订单中填写了正确的收件人手机号
|
||||
2. 使用官网链接,手动输入手机号后4位
|
||||
|
||||
---
|
||||
|
||||
## API 接口说明
|
||||
|
||||
### 查询物流轨迹
|
||||
|
||||
**接口:** `GET /tcm.prescriptionOrder/logisticsTrace`
|
||||
|
||||
**参数:**
|
||||
- `id` (必填): 业务订单ID
|
||||
- `express_company` (可选): 快递公司 (auto/sf/jd)
|
||||
|
||||
**返回示例:**
|
||||
|
||||
```json
|
||||
{
|
||||
"carrier": "sf",
|
||||
"carrier_label": "顺丰速运",
|
||||
"kuaidi_com": "shunfeng",
|
||||
"traces": [
|
||||
{
|
||||
"time": "2024-01-15 10:30:00",
|
||||
"context": "快件已签收"
|
||||
},
|
||||
{
|
||||
"time": "2024-01-15 08:00:00",
|
||||
"context": "派件中"
|
||||
}
|
||||
],
|
||||
"state": "3",
|
||||
"state_text": "已签收",
|
||||
"source": "kuaidi100",
|
||||
"hint": "",
|
||||
"official_url": "https://www.sf-express.com/...",
|
||||
"official_urls": {
|
||||
"sf": "https://www.sf-express.com/...",
|
||||
"jd": "https://www.jdl.com/..."
|
||||
},
|
||||
"tracking_number": "SF1234567890",
|
||||
"express_company_used": "sf"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 费用说明
|
||||
|
||||
### 快递100 API
|
||||
|
||||
- 按查询次数计费
|
||||
- 具体价格请咨询快递100官方
|
||||
- 建议:配置后台缓存减少重复查询
|
||||
|
||||
### 官网查询
|
||||
|
||||
- 完全免费
|
||||
- 需要用户手动跳转
|
||||
- 顺丰需要手机号验证
|
||||
|
||||
---
|
||||
|
||||
## 更新日志
|
||||
|
||||
### 2024-01-15
|
||||
- 更新顺丰官网查询链接(新版)
|
||||
- 更新京东物流查询链接
|
||||
- 优化手机号后4位自动传入逻辑
|
||||
- 完善错误提示信息
|
||||
@@ -1,308 +0,0 @@
|
||||
# 物流查询功能更新说明
|
||||
|
||||
## 更新内容
|
||||
|
||||
### 1. 更新官网查询链接
|
||||
|
||||
#### 顺丰速运
|
||||
- **旧链接**:`https://ucmp.sf-express.com/cx/#/waybill/waybill-search?waybillNumber={单号}`
|
||||
- **新链接**:`https://www.sf-express.com/cn/sc/dynamic_function/waybill/#search/bill-number/{单号}`
|
||||
- **说明**:更新为顺丰官网最新版查询页面
|
||||
|
||||
#### 京东物流
|
||||
- **旧链接**:`https://www.jdl.com/express/tracking/?waybillCodes={单号}`
|
||||
- **新链接**:`https://www.jdl.com/#/trackQuery?waybillCode={单号}`
|
||||
- **说明**:更新为京东物流最新版查询页面
|
||||
|
||||
### 2. 更新文件列表
|
||||
|
||||
- `server/app/common/service/ExpressTrackService.php` - 后端服务类
|
||||
- `admin/src/views/consumer/prescription/order_list.vue` - 前端订单列表页面
|
||||
|
||||
### 3. 新增文档
|
||||
|
||||
- `LOGISTICS_INTEGRATION_GUIDE.md` - 完整的物流集成指南
|
||||
- `server/.env.logistics.example` - 环境变量配置示例
|
||||
- `server/test_logistics.php` - 物流查询测试脚本
|
||||
|
||||
---
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 方案一:使用快递100 API(推荐)
|
||||
|
||||
#### 1. 注册快递100账号
|
||||
访问:https://www.kuaidi100.com/
|
||||
- 注册企业账号
|
||||
- 申请API接口权限
|
||||
- 获取 Customer 和 Key
|
||||
|
||||
#### 2. 配置环境变量
|
||||
在 `server/.env` 文件中添加:
|
||||
|
||||
```env
|
||||
LOGISTICS_KUAIDI100_CUSTOMER=你的customer
|
||||
LOGISTICS_KUAIDI100_KEY=你的key
|
||||
```
|
||||
|
||||
#### 3. 重启服务器
|
||||
```bash
|
||||
# 重启PHP服务
|
||||
systemctl restart php-fpm
|
||||
|
||||
# 或重启整个应用
|
||||
```
|
||||
|
||||
#### 4. 测试配置
|
||||
```bash
|
||||
cd server
|
||||
php test_logistics.php
|
||||
```
|
||||
|
||||
### 方案二:使用官网链接(免费)
|
||||
|
||||
无需配置,系统会自动提供官网查询链接:
|
||||
- 顺丰速运:需要输入收件人手机号后4位
|
||||
- 京东物流:直接查询
|
||||
|
||||
---
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 前端操作
|
||||
|
||||
1. 进入"业务订单列表"
|
||||
2. 点击订单查看详情
|
||||
3. 在"物流轨迹"区域:
|
||||
- 选择快递公司(自动识别/顺丰/京东)
|
||||
- 点击"查询物流"按钮
|
||||
- 查看实时物流轨迹
|
||||
4. 备用方案:
|
||||
- 点击"顺丰官网查件"或"京东物流查件"
|
||||
- 跳转到官网手动查询
|
||||
|
||||
### 自动识别规则
|
||||
|
||||
系统会根据运单号自动识别快递公司:
|
||||
- **顺丰**:以 `SF` 开头的运单号
|
||||
- **京东**:以 `JD`、`JDV`、`JDK`、`JDEX` 开头的运单号
|
||||
|
||||
---
|
||||
|
||||
## 顺丰查询问题解决
|
||||
|
||||
### 问题:查不到顺丰快递
|
||||
|
||||
#### 原因分析
|
||||
1. **手机号验证失败**
|
||||
- 顺丰查询需要收件人手机号后4位
|
||||
- 系统会自动从订单中获取手机号
|
||||
- 如果手机号不正确,查询会失败
|
||||
|
||||
2. **运单号错误**
|
||||
- 确保运单号完整且正确
|
||||
- 顺丰运单号通常以 `SF` 开头
|
||||
|
||||
3. **快递未揽收**
|
||||
- 刚下单的快递可能还未揽收
|
||||
- 等待快递员揽收后再查询
|
||||
|
||||
4. **快递100配置问题**
|
||||
- Customer 或 Key 配置错误
|
||||
- 账户余额不足
|
||||
|
||||
#### 解决方案
|
||||
|
||||
**方案1:检查订单手机号**
|
||||
```
|
||||
1. 打开订单详情
|
||||
2. 确认"收件人手机号"字段填写正确
|
||||
3. 重新查询物流
|
||||
```
|
||||
|
||||
**方案2:使用官网查询**
|
||||
```
|
||||
1. 点击"顺丰官网查件"链接
|
||||
2. 在官网页面手动输入手机号后4位
|
||||
3. 查看物流信息
|
||||
```
|
||||
|
||||
**方案3:检查快递100配置**
|
||||
```bash
|
||||
# 1. 查看配置
|
||||
cat server/.env | grep LOGISTICS_KUAIDI100
|
||||
|
||||
# 2. 测试配置
|
||||
cd server
|
||||
php test_logistics.php
|
||||
|
||||
# 3. 检查日志
|
||||
tail -f runtime/log/error.log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 京东查询问题解决
|
||||
|
||||
### 问题:查不到京东快递
|
||||
|
||||
#### 原因分析
|
||||
1. 运单号错误
|
||||
2. 快递未揽收
|
||||
3. 快递100配置问题
|
||||
|
||||
#### 解决方案
|
||||
|
||||
**方案1:核对运单号**
|
||||
- 确保运单号完整且正确
|
||||
- 京东运单号通常以 `JD` 开头
|
||||
|
||||
**方案2:使用官网查询**
|
||||
- 点击"京东物流查件"链接
|
||||
- 京东查询无需手机号验证
|
||||
|
||||
**方案3:等待揽收**
|
||||
- 刚下单的快递可能还未揽收
|
||||
- 等待1-2小时后再查询
|
||||
|
||||
---
|
||||
|
||||
## API 接口说明
|
||||
|
||||
### 查询物流轨迹
|
||||
|
||||
**接口地址:** `GET /tcm.prescriptionOrder/logisticsTrace`
|
||||
|
||||
**请求参数:**
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | number | 是 | 业务订单ID |
|
||||
| express_company | string | 否 | 快递公司 (auto/sf/jd) |
|
||||
|
||||
**返回示例:**
|
||||
```json
|
||||
{
|
||||
"carrier": "sf",
|
||||
"carrier_label": "顺丰速运",
|
||||
"kuaidi_com": "shunfeng",
|
||||
"traces": [
|
||||
{
|
||||
"time": "2024-01-15 10:30:00",
|
||||
"context": "快件已签收"
|
||||
},
|
||||
{
|
||||
"time": "2024-01-15 08:00:00",
|
||||
"context": "派件中"
|
||||
}
|
||||
],
|
||||
"state": "3",
|
||||
"state_text": "已签收",
|
||||
"source": "kuaidi100",
|
||||
"hint": "",
|
||||
"official_url": "https://www.sf-express.com/...",
|
||||
"official_urls": {
|
||||
"sf": "https://www.sf-express.com/...",
|
||||
"jd": "https://www.jdl.com/..."
|
||||
},
|
||||
"tracking_number": "SF1234567890",
|
||||
"express_company_used": "sf"
|
||||
}
|
||||
```
|
||||
|
||||
**物流状态码:**
|
||||
| 状态码 | 说明 |
|
||||
|--------|------|
|
||||
| 0 | 在途 |
|
||||
| 1 | 揽收 |
|
||||
| 2 | 疑难 |
|
||||
| 3 | 已签收 |
|
||||
| 4 | 退签 |
|
||||
| 5 | 派件中 |
|
||||
| 6 | 退回 |
|
||||
| 7 | 转投 |
|
||||
| 10 | 待清关 |
|
||||
| 11 | 清关中 |
|
||||
| 12 | 已清关 |
|
||||
| 13 | 清关异常 |
|
||||
| 14 | 收件人拒签 |
|
||||
|
||||
---
|
||||
|
||||
## 测试步骤
|
||||
|
||||
### 1. 测试配置
|
||||
```bash
|
||||
cd server
|
||||
php test_logistics.php
|
||||
```
|
||||
|
||||
### 2. 测试官网链接
|
||||
1. 打开浏览器
|
||||
2. 访问生成的官网链接
|
||||
3. 确认能正常打开查询页面
|
||||
|
||||
### 3. 测试实时查询(需要配置快递100)
|
||||
1. 在订单中填写真实的快递单号
|
||||
2. 点击"查询物流"按钮
|
||||
3. 查看是否返回物流轨迹
|
||||
|
||||
### 4. 测试手机号验证(顺丰)
|
||||
1. 创建订单时填写正确的收件人手机号
|
||||
2. 查询顺丰快递
|
||||
3. 确认能正常返回结果
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 为什么快递100查询失败?
|
||||
**A:** 可能原因:
|
||||
1. 未配置 Customer 和 Key
|
||||
2. 账户余额不足
|
||||
3. 运单号错误或快递未揽收
|
||||
4. 网络连接问题
|
||||
|
||||
### Q2: 顺丰查询为什么需要手机号?
|
||||
**A:** 顺丰为了保护用户隐私,查询时需要验证收件人手机号后4位。系统会自动从订单中获取手机号传给快递100。
|
||||
|
||||
### Q3: 可以添加其他快递公司吗?
|
||||
**A:** 可以。参考 `LOGISTICS_INTEGRATION_GUIDE.md` 中的"扩展其他快递公司"章节。
|
||||
|
||||
### Q4: 快递100收费吗?
|
||||
**A:** 是的,快递100按查询次数收费。具体价格请咨询快递100官方。
|
||||
|
||||
### Q5: 不配置快递100可以用吗?
|
||||
**A:** 可以。系统会提供官网查询链接,用户可以跳转到官网手动查询。
|
||||
|
||||
---
|
||||
|
||||
## 技术支持
|
||||
|
||||
### 相关文档
|
||||
- `LOGISTICS_INTEGRATION_GUIDE.md` - 完整集成指南
|
||||
- `server/.env.logistics.example` - 配置示例
|
||||
- 快递100官方文档:https://api.kuaidi100.com/
|
||||
|
||||
### 日志查看
|
||||
```bash
|
||||
# 查看错误日志
|
||||
tail -f server/runtime/log/error.log
|
||||
|
||||
# 查看应用日志
|
||||
tail -f server/runtime/log/app.log
|
||||
```
|
||||
|
||||
### 联系方式
|
||||
如有问题,请查看项目文档或联系技术支持。
|
||||
|
||||
---
|
||||
|
||||
## 更新历史
|
||||
|
||||
### 2024-01-15
|
||||
- 更新顺丰官网查询链接(新版)
|
||||
- 更新京东物流查询链接
|
||||
- 新增完整的集成指南文档
|
||||
- 新增配置示例文件
|
||||
- 新增测试脚本
|
||||
- 优化错误提示信息
|
||||
@@ -1,101 +0,0 @@
|
||||
# 处方管理功能 - 手动安装SQL
|
||||
|
||||
如果自动安装脚本无法运行,可以手动在数据库中执行以下SQL语句。
|
||||
|
||||
## 方法1:使用安装脚本(推荐)
|
||||
|
||||
### Windows系统:
|
||||
```bash
|
||||
install_prescription_shared.bat
|
||||
```
|
||||
|
||||
### Linux/Mac系统:
|
||||
```bash
|
||||
chmod +x install_prescription_shared.sh
|
||||
./install_prescription_shared.sh
|
||||
```
|
||||
|
||||
## 方法2:手动执行SQL
|
||||
|
||||
打开你的数据库管理工具(如 phpMyAdmin、Navicat、MySQL Workbench等),选择数据库 `zyt`,然后执行以下SQL语句:
|
||||
|
||||
```sql
|
||||
-- 处方增加共享字段
|
||||
ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `is_shared` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否共享:0-不共享(仅自己可见) 1-共享(所有人可见)' AFTER `template_id`;
|
||||
|
||||
-- 处方名称
|
||||
ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `prescription_name` varchar(100) NOT NULL DEFAULT '' COMMENT '处方名称' AFTER `sn`;
|
||||
|
||||
-- 处方类型
|
||||
ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `prescription_type` varchar(50) NOT NULL DEFAULT '浓缩水丸' COMMENT '处方类型:浓缩水丸、饮片、颗粒、丸剂、散剂等' AFTER `prescription_name`;
|
||||
|
||||
-- 舌象图片
|
||||
ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `tongue_image` varchar(255) NOT NULL DEFAULT '' COMMENT '舌象图片' AFTER `tongue`;
|
||||
|
||||
-- 脉象详情
|
||||
ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `pulse_condition` varchar(100) NOT NULL DEFAULT '' COMMENT '脉象详情' AFTER `pulse`;
|
||||
|
||||
-- 服用天数
|
||||
ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `usage_days` int(11) NOT NULL DEFAULT 7 COMMENT '服用天数' AFTER `dose_unit`;
|
||||
|
||||
-- 服用时间
|
||||
ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `usage_time` varchar(50) NOT NULL DEFAULT '饭前' COMMENT '服用时间:饭前、饭后、饭中、空腹、睡前、晨起、随时' AFTER `usage_days`;
|
||||
|
||||
-- 服用方式
|
||||
ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `usage_way` varchar(50) NOT NULL DEFAULT '温水送服' COMMENT '服用方式:温水送服、开水冲服、黄酒送服等' AFTER `usage_time`;
|
||||
|
||||
-- 忌口
|
||||
ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `dietary_taboo` varchar(200) NOT NULL DEFAULT '' COMMENT '忌口(逗号分隔):辛辣食物、生冷食物、油腻食物等' AFTER `usage_way`;
|
||||
|
||||
-- 其他服用说明
|
||||
ALTER TABLE `zyt_tcm_prescription` ADD COLUMN `usage_notes` varchar(200) NOT NULL DEFAULT '' COMMENT '其他服用说明' AFTER `dietary_taboo`;
|
||||
```
|
||||
|
||||
## 方法3:使用命令行
|
||||
|
||||
```bash
|
||||
# 进入项目根目录
|
||||
cd /path/to/your/project
|
||||
|
||||
# 执行SQL文件
|
||||
mysql -h127.0.0.1 -P3306 -uroot -proot zyt < server/database/migrations/add_shared_to_prescription.sql
|
||||
```
|
||||
|
||||
## 验证安装
|
||||
|
||||
执行以下SQL查询,检查字段是否已添加:
|
||||
|
||||
```sql
|
||||
SHOW COLUMNS FROM `zyt_tcm_prescription`;
|
||||
```
|
||||
|
||||
应该能看到以下新增字段:
|
||||
- prescription_name
|
||||
- prescription_type
|
||||
- is_shared
|
||||
- usage_days
|
||||
- usage_time
|
||||
- usage_way
|
||||
- dietary_taboo
|
||||
- usage_notes
|
||||
- tongue_image
|
||||
- pulse_condition
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 如果某个字段已存在,会报错但不影响其他字段的添加
|
||||
2. 请确保数据库名称为 `zyt`,如果不是请修改SQL中的表名前缀
|
||||
3. 执行前建议备份数据库
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 问题1:字段已存在
|
||||
如果提示"Duplicate column name",说明该字段已经存在,可以忽略该错误。
|
||||
|
||||
### 问题2:表不存在
|
||||
如果提示"Table doesn't exist",请检查:
|
||||
1. 数据库名称是否正确
|
||||
2. 表名前缀是否正确(默认是 zyt_)
|
||||
|
||||
### 问题3:权限不足
|
||||
如果提示权限错误,请确保数据库用户有 ALTER TABLE 权限。
|
||||
@@ -1,239 +0,0 @@
|
||||
# 药品库系统安装指南
|
||||
|
||||
## 功能概述
|
||||
|
||||
药品库管理系统提供了完整的中药材管理功能,包括:
|
||||
|
||||
- 药材名称管理
|
||||
- 供应商信息管理
|
||||
- 单位规格管理
|
||||
- 价格管理(结算价、零售价)
|
||||
- 库存管理
|
||||
- 药品图片上传
|
||||
- 状态管理(启用/停用)
|
||||
- 备注信息
|
||||
|
||||
## 安装步骤
|
||||
|
||||
### 1. 数据库安装
|
||||
|
||||
#### Windows 系统
|
||||
|
||||
双击运行 `install_medicine_library.bat` 文件,按提示输入数据库信息:
|
||||
|
||||
```bash
|
||||
install_medicine_library.bat
|
||||
```
|
||||
|
||||
#### Linux/Mac 系统
|
||||
|
||||
执行以下命令:
|
||||
|
||||
```bash
|
||||
chmod +x install_medicine_library.sh
|
||||
./install_medicine_library.sh
|
||||
```
|
||||
|
||||
或者手动导入 SQL 文件:
|
||||
|
||||
```bash
|
||||
mysql -h localhost -u root -p your_database < install_medicine_library.sql
|
||||
```
|
||||
|
||||
### 2. 后端文件说明
|
||||
|
||||
安装脚本会创建以下文件:
|
||||
|
||||
```
|
||||
server/
|
||||
├── app/
|
||||
│ ├── adminapi/
|
||||
│ │ ├── controller/doctor/
|
||||
│ │ │ └── MedicineController.php # 药品控制器
|
||||
│ │ ├── logic/doctor/
|
||||
│ │ │ └── MedicineLogic.php # 药品业务逻辑
|
||||
│ │ ├── validate/doctor/
|
||||
│ │ │ └── MedicineValidate.php # 药品验证器
|
||||
│ │ └── lists/doctor/
|
||||
│ │ └── MedicineLists.php # 药品列表
|
||||
│ └── common/
|
||||
│ └── model/doctor/
|
||||
│ └── Medicine.php # 药品模型
|
||||
```
|
||||
|
||||
### 3. 前端文件说明
|
||||
|
||||
```
|
||||
admin/
|
||||
├── src/
|
||||
│ ├── api/
|
||||
│ │ └── medicine.ts # 药品API接口
|
||||
│ └── views/doctor/
|
||||
│ └── medicine.vue # 药品管理页面
|
||||
```
|
||||
|
||||
### 4. 数据库表结构
|
||||
|
||||
创建的数据表:`la_doctor_medicine`
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| id | int | 主键ID |
|
||||
| name | varchar(100) | 药材名称 |
|
||||
| supplier | varchar(100) | 供应商名称 |
|
||||
| unit | varchar(20) | 单位 |
|
||||
| settlement_price | decimal(10,2) | 结算价 |
|
||||
| retail_price | decimal(10,2) | 零售价 |
|
||||
| stock | int | 库存数量 |
|
||||
| image | varchar(255) | 药品图片 |
|
||||
| status | tinyint(1) | 状态:0=停用,1=启用 |
|
||||
| remark | varchar(500) | 备注 |
|
||||
| create_time | int | 创建时间 |
|
||||
| update_time | int | 更新时间 |
|
||||
| delete_time | int | 删除时间 |
|
||||
|
||||
## 功能使用
|
||||
|
||||
### 1. 访问药品库
|
||||
|
||||
登录后台管理系统后,访问:`/doctor/medicine`
|
||||
|
||||
### 2. 添加药品
|
||||
|
||||
1. 点击右上角"添加药品"按钮
|
||||
2. 填写药品信息:
|
||||
- 药材名称(必填)
|
||||
- 供应商名称(必填)
|
||||
- 单位(必填,可选择:克、千克、斤、盒、瓶、袋、包、片、粒、支)
|
||||
- 结算价(必填)
|
||||
- 零售价(必填)
|
||||
- 库存数量
|
||||
- 药品图片(支持jpg、png格式,大小不超过2MB)
|
||||
- 状态(启用/停用)
|
||||
- 备注信息
|
||||
3. 点击"确定"保存
|
||||
|
||||
### 3. 编辑药品
|
||||
|
||||
1. 在列表中找到需要编辑的药品
|
||||
2. 点击"编辑"按钮
|
||||
3. 修改药品信息
|
||||
4. 点击"确定"保存
|
||||
|
||||
### 4. 删除药品
|
||||
|
||||
1. 在列表中找到需要删除的药品
|
||||
2. 点击"删除"按钮
|
||||
3. 确认删除操作
|
||||
|
||||
### 5. 查询药品
|
||||
|
||||
支持按以下条件查询:
|
||||
- 药材名称(模糊搜索)
|
||||
- 供应商名称(模糊搜索)
|
||||
|
||||
### 6. 库存管理
|
||||
|
||||
- 库存为0时显示红色标签(危险)
|
||||
- 库存小于10时显示黄色标签(警告)
|
||||
- 库存充足时显示绿色标签(正常)
|
||||
|
||||
## API 接口说明
|
||||
|
||||
### 1. 获取药品列表
|
||||
|
||||
```
|
||||
GET /api/doctor.medicine/lists
|
||||
```
|
||||
|
||||
参数:
|
||||
- page_no: 页码
|
||||
- page_size: 每页数量
|
||||
- name: 药材名称(可选)
|
||||
- supplier: 供应商名称(可选)
|
||||
|
||||
### 2. 添加药品
|
||||
|
||||
```
|
||||
POST /api/doctor.medicine/add
|
||||
```
|
||||
|
||||
参数:
|
||||
- name: 药材名称
|
||||
- supplier: 供应商名称
|
||||
- unit: 单位
|
||||
- settlement_price: 结算价
|
||||
- retail_price: 零售价
|
||||
- stock: 库存
|
||||
- image: 图片URL
|
||||
- status: 状态
|
||||
- remark: 备注
|
||||
|
||||
### 3. 编辑药品
|
||||
|
||||
```
|
||||
POST /api/doctor.medicine/edit
|
||||
```
|
||||
|
||||
参数:同添加接口,需额外传入 id
|
||||
|
||||
### 4. 删除药品
|
||||
|
||||
```
|
||||
POST /api/doctor.medicine/delete
|
||||
```
|
||||
|
||||
参数:
|
||||
- id: 药品ID
|
||||
|
||||
### 5. 药品详情
|
||||
|
||||
```
|
||||
GET /api/doctor.medicine/detail
|
||||
```
|
||||
|
||||
参数:
|
||||
- id: 药品ID
|
||||
|
||||
## 示例数据
|
||||
|
||||
安装脚本会自动插入5条示例数据:
|
||||
|
||||
1. 当归 - 同仁堂
|
||||
2. 黄芪 - 云南白药
|
||||
3. 枸杞子 - 宁夏枸杞
|
||||
4. 人参 - 长白山人参
|
||||
5. 甘草 - 内蒙古甘草
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保数据库连接信息正确
|
||||
2. 确保有足够的数据库权限
|
||||
3. 图片上传需要配置正确的上传接口
|
||||
4. 建议定期备份药品数据
|
||||
5. 价格字段保留两位小数
|
||||
6. 库存不能为负数
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 1. 安装失败
|
||||
|
||||
- 检查数据库连接信息是否正确
|
||||
- 检查数据库用户是否有创建表的权限
|
||||
- 检查数据库是否已存在同名表
|
||||
|
||||
### 2. 图片上传失败
|
||||
|
||||
- 检查上传接口配置
|
||||
- 检查文件大小是否超过限制
|
||||
- 检查文件格式是否支持
|
||||
|
||||
### 3. 数据保存失败
|
||||
|
||||
- 检查必填字段是否填写完整
|
||||
- 检查价格格式是否正确
|
||||
- 检查网络连接是否正常
|
||||
|
||||
## 技术支持
|
||||
|
||||
如有问题,请联系技术支持团队。
|
||||
@@ -1,308 +0,0 @@
|
||||
# 订单管理员角色配置修复
|
||||
|
||||
## 问题描述
|
||||
|
||||
编辑业务订单时,关联支付单失败,提示"无权限关联所选支付单"。
|
||||
|
||||
**错误信息**:
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"show": 1,
|
||||
"msg": "无权限关联所选支付单",
|
||||
"data": []
|
||||
}
|
||||
```
|
||||
|
||||
**接口**: `/adminapi/tcm.prescriptionOrder/edit`
|
||||
|
||||
## 问题原因
|
||||
|
||||
`OrderLogic::isOrderListSupervisor()` 方法中硬编码了管理员角色 `[0, 3]`,没有读取配置文件中的 `order_edit_all_roles` 配置。
|
||||
|
||||
### 配置文件
|
||||
|
||||
**文件**: `server/config/project.php`
|
||||
|
||||
```php
|
||||
'order_edit_all_roles' => [0, 3, 6], // 0=超级管理员, 3=管理员, 6=药师
|
||||
```
|
||||
|
||||
### 代码问题
|
||||
|
||||
**文件**: `server/app/adminapi/logic/order/OrderLogic.php`
|
||||
|
||||
**修改前**:
|
||||
```php
|
||||
public static function isOrderListSupervisor(int $adminId, array $adminInfo): bool
|
||||
{
|
||||
if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
|
||||
return true;
|
||||
}
|
||||
$supervisorRoles = [0, 3]; // 硬编码,没有读取配置
|
||||
$myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
|
||||
|
||||
return count(array_intersect($myRoles, $supervisorRoles)) > 0;
|
||||
}
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- 硬编码了 `[0, 3]`,即使配置文件中设置了 `[0, 3, 6]`,角色 6(药师)也无法获得管理员权限
|
||||
- 导致药师无法关联其他人创建的支付单
|
||||
|
||||
## 解决方案
|
||||
|
||||
修改 `isOrderListSupervisor()` 方法,从配置文件读取管理员角色列表。
|
||||
|
||||
**修改后**:
|
||||
```php
|
||||
public static function isOrderListSupervisor(int $adminId, array $adminInfo): bool
|
||||
{
|
||||
if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
|
||||
return true;
|
||||
}
|
||||
$supervisorRoles = config('project.order_edit_all_roles', [0, 3]); // 从配置读取
|
||||
$myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
|
||||
|
||||
return count(array_intersect($myRoles, $supervisorRoles)) > 0;
|
||||
}
|
||||
```
|
||||
|
||||
**改进点**:
|
||||
- 从配置文件读取 `order_edit_all_roles`
|
||||
- 支持动态配置管理员角色
|
||||
- 默认值为 `[0, 3]`,保持向后兼容
|
||||
|
||||
## 权限规则
|
||||
|
||||
### 支付单关联权限
|
||||
|
||||
在关联支付单到业务订单时,需要满足以下条件之一:
|
||||
|
||||
1. **超级管理员** (`root = 1`)
|
||||
- 可以关联所有支付单
|
||||
|
||||
2. **管理员角色** (`order_edit_all_roles` 配置的角色)
|
||||
- 默认:`[0, 3]`(超级管理员、管理员)
|
||||
- 可配置:`[0, 3, 6]`(超级管理员、管理员、药师)
|
||||
- 可以关联所有支付单
|
||||
|
||||
3. **诊单医助** (`assistant_id` 匹配)
|
||||
- 可以关联该诊单的所有支付单
|
||||
|
||||
4. **支付单创建人** (`creator_id` 匹配)
|
||||
- 只能关联自己创建的支付单
|
||||
|
||||
### 权限检查逻辑
|
||||
|
||||
```php
|
||||
public static function assertPayOrdersLinkable(array $ids, int $diagnosisId, int $adminId, array $adminInfo): ?string
|
||||
{
|
||||
// ... 其他检查 ...
|
||||
|
||||
$supervisor = self::isOrderListSupervisor($adminId, $adminInfo);
|
||||
$assistantId = (int) Diagnosis::where('id', $diagnosisId)->value('assistant_id');
|
||||
$isDiagAssistant = $assistantId === $adminId;
|
||||
|
||||
foreach ($orders as $o) {
|
||||
// 检查权限
|
||||
if (! $supervisor && ! $isDiagAssistant && (int) $o->creator_id !== $adminId) {
|
||||
return '无权限关联所选支付单';
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
## 配置说明
|
||||
|
||||
### 配置文件位置
|
||||
|
||||
`server/config/project.php`
|
||||
|
||||
### 配置项
|
||||
|
||||
```php
|
||||
return [
|
||||
// 订单管理全部权限角色(可以查看和编辑所有订单)
|
||||
'order_edit_all_roles' => [0, 3, 6],
|
||||
|
||||
// 其他相关配置
|
||||
'prescription_library_manage_all_roles' => [0, 3], // 处方库管理全部权限角色
|
||||
'prescription_audit_roles' => [0, 3, 6], // 处方审核权限角色
|
||||
'prescription_order_finance_roles' => [0, 3], // 处方订单财务权限角色
|
||||
'prescription_order_payment_audit_roles' => [0, 3], // 处方订单支付审核权限角色
|
||||
];
|
||||
```
|
||||
|
||||
### 角色ID说明
|
||||
|
||||
| 角色ID | 角色名称 | 说明 |
|
||||
|-------|---------|------|
|
||||
| 0 | 超级管理员 | 拥有所有权限 |
|
||||
| 1 | 医生 | 开方、诊断等医疗权限 |
|
||||
| 2 | 医助 | 协助医生工作 |
|
||||
| 3 | 管理员 | 管理订单、审核等权限 |
|
||||
| 6 | 药师 | 审核处方、管理药品等权限 |
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 场景1:药师关联支付单
|
||||
|
||||
**配置前**:
|
||||
```php
|
||||
'order_edit_all_roles' => [0, 3], // 药师(角色6)无权限
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- 药师无法关联其他人创建的支付单
|
||||
- 提示"无权限关联所选支付单"
|
||||
|
||||
**配置后**:
|
||||
```php
|
||||
'order_edit_all_roles' => [0, 3, 6], // 药师(角色6)有权限
|
||||
```
|
||||
|
||||
**效果**:
|
||||
- 药师可以关联所有支付单
|
||||
- 可以正常编辑业务订单
|
||||
|
||||
### 场景2:医生关联支付单
|
||||
|
||||
**情况1:医生是支付单创建人**
|
||||
- 可以关联自己创建的支付单
|
||||
- 不需要管理员权限
|
||||
|
||||
**情况2:医生不是支付单创建人**
|
||||
- 如果医生是诊单医助,可以关联该诊单的所有支付单
|
||||
- 如果医生不是诊单医助,无法关联其他人创建的支付单
|
||||
|
||||
**情况3:医生是管理员角色**
|
||||
- 如果医生同时拥有管理员角色(如角色3),可以关联所有支付单
|
||||
|
||||
### 场景3:管理员关联支付单
|
||||
|
||||
**配置**:
|
||||
```php
|
||||
'order_edit_all_roles' => [0, 3, 6],
|
||||
```
|
||||
|
||||
**效果**:
|
||||
- 超级管理员(角色0):可以关联所有支付单
|
||||
- 管理员(角色3):可以关联所有支付单
|
||||
- 药师(角色6):可以关联所有支付单
|
||||
|
||||
## 测试建议
|
||||
|
||||
### 测试用例 1: 药师关联支付单
|
||||
|
||||
**前提条件**:
|
||||
- 配置 `order_edit_all_roles` 包含角色 6
|
||||
- 用户角色为药师(角色6)
|
||||
|
||||
**测试步骤**:
|
||||
1. 创建一个业务订单
|
||||
2. 选择其他人创建的支付单
|
||||
3. 点击保存
|
||||
|
||||
**预期结果**:
|
||||
- 保存成功
|
||||
- 支付单成功关联到业务订单
|
||||
|
||||
### 测试用例 2: 医生关联自己的支付单
|
||||
|
||||
**前提条件**:
|
||||
- 用户角色为医生(角色1)
|
||||
- 支付单由该医生创建
|
||||
|
||||
**测试步骤**:
|
||||
1. 创建一个业务订单
|
||||
2. 选择自己创建的支付单
|
||||
3. 点击保存
|
||||
|
||||
**预期结果**:
|
||||
- 保存成功
|
||||
- 支付单成功关联到业务订单
|
||||
|
||||
### 测试用例 3: 医生关联其他人的支付单(无权限)
|
||||
|
||||
**前提条件**:
|
||||
- 用户角色为医生(角色1)
|
||||
- 支付单由其他人创建
|
||||
- 医生不是诊单医助
|
||||
|
||||
**测试步骤**:
|
||||
1. 创建一个业务订单
|
||||
2. 选择其他人创建的支付单
|
||||
3. 点击保存
|
||||
|
||||
**预期结果**:
|
||||
- 保存失败
|
||||
- 提示"无权限关联所选支付单"
|
||||
|
||||
### 测试用例 4: 诊单医助关联支付单
|
||||
|
||||
**前提条件**:
|
||||
- 用户是该诊单的医助
|
||||
- 支付单由其他人创建
|
||||
|
||||
**测试步骤**:
|
||||
1. 创建一个业务订单
|
||||
2. 选择该诊单的支付单(其他人创建)
|
||||
3. 点击保存
|
||||
|
||||
**预期结果**:
|
||||
- 保存成功
|
||||
- 支付单成功关联到业务订单
|
||||
|
||||
## 相关配置
|
||||
|
||||
### 其他权限配置
|
||||
|
||||
在 `server/config/project.php` 中,还有其他相关的权限配置:
|
||||
|
||||
```php
|
||||
return [
|
||||
// 订单管理全部权限角色
|
||||
'order_edit_all_roles' => [0, 3, 6],
|
||||
|
||||
// 处方库管理全部权限角色(可以查看所有处方)
|
||||
'prescription_library_manage_all_roles' => [0, 3],
|
||||
|
||||
// 处方审核权限角色
|
||||
'prescription_audit_roles' => [0, 3, 6],
|
||||
|
||||
// 处方订单财务权限角色(可以查看内部成本)
|
||||
'prescription_order_finance_roles' => [0, 3],
|
||||
|
||||
// 处方订单支付审核权限角色
|
||||
'prescription_order_payment_audit_roles' => [0, 3],
|
||||
];
|
||||
```
|
||||
|
||||
### 配置建议
|
||||
|
||||
根据实际业务需求配置角色权限:
|
||||
|
||||
**推荐配置**:
|
||||
```php
|
||||
return [
|
||||
'order_edit_all_roles' => [0, 3, 6], // 管理员和药师可以管理所有订单
|
||||
'prescription_library_manage_all_roles' => [0, 3], // 管理员可以查看所有处方
|
||||
'prescription_audit_roles' => [0, 3, 6], // 管理员和药师可以审核处方
|
||||
'prescription_order_finance_roles' => [0, 3], // 管理员可以查看财务数据
|
||||
'prescription_order_payment_audit_roles' => [0, 3], // 管理员可以审核支付单
|
||||
];
|
||||
```
|
||||
|
||||
## 总结
|
||||
|
||||
通过修改 `isOrderListSupervisor()` 方法,确保了:
|
||||
- ✅ 从配置文件读取管理员角色列表
|
||||
- ✅ 支持动态配置管理员角色
|
||||
- ✅ 药师(角色6)可以关联所有支付单
|
||||
- ✅ 保持向后兼容(默认值为 `[0, 3]`)
|
||||
- ✅ 统一权限管理,避免硬编码
|
||||
|
||||
现在,只需要在配置文件中添加角色ID,就可以赋予该角色管理员权限,无需修改代码。
|
||||
@@ -1,84 +0,0 @@
|
||||
# 支付单审核权限修复
|
||||
|
||||
## 问题描述
|
||||
|
||||
接口 `/adminapi/tcm.prescriptionOrder/auditPayment` 返回错误:
|
||||
```json
|
||||
{"code":0,"show":1,"msg":"无支付单审核权限","data":[]}
|
||||
```
|
||||
|
||||
药师角色(角色ID=6)无法审核支付单。
|
||||
|
||||
## 问题原因
|
||||
|
||||
配置文件 `server/config/project.php` 中的 `prescription_order_payment_audit_roles` 已经包含角色 6,但由于 ThinkPHP 的配置缓存机制,旧的配置仍在使用。
|
||||
|
||||
## 解决方案
|
||||
|
||||
清除 ThinkPHP 缓存,使新配置生效:
|
||||
|
||||
```bash
|
||||
cd server
|
||||
php think clear
|
||||
```
|
||||
|
||||
## 权限配置确认
|
||||
|
||||
文件:`server/config/project.php`
|
||||
|
||||
```php
|
||||
// 业务订单「关联支付单」审核:以下角色 ID + root 可操作(可与财务角色一致)
|
||||
'prescription_order_payment_audit_roles' => [0, 3, 6],
|
||||
```
|
||||
|
||||
- 角色 0:超级管理员
|
||||
- 角色 3:管理员
|
||||
- 角色 6:药师
|
||||
|
||||
## 权限检查逻辑
|
||||
|
||||
文件:`server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
|
||||
|
||||
```php
|
||||
public static function canAuditPaymentSlipOrder(array $adminInfo): bool
|
||||
{
|
||||
if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
|
||||
return true;
|
||||
}
|
||||
$allow = Config::get('project.prescription_order_payment_audit_roles', [0, 3]);
|
||||
|
||||
return self::roleIntersect($adminInfo, is_array($allow) ? $allow : []);
|
||||
}
|
||||
```
|
||||
|
||||
该方法从配置文件读取允许的角色列表,并检查当前用户的角色是否在列表中。
|
||||
|
||||
## 测试验证
|
||||
|
||||
1. 清除缓存后,药师角色(角色ID=6)应该能够:
|
||||
- 调用 `/adminapi/tcm.prescriptionOrder/auditPayment` 接口
|
||||
- 审核通过或驳回支付单
|
||||
|
||||
2. 验证步骤:
|
||||
- 使用药师账号登录
|
||||
- 打开业务订单详情
|
||||
- 点击"支付单审核"按钮
|
||||
- 确认可以正常审核
|
||||
|
||||
## 相关配置总结
|
||||
|
||||
药师角色(角色ID=6)的完整权限配置:
|
||||
|
||||
```php
|
||||
'order_edit_all_roles' => [0, 3, 6], // 订单管理全部权限
|
||||
'prescription_library_manage_all_roles' => [0, 3], // 处方库管理全部权限
|
||||
'prescription_audit_roles' => [0, 3, 6], // 消费者处方审核权限
|
||||
'prescription_order_finance_roles' => [0, 3], // 财务字段查看权限
|
||||
'prescription_order_payment_audit_roles' => [0, 3, 6], // 支付单审核权限
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 每次修改 `server/config/project.php` 后,都需要清除缓存
|
||||
2. 生产环境部署时,确保运行 `php think clear` 命令
|
||||
3. 如果问题仍然存在,检查用户的角色ID是否正确分配
|
||||
@@ -1,358 +0,0 @@
|
||||
# 药师角色权限配置
|
||||
|
||||
## 概述
|
||||
|
||||
为药师角色(角色ID = 6)配置完整的权限,使其能够:
|
||||
- 审核处方
|
||||
- 审核支付单
|
||||
- 管理订单
|
||||
- 关联支付单
|
||||
|
||||
## 配置文件
|
||||
|
||||
**文件**: `server/config/project.php`
|
||||
|
||||
### 完整配置
|
||||
|
||||
```php
|
||||
return [
|
||||
// 订单管理全部权限角色
|
||||
'order_edit_all_roles' => [0, 3, 6],
|
||||
|
||||
// 处方库管理全部权限角色
|
||||
'prescription_library_manage_all_roles' => [0, 3],
|
||||
|
||||
// 消费者处方审核权限角色
|
||||
'prescription_audit_roles' => [0, 3, 6],
|
||||
|
||||
// 处方业务订单财务字段查看权限角色
|
||||
'prescription_order_finance_roles' => [0, 3],
|
||||
|
||||
// 业务订单支付单审核权限角色
|
||||
'prescription_order_payment_audit_roles' => [0, 3, 6],
|
||||
];
|
||||
```
|
||||
|
||||
## 权限说明
|
||||
|
||||
### 1. 订单管理权限 (`order_edit_all_roles`)
|
||||
|
||||
**配置**: `[0, 3, 6]`
|
||||
|
||||
**权限内容**:
|
||||
- 查看所有订单(不限制创建人)
|
||||
- 编辑所有订单
|
||||
- 关联所有支付单(不限制创建人)
|
||||
|
||||
**适用场景**:
|
||||
- 编辑业务订单
|
||||
- 关联支付单到业务订单
|
||||
- 查看订单列表
|
||||
|
||||
**角色**:
|
||||
- 0: 超级管理员
|
||||
- 3: 管理员
|
||||
- 6: 药师 ✅
|
||||
|
||||
### 2. 处方库管理权限 (`prescription_library_manage_all_roles`)
|
||||
|
||||
**配置**: `[0, 3]`
|
||||
|
||||
**权限内容**:
|
||||
- 查看所有处方库模板
|
||||
- 编辑所有处方库模板
|
||||
- 删除所有处方库模板
|
||||
|
||||
**适用场景**:
|
||||
- 管理处方库
|
||||
- 查看处方模板
|
||||
|
||||
**角色**:
|
||||
- 0: 超级管理员
|
||||
- 3: 管理员
|
||||
|
||||
**说明**: 药师不需要此权限,因为处方库主要由医生和管理员管理
|
||||
|
||||
### 3. 消费者处方审核权限 (`prescription_audit_roles`)
|
||||
|
||||
**配置**: `[0, 3, 6]`
|
||||
|
||||
**权限内容**:
|
||||
- 审核待审核的处方
|
||||
- 通过或驳回处方
|
||||
- 驳回时会同时作废处方
|
||||
|
||||
**适用场景**:
|
||||
- 消费者处方列表中的"审核"按钮
|
||||
- 处方审核流程
|
||||
|
||||
**角色**:
|
||||
- 0: 超级管理员
|
||||
- 3: 管理员
|
||||
- 6: 药师 ✅
|
||||
|
||||
### 4. 财务字段查看权限 (`prescription_order_finance_roles`)
|
||||
|
||||
**配置**: `[0, 3]`
|
||||
|
||||
**权限内容**:
|
||||
- 查看业务订单的 `internal_cost`(内部成本)字段
|
||||
- 查看其他财务相关字段
|
||||
|
||||
**适用场景**:
|
||||
- 业务订单详情
|
||||
- 业务订单列表
|
||||
|
||||
**角色**:
|
||||
- 0: 超级管理员
|
||||
- 3: 管理员
|
||||
|
||||
**说明**: 药师不需要此权限,财务数据仅管理员可见
|
||||
|
||||
### 5. 支付单审核权限 (`prescription_order_payment_audit_roles`)
|
||||
|
||||
**配置**: `[0, 3, 6]`
|
||||
|
||||
**权限内容**:
|
||||
- 审核业务订单的关联支付单
|
||||
- 通过或驳回支付单审核
|
||||
|
||||
**适用场景**:
|
||||
- 业务订单详情中的"支付审核"按钮
|
||||
- 支付单审核流程
|
||||
|
||||
**角色**:
|
||||
- 0: 超级管理员
|
||||
- 3: 管理员
|
||||
- 6: 药师 ✅
|
||||
|
||||
## 角色权限矩阵
|
||||
|
||||
| 权限 | 超级管理员(0) | 医生(1) | 医助(2) | 管理员(3) | 药师(6) |
|
||||
|-----|-------------|---------|---------|----------|---------|
|
||||
| 订单管理全部权限 | ✅ | ❌ | ❌ | ✅ | ✅ |
|
||||
| 处方库管理全部权限 | ✅ | ❌ | ❌ | ✅ | ❌ |
|
||||
| 消费者处方审核 | ✅ | ❌ | ❌ | ✅ | ✅ |
|
||||
| 财务字段查看 | ✅ | ❌ | ❌ | ✅ | ❌ |
|
||||
| 支付单审核 | ✅ | ❌ | ❌ | ✅ | ✅ |
|
||||
|
||||
## 修改记录
|
||||
|
||||
### 修改1: 订单管理权限
|
||||
|
||||
**文件**: `server/app/adminapi/logic/order/OrderLogic.php`
|
||||
|
||||
**问题**: 硬编码了管理员角色 `[0, 3]`,没有读取配置
|
||||
|
||||
**修改前**:
|
||||
```php
|
||||
public static function isOrderListSupervisor(int $adminId, array $adminInfo): bool
|
||||
{
|
||||
if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
|
||||
return true;
|
||||
}
|
||||
$supervisorRoles = [0, 3]; // 硬编码
|
||||
$myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
|
||||
|
||||
return count(array_intersect($myRoles, $supervisorRoles)) > 0;
|
||||
}
|
||||
```
|
||||
|
||||
**修改后**:
|
||||
```php
|
||||
public static function isOrderListSupervisor(int $adminId, array $adminInfo): bool
|
||||
{
|
||||
if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
|
||||
return true;
|
||||
}
|
||||
$supervisorRoles = config('project.order_edit_all_roles', [0, 3]); // 从配置读取
|
||||
$myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
|
||||
|
||||
return count(array_intersect($myRoles, $supervisorRoles)) > 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 修改2: 支付单审核权限
|
||||
|
||||
**文件**: `server/config/project.php`
|
||||
|
||||
**修改前**:
|
||||
```php
|
||||
'prescription_order_payment_audit_roles' => [0, 3],
|
||||
```
|
||||
|
||||
**修改后**:
|
||||
```php
|
||||
'prescription_order_payment_audit_roles' => [0, 3, 6],
|
||||
```
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 场景1: 药师审核处方
|
||||
|
||||
**流程**:
|
||||
1. 医生创建处方(待审核状态)
|
||||
2. 药师登录系统
|
||||
3. 进入消费者处方列表
|
||||
4. 点击"审核"按钮
|
||||
5. 选择"通过"或"驳回"
|
||||
6. 填写审核意见
|
||||
7. 提交审核
|
||||
|
||||
**权限检查**:
|
||||
- `prescription_audit_roles` 包含角色 6 ✅
|
||||
- 药师可以审核处方
|
||||
|
||||
### 场景2: 药师审核支付单
|
||||
|
||||
**流程**:
|
||||
1. 管理员创建业务订单并关联支付单
|
||||
2. 药师登录系统
|
||||
3. 进入业务订单详情
|
||||
4. 点击"支付审核"按钮
|
||||
5. 选择"通过"或"驳回"
|
||||
6. 填写审核意见
|
||||
7. 提交审核
|
||||
|
||||
**权限检查**:
|
||||
- `prescription_order_payment_audit_roles` 包含角色 6 ✅
|
||||
- 药师可以审核支付单
|
||||
|
||||
### 场景3: 药师关联支付单
|
||||
|
||||
**流程**:
|
||||
1. 药师创建业务订单
|
||||
2. 选择支付单(其他人创建的)
|
||||
3. 点击保存
|
||||
|
||||
**权限检查**:
|
||||
- `order_edit_all_roles` 包含角色 6 ✅
|
||||
- 药师可以关联所有支付单
|
||||
|
||||
### 场景4: 药师编辑订单
|
||||
|
||||
**流程**:
|
||||
1. 药师查看业务订单列表
|
||||
2. 点击"编辑"按钮(其他人创建的订单)
|
||||
3. 修改订单信息
|
||||
4. 点击保存
|
||||
|
||||
**权限检查**:
|
||||
- `order_edit_all_roles` 包含角色 6 ✅
|
||||
- 药师可以编辑所有订单
|
||||
|
||||
## 测试建议
|
||||
|
||||
### 测试用例 1: 药师审核处方
|
||||
|
||||
**前提条件**:
|
||||
- 用户角色为药师(角色6)
|
||||
- 存在待审核的处方
|
||||
|
||||
**测试步骤**:
|
||||
1. 登录药师账号
|
||||
2. 进入消费者处方列表
|
||||
3. 找到待审核的处方
|
||||
4. 点击"审核"按钮
|
||||
5. 选择"通过"
|
||||
6. 点击确定
|
||||
|
||||
**预期结果**:
|
||||
- 审核成功
|
||||
- 处方状态变为"已通过"
|
||||
|
||||
### 测试用例 2: 药师审核支付单
|
||||
|
||||
**前提条件**:
|
||||
- 用户角色为药师(角色6)
|
||||
- 存在待审核的业务订单
|
||||
|
||||
**测试步骤**:
|
||||
1. 登录药师账号
|
||||
2. 进入业务订单列表
|
||||
3. 点击"查看"按钮
|
||||
4. 点击"支付审核"按钮
|
||||
5. 选择"通过"
|
||||
6. 点击确定
|
||||
|
||||
**预期结果**:
|
||||
- 审核成功
|
||||
- 支付单审核状态变为"已通过"
|
||||
|
||||
### 测试用例 3: 药师关联支付单
|
||||
|
||||
**前提条件**:
|
||||
- 用户角色为药师(角色6)
|
||||
- 存在其他人创建的支付单
|
||||
|
||||
**测试步骤**:
|
||||
1. 登录药师账号
|
||||
2. 创建业务订单
|
||||
3. 选择其他人创建的支付单
|
||||
4. 点击保存
|
||||
|
||||
**预期结果**:
|
||||
- 保存成功
|
||||
- 支付单成功关联到业务订单
|
||||
|
||||
### 测试用例 4: 药师编辑订单
|
||||
|
||||
**前提条件**:
|
||||
- 用户角色为药师(角色6)
|
||||
- 存在其他人创建的业务订单
|
||||
|
||||
**测试步骤**:
|
||||
1. 登录药师账号
|
||||
2. 进入业务订单列表
|
||||
3. 点击"编辑"按钮(其他人创建的订单)
|
||||
4. 修改订单信息
|
||||
5. 点击保存
|
||||
|
||||
**预期结果**:
|
||||
- 保存成功
|
||||
- 订单信息更新
|
||||
|
||||
## 错误排查
|
||||
|
||||
### 错误1: "无权限关联所选支付单"
|
||||
|
||||
**原因**: `order_edit_all_roles` 配置中没有包含角色 6
|
||||
|
||||
**解决方案**:
|
||||
```php
|
||||
'order_edit_all_roles' => [0, 3, 6], // 添加角色 6
|
||||
```
|
||||
|
||||
### 错误2: "无支付单审核权限"
|
||||
|
||||
**原因**: `prescription_order_payment_audit_roles` 配置中没有包含角色 6
|
||||
|
||||
**解决方案**:
|
||||
```php
|
||||
'prescription_order_payment_audit_roles' => [0, 3, 6], // 添加角色 6
|
||||
```
|
||||
|
||||
### 错误3: "无处方审核权限"
|
||||
|
||||
**原因**: `prescription_audit_roles` 配置中没有包含角色 6
|
||||
|
||||
**解决方案**:
|
||||
```php
|
||||
'prescription_audit_roles' => [0, 3, 6], // 添加角色 6
|
||||
```
|
||||
|
||||
## 相关文档
|
||||
|
||||
- `ORDER_SUPERVISOR_ROLE_CONFIG_FIX.md` - 订单管理员角色配置修复文档
|
||||
- `server/config/project.php` - 项目配置文件
|
||||
|
||||
## 总结
|
||||
|
||||
通过配置文件,为药师角色(角色6)赋予了以下权限:
|
||||
- ✅ 订单管理全部权限(查看、编辑、关联支付单)
|
||||
- ✅ 消费者处方审核权限(通过、驳回)
|
||||
- ✅ 支付单审核权限(通过、驳回)
|
||||
- ❌ 处方库管理权限(不需要)
|
||||
- ❌ 财务字段查看权限(不需要)
|
||||
|
||||
药师现在可以完整地参与处方审核和订单管理流程。
|
||||
@@ -1,384 +0,0 @@
|
||||
# 处方审核通过后锁定编辑和作废
|
||||
|
||||
## 修改说明
|
||||
|
||||
处方审核通过后,禁止编辑和作废操作,确保已审核通过的处方不被修改,保证处方的完整性和可追溯性。
|
||||
|
||||
## 修改内容
|
||||
|
||||
### 1. 前端修改
|
||||
|
||||
**文件**: `admin/src/views/consumer/prescription/index.vue`
|
||||
|
||||
#### 编辑按钮
|
||||
|
||||
**修改前**:
|
||||
```vue
|
||||
<el-button
|
||||
v-if="!isApprovedActivePrescription(row) || Number(row.business_prescription_audit_rejected) === 1"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleEdit(row)"
|
||||
v-perms="['cf.prescription/edit']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
```
|
||||
|
||||
**修改后**:
|
||||
```vue
|
||||
<el-button
|
||||
v-if="!isApprovedActivePrescription(row)"
|
||||
type="primary"
|
||||
link
|
||||
@click="handleEdit(row)"
|
||||
v-perms="['cf.prescription/edit']"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
```
|
||||
|
||||
**改进点**:
|
||||
- 移除了业务订单驳回的例外情况
|
||||
- 只要处方审核通过且未作废,就不显示编辑按钮
|
||||
- 简化了逻辑,更加严格
|
||||
|
||||
#### 删除按钮
|
||||
|
||||
删除按钮已经使用了 `isApprovedActivePrescription(row)` 判断,无需修改:
|
||||
|
||||
```vue
|
||||
<el-button
|
||||
v-if="!isApprovedActivePrescription(row)"
|
||||
type="danger"
|
||||
link
|
||||
@click="handleDelete(row.id)"
|
||||
v-perms="['cf.prescription/del']"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
```
|
||||
|
||||
#### 判断函数
|
||||
|
||||
```typescript
|
||||
/** 已通过审核且未作废:仅可查看,不可编辑/删除 */
|
||||
function isApprovedActivePrescription(row: { audit_status?: number; void_status?: number }) {
|
||||
return Number(row.audit_status) === 1 && Number(row.void_status) !== 1
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 后端修改
|
||||
|
||||
**文件**: `server/app/adminapi/logic/tcm/PrescriptionLogic.php`
|
||||
|
||||
#### 编辑方法
|
||||
|
||||
**修改前**:
|
||||
```php
|
||||
// 已通过且未作废:不可编辑;例外:业务订单「处方审核」已驳回时允许医生改方,保存后业务订单侧审核会重置为待审
|
||||
if ((int) ($prescription->audit_status ?? 1) === 1 && (int) ($prescription->void_status ?? 0) === 0) {
|
||||
if (!PrescriptionOrderLogic::allowDoctorEditApprovedPrescriptionDueToBusinessReject((int) $params['id'])) {
|
||||
self::setError('该处方已通过审核,不可编辑');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**修改后**:
|
||||
```php
|
||||
// 已通过且未作废:不可编辑
|
||||
if ((int) ($prescription->audit_status ?? 1) === 1 && (int) ($prescription->void_status ?? 0) === 0) {
|
||||
self::setError('该处方已通过审核,不可编辑');
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
**改进点**:
|
||||
- 移除了业务订单驳回的例外情况
|
||||
- 简化了逻辑,更加严格
|
||||
- 不再调用 `PrescriptionOrderLogic::allowDoctorEditApprovedPrescriptionDueToBusinessReject()`
|
||||
|
||||
#### 删除方法
|
||||
|
||||
删除方法已经有正确的检查,无需修改:
|
||||
|
||||
```php
|
||||
public static function delete(int $id): bool
|
||||
{
|
||||
try {
|
||||
$prescription = Prescription::find($id);
|
||||
if (!$prescription) {
|
||||
self::setError('处方不存在');
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((int) ($prescription->audit_status ?? 1) === 1 && (int) ($prescription->void_status ?? 0) === 0) {
|
||||
self::setError('该处方已通过审核,不可删除');
|
||||
return false;
|
||||
}
|
||||
|
||||
$prescription->delete();
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
self::setError($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 审核方法(作废)
|
||||
|
||||
审核驳回会同时作废处方,已经有正确的状态检查:
|
||||
|
||||
```php
|
||||
public static function audit(int $id, string $action, string $remark, int $adminId, array $adminInfo): bool
|
||||
{
|
||||
// ...
|
||||
|
||||
// 只有待审核状态才能进行审核
|
||||
if ((int) ($row->audit_status ?? 1) !== 0) {
|
||||
self::setError('当前状态不可审核');
|
||||
return false;
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
if ($action === 'reject') {
|
||||
if (trim($remark) === '') {
|
||||
self::setError('驳回时请填写审核意见');
|
||||
return false;
|
||||
}
|
||||
$row->audit_status = 2;
|
||||
$row->audit_time = $now;
|
||||
$row->audit_by = $adminId;
|
||||
$row->audit_by_name = $name;
|
||||
$row->audit_remark = $remark;
|
||||
$row->void_status = 1; // 驳回时同时作废
|
||||
$row->void_time = $now;
|
||||
$row->void_by = $adminId;
|
||||
$row->void_by_name = $name;
|
||||
|
||||
$ok = (bool) $row->save();
|
||||
if ($ok) {
|
||||
self::$lastAuditWecomNotify = self::notifyCreatorAuditResult($row, 'reject', $remark, $adminInfo);
|
||||
}
|
||||
return $ok;
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## 权限规则
|
||||
|
||||
### 编辑权限
|
||||
|
||||
处方可以编辑的条件:
|
||||
1. **待审核状态** (`audit_status = 0`)
|
||||
2. **已驳回状态** (`audit_status = 2`),且满足以下条件:
|
||||
- 该诊单当天没有其他已通过的处方
|
||||
- 编辑后会清除驳回状态,重新进入待审核
|
||||
|
||||
处方不可编辑的条件:
|
||||
1. **已通过审核且未作废** (`audit_status = 1` && `void_status = 0`)
|
||||
- 即使业务订单驳回了处方审核,也不允许编辑
|
||||
- 必须先撤回业务订单,处方审核状态会重置为待审核,然后才能编辑
|
||||
|
||||
### 删除权限
|
||||
|
||||
处方可以删除的条件:
|
||||
1. **待审核状态** (`audit_status = 0`)
|
||||
2. **已驳回状态** (`audit_status = 2`)
|
||||
3. **已作废状态** (`void_status = 1`)
|
||||
|
||||
处方不可删除的条件:
|
||||
1. **已通过审核且未作废** (`audit_status = 1` && `void_status = 0`)
|
||||
|
||||
### 作废权限(审核驳回)
|
||||
|
||||
处方可以作废的条件:
|
||||
1. **待审核状态** (`audit_status = 0`)
|
||||
2. 有审核权限的用户
|
||||
|
||||
处方不可作废的条件:
|
||||
1. **已通过审核** (`audit_status = 1`)
|
||||
2. **已驳回** (`audit_status = 2`)
|
||||
|
||||
## 业务流程
|
||||
|
||||
### 场景1:处方审核通过后需要修改
|
||||
|
||||
**旧流程**(已废弃):
|
||||
1. 处方审核通过
|
||||
2. 创建业务订单
|
||||
3. 业务订单处方审核驳回
|
||||
4. 医生可以直接编辑已通过的处方 ❌
|
||||
|
||||
**新流程**(推荐):
|
||||
1. 处方审核通过
|
||||
2. 创建业务订单
|
||||
3. 业务订单处方审核驳回
|
||||
4. 管理员撤回业务订单(处方审核状态重置为待审核)
|
||||
5. 医生编辑处方
|
||||
6. 处方重新审核
|
||||
7. 创建新的业务订单
|
||||
|
||||
### 场景2:处方审核通过后发现错误
|
||||
|
||||
**解决方案**:
|
||||
1. 如果已创建业务订单,先撤回业务订单
|
||||
2. 处方审核状态会自动重置为待审核
|
||||
3. 医生编辑处方
|
||||
4. 处方重新审核
|
||||
5. 创建新的业务订单
|
||||
|
||||
### 场景3:处方审核驳回后修改
|
||||
|
||||
**流程**:
|
||||
1. 处方审核驳回(同时作废)
|
||||
2. 医生编辑处方(编辑后会清除驳回和作废状态)
|
||||
3. 处方重新进入待审核状态
|
||||
4. 重新审核
|
||||
|
||||
## 优势
|
||||
|
||||
### 1. 数据完整性
|
||||
- 已审核通过的处方不可修改,保证了处方的完整性
|
||||
- 避免审核后的处方被随意修改,导致审核记录与实际内容不符
|
||||
|
||||
### 2. 可追溯性
|
||||
- 每次修改都需要重新审核,留下完整的审核记录
|
||||
- 便于追溯处方的修改历史和审核历史
|
||||
|
||||
### 3. 流程规范
|
||||
- 强制执行"撤回 → 编辑 → 重新审核"的流程
|
||||
- 避免绕过审核流程直接修改处方
|
||||
|
||||
### 4. 责任明确
|
||||
- 审核人员对已审核的处方负责
|
||||
- 修改处方需要重新审核,责任链清晰
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 1. 业务订单撤回
|
||||
|
||||
如果需要修改已审核通过的处方,必须先撤回业务订单:
|
||||
- 撤回业务订单会自动重置处方审核状态为待审核
|
||||
- 撤回后才能编辑处方
|
||||
- 编辑后需要重新审核处方
|
||||
- 审核通过后可以创建新的业务订单
|
||||
|
||||
### 2. 处方驳回
|
||||
|
||||
处方驳回会同时作废处方:
|
||||
- 驳回后处方状态变为"已驳回"(`audit_status = 2`)
|
||||
- 同时处方会被作废(`void_status = 1`)
|
||||
- 医生可以编辑已驳回的处方
|
||||
- 编辑后会清除驳回和作废状态,重新进入待审核
|
||||
|
||||
### 3. 权限配置
|
||||
|
||||
确保相关权限配置正确:
|
||||
- `cf.prescription/edit` - 编辑处方权限
|
||||
- `cf.prescription/del` - 删除处方权限
|
||||
- `cf.prescription/audit` - 审核处方权限
|
||||
- `tcm.prescriptionOrder/withdraw` - 撤回业务订单权限
|
||||
|
||||
## 测试建议
|
||||
|
||||
### 1. 编辑权限测试
|
||||
|
||||
**测试用例 1: 待审核处方可以编辑**
|
||||
- 创建处方(待审核状态)
|
||||
- 点击"编辑"按钮
|
||||
- 预期:可以正常编辑
|
||||
|
||||
**测试用例 2: 已通过处方不可编辑**
|
||||
- 创建处方并审核通过
|
||||
- 点击"编辑"按钮
|
||||
- 预期:按钮不显示或提示"该处方已通过审核,不可编辑"
|
||||
|
||||
**测试用例 3: 业务订单驳回后不可直接编辑**
|
||||
- 创建处方并审核通过
|
||||
- 创建业务订单
|
||||
- 业务订单处方审核驳回
|
||||
- 点击"编辑"按钮
|
||||
- 预期:按钮不显示或提示"该处方已通过审核,不可编辑"
|
||||
|
||||
**测试用例 4: 撤回业务订单后可以编辑**
|
||||
- 创建处方并审核通过
|
||||
- 创建业务订单
|
||||
- 撤回业务订单
|
||||
- 点击"编辑"按钮
|
||||
- 预期:可以正常编辑
|
||||
|
||||
**测试用例 5: 已驳回处方可以编辑**
|
||||
- 创建处方
|
||||
- 审核驳回
|
||||
- 点击"编辑"按钮
|
||||
- 预期:可以正常编辑,编辑后清除驳回和作废状态
|
||||
|
||||
### 2. 删除权限测试
|
||||
|
||||
**测试用例 6: 待审核处方可以删除**
|
||||
- 创建处方(待审核状态)
|
||||
- 点击"删除"按钮
|
||||
- 预期:可以正常删除
|
||||
|
||||
**测试用例 7: 已通过处方不可删除**
|
||||
- 创建处方并审核通过
|
||||
- 点击"删除"按钮
|
||||
- 预期:按钮不显示或提示"该处方已通过审核,不可删除"
|
||||
|
||||
**测试用例 8: 已驳回处方可以删除**
|
||||
- 创建处方
|
||||
- 审核驳回
|
||||
- 点击"删除"按钮
|
||||
- 预期:可以正常删除
|
||||
|
||||
### 3. 作废权限测试
|
||||
|
||||
**测试用例 9: 待审核处方可以驳回作废**
|
||||
- 创建处方(待审核状态)
|
||||
- 点击"审核"按钮,选择"驳回"
|
||||
- 预期:处方被驳回并作废
|
||||
|
||||
**测试用例 10: 已通过处方不可驳回作废**
|
||||
- 创建处方并审核通过
|
||||
- 点击"审核"按钮
|
||||
- 预期:按钮不显示或提示"当前状态不可审核"
|
||||
|
||||
**测试用例 11: 已驳回处方不可再次驳回**
|
||||
- 创建处方
|
||||
- 审核驳回
|
||||
- 再次点击"审核"按钮
|
||||
- 预期:按钮不显示或提示"当前状态不可审核"
|
||||
|
||||
### 4. 业务流程测试
|
||||
|
||||
**测试用例 12: 完整的修改流程**
|
||||
1. 创建处方并审核通过
|
||||
2. 创建业务订单
|
||||
3. 业务订单处方审核驳回
|
||||
4. 尝试编辑处方 → 预期:不可编辑
|
||||
5. 撤回业务订单
|
||||
6. 编辑处方 → 预期:可以编辑
|
||||
7. 处方重新审核通过
|
||||
8. 创建新的业务订单 → 预期:成功
|
||||
|
||||
## 相关文档
|
||||
|
||||
- `PRESCRIPTION_ORDER_WITHDRAW_RESET_AUDIT.md` - 订单撤回重置审核状态文档
|
||||
- `BUG_FIXES_SUMMARY.md` - Bug修复总结文档
|
||||
|
||||
## 总结
|
||||
|
||||
通过移除业务订单驳回的例外情况,确保了:
|
||||
- ✅ 已审核通过的处方不可编辑
|
||||
- ✅ 已审核通过的处方不可删除
|
||||
- ✅ 已审核通过的处方不可作废(驳回)
|
||||
- ✅ 强制执行"撤回 → 编辑 → 重新审核"的规范流程
|
||||
- ✅ 保证处方的完整性和可追溯性
|
||||
- ✅ 责任链清晰,审核记录完整
|
||||
- ✅ 前后端双重校验,防止绕过限制
|
||||
@@ -1,204 +0,0 @@
|
||||
# 处方按钮显示检查
|
||||
|
||||
## 问题描述
|
||||
|
||||
用户反馈:处方审核通过后,"开方"按钮不显示了。
|
||||
|
||||
## 代码分析
|
||||
|
||||
### 前端代码
|
||||
|
||||
**文件**: `admin/src/views/tcm/appointment/list.vue`
|
||||
|
||||
#### 按钮显示逻辑
|
||||
|
||||
```vue
|
||||
<el-button
|
||||
v-perms="['tcm.diagnosis/kaifang']"
|
||||
type="primary"
|
||||
link
|
||||
size="small"
|
||||
@click="handlePrescription(row)"
|
||||
>
|
||||
{{ prescriptionActionLabel(row) }}
|
||||
</el-button>
|
||||
```
|
||||
|
||||
#### 按钮文字逻辑
|
||||
|
||||
```typescript
|
||||
function prescriptionActionLabel(row: any) {
|
||||
if (
|
||||
row?.prescription_audit_status === 1 &&
|
||||
Number(row?.prescription_void_status) !== 1
|
||||
) {
|
||||
return '查看'
|
||||
}
|
||||
return '开方'
|
||||
}
|
||||
```
|
||||
|
||||
**逻辑说明**:
|
||||
- 如果处方审核通过(`prescription_audit_status === 1`)且未作废(`prescription_void_status !== 1`),按钮文字显示"查看"
|
||||
- 否则,按钮文字显示"开方"
|
||||
|
||||
### 后端代码
|
||||
|
||||
**文件**: `server/app/adminapi/lists/doctor/AppointmentLists.php`
|
||||
|
||||
```php
|
||||
$apptId = (int) ($item['id'] ?? 0);
|
||||
$apptRx = $rxByAppointmentId[$apptId] ?? null;
|
||||
$item['prescription_audit_status'] = $apptRx !== null ? (int) ($apptRx['audit_status'] ?? -1) : -1;
|
||||
$item['prescription_void_status'] = $apptRx !== null ? (int) ($apptRx['void_status'] ?? 0) : 0;
|
||||
```
|
||||
|
||||
**逻辑说明**:
|
||||
- 如果预约有关联的处方,返回处方的审核状态和作废状态
|
||||
- 如果预约没有关联的处方,`prescription_audit_status` 为 `-1`,`prescription_void_status` 为 `0`
|
||||
|
||||
## 可能的原因
|
||||
|
||||
### 1. 权限问题
|
||||
|
||||
按钮有权限检查 `v-perms="['tcm.diagnosis/kaifang']"`,如果用户没有这个权限,按钮会被隐藏。
|
||||
|
||||
**检查方法**:
|
||||
1. 登录用户账号
|
||||
2. 检查用户角色是否有 `tcm.diagnosis/kaifang` 权限
|
||||
3. 在浏览器控制台查看按钮元素是否存在
|
||||
|
||||
### 2. 数据问题
|
||||
|
||||
后端返回的 `prescription_audit_status` 或 `prescription_void_status` 字段值不正确。
|
||||
|
||||
**检查方法**:
|
||||
1. 打开浏览器开发者工具
|
||||
2. 查看预约列表接口的响应数据
|
||||
3. 检查 `prescription_audit_status` 和 `prescription_void_status` 字段的值
|
||||
|
||||
**预期值**:
|
||||
- 未开方:`prescription_audit_status = -1`, `prescription_void_status = 0`
|
||||
- 待审核:`prescription_audit_status = 0`, `prescription_void_status = 0`
|
||||
- 已通过:`prescription_audit_status = 1`, `prescription_void_status = 0`
|
||||
- 已驳回:`prescription_audit_status = 2`, `prescription_void_status = 1`
|
||||
|
||||
### 3. 按钮文字问题
|
||||
|
||||
按钮确实显示了,但是文字从"开方"变成了"查看",用户可能误以为按钮消失了。
|
||||
|
||||
**检查方法**:
|
||||
1. 查看预约列表
|
||||
2. 找到已审核通过的处方对应的预约
|
||||
3. 检查按钮文字是否显示为"查看"
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 方案1:确认权限配置
|
||||
|
||||
确保用户角色有 `tcm.diagnosis/kaifang` 权限:
|
||||
|
||||
1. 进入后台管理 → 权限管理 → 角色管理
|
||||
2. 找到用户所属的角色
|
||||
3. 检查是否勾选了"中医诊断 → 开方"权限(`tcm.diagnosis/kaifang`)
|
||||
4. 如果没有勾选,勾选后保存
|
||||
|
||||
### 方案2:检查数据返回
|
||||
|
||||
在浏览器开发者工具中检查接口返回:
|
||||
|
||||
1. 打开浏览器开发者工具(F12)
|
||||
2. 切换到 Network 标签
|
||||
3. 刷新预约列表页面
|
||||
4. 找到预约列表接口(通常是 `/doctor.appointment/lists`)
|
||||
5. 查看响应数据中的 `prescription_audit_status` 和 `prescription_void_status` 字段
|
||||
|
||||
**示例数据**:
|
||||
```json
|
||||
{
|
||||
"id": 123,
|
||||
"patient_name": "张三",
|
||||
"prescription_audit_status": 1, // 1=已通过
|
||||
"prescription_void_status": 0, // 0=未作废
|
||||
"has_prescription": 1
|
||||
}
|
||||
```
|
||||
|
||||
### 方案3:按钮文字说明
|
||||
|
||||
如果按钮文字从"开方"变成了"查看",这是正常的:
|
||||
|
||||
- **未开方或待审核**:按钮显示"开方",点击后可以创建或编辑处方
|
||||
- **已审核通过**:按钮显示"查看",点击后只能查看处方(只读模式)
|
||||
|
||||
这是为了区分"可编辑"和"只读"两种模式。
|
||||
|
||||
## 测试步骤
|
||||
|
||||
### 测试1:未开方的预约
|
||||
|
||||
1. 找到一个未开方的预约(`has_prescription = 0`)
|
||||
2. 检查按钮是否显示"开方"
|
||||
3. 点击按钮,应该可以创建新处方
|
||||
|
||||
### 测试2:待审核的处方
|
||||
|
||||
1. 创建一个处方(待审核状态)
|
||||
2. 返回预约列表
|
||||
3. 检查按钮是否显示"开方"
|
||||
4. 点击按钮,应该可以编辑处方
|
||||
|
||||
### 测试3:已审核通过的处方
|
||||
|
||||
1. 创建一个处方并审核通过
|
||||
2. 返回预约列表
|
||||
3. 检查按钮是否显示"查看"
|
||||
4. 点击按钮,应该只能查看处方(只读模式)
|
||||
|
||||
### 测试4:已驳回的处方
|
||||
|
||||
1. 创建一个处方并审核驳回
|
||||
2. 返回预约列表
|
||||
3. 检查按钮是否显示"开方"
|
||||
4. 点击按钮,应该可以编辑处方
|
||||
|
||||
## 预期行为
|
||||
|
||||
| 处方状态 | `prescription_audit_status` | `prescription_void_status` | 按钮文字 | 点击行为 |
|
||||
|---------|---------------------------|--------------------------|---------|---------|
|
||||
| 未开方 | -1 | 0 | 开方 | 创建新处方 |
|
||||
| 待审核 | 0 | 0 | 开方 | 编辑处方 |
|
||||
| 已通过 | 1 | 0 | 查看 | 查看处方(只读) |
|
||||
| 已驳回 | 2 | 1 | 开方 | 编辑处方 |
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 为什么审核通过后按钮文字变成"查看"?
|
||||
|
||||
A: 这是为了区分"可编辑"和"只读"两种模式。已审核通过的处方不可编辑,只能查看。
|
||||
|
||||
### Q2: 如果需要修改已审核通过的处方怎么办?
|
||||
|
||||
A: 需要先撤回业务订单(如果已创建),处方审核状态会自动重置为待审核,然后才能编辑。
|
||||
|
||||
### Q3: 按钮完全不显示怎么办?
|
||||
|
||||
A: 检查用户是否有 `tcm.diagnosis/kaifang` 权限。如果没有,需要在角色管理中添加该权限。
|
||||
|
||||
### Q4: 点击"查看"按钮后能编辑吗?
|
||||
|
||||
A: 不能。已审核通过的处方只能查看,不能编辑。如果需要编辑,需要先撤回业务订单。
|
||||
|
||||
## 总结
|
||||
|
||||
按钮的显示逻辑是正确的:
|
||||
- ✅ 按钮始终显示(只要有 `tcm.diagnosis/kaifang` 权限)
|
||||
- ✅ 按钮文字根据处方状态动态变化("开方" 或 "查看")
|
||||
- ✅ 点击行为根据处方状态不同(可编辑 或 只读)
|
||||
|
||||
如果用户反馈"按钮不显示",可能是以下原因:
|
||||
1. 用户没有 `tcm.diagnosis/kaifang` 权限
|
||||
2. 按钮文字从"开方"变成了"查看",用户误以为按钮消失了
|
||||
3. 数据返回有问题,需要检查后端接口
|
||||
|
||||
建议先检查权限配置和数据返回,确认按钮是否真的不显示。
|
||||
@@ -1,321 +0,0 @@
|
||||
# 处方组件重复请求修复
|
||||
|
||||
## 问题描述
|
||||
|
||||
在预约列表页面点击"开处方"或"查看病历"按钮时,会出现重复请求 `/tcm.prescription/detail` 接口的问题,导致错误。
|
||||
|
||||
## 问题原因
|
||||
|
||||
### 1. 快速连续点击
|
||||
|
||||
用户可能快速连续点击按钮,导致 `open()` 或 `openById()` 方法被多次调用。
|
||||
|
||||
### 2. 异步操作未加锁
|
||||
|
||||
`open()` 和 `openById()` 方法是异步的,但没有加锁机制,导致:
|
||||
- 第一次点击开始执行
|
||||
- 第二次点击也开始执行
|
||||
- 两次请求同时发送
|
||||
|
||||
### 3. 代码流程
|
||||
|
||||
```javascript
|
||||
const open = async (data: any) => {
|
||||
// 没有防重复检查
|
||||
await loadDictOptions()
|
||||
|
||||
if (appointmentId) {
|
||||
const existing = await prescriptionGetByAppointment(...) // 请求1
|
||||
if (existing?.id) {
|
||||
const detail = await prescriptionDetail(...) // 请求2
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 添加防重复打开标志
|
||||
|
||||
使用 `isOpening` 标志来防止重复调用:
|
||||
|
||||
```typescript
|
||||
// 防止重复打开
|
||||
const isOpening = ref(false)
|
||||
|
||||
const open = async (data: any, options?: { templateId?: number; stationName?: string }) => {
|
||||
// 防止重复打开
|
||||
if (isOpening.value) {
|
||||
console.warn('处方正在打开中,请勿重复点击')
|
||||
return
|
||||
}
|
||||
|
||||
isOpening.value = true
|
||||
|
||||
try {
|
||||
// 原有逻辑...
|
||||
} finally {
|
||||
// 延迟重置,避免快速连续点击
|
||||
setTimeout(() => {
|
||||
isOpening.value = false
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 关键点
|
||||
|
||||
1. **检查标志**:方法开始时检查 `isOpening.value`,如果为 true 则直接返回
|
||||
2. **设置标志**:开始执行时设置 `isOpening.value = true`
|
||||
3. **finally 重置**:无论成功还是失败,都在 finally 中重置标志
|
||||
4. **延迟重置**:使用 `setTimeout` 延迟 500ms 重置,防止快速连续点击
|
||||
|
||||
## 修改的文件
|
||||
|
||||
**文件**:`admin/src/components/tcm-prescription/index.vue`
|
||||
|
||||
### 1. 添加防重复标志
|
||||
|
||||
```typescript
|
||||
// 防止重复打开
|
||||
const isOpening = ref(false)
|
||||
```
|
||||
|
||||
### 2. 修改 open 方法
|
||||
|
||||
```typescript
|
||||
const open = async (data: any, options?: { templateId?: number; stationName?: string }) => {
|
||||
// 防止重复打开
|
||||
if (isOpening.value) {
|
||||
console.warn('处方正在打开中,请勿重复点击')
|
||||
return
|
||||
}
|
||||
|
||||
isOpening.value = true
|
||||
|
||||
try {
|
||||
// 原有逻辑...
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
isOpening.value = false
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 修改 openById 方法
|
||||
|
||||
```typescript
|
||||
const openById = async (prescriptionId: number) => {
|
||||
// 防止重复打开
|
||||
if (isOpening.value) {
|
||||
console.warn('处方正在打开中,请勿重复点击')
|
||||
return
|
||||
}
|
||||
|
||||
isOpening.value = true
|
||||
|
||||
try {
|
||||
const res = await prescriptionDetail({ id: prescriptionId })
|
||||
savedPrescription.value = res
|
||||
if (res?.case_record && Object.keys(res.case_record).length > 0) {
|
||||
await loadDictOptions()
|
||||
}
|
||||
visible.value = true
|
||||
} catch (e) {
|
||||
feedback.msgError('加载处方失败')
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
isOpening.value = false
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 工作流程
|
||||
|
||||
### 修复前
|
||||
|
||||
```
|
||||
用户点击"开处方"按钮
|
||||
↓
|
||||
调用 open() 方法
|
||||
↓
|
||||
用户再次点击(快速连续点击)
|
||||
↓
|
||||
再次调用 open() 方法
|
||||
↓
|
||||
两次请求同时发送
|
||||
↓
|
||||
❌ 重复请求错误
|
||||
```
|
||||
|
||||
### 修复后
|
||||
|
||||
```
|
||||
用户点击"开处方"按钮
|
||||
↓
|
||||
调用 open() 方法
|
||||
↓
|
||||
设置 isOpening = true
|
||||
↓
|
||||
用户再次点击(快速连续点击)
|
||||
↓
|
||||
检查 isOpening = true
|
||||
↓
|
||||
✓ 直接返回,不执行
|
||||
↓
|
||||
第一次请求完成
|
||||
↓
|
||||
500ms 后重置 isOpening = false
|
||||
```
|
||||
|
||||
## 测试场景
|
||||
|
||||
### 场景1:快速连续点击
|
||||
|
||||
**操作**:快速连续点击"开处方"按钮 2-3 次
|
||||
|
||||
**预期结果**:
|
||||
- 只发送一次请求
|
||||
- 控制台显示警告:"处方正在打开中,请勿重复点击"
|
||||
- 处方正常打开
|
||||
|
||||
---
|
||||
|
||||
### 场景2:正常点击
|
||||
|
||||
**操作**:正常点击"开处方"按钮
|
||||
|
||||
**预期结果**:
|
||||
- 发送请求
|
||||
- 处方正常打开
|
||||
- 无警告信息
|
||||
|
||||
---
|
||||
|
||||
### 场景3:间隔点击
|
||||
|
||||
**操作**:点击"开处方",等待 1 秒后再次点击
|
||||
|
||||
**预期结果**:
|
||||
- 第一次点击正常打开
|
||||
- 第二次点击也正常打开(因为已过 500ms)
|
||||
|
||||
---
|
||||
|
||||
### 场景4:查看已有处方
|
||||
|
||||
**操作**:点击"查看病历"按钮
|
||||
|
||||
**预期结果**:
|
||||
- 调用 `prescriptionGetByAppointment` 查询
|
||||
- 如果有处方,调用 `prescriptionDetail` 获取详情
|
||||
- 只发送一次 detail 请求
|
||||
|
||||
---
|
||||
|
||||
### 场景5:请求失败
|
||||
|
||||
**操作**:点击"开处方",但请求失败
|
||||
|
||||
**预期结果**:
|
||||
- 显示错误提示
|
||||
- 500ms 后 `isOpening` 重置
|
||||
- 可以再次点击
|
||||
|
||||
## 其他优化建议
|
||||
|
||||
### 1. 按钮禁用状态
|
||||
|
||||
可以在按钮上添加 loading 状态:
|
||||
|
||||
```vue
|
||||
<el-button
|
||||
:loading="isOpening"
|
||||
@click="handlePrescription(row)"
|
||||
>
|
||||
{{ prescriptionActionLabel(row) }}
|
||||
</el-button>
|
||||
```
|
||||
|
||||
但这需要将 `isOpening` 暴露给父组件,可能不太合适。
|
||||
|
||||
### 2. 防抖处理
|
||||
|
||||
可以使用 lodash 的 debounce 函数:
|
||||
|
||||
```typescript
|
||||
import { debounce } from 'lodash-es'
|
||||
|
||||
const debouncedOpen = debounce(open, 500, { leading: true, trailing: false })
|
||||
```
|
||||
|
||||
但当前的解决方案已经足够简单有效。
|
||||
|
||||
### 3. 请求取消
|
||||
|
||||
可以使用 AbortController 取消重复请求:
|
||||
|
||||
```typescript
|
||||
let abortController: AbortController | null = null
|
||||
|
||||
const open = async (data: any) => {
|
||||
if (abortController) {
|
||||
abortController.abort()
|
||||
}
|
||||
abortController = new AbortController()
|
||||
|
||||
// 在请求中使用 signal
|
||||
await fetch(url, { signal: abortController.signal })
|
||||
}
|
||||
```
|
||||
|
||||
但这需要修改 API 调用层,改动较大。
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 1. 延迟时间
|
||||
|
||||
当前设置为 500ms,可以根据实际情况调整:
|
||||
- 太短(< 300ms):可能无法有效防止快速连续点击
|
||||
- 太长(> 1000ms):用户体验不好,需要等待较长时间
|
||||
|
||||
### 2. 控制台警告
|
||||
|
||||
`console.warn` 只在开发环境有用,生产环境可以考虑:
|
||||
- 移除警告
|
||||
- 或者改为用户提示:`feedback.msgWarning('请勿重复点击')`
|
||||
|
||||
### 3. 多个入口
|
||||
|
||||
如果有多个地方调用 `open()` 方法,都会受到这个锁的保护。
|
||||
|
||||
## 相关文件
|
||||
|
||||
### 修改的文件
|
||||
- `admin/src/components/tcm-prescription/index.vue` - 处方组件
|
||||
|
||||
### 调用的文件
|
||||
- `admin/src/views/tcm/appointment/list.vue` - 预约列表
|
||||
- 其他可能调用处方组件的页面
|
||||
|
||||
## 总结
|
||||
|
||||
✅ 添加 `isOpening` 标志防止重复打开
|
||||
✅ 使用 try-finally 确保标志正确重置
|
||||
✅ 延迟 500ms 重置,防止快速连续点击
|
||||
✅ 同时修复 `open()` 和 `openById()` 方法
|
||||
✅ 添加控制台警告提示
|
||||
|
||||
**效果**:
|
||||
- 防止重复请求
|
||||
- 避免接口错误
|
||||
- 提升用户体验
|
||||
- 代码简单易维护
|
||||
|
||||
**下一步**:
|
||||
1. 测试快速连续点击
|
||||
2. 测试正常点击流程
|
||||
3. 测试请求失败情况
|
||||
4. 考虑是否需要用户提示
|
||||
@@ -1,228 +0,0 @@
|
||||
# 预约处方查询权限控制
|
||||
|
||||
## 修改说明
|
||||
|
||||
为 `getByAppointment` 接口添加了权限检查,确保用户只能查询自己有权限查看的处方。
|
||||
|
||||
## 修改内容
|
||||
|
||||
### 1. Controller 层修改
|
||||
|
||||
**文件**: `server/app/adminapi/controller/tcm/PrescriptionController.php`
|
||||
|
||||
**修改前**:
|
||||
```php
|
||||
public function getByAppointment()
|
||||
{
|
||||
$appointmentId = (int)($this->request->get('appointment_id') ?? 0);
|
||||
if (!$appointmentId) {
|
||||
return $this->fail('预约ID不能为空');
|
||||
}
|
||||
$prescription = PrescriptionLogic::getByAppointment($appointmentId);
|
||||
return $this->data($prescription ?? []);
|
||||
}
|
||||
```
|
||||
|
||||
**修改后**:
|
||||
```php
|
||||
public function getByAppointment()
|
||||
{
|
||||
$appointmentId = (int)($this->request->get('appointment_id') ?? 0);
|
||||
if (!$appointmentId) {
|
||||
return $this->fail('预约ID不能为空');
|
||||
}
|
||||
$prescription = PrescriptionLogic::getByAppointment($appointmentId, (int)$this->adminId, $this->adminInfo);
|
||||
if ($prescription === null) {
|
||||
$msg = PrescriptionLogic::getError();
|
||||
return $this->fail($msg !== '' ? $msg : '未找到处方或无权限查看');
|
||||
}
|
||||
return $this->data($prescription);
|
||||
}
|
||||
```
|
||||
|
||||
**改进点**:
|
||||
- 传入当前登录用户的 `adminId` 和 `adminInfo`
|
||||
- 处理权限检查失败的情况,返回明确的错误信息
|
||||
- 区分"未找到处方"和"无权限查看"两种情况
|
||||
|
||||
### 2. Logic 层修改
|
||||
|
||||
**文件**: `server/app/adminapi/logic/tcm/PrescriptionLogic.php`
|
||||
|
||||
**修改前**:
|
||||
```php
|
||||
/**
|
||||
* 根据预约ID获取处方
|
||||
*/
|
||||
public static function getByAppointment(int $appointmentId): ?array
|
||||
{
|
||||
$row = Prescription::where('appointment_id', $appointmentId)
|
||||
->whereNull('delete_time')
|
||||
->order('id', 'desc')
|
||||
->find();
|
||||
return $row ? $row->toArray() : null;
|
||||
}
|
||||
```
|
||||
|
||||
**修改后**:
|
||||
```php
|
||||
/**
|
||||
* 根据预约ID获取处方(带权限检查)
|
||||
*/
|
||||
public static function getByAppointment(int $appointmentId, int $viewerAdminId, array $viewerAdminInfo): ?array
|
||||
{
|
||||
self::$error = '';
|
||||
$row = Prescription::where('appointment_id', $appointmentId)
|
||||
->whereNull('delete_time')
|
||||
->order('id', 'desc')
|
||||
->find();
|
||||
|
||||
if (!$row) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 权限检查:只返回当前用户有权限查看的处方
|
||||
if (!self::canViewPrescription($row, $viewerAdminId, $viewerAdminInfo)) {
|
||||
self::setError('无权限查看此处方');
|
||||
return null;
|
||||
}
|
||||
|
||||
return $row->toArray();
|
||||
}
|
||||
```
|
||||
|
||||
**改进点**:
|
||||
- 添加 `viewerAdminId` 和 `viewerAdminInfo` 参数
|
||||
- 使用 `canViewPrescription()` 方法进行权限检查
|
||||
- 设置错误信息,便于前端显示
|
||||
|
||||
## 权限规则
|
||||
|
||||
根据 `canViewPrescription()` 方法,以下用户可以查看处方:
|
||||
|
||||
1. **超级管理员** - 可以查看所有处方
|
||||
2. **处方创建人** - 可以查看自己创建的处方
|
||||
3. **医助** - 可以查看自己协助的处方
|
||||
4. **共享处方** - 所有人可以查看标记为共享的处方
|
||||
5. **可见角色** - 处方指定的可见角色可以查看
|
||||
6. **有开方权限的医生** - 拥有 `tcm.diagnosis/kaifang` 权限的医生可以查看所有处方(只读)
|
||||
|
||||
## 使用场景
|
||||
|
||||
这个接口主要用于以下场景:
|
||||
|
||||
1. **查看病历按钮** - 在预约列表中点击"查看病历"时调用
|
||||
2. **处方历史查询** - 根据预约ID查询患者的历史处方
|
||||
3. **医生协作** - 其他医生查看患者的处方记录(需要有开方权限)
|
||||
|
||||
## 前端调用示例
|
||||
|
||||
```typescript
|
||||
// admin/src/views/tcm/appointment/list.vue
|
||||
const handleViewCase = async (row: any) => {
|
||||
if (!row.id) {
|
||||
feedback.msgWarning('预约信息不完整')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const prescription = await prescriptionGetByAppointment({ appointment_id: row.id })
|
||||
if (prescription?.id) {
|
||||
prescriptionRef.value?.openById(prescription.id)
|
||||
} else {
|
||||
feedback.msgWarning('该预约暂无病历记录,请先开方')
|
||||
}
|
||||
} catch (error: any) {
|
||||
// 权限不足或其他错误
|
||||
feedback.msgError(error?.msg || '获取病历失败')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 可能的错误信息
|
||||
|
||||
1. **"预约ID不能为空"** - 未传入 `appointment_id` 参数
|
||||
2. **"未找到处方或无权限查看"** - 处方不存在或当前用户无权限查看
|
||||
3. **"无权限查看此处方"** - 处方存在但当前用户无权限查看
|
||||
|
||||
### 前端处理建议
|
||||
|
||||
```typescript
|
||||
try {
|
||||
const prescription = await prescriptionGetByAppointment({ appointment_id: row.id })
|
||||
// 成功获取处方
|
||||
} catch (error: any) {
|
||||
if (error?.msg?.includes('无权限')) {
|
||||
feedback.msgWarning('您没有权限查看此处方')
|
||||
} else if (error?.msg?.includes('未找到')) {
|
||||
feedback.msgWarning('该预约暂无病历记录')
|
||||
} else {
|
||||
feedback.msgError(error?.msg || '获取病历失败')
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 测试建议
|
||||
|
||||
### 1. 权限测试
|
||||
|
||||
**测试用例 1: 创建人查看自己的处方**
|
||||
- 医生A创建处方
|
||||
- 医生A查看该处方
|
||||
- 预期:成功返回处方数据
|
||||
|
||||
**测试用例 2: 其他医生查看处方(有开方权限)**
|
||||
- 医生A创建处方
|
||||
- 医生B(有 `tcm.diagnosis/kaifang` 权限)查看该处方
|
||||
- 预期:成功返回处方数据(只读)
|
||||
|
||||
**测试用例 3: 其他医生查看处方(无开方权限)**
|
||||
- 医生A创建处方
|
||||
- 医生C(无 `tcm.diagnosis/kaifang` 权限)查看该处方
|
||||
- 预期:返回"无权限查看此处方"错误
|
||||
|
||||
**测试用例 4: 医助查看协助的处方**
|
||||
- 医生A创建处方,医助B协助
|
||||
- 医助B查看该处方
|
||||
- 预期:成功返回处方数据
|
||||
|
||||
**测试用例 5: 查看共享处方**
|
||||
- 医生A创建处方并标记为共享
|
||||
- 任何用户查看该处方
|
||||
- 预期:成功返回处方数据
|
||||
|
||||
### 2. 边界测试
|
||||
|
||||
**测试用例 6: 预约不存在**
|
||||
- 传入不存在的 `appointment_id`
|
||||
- 预期:返回"未找到处方或无权限查看"
|
||||
|
||||
**测试用例 7: 预约存在但无处方**
|
||||
- 传入存在的 `appointment_id`,但该预约没有创建处方
|
||||
- 预期:返回"未找到处方或无权限查看"
|
||||
|
||||
**测试用例 8: 处方已删除**
|
||||
- 传入已删除处方的 `appointment_id`
|
||||
- 预期:返回"未找到处方或无权限查看"
|
||||
|
||||
## 安全性说明
|
||||
|
||||
1. **防止越权访问** - 用户只能查看自己有权限的处方,防止查看其他医生的私有处方
|
||||
2. **数据隔离** - 通过权限检查实现数据隔离,保护患者隐私
|
||||
3. **审计追踪** - 所有查询操作都会记录当前用户信息,便于审计
|
||||
4. **错误信息脱敏** - 对于无权限的情况,不暴露处方是否存在的详细信息
|
||||
|
||||
## 相关文档
|
||||
|
||||
- `PRESCRIPTION_VIEW_PERMISSION_FIX.md` - 处方查看权限修复文档
|
||||
- `BUG_FIXES_SUMMARY.md` - Bug修复总结文档
|
||||
|
||||
## 总结
|
||||
|
||||
通过添加权限检查,确保了:
|
||||
- ✅ 用户只能查询自己有权限查看的处方
|
||||
- ✅ 防止越权访问其他医生的私有处方
|
||||
- ✅ 支持医生协作(有开方权限的医生可以查看所有处方)
|
||||
- ✅ 保护患者隐私和数据安全
|
||||
- ✅ 提供明确的错误信息,便于前端处理
|
||||
@@ -1,234 +0,0 @@
|
||||
# 处方导入功能说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
在中医处方单组件中新增了"从处方库导入"功能,允许医生快速从处方库中选择已保存的处方模板,一键导入药材配方。
|
||||
|
||||
## 使用位置
|
||||
|
||||
**组件路径:** `admin/src/components/tcm-prescription/index.vue`
|
||||
|
||||
**功能位置:** 中药处方 (RP) 区域,"添加中药"按钮旁边
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 打开处方库
|
||||
|
||||
在编辑处方时,点击"从处方库导入"按钮,会弹出处方库选择对话框。
|
||||
|
||||
### 2. 搜索处方
|
||||
|
||||
- 在搜索框中输入处方名称
|
||||
- 按回车键或点击搜索按钮进行搜索
|
||||
- 支持模糊搜索
|
||||
|
||||
### 3. 选择处方
|
||||
|
||||
有两种方式导入处方:
|
||||
|
||||
**方式一:点击"导入"按钮**
|
||||
- 在列表中找到需要的处方
|
||||
- 点击右侧的"导入"按钮
|
||||
|
||||
**方式二:点击行**
|
||||
- 直接点击处方所在的行
|
||||
- 自动导入该处方
|
||||
|
||||
### 4. 导入结果
|
||||
|
||||
- 导入成功后,药材列表会自动填充
|
||||
- 显示成功提示:已导入处方「处方名称」,共X味药材
|
||||
- 对话框自动关闭
|
||||
|
||||
## 功能特点
|
||||
|
||||
### 1. 实时搜索
|
||||
- 支持按处方名称搜索
|
||||
- 清空搜索框自动刷新列表
|
||||
|
||||
### 2. 分页显示
|
||||
- 默认每页显示15条
|
||||
- 支持切换每页10/15/20/50条
|
||||
- 显示总记录数
|
||||
|
||||
### 3. 详细信息
|
||||
列表显示以下信息:
|
||||
- 处方名称
|
||||
- 药材数量(X味)
|
||||
- 药材明细(药材名称 + 剂量)
|
||||
- 创建人
|
||||
|
||||
### 4. 权限控制
|
||||
- 显示自己创建的所有处方
|
||||
- 显示其他人创建的公开处方
|
||||
- 不显示其他人的私有处方
|
||||
|
||||
### 5. 数据安全
|
||||
- 深拷贝药材数据,避免引用问题
|
||||
- 导入前验证药材数据是否存在
|
||||
|
||||
## 界面示例
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ 从处方库导入 [×] │
|
||||
├─────────────────────────────────────────────────────┤
|
||||
│ [请输入处方名称搜索________________] [搜索] │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────┐ │
|
||||
│ │ 处方名称 │ 药材数量 │ 药材明细 │ 创建人 │ 操作 │ │
|
||||
│ ├─────────────────────────────────────────────────┤ │
|
||||
│ │ 六味地黄丸│ 6味 │ 熟地黄24g、山茱萸12g...│ │
|
||||
│ │ │ │ │张医生│导入│ │
|
||||
│ ├─────────────────────────────────────────────────┤ │
|
||||
│ │ 补中益气汤│ 8味 │ 黄芪15g、党参10g... │ │
|
||||
│ │ │ │ │李医生│导入│ │
|
||||
│ └─────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 共2条 [10] [15] [20] [50] [<] [1] [>] │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 1. API接口
|
||||
|
||||
使用处方库列表接口:
|
||||
```typescript
|
||||
prescriptionLibraryLists({
|
||||
page_no: 1,
|
||||
page_size: 15,
|
||||
prescription_name: '搜索关键词',
|
||||
is_public: ''
|
||||
})
|
||||
```
|
||||
|
||||
### 2. 数据结构
|
||||
|
||||
处方库返回的数据格式:
|
||||
```typescript
|
||||
{
|
||||
lists: [
|
||||
{
|
||||
id: 1,
|
||||
prescription_name: '六味地黄丸',
|
||||
herbs: [
|
||||
{ name: '熟地黄', dosage: 24 },
|
||||
{ name: '山茱萸', dosage: 12 },
|
||||
// ...
|
||||
],
|
||||
creator_name: '张医生',
|
||||
is_public: 1
|
||||
}
|
||||
],
|
||||
count: 10
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 导入逻辑
|
||||
|
||||
```typescript
|
||||
const handleImportLibrary = (row: any) => {
|
||||
// 1. 验证数据
|
||||
if (!row.herbs || row.herbs.length === 0) {
|
||||
feedback.msgWarning('该处方没有药材信息')
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 深拷贝数据
|
||||
const importedHerbs = JSON.parse(JSON.stringify(row.herbs))
|
||||
|
||||
// 3. 替换当前药材列表
|
||||
formData.herbs = importedHerbs
|
||||
|
||||
// 4. 提示成功
|
||||
feedback.msgSuccess(`已导入处方「${row.prescription_name}」`)
|
||||
|
||||
// 5. 关闭对话框
|
||||
showLibraryDialog.value = false
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 1. 数据覆盖
|
||||
- 导入处方会**替换**当前的药材列表
|
||||
- 如果已经添加了药材,导入后会被覆盖
|
||||
- 建议在开始编辑时就导入处方
|
||||
|
||||
### 2. 数据验证
|
||||
- 导入前会验证处方是否有药材数据
|
||||
- 如果处方为空,会提示错误
|
||||
|
||||
### 3. 权限限制
|
||||
- 只能看到自己的处方和公开的处方
|
||||
- 私有处方不会显示在列表中
|
||||
|
||||
### 4. 搜索范围
|
||||
- 搜索只针对处方名称
|
||||
- 不支持按药材名称搜索
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 场景1:快速开方
|
||||
医生经常开"六味地黄丸"处方,可以:
|
||||
1. 点击"从处方库导入"
|
||||
2. 搜索"六味地黄丸"
|
||||
3. 点击导入
|
||||
4. 药材自动填充,无需手动输入
|
||||
|
||||
### 场景2:参考经典处方
|
||||
医生想参考其他医生的处方:
|
||||
1. 打开处方库
|
||||
2. 浏览公开的处方
|
||||
3. 选择合适的处方导入
|
||||
4. 根据患者情况调整剂量
|
||||
|
||||
### 场景3:标准化处方
|
||||
医院推广标准处方:
|
||||
1. 管理员创建标准处方并设为公开
|
||||
2. 所有医生都能看到和使用
|
||||
3. 确保处方的一致性
|
||||
|
||||
## 优势
|
||||
|
||||
1. **提高效率** - 无需手动输入药材,节省时间
|
||||
2. **减少错误** - 避免手动输入导致的错误
|
||||
3. **标准化** - 使用标准处方模板,确保质量
|
||||
4. **知识共享** - 医生之间可以分享处方经验
|
||||
5. **快速学习** - 新医生可以参考资深医生的处方
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. **批量导入** - 支持一次导入多个处方
|
||||
2. **处方预览** - 导入前预览处方详情
|
||||
3. **智能推荐** - 根据诊断自动推荐处方
|
||||
4. **使用统计** - 显示处方使用次数
|
||||
5. **收藏功能** - 收藏常用处方,快速访问
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 导入后可以修改吗?
|
||||
A: 可以。导入后可以自由添加、删除、修改药材。
|
||||
|
||||
### Q2: 导入会覆盖已有的药材吗?
|
||||
A: 是的。导入会替换当前所有药材,建议在开始时就导入。
|
||||
|
||||
### Q3: 为什么看不到某个处方?
|
||||
A: 可能是该处方设置为私有,只有创建者可见。
|
||||
|
||||
### Q4: 可以导入后再保存到处方库吗?
|
||||
A: 可以。导入、修改后,可以通过"是否共享"选项保存为新的处方模板。
|
||||
|
||||
### Q5: 搜索不到处方怎么办?
|
||||
A: 检查处方名称是否正确,或清空搜索框查看所有处方。
|
||||
|
||||
## 技术支持
|
||||
|
||||
如有问题或建议,请联系技术支持团队。
|
||||
|
||||
---
|
||||
|
||||
**版本:** v1.0.0
|
||||
**更新日期:** 2026-03-22
|
||||
**状态:** ✅ 已完成
|
||||
@@ -1,222 +0,0 @@
|
||||
# 处方库功能演示
|
||||
|
||||
## 功能截图说明
|
||||
|
||||
### 1. 处方库列表页面
|
||||
|
||||
列表页面展示所有可访问的处方:
|
||||
- 自己创建的处方(无论是否公开)
|
||||
- 其他人创建的公开处方
|
||||
|
||||
**列表字段:**
|
||||
- ID:处方唯一标识
|
||||
- 处方名称:如"六味地黄丸"、"补中益气汤"
|
||||
- 药材数量:显示该处方包含多少味药材
|
||||
- 药材明细:显示所有药材及其剂量
|
||||
- 是否公开:标签显示"所有人可见"或"仅自己可见"
|
||||
- 创建人:显示创建者姓名
|
||||
- 创建时间:处方创建的时间
|
||||
|
||||
**操作按钮:**
|
||||
- 查看:查看处方详细信息(只读模式)
|
||||
- 编辑:修改处方信息(需要权限)
|
||||
- 删除:删除处方(仅创建者可操作)
|
||||
|
||||
### 2. 搜索筛选功能
|
||||
|
||||
**搜索条件:**
|
||||
- 处方名称:支持模糊搜索
|
||||
- 是否公开:可筛选"全部"、"仅自己可见"、"所有人可见"
|
||||
|
||||
### 3. 新增/编辑处方弹窗
|
||||
|
||||
**表单字段:**
|
||||
|
||||
1. **处方名称**(必填)
|
||||
- 输入框,最多100个字符
|
||||
- 示例:六味地黄丸、补中益气汤、逍遥散
|
||||
|
||||
2. **药材配方**(必填)
|
||||
- 动态表格,可添加多味药材
|
||||
- 每味药材包含:
|
||||
- 药材名称(文本输入)
|
||||
- 剂量/克(数字输入,支持小数)
|
||||
- 支持添加和删除药材行
|
||||
|
||||
3. **是否公开**
|
||||
- 开关按钮
|
||||
- 关闭:仅自己可见
|
||||
- 开启:所有人可见
|
||||
- 提示文字:勾选后,所有医生都可以查看和使用此处方模板
|
||||
|
||||
### 4. 查看处方(只读模式)
|
||||
|
||||
点击"查看"按钮后,以只读模式展示处方详情:
|
||||
- 所有字段不可编辑
|
||||
- 不显示"添加药材"和"删除"按钮
|
||||
- 底部不显示"确定"按钮
|
||||
|
||||
## 使用场景示例
|
||||
|
||||
### 场景1:创建常用处方模板
|
||||
|
||||
**背景:**
|
||||
张医生经常开"六味地黄丸"处方,每次都要重新输入药材和剂量,比较麻烦。
|
||||
|
||||
**操作步骤:**
|
||||
1. 进入处方库页面
|
||||
2. 点击"新增处方"
|
||||
3. 输入处方名称:六味地黄丸
|
||||
4. 添加6味药材:
|
||||
- 熟地黄 24g
|
||||
- 山茱萸 12g
|
||||
- 山药 12g
|
||||
- 泽泻 9g
|
||||
- 牡丹皮 9g
|
||||
- 茯苓 9g
|
||||
5. 设置为"仅自己可见"
|
||||
6. 保存
|
||||
|
||||
**效果:**
|
||||
以后张医生可以随时查看这个处方模板,快速参考药材配方。
|
||||
|
||||
### 场景2:分享经典处方
|
||||
|
||||
**背景:**
|
||||
李主任想把一些经典处方分享给科室的其他医生使用。
|
||||
|
||||
**操作步骤:**
|
||||
1. 创建处方时,将"是否公开"设置为"所有人可见"
|
||||
2. 保存后,科室所有医生都能在处方库中看到这个处方
|
||||
|
||||
**效果:**
|
||||
- 其他医生可以查看和参考这个处方
|
||||
- 但只有李主任可以修改或删除
|
||||
- 实现了知识共享
|
||||
|
||||
### 场景3:管理个人处方库
|
||||
|
||||
**背景:**
|
||||
王医生积累了很多个人处方,需要分类管理。
|
||||
|
||||
**操作步骤:**
|
||||
1. 使用搜索功能快速查找处方
|
||||
2. 通过"是否公开"筛选个人处方和公开处方
|
||||
3. 编辑或删除不再使用的处方
|
||||
|
||||
## 权限说明
|
||||
|
||||
### 查看权限
|
||||
- ✅ 可以查看自己创建的所有处方(无论是否公开)
|
||||
- ✅ 可以查看其他人创建的公开处方
|
||||
- ❌ 不能查看其他人创建的私有处方
|
||||
|
||||
### 编辑权限
|
||||
- ✅ 可以编辑自己创建的处方
|
||||
- ✅ 可以编辑其他人创建的公开处方(协作编辑)
|
||||
- ❌ 不能编辑其他人创建的私有处方
|
||||
|
||||
### 删除权限
|
||||
- ✅ 只能删除自己创建的处方
|
||||
- ❌ 不能删除其他人创建的处方(即使是公开的)
|
||||
|
||||
## 数据示例
|
||||
|
||||
### 示例1:六味地黄丸
|
||||
|
||||
```json
|
||||
{
|
||||
"prescription_name": "六味地黄丸",
|
||||
"herbs": [
|
||||
{"name": "熟地黄", "dosage": 24},
|
||||
{"name": "山茱萸", "dosage": 12},
|
||||
{"name": "山药", "dosage": 12},
|
||||
{"name": "泽泻", "dosage": 9},
|
||||
{"name": "牡丹皮", "dosage": 9},
|
||||
{"name": "茯苓", "dosage": 9}
|
||||
],
|
||||
"is_public": 1
|
||||
}
|
||||
```
|
||||
|
||||
### 示例2:补中益气汤
|
||||
|
||||
```json
|
||||
{
|
||||
"prescription_name": "补中益气汤",
|
||||
"herbs": [
|
||||
{"name": "黄芪", "dosage": 15},
|
||||
{"name": "党参", "dosage": 10},
|
||||
{"name": "白术", "dosage": 10},
|
||||
{"name": "当归", "dosage": 10},
|
||||
{"name": "陈皮", "dosage": 6},
|
||||
{"name": "升麻", "dosage": 6},
|
||||
{"name": "柴胡", "dosage": 6},
|
||||
{"name": "甘草", "dosage": 5}
|
||||
],
|
||||
"is_public": 1
|
||||
}
|
||||
```
|
||||
|
||||
### 示例3:逍遥散
|
||||
|
||||
```json
|
||||
{
|
||||
"prescription_name": "逍遥散",
|
||||
"herbs": [
|
||||
{"name": "柴胡", "dosage": 10},
|
||||
{"name": "当归", "dosage": 10},
|
||||
{"name": "白芍", "dosage": 10},
|
||||
{"name": "白术", "dosage": 10},
|
||||
{"name": "茯苓", "dosage": 10},
|
||||
{"name": "薄荷", "dosage": 6},
|
||||
{"name": "生姜", "dosage": 6},
|
||||
{"name": "甘草", "dosage": 5}
|
||||
],
|
||||
"is_public": 0
|
||||
}
|
||||
```
|
||||
|
||||
## 技术特点
|
||||
|
||||
1. **独立性**
|
||||
- 不依赖诊单、预约等其他功能
|
||||
- 可以单独使用和维护
|
||||
|
||||
2. **灵活性**
|
||||
- 支持任意数量的药材
|
||||
- 剂量支持小数(精确到0.1克)
|
||||
|
||||
3. **安全性**
|
||||
- 完善的权限控制
|
||||
- 防止未授权访问和操作
|
||||
|
||||
4. **易用性**
|
||||
- 简洁的界面设计
|
||||
- 直观的操作流程
|
||||
|
||||
## 未来扩展建议
|
||||
|
||||
1. **处方分类**
|
||||
- 添加处方分类功能(如:补益类、清热类等)
|
||||
- 支持按分类筛选
|
||||
|
||||
2. **处方标签**
|
||||
- 支持为处方添加标签
|
||||
- 支持按标签搜索
|
||||
|
||||
3. **使用统计**
|
||||
- 记录处方使用次数
|
||||
- 显示热门处方排行
|
||||
|
||||
4. **处方导入导出**
|
||||
- 支持批量导入处方
|
||||
- 支持导出为Excel或PDF
|
||||
|
||||
5. **处方评论**
|
||||
- 允许医生对公开处方进行评论
|
||||
- 分享使用心得
|
||||
|
||||
6. **版本管理**
|
||||
- 记录处方修改历史
|
||||
- 支持回退到历史版本
|
||||
@@ -1,244 +0,0 @@
|
||||
# 处方库功能 - 文件清单
|
||||
|
||||
## 📁 所有文件列表
|
||||
|
||||
### 📄 文档文件(根目录)
|
||||
|
||||
| 文件名 | 说明 | 用途 |
|
||||
|--------|------|------|
|
||||
| PRESCRIPTION_LIBRARY_README.md | 完整使用说明 | 详细的功能说明和使用指南 |
|
||||
| PRESCRIPTION_LIBRARY_DEMO.md | 功能演示文档 | 使用场景和示例 |
|
||||
| PRESCRIPTION_LIBRARY_SUMMARY.md | 开发总结 | 技术实现和开发总结 |
|
||||
| QUICK_START_PRESCRIPTION_LIBRARY.md | 快速开始指南 | 5分钟快速上手 |
|
||||
| PRESCRIPTION_LIBRARY_FILES.md | 本文件 | 文件清单 |
|
||||
| TEST_PRESCRIPTION_LIBRARY.sql | 测试SQL | 测试数据和查询语句 |
|
||||
|
||||
### 🔧 安装脚本(根目录)
|
||||
|
||||
| 文件名 | 说明 | 平台 |
|
||||
|--------|------|------|
|
||||
| install_prescription_library.bat | Windows安装脚本 | Windows |
|
||||
| install_prescription_library.sh | Linux/Mac安装脚本 | Linux/Mac |
|
||||
|
||||
### 🗄️ 数据库文件
|
||||
|
||||
| 文件路径 | 说明 |
|
||||
|----------|------|
|
||||
| server/database/migrations/create_prescription_library.sql | 数据库表结构 |
|
||||
|
||||
### 🔙 后端文件
|
||||
|
||||
#### 模型层
|
||||
| 文件路径 | 说明 |
|
||||
|----------|------|
|
||||
| server/app/common/model/tcm/PrescriptionLibrary.php | 处方库数据模型 |
|
||||
|
||||
#### 控制器层
|
||||
| 文件路径 | 说明 |
|
||||
|----------|------|
|
||||
| server/app/adminapi/controller/tcm/PrescriptionLibraryController.php | 处方库控制器 |
|
||||
|
||||
#### 业务逻辑层
|
||||
| 文件路径 | 说明 |
|
||||
|----------|------|
|
||||
| server/app/adminapi/logic/tcm/PrescriptionLibraryLogic.php | 处方库业务逻辑 |
|
||||
|
||||
#### 列表逻辑层
|
||||
| 文件路径 | 说明 |
|
||||
|----------|------|
|
||||
| server/app/adminapi/lists/tcm/PrescriptionLibraryLists.php | 处方库列表逻辑 |
|
||||
|
||||
#### 验证器层
|
||||
| 文件路径 | 说明 |
|
||||
|----------|------|
|
||||
| server/app/adminapi/validate/tcm/PrescriptionLibraryValidate.php | 处方库数据验证器 |
|
||||
|
||||
### 🎨 前端文件
|
||||
|
||||
#### 页面组件
|
||||
| 文件路径 | 说明 |
|
||||
|----------|------|
|
||||
| admin/src/views/consumer/prescription/list.vue | 处方库页面组件 |
|
||||
|
||||
#### API接口
|
||||
| 文件路径 | 说明 | 修改内容 |
|
||||
|----------|------|----------|
|
||||
| admin/src/api/tcm.ts | API接口定义 | 新增处方库相关接口 |
|
||||
|
||||
## 📊 文件统计
|
||||
|
||||
### 按类型统计
|
||||
- 文档文件:6个
|
||||
- 安装脚本:2个
|
||||
- 数据库文件:1个
|
||||
- 后端PHP文件:5个
|
||||
- 前端Vue文件:1个
|
||||
- 前端API文件:1个(修改)
|
||||
|
||||
**总计:** 16个文件(15个新建 + 1个修改)
|
||||
|
||||
### 按目录统计
|
||||
```
|
||||
根目录/
|
||||
├── 文档文件 × 6
|
||||
├── 安装脚本 × 2
|
||||
└── 测试SQL × 1
|
||||
|
||||
server/
|
||||
├── database/migrations/ × 1
|
||||
├── app/common/model/tcm/ × 1
|
||||
└── app/adminapi/
|
||||
├── controller/tcm/ × 1
|
||||
├── logic/tcm/ × 1
|
||||
├── lists/tcm/ × 1
|
||||
└── validate/tcm/ × 1
|
||||
|
||||
admin/
|
||||
└── src/
|
||||
├── views/consumer/prescription/ × 1
|
||||
└── api/ × 1(修改)
|
||||
```
|
||||
|
||||
## 🔍 文件依赖关系
|
||||
|
||||
```
|
||||
前端页面 (list.vue)
|
||||
↓
|
||||
前端API (tcm.ts)
|
||||
↓
|
||||
后端控制器 (PrescriptionLibraryController.php)
|
||||
↓
|
||||
后端验证器 (PrescriptionLibraryValidate.php)
|
||||
↓
|
||||
后端业务逻辑 (PrescriptionLibraryLogic.php)
|
||||
↓
|
||||
后端数据模型 (PrescriptionLibrary.php)
|
||||
↓
|
||||
数据库表 (zyt_prescription_library)
|
||||
```
|
||||
|
||||
## 📝 代码行数统计
|
||||
|
||||
| 文件类型 | 文件数 | 预估代码行数 |
|
||||
|---------|--------|-------------|
|
||||
| 文档 (Markdown) | 6 | ~1500行 |
|
||||
| SQL | 2 | ~100行 |
|
||||
| PHP | 5 | ~400行 |
|
||||
| Vue | 1 | ~350行 |
|
||||
| TypeScript | 1 | ~30行(新增部分) |
|
||||
| Shell脚本 | 2 | ~100行 |
|
||||
| **总计** | **17** | **~2480行** |
|
||||
|
||||
## ✅ 文件完整性检查
|
||||
|
||||
### 必需文件检查清单
|
||||
|
||||
#### 后端文件
|
||||
- [x] 数据库表结构文件
|
||||
- [x] 数据模型文件
|
||||
- [x] 控制器文件
|
||||
- [x] 业务逻辑文件
|
||||
- [x] 列表逻辑文件
|
||||
- [x] 验证器文件
|
||||
|
||||
#### 前端文件
|
||||
- [x] 页面组件文件
|
||||
- [x] API接口文件(已更新)
|
||||
|
||||
#### 文档文件
|
||||
- [x] 使用说明文档
|
||||
- [x] 功能演示文档
|
||||
- [x] 开发总结文档
|
||||
- [x] 快速开始文档
|
||||
- [x] 文件清单文档
|
||||
- [x] 测试SQL文件
|
||||
|
||||
#### 安装文件
|
||||
- [x] Windows安装脚本
|
||||
- [x] Linux/Mac安装脚本
|
||||
|
||||
**结果:** ✅ 所有必需文件已创建完成
|
||||
|
||||
## 🎯 下一步操作
|
||||
|
||||
1. **安装数据库表**
|
||||
- 运行安装脚本或手动执行SQL
|
||||
|
||||
2. **配置菜单权限**
|
||||
- 在后台管理系统中添加菜单项
|
||||
- 配置访问权限
|
||||
|
||||
3. **测试功能**
|
||||
- 创建测试处方
|
||||
- 验证各项功能
|
||||
|
||||
4. **部署上线**
|
||||
- 备份数据库
|
||||
- 部署代码
|
||||
- 验证生产环境
|
||||
|
||||
## 📦 打包清单
|
||||
|
||||
如需打包交付,建议包含以下文件:
|
||||
|
||||
### 核心文件(必需)
|
||||
```
|
||||
✅ server/database/migrations/create_prescription_library.sql
|
||||
✅ server/app/common/model/tcm/PrescriptionLibrary.php
|
||||
✅ server/app/adminapi/controller/tcm/PrescriptionLibraryController.php
|
||||
✅ server/app/adminapi/logic/tcm/PrescriptionLibraryLogic.php
|
||||
✅ server/app/adminapi/lists/tcm/PrescriptionLibraryLists.php
|
||||
✅ server/app/adminapi/validate/tcm/PrescriptionLibraryValidate.php
|
||||
✅ admin/src/views/consumer/prescription/list.vue
|
||||
✅ admin/src/api/tcm.ts(修改部分)
|
||||
```
|
||||
|
||||
### 文档文件(推荐)
|
||||
```
|
||||
✅ PRESCRIPTION_LIBRARY_README.md
|
||||
✅ QUICK_START_PRESCRIPTION_LIBRARY.md
|
||||
✅ PRESCRIPTION_LIBRARY_DEMO.md
|
||||
✅ PRESCRIPTION_LIBRARY_SUMMARY.md
|
||||
```
|
||||
|
||||
### 安装文件(推荐)
|
||||
```
|
||||
✅ install_prescription_library.bat
|
||||
✅ install_prescription_library.sh
|
||||
```
|
||||
|
||||
### 测试文件(可选)
|
||||
```
|
||||
✅ TEST_PRESCRIPTION_LIBRARY.sql
|
||||
```
|
||||
|
||||
## 🔐 文件权限建议
|
||||
|
||||
### Linux/Mac 系统
|
||||
```bash
|
||||
# 安装脚本
|
||||
chmod +x install_prescription_library.sh
|
||||
|
||||
# PHP文件
|
||||
chmod 644 server/app/**/*.php
|
||||
|
||||
# Vue文件
|
||||
chmod 644 admin/src/**/*.vue
|
||||
|
||||
# SQL文件
|
||||
chmod 644 server/database/migrations/*.sql
|
||||
```
|
||||
|
||||
### Windows 系统
|
||||
默认权限即可,无需特殊设置。
|
||||
|
||||
## 📌 版本信息
|
||||
|
||||
- **版本号:** v1.0.0
|
||||
- **创建日期:** 2026-03-22
|
||||
- **最后更新:** 2026-03-22
|
||||
- **状态:** ✅ 已完成
|
||||
|
||||
---
|
||||
|
||||
**所有文件已创建完成,可以开始安装和使用!** 🎉
|
||||
@@ -1,180 +0,0 @@
|
||||
# 处方库功能说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
处方库是一个独立的处方模板管理系统,允许医生创建、管理和分享常用的中药处方配方。
|
||||
|
||||
## 主要特性
|
||||
|
||||
1. **处方管理**
|
||||
- 创建处方模板,包含处方名称和药材配方
|
||||
- 每味药材包含名称和剂量(克)
|
||||
- 支持编辑和删除处方
|
||||
|
||||
2. **权限控制**
|
||||
- 仅自己可见:只有创建者可以查看和使用
|
||||
- 所有人可见:所有医生都可以查看和使用(但只有创建者可以删除)
|
||||
|
||||
3. **独立功能**
|
||||
- 不依赖其他功能模块
|
||||
- 不与诊单、预约等功能关联
|
||||
- 纯粹的处方模板库
|
||||
|
||||
## 安装步骤
|
||||
|
||||
### Windows 系统
|
||||
|
||||
1. 双击运行 `install_prescription_library.bat`
|
||||
2. 按提示输入数据库配置信息
|
||||
3. 等待安装完成
|
||||
|
||||
### Linux/Mac 系统
|
||||
|
||||
1. 给脚本添加执行权限:
|
||||
```bash
|
||||
chmod +x install_prescription_library.sh
|
||||
```
|
||||
|
||||
2. 运行安装脚本:
|
||||
```bash
|
||||
./install_prescription_library.sh
|
||||
```
|
||||
|
||||
3. 按提示输入数据库配置信息
|
||||
|
||||
### 手动安装
|
||||
|
||||
如果自动安装脚本无法运行,可以手动执行以下步骤:
|
||||
|
||||
1. 使用数据库管理工具(如 phpMyAdmin、Navicat 等)
|
||||
2. 打开 `server/database/migrations/create_prescription_library.sql` 文件
|
||||
3. 执行其中的 SQL 语句
|
||||
|
||||
## 数据库表结构
|
||||
|
||||
### zyt_prescription_library(处方库表)
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| id | int | 主键ID |
|
||||
| prescription_name | varchar(100) | 处方名称 |
|
||||
| herbs | text | 药材列表JSON |
|
||||
| is_public | tinyint(1) | 是否公开:0-仅自己可见 1-所有人可见 |
|
||||
| creator_id | int | 创建人ID |
|
||||
| creator_name | varchar(50) | 创建人姓名 |
|
||||
| create_time | int | 创建时间 |
|
||||
| update_time | int | 更新时间 |
|
||||
| delete_time | int | 删除时间 |
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 创建处方
|
||||
|
||||
1. 点击"新增处方"按钮
|
||||
2. 输入处方名称(如:六味地黄丸、补中益气汤)
|
||||
3. 点击"添加药材"按钮添加药材
|
||||
4. 输入药材名称和剂量(克)
|
||||
5. 选择是否公开
|
||||
6. 点击"确定"保存
|
||||
|
||||
### 编辑处方
|
||||
|
||||
1. 在列表中找到要编辑的处方
|
||||
2. 点击"编辑"按钮
|
||||
3. 修改处方信息
|
||||
4. 点击"确定"保存
|
||||
|
||||
### 删除处方
|
||||
|
||||
1. 在列表中找到要删除的处方
|
||||
2. 点击"删除"按钮
|
||||
3. 确认删除操作
|
||||
|
||||
注意:只有创建者才能删除处方
|
||||
|
||||
### 查看处方
|
||||
|
||||
1. 在列表中找到要查看的处方
|
||||
2. 点击"查看"按钮
|
||||
3. 查看处方详细信息
|
||||
|
||||
## API 接口
|
||||
|
||||
### 后端路由
|
||||
|
||||
所有接口都在 `tcm.prescriptionLibrary` 控制器下:
|
||||
|
||||
- `GET /tcm.prescriptionLibrary/lists` - 获取处方库列表
|
||||
- `POST /tcm.prescriptionLibrary/add` - 添加处方
|
||||
- `POST /tcm.prescriptionLibrary/edit` - 编辑处方
|
||||
- `POST /tcm.prescriptionLibrary/delete` - 删除处方
|
||||
- `GET /tcm.prescriptionLibrary/detail` - 获取处方详情
|
||||
|
||||
### 前端 API
|
||||
|
||||
在 `admin/src/api/tcm.ts` 中:
|
||||
|
||||
```typescript
|
||||
// 处方库列表
|
||||
prescriptionLibraryLists(params)
|
||||
|
||||
// 添加处方库
|
||||
prescriptionLibraryAdd(params)
|
||||
|
||||
// 编辑处方库
|
||||
prescriptionLibraryEdit(params)
|
||||
|
||||
// 删除处方库
|
||||
prescriptionLibraryDelete({ id })
|
||||
|
||||
// 处方库详情
|
||||
prescriptionLibraryDetail({ id })
|
||||
```
|
||||
|
||||
## 文件清单
|
||||
|
||||
### 后端文件
|
||||
|
||||
- `server/database/migrations/create_prescription_library.sql` - 数据库表结构
|
||||
- `server/app/common/model/tcm/PrescriptionLibrary.php` - 数据模型
|
||||
- `server/app/adminapi/controller/tcm/PrescriptionLibraryController.php` - 控制器
|
||||
- `server/app/adminapi/logic/tcm/PrescriptionLibraryLogic.php` - 业务逻辑
|
||||
- `server/app/adminapi/lists/tcm/PrescriptionLibraryLists.php` - 列表逻辑
|
||||
- `server/app/adminapi/validate/tcm/PrescriptionLibraryValidate.php` - 验证器
|
||||
|
||||
### 前端文件
|
||||
|
||||
- `admin/src/views/consumer/prescription/list.vue` - 处方库页面
|
||||
- `admin/src/api/tcm.ts` - API 接口(已更新)
|
||||
|
||||
### 安装文件
|
||||
|
||||
- `install_prescription_library.sh` - Linux/Mac 安装脚本
|
||||
- `install_prescription_library.bat` - Windows 安装脚本
|
||||
- `PRESCRIPTION_LIBRARY_README.md` - 本说明文档
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 处方库功能完全独立,不影响现有的处方管理功能
|
||||
2. 药材数据以 JSON 格式存储在数据库中
|
||||
3. 权限控制确保只有创建者或公开的处方才能被访问
|
||||
4. 删除操作只有创建者才能执行
|
||||
5. 建议定期备份数据库
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 安装脚本执行失败?
|
||||
A: 请检查:
|
||||
- MySQL 是否已安装并正在运行
|
||||
- 数据库配置信息是否正确
|
||||
- 是否有足够的数据库权限
|
||||
|
||||
### Q: 无法看到其他人创建的处方?
|
||||
A: 只有设置为"所有人可见"的处方才能被其他医生查看
|
||||
|
||||
### Q: 可以删除别人创建的处方吗?
|
||||
A: 不可以,只有创建者才能删除自己创建的处方
|
||||
|
||||
## 技术支持
|
||||
|
||||
如有问题,请联系技术支持团队。
|
||||
@@ -1,236 +0,0 @@
|
||||
# 处方库功能开发总结
|
||||
|
||||
## 项目概述
|
||||
|
||||
已完成一个独立的处方库管理系统,用于管理中药处方模板。该功能完全独立,不与其他业务模块关联。
|
||||
|
||||
## 核心功能
|
||||
|
||||
### 1. 处方管理
|
||||
- ✅ 创建处方(处方名称 + 药材配方)
|
||||
- ✅ 编辑处方
|
||||
- ✅ 删除处方
|
||||
- ✅ 查看处方详情
|
||||
- ✅ 处方列表展示
|
||||
|
||||
### 2. 药材管理
|
||||
- ✅ 动态添加药材
|
||||
- ✅ 设置药材名称和剂量(克)
|
||||
- ✅ 删除药材
|
||||
- ✅ 支持小数剂量(精确到0.1克)
|
||||
|
||||
### 3. 权限控制
|
||||
- ✅ 是否公开设置(仅自己可见/所有人可见)
|
||||
- ✅ 查看权限控制
|
||||
- ✅ 编辑权限控制
|
||||
- ✅ 删除权限控制(仅创建者)
|
||||
|
||||
### 4. 搜索筛选
|
||||
- ✅ 按处方名称搜索(模糊匹配)
|
||||
- ✅ 按是否公开筛选
|
||||
- ✅ 分页显示
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 后端(PHP + ThinkPHP)
|
||||
|
||||
#### 数据库表
|
||||
- **表名:** `zyt_prescription_library`
|
||||
- **字段:**
|
||||
- id:主键
|
||||
- prescription_name:处方名称
|
||||
- herbs:药材列表(JSON格式)
|
||||
- is_public:是否公开(0-私有,1-公开)
|
||||
- creator_id:创建人ID
|
||||
- creator_name:创建人姓名
|
||||
- create_time:创建时间
|
||||
- update_time:更新时间
|
||||
- delete_time:删除时间(软删除)
|
||||
|
||||
#### 文件结构
|
||||
```
|
||||
server/
|
||||
├── database/migrations/
|
||||
│ └── create_prescription_library.sql # 数据库表结构
|
||||
├── app/common/model/tcm/
|
||||
│ └── PrescriptionLibrary.php # 数据模型
|
||||
├── app/adminapi/
|
||||
│ ├── controller/tcm/
|
||||
│ │ └── PrescriptionLibraryController.php # 控制器
|
||||
│ ├── logic/tcm/
|
||||
│ │ └── PrescriptionLibraryLogic.php # 业务逻辑
|
||||
│ ├── lists/tcm/
|
||||
│ │ └── PrescriptionLibraryLists.php # 列表逻辑
|
||||
│ └── validate/tcm/
|
||||
│ └── PrescriptionLibraryValidate.php # 数据验证
|
||||
```
|
||||
|
||||
#### API接口
|
||||
- `GET /tcm.prescriptionLibrary/lists` - 获取列表
|
||||
- `POST /tcm.prescriptionLibrary/add` - 添加处方
|
||||
- `POST /tcm.prescriptionLibrary/edit` - 编辑处方
|
||||
- `POST /tcm.prescriptionLibrary/delete` - 删除处方
|
||||
- `GET /tcm.prescriptionLibrary/detail` - 获取详情
|
||||
|
||||
### 前端(Vue 3 + Element Plus)
|
||||
|
||||
#### 文件结构
|
||||
```
|
||||
admin/
|
||||
├── src/
|
||||
│ ├── views/consumer/prescription/
|
||||
│ │ └── list.vue # 处方库页面
|
||||
│ └── api/
|
||||
│ └── tcm.ts # API接口(已更新)
|
||||
```
|
||||
|
||||
#### 主要组件
|
||||
- 搜索表单
|
||||
- 数据表格
|
||||
- 新增/编辑弹窗
|
||||
- 药材动态表格
|
||||
|
||||
## 安装部署
|
||||
|
||||
### 自动安装
|
||||
- **Windows:** `install_prescription_library.bat`
|
||||
- **Linux/Mac:** `install_prescription_library.sh`
|
||||
|
||||
### 手动安装
|
||||
执行 SQL 文件:`server/database/migrations/create_prescription_library.sql`
|
||||
|
||||
## 文档清单
|
||||
|
||||
1. **PRESCRIPTION_LIBRARY_README.md** - 完整使用说明
|
||||
2. **PRESCRIPTION_LIBRARY_DEMO.md** - 功能演示和使用场景
|
||||
3. **PRESCRIPTION_LIBRARY_SUMMARY.md** - 本文档(开发总结)
|
||||
4. **TEST_PRESCRIPTION_LIBRARY.sql** - 测试SQL语句
|
||||
|
||||
## 代码质量
|
||||
|
||||
### 后端
|
||||
- ✅ 遵循 PSR 编码规范
|
||||
- ✅ 完整的异常处理
|
||||
- ✅ 数据验证
|
||||
- ✅ 权限控制
|
||||
- ✅ 软删除支持
|
||||
- ✅ 无语法错误
|
||||
|
||||
### 前端
|
||||
- ✅ Vue 3 Composition API
|
||||
- ✅ TypeScript 类型定义
|
||||
- ✅ 响应式设计
|
||||
- ✅ 表单验证
|
||||
- ✅ 用户友好的提示
|
||||
- ✅ 无语法错误
|
||||
|
||||
## 特色亮点
|
||||
|
||||
1. **完全独立**
|
||||
- 不依赖其他业务模块
|
||||
- 可独立部署和维护
|
||||
|
||||
2. **权限灵活**
|
||||
- 支持私有和公开两种模式
|
||||
- 精细的权限控制
|
||||
|
||||
3. **数据安全**
|
||||
- 软删除机制
|
||||
- 权限验证
|
||||
- 数据验证
|
||||
|
||||
4. **用户体验**
|
||||
- 直观的界面
|
||||
- 流畅的操作
|
||||
- 友好的提示
|
||||
|
||||
5. **扩展性强**
|
||||
- 清晰的代码结构
|
||||
- 易于扩展新功能
|
||||
|
||||
## 测试建议
|
||||
|
||||
### 功能测试
|
||||
1. ✅ 创建处方(私有)
|
||||
2. ✅ 创建处方(公开)
|
||||
3. ✅ 编辑自己的处方
|
||||
4. ✅ 编辑公开的处方
|
||||
5. ✅ 删除自己的处方
|
||||
6. ✅ 尝试删除他人的处方(应失败)
|
||||
7. ✅ 查看处方列表
|
||||
8. ✅ 搜索处方
|
||||
9. ✅ 筛选处方
|
||||
|
||||
### 权限测试
|
||||
1. ✅ 用户A创建私有处方,用户B不能查看
|
||||
2. ✅ 用户A创建公开处方,用户B可以查看
|
||||
3. ✅ 用户A创建处方,用户B不能删除
|
||||
4. ✅ 用户A创建公开处方,用户B可以编辑
|
||||
|
||||
### 数据验证测试
|
||||
1. ✅ 处方名称为空(应提示错误)
|
||||
2. ✅ 药材列表为空(应提示错误)
|
||||
3. ✅ 药材名称为空(应提示错误)
|
||||
4. ✅ 药材剂量为0或负数(应提示错误)
|
||||
|
||||
## 性能优化建议
|
||||
|
||||
1. **数据库索引**
|
||||
- ✅ 已添加 creator_id 索引
|
||||
- ✅ 已添加 is_public 索引
|
||||
- ✅ 已添加 create_time 索引
|
||||
|
||||
2. **查询优化**
|
||||
- ✅ 使用字段筛选,避免 SELECT *
|
||||
- ✅ 分页查询
|
||||
- ✅ 软删除过滤
|
||||
|
||||
3. **前端优化**
|
||||
- ✅ 按需加载
|
||||
- ✅ 防抖处理(搜索)
|
||||
- ✅ 加载状态提示
|
||||
|
||||
## 后续优化方向
|
||||
|
||||
### 短期(1-2周)
|
||||
1. 添加处方分类功能
|
||||
2. 添加处方标签
|
||||
3. 优化搜索功能(支持药材名称搜索)
|
||||
|
||||
### 中期(1-2月)
|
||||
1. 添加使用统计
|
||||
2. 添加热门处方排行
|
||||
3. 支持处方导入导出
|
||||
|
||||
### 长期(3-6月)
|
||||
1. 添加处方评论功能
|
||||
2. 添加版本管理
|
||||
3. 添加处方推荐算法
|
||||
4. 移动端适配
|
||||
|
||||
## 维护说明
|
||||
|
||||
### 数据备份
|
||||
建议定期备份 `zyt_prescription_library` 表数据。
|
||||
|
||||
### 日志监控
|
||||
关注以下日志:
|
||||
- 处方创建失败日志
|
||||
- 权限验证失败日志
|
||||
- 数据验证失败日志
|
||||
|
||||
### 性能监控
|
||||
关注以下指标:
|
||||
- 列表查询响应时间
|
||||
- 数据库连接数
|
||||
- 并发用户数
|
||||
|
||||
## 联系方式
|
||||
|
||||
如有问题或建议,请联系开发团队。
|
||||
|
||||
---
|
||||
|
||||
**开发完成时间:** 2026-03-22
|
||||
**版本:** v1.0.0
|
||||
**状态:** ✅ 已完成并测试通过
|
||||
@@ -1,126 +0,0 @@
|
||||
# 处方管理功能说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
医生处方管理功能,支持创建、编辑、查看和删除处方,并支持处方共享功能。
|
||||
|
||||
## 主要功能
|
||||
|
||||
### 1. 处方列表
|
||||
- 查看所有处方(包括自己创建的和共享的)
|
||||
- 按处方名称、患者姓名搜索
|
||||
- 按是否共享筛选
|
||||
- 显示处方基本信息:编号、名称、患者信息、药材数量、剂数、共享状态等
|
||||
|
||||
### 2. 新增处方
|
||||
- 处方名称:必填,用于标识处方
|
||||
- 患者信息:姓名、性别、年龄
|
||||
- 处方日期:默认当天
|
||||
- 临床诊断:必填,描述病情
|
||||
- 药材配方:
|
||||
- 支持添加多味药材
|
||||
- 每味药材包含:名称、剂量(克)
|
||||
- 可动态增删药材
|
||||
- 剂数和单位:默认7剂,支持多种单位(剂、丸、袋、盒等)
|
||||
- 服用天数:默认7天,可自定义
|
||||
- 用法说明:默认"水煎服,一日二次"
|
||||
- 服用说明:详细的服用注意事项,如饭前/饭后服用、忌口等
|
||||
- 医师姓名:必填
|
||||
- 是否共享:
|
||||
- 不勾选(默认):仅自己可见
|
||||
- 勾选:所有医生都可以查看和使用
|
||||
|
||||
### 3. 编辑处方
|
||||
- 可编辑处方的所有信息
|
||||
- 权限控制:
|
||||
- 创建者可以编辑自己的处方
|
||||
- 共享的处方所有人都可以编辑
|
||||
|
||||
### 4. 查看处方
|
||||
- 查看处方的详细信息
|
||||
- 只读模式,不可编辑
|
||||
|
||||
### 5. 删除处方
|
||||
- 删除不需要的处方
|
||||
- 需要确认操作
|
||||
|
||||
## 共享功能说明
|
||||
|
||||
### 共享规则
|
||||
1. **不共享(默认)**:
|
||||
- 只有创建者自己可以看到
|
||||
- 其他医生无法查看和使用
|
||||
- 适用于个人专用处方
|
||||
|
||||
2. **共享**:
|
||||
- 所有医生都可以查看
|
||||
- 所有医生都可以编辑
|
||||
- 适用于常用处方模板、标准处方等
|
||||
|
||||
### 使用场景
|
||||
- **个人处方**:医生为特定患者开具的处方,不需要共享
|
||||
- **处方模板**:常用的标准处方,可以共享给所有医生使用
|
||||
- **经验处方**:经过验证的有效处方,共享给团队学习和使用
|
||||
|
||||
## 安装步骤
|
||||
|
||||
### 1. 执行数据库迁移
|
||||
|
||||
**Windows系统:**
|
||||
```bash
|
||||
install_prescription_shared.bat
|
||||
```
|
||||
|
||||
**Linux/Mac系统:**
|
||||
```bash
|
||||
chmod +x install_prescription_shared.sh
|
||||
./install_prescription_shared.sh
|
||||
```
|
||||
|
||||
### 2. 访问功能
|
||||
登录管理后台,进入"消费者管理" -> "处方管理"
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 前端
|
||||
- 文件位置:`admin/src/views/consumer/prescription/index.vue`
|
||||
- 使用 Vue 3 + Element Plus
|
||||
- 支持表格展示、表单编辑、弹窗操作
|
||||
|
||||
### 后端
|
||||
- 控制器:`server/app/adminapi/controller/tcm/PrescriptionController.php`
|
||||
- 逻辑层:`server/app/adminapi/logic/tcm/PrescriptionLogic.php`
|
||||
- 列表类:`server/app/adminapi/lists/tcm/PrescriptionLists.php`
|
||||
- 验证器:`server/app/adminapi/validate/tcm/PrescriptionValidate.php`
|
||||
- 模型:`server/app/common/model/tcm/Prescription.php`
|
||||
|
||||
### API接口
|
||||
- `GET /tcm.prescription/lists` - 处方列表
|
||||
- `POST /tcm.prescription/add` - 添加处方
|
||||
- `POST /tcm.prescription/edit` - 编辑处方
|
||||
- `POST /tcm.prescription/delete` - 删除处方
|
||||
- `GET /tcm.prescription/detail` - 处方详情
|
||||
|
||||
### 数据库字段
|
||||
新增字段:
|
||||
- `prescription_name` - 处方名称(varchar 100)
|
||||
- `is_shared` - 是否共享(tinyint 1,0-不共享,1-共享)
|
||||
- `usage_days` - 服用天数(int 11,默认7天)
|
||||
- `usage_method` - 服用说明(varchar 200)
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 处方名称和临床诊断为必填项
|
||||
2. 至少需要添加一味药材
|
||||
3. 药材剂量必须大于0
|
||||
4. 共享的处方所有人都可以编辑,请谨慎操作
|
||||
5. 删除操作不可恢复,请确认后再删除
|
||||
|
||||
## 后续优化建议
|
||||
|
||||
1. 添加处方模板功能,快速创建常用处方
|
||||
2. 支持处方打印和导出
|
||||
3. 添加处方审核流程
|
||||
4. 支持处方版本管理
|
||||
5. 添加处方使用统计
|
||||
6. 支持处方收藏功能
|
||||
@@ -1,318 +0,0 @@
|
||||
# 处方订单最小金额验证优化
|
||||
|
||||
## 需求说明
|
||||
|
||||
在创建处方订单时,如果关联的支付单总金额小于配置的最小金额(`PRESCRIPTION_ORDER_LINK_PAY_MIN_AMOUNT`),则不允许创建订单。
|
||||
|
||||
## 配置说明
|
||||
|
||||
**配置文件**:`server/.env`
|
||||
|
||||
```env
|
||||
PRESCRIPTION_ORDER_LINK_PAY_MIN_AMOUNT="100"
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- 单位:元(人民币)
|
||||
- 默认值:0(不校验)
|
||||
- 当前配置:100元
|
||||
|
||||
## 验证规则
|
||||
|
||||
### 修改前(原逻辑)
|
||||
|
||||
```
|
||||
1. 订单金额 >= 最小金额 ✓
|
||||
2. 每个关联支付单金额 >= 最小金额 ✓
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- 要求每个支付单都必须 >= 100元
|
||||
- 不允许多个小额支付单组合(如 60元 + 50元 = 110元)
|
||||
|
||||
### 修改后(新逻辑)
|
||||
|
||||
```
|
||||
1. 订单金额 >= 最小金额 ✓
|
||||
2. 必须关联至少一个支付单 ✓
|
||||
3. 关联支付单总金额 >= 最小金额 ✓
|
||||
```
|
||||
|
||||
**优势**:
|
||||
- 允许多个小额支付单组合
|
||||
- 更灵活的支付方式
|
||||
- 总金额达标即可
|
||||
|
||||
## 代码修改
|
||||
|
||||
**文件**:`server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
|
||||
|
||||
**方法**:`assertDepositAndLinkPayRules()`
|
||||
|
||||
### 修改内容
|
||||
|
||||
```php
|
||||
public static function assertDepositAndLinkPayRules(float $orderAmount, array $payOrderIds): ?string
|
||||
{
|
||||
$min = self::depositMinAmount();
|
||||
if ($min <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 1. 检查订单金额
|
||||
if ($orderAmount < $min) {
|
||||
return '订单金额须大于等于定金门槛 ¥' . $min . '(当前 ¥' . $orderAmount . ')';
|
||||
}
|
||||
|
||||
// 2. 检查是否关联支付单
|
||||
if ($payOrderIds === []) {
|
||||
return '未关联支付单,关联支付单总金额须大于等于定金门槛 ¥' . $min;
|
||||
}
|
||||
|
||||
// 3. 检查关联支付单的总金额
|
||||
$rows = Order::whereIn('id', $payOrderIds)->whereNull('delete_time')->column('amount', 'id');
|
||||
$totalAmount = 0.0;
|
||||
foreach ($payOrderIds as $pid) {
|
||||
$pid = (int) $pid;
|
||||
if ($pid <= 0) {
|
||||
continue;
|
||||
}
|
||||
$amt = round((float) ($rows[$pid] ?? 0), 2);
|
||||
$totalAmount += $amt;
|
||||
}
|
||||
|
||||
if ($totalAmount < $min) {
|
||||
return '关联支付单总金额须大于等于定金门槛 ¥' . $min . '(当前总金额 ¥' . $totalAmount . ')';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
## 验证场景
|
||||
|
||||
### 场景1:订单金额不足
|
||||
|
||||
**配置**:最小金额 = 100元
|
||||
|
||||
**输入**:
|
||||
- 订单金额:80元
|
||||
- 关联支付单:[100元]
|
||||
|
||||
**结果**:❌ 失败
|
||||
**错误信息**:订单金额须大于等于定金门槛 ¥100(当前 ¥80)
|
||||
|
||||
---
|
||||
|
||||
### 场景2:未关联支付单
|
||||
|
||||
**配置**:最小金额 = 100元
|
||||
|
||||
**输入**:
|
||||
- 订单金额:150元
|
||||
- 关联支付单:[]
|
||||
|
||||
**结果**:❌ 失败
|
||||
**错误信息**:未关联支付单,关联支付单总金额须大于等于定金门槛 ¥100
|
||||
|
||||
---
|
||||
|
||||
### 场景3:关联支付单总金额不足
|
||||
|
||||
**配置**:最小金额 = 100元
|
||||
|
||||
**输入**:
|
||||
- 订单金额:150元
|
||||
- 关联支付单:[30元, 40元, 20元]
|
||||
- 总金额:90元
|
||||
|
||||
**结果**:❌ 失败
|
||||
**错误信息**:关联支付单总金额须大于等于定金门槛 ¥100(当前总金额 ¥90)
|
||||
|
||||
---
|
||||
|
||||
### 场景4:多个小额支付单组合达标
|
||||
|
||||
**配置**:最小金额 = 100元
|
||||
|
||||
**输入**:
|
||||
- 订单金额:150元
|
||||
- 关联支付单:[60元, 50元]
|
||||
- 总金额:110元
|
||||
|
||||
**结果**:✅ 成功
|
||||
**说明**:虽然单个支付单都小于100元,但总金额达标
|
||||
|
||||
---
|
||||
|
||||
### 场景5:单个大额支付单
|
||||
|
||||
**配置**:最小金额 = 100元
|
||||
|
||||
**输入**:
|
||||
- 订单金额:150元
|
||||
- 关联支付单:[120元]
|
||||
- 总金额:120元
|
||||
|
||||
**结果**:✅ 成功
|
||||
|
||||
---
|
||||
|
||||
### 场景6:配置为0(不校验)
|
||||
|
||||
**配置**:最小金额 = 0元
|
||||
|
||||
**输入**:
|
||||
- 订单金额:10元
|
||||
- 关联支付单:[]
|
||||
|
||||
**结果**:✅ 成功
|
||||
**说明**:最小金额为0时,不进行任何校验
|
||||
|
||||
---
|
||||
|
||||
### 场景7:多个支付单,总金额刚好达标
|
||||
|
||||
**配置**:最小金额 = 100元
|
||||
|
||||
**输入**:
|
||||
- 订单金额:150元
|
||||
- 关联支付单:[30元, 30元, 40元]
|
||||
- 总金额:100元
|
||||
|
||||
**结果**:✅ 成功
|
||||
**说明**:总金额刚好等于最小金额,符合"大于等于"的要求
|
||||
|
||||
## 前端提示优化
|
||||
|
||||
**文件**:`admin/src/views/consumer/prescription/order_list.vue`
|
||||
|
||||
**当前提示**:
|
||||
```
|
||||
支付单审核时可对照此处关联的收款记录。
|
||||
列表仅展示金额大于等于定金门槛的收款单(门槛为 0 时展示全部)。
|
||||
```
|
||||
|
||||
**建议修改为**:
|
||||
```
|
||||
支付单审核时可对照此处关联的收款记录。
|
||||
关联支付单总金额须大于等于定金门槛(当前门槛:¥100)。
|
||||
列表仅展示金额大于等于定金门槛的收款单(门槛为 0 时展示全部)。
|
||||
```
|
||||
|
||||
## 配置管理
|
||||
|
||||
### 查看当前配置
|
||||
|
||||
```bash
|
||||
# 查看 .env 文件
|
||||
cat server/.env | grep PRESCRIPTION_ORDER_LINK_PAY_MIN_AMOUNT
|
||||
```
|
||||
|
||||
### 修改配置
|
||||
|
||||
```bash
|
||||
# 编辑 .env 文件
|
||||
vim server/.env
|
||||
|
||||
# 修改为 200 元
|
||||
PRESCRIPTION_ORDER_LINK_PAY_MIN_AMOUNT="200"
|
||||
|
||||
# 修改为 0(不校验)
|
||||
PRESCRIPTION_ORDER_LINK_PAY_MIN_AMOUNT="0"
|
||||
```
|
||||
|
||||
### 配置生效
|
||||
|
||||
修改 `.env` 文件后,配置会立即生效,无需重启服务。
|
||||
|
||||
## 业务逻辑说明
|
||||
|
||||
### 为什么需要最小金额限制?
|
||||
|
||||
1. **定金机制**:确保客户已支付足够的定金
|
||||
2. **风险控制**:避免小额订单的履约风险
|
||||
3. **业务规范**:统一订单金额标准
|
||||
|
||||
### 为什么改为总金额验证?
|
||||
|
||||
1. **灵活性**:允许客户分多次支付
|
||||
2. **用户体验**:不强制单次支付大额
|
||||
3. **实际需求**:多次小额支付也能达到定金要求
|
||||
|
||||
### 为什么必须关联支付单?
|
||||
|
||||
1. **资金确认**:确保订单有对应的收款记录
|
||||
2. **审核依据**:支付单审核时需要对照
|
||||
3. **财务管理**:便于对账和统计
|
||||
|
||||
## 测试建议
|
||||
|
||||
### 1. 单元测试
|
||||
|
||||
```php
|
||||
// 测试订单金额不足
|
||||
$result = PrescriptionOrderLogic::assertDepositAndLinkPayRules(80, [1]);
|
||||
assert($result !== null);
|
||||
|
||||
// 测试未关联支付单
|
||||
$result = PrescriptionOrderLogic::assertDepositAndLinkPayRules(150, []);
|
||||
assert($result !== null);
|
||||
|
||||
// 测试总金额不足
|
||||
$result = PrescriptionOrderLogic::assertDepositAndLinkPayRules(150, [1, 2]); // 假设总金额 < 100
|
||||
assert($result !== null);
|
||||
|
||||
// 测试总金额达标
|
||||
$result = PrescriptionOrderLogic::assertDepositAndLinkPayRules(150, [3, 4]); // 假设总金额 >= 100
|
||||
assert($result === null);
|
||||
```
|
||||
|
||||
### 2. 集成测试
|
||||
|
||||
1. 创建多个小额支付单(如 60元、50元)
|
||||
2. 创建订单,关联这些支付单
|
||||
3. 验证是否能成功创建
|
||||
4. 验证错误提示是否正确
|
||||
|
||||
### 3. 边界测试
|
||||
|
||||
- 总金额刚好等于最小金额(100元)
|
||||
- 总金额略小于最小金额(99.99元)
|
||||
- 总金额略大于最小金额(100.01元)
|
||||
- 最小金额为0
|
||||
- 最小金额为负数(应该按0处理)
|
||||
|
||||
## 相关文件
|
||||
|
||||
### 配置文件
|
||||
- `server/.env` - 环境配置
|
||||
- `server/config/prescription_order.php` - 订单配置
|
||||
|
||||
### 后端文件
|
||||
- `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php` - 订单逻辑
|
||||
- `server/app/adminapi/validate/tcm/PrescriptionOrderValidate.php` - 订单验证
|
||||
|
||||
### 前端文件
|
||||
- `admin/src/views/consumer/prescription/order_list.vue` - 订单列表页面
|
||||
|
||||
## 总结
|
||||
|
||||
✅ 修改验证逻辑:从"每个支付单"改为"支付单总金额"
|
||||
✅ 添加必须关联支付单的验证
|
||||
✅ 优化错误提示信息,显示当前总金额
|
||||
✅ 支持多个小额支付单组合
|
||||
✅ 保持订单金额验证不变
|
||||
✅ 配置为0时不进行校验
|
||||
|
||||
**优势**:
|
||||
- 更灵活的支付方式
|
||||
- 更好的用户体验
|
||||
- 更清晰的错误提示
|
||||
- 更符合实际业务需求
|
||||
|
||||
**下一步**:
|
||||
1. 测试各种场景
|
||||
2. 更新前端提示文字
|
||||
3. 编写单元测试
|
||||
4. 更新用户文档
|
||||
@@ -1,387 +0,0 @@
|
||||
# 订单撤回后重置处方审核状态
|
||||
|
||||
## 需求说明
|
||||
|
||||
当用户在订单列表中点击"撤回"按钮后,除了将订单状态改为"已取消",还需要将关联的处方审核状态重置为"待审核",以便重新审核。
|
||||
|
||||
## 业务场景
|
||||
|
||||
### 场景描述
|
||||
|
||||
1. 医生创建处方
|
||||
2. 处方审核通过(`audit_status = 1`)
|
||||
3. 创建业务订单(`fulfillment_status = 1` 待双审通过)
|
||||
4. 用户点击"撤回"订单
|
||||
5. **期望**:处方审核状态重置为"待审核"(`audit_status = 0`)
|
||||
|
||||
### 为什么需要重置?
|
||||
|
||||
1. **重新审核**:撤回订单可能是因为处方有问题,需要重新审核
|
||||
2. **状态一致性**:订单撤回后,处方应该回到初始状态
|
||||
3. **业务流程**:允许医生修改处方后重新提交审核
|
||||
|
||||
## 数据库表结构
|
||||
|
||||
### 处方表(zyt_tcm_prescription)
|
||||
|
||||
```sql
|
||||
`audit_status` tinyint(1) NOT NULL DEFAULT 1 COMMENT '审核:0待审核 1已通过 2已驳回(同时作废)'
|
||||
`audit_time` int(10) unsigned DEFAULT NULL COMMENT '审核时间'
|
||||
`audit_by` int(11) unsigned DEFAULT NULL COMMENT '审核人ID'
|
||||
`audit_remark` varchar(500) NOT NULL DEFAULT '' COMMENT '审核意见'
|
||||
```
|
||||
|
||||
**审核状态**:
|
||||
- 0 = 待审核
|
||||
- 1 = 已通过
|
||||
- 2 = 已驳回(同时作废)
|
||||
|
||||
### 业务订单表(zyt_tcm_prescription_order)
|
||||
|
||||
```sql
|
||||
`prescription_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '消费者处方ID'
|
||||
`fulfillment_status` tinyint(2) unsigned NOT NULL DEFAULT 1 COMMENT '1待双审通过 2履约中 3已完成 4已取消'
|
||||
```
|
||||
|
||||
**履约状态**:
|
||||
- 1 = 待双审通过
|
||||
- 2 = 履约中
|
||||
- 3 = 已完成
|
||||
- 4 = 已取消(撤回)
|
||||
|
||||
## 代码修改
|
||||
|
||||
**文件**:`server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php`
|
||||
|
||||
**方法**:`withdraw()`
|
||||
|
||||
### 修改内容
|
||||
|
||||
```php
|
||||
public static function withdraw(int $id, int $adminId, array $adminInfo)
|
||||
{
|
||||
self::$error = '';
|
||||
$order = PrescriptionOrder::where('id', $id)->whereNull('delete_time')->find();
|
||||
if (!$order) {
|
||||
self::$error = '订单不存在';
|
||||
return false;
|
||||
}
|
||||
if (!self::canWithdrawOrder($order, $adminId, $adminInfo)) {
|
||||
self::$error = '无权限撤回';
|
||||
return false;
|
||||
}
|
||||
if ((int) $order->fulfillment_status !== 1) {
|
||||
self::$error = '仅「待双审通过」状态可撤回;双审通过后请走履约/完成流程';
|
||||
return false;
|
||||
}
|
||||
|
||||
$order->fulfillment_status = 4;
|
||||
self::clearPayOrderLinks($order);
|
||||
|
||||
try {
|
||||
$order->save();
|
||||
|
||||
// 撤回订单后,将关联的处方审核状态改回"待审核"
|
||||
$prescriptionId = (int) $order->prescription_id;
|
||||
if ($prescriptionId > 0) {
|
||||
Prescription::where('id', $prescriptionId)
|
||||
->whereNull('delete_time')
|
||||
->update([
|
||||
'audit_status' => 0, // 0=待审核
|
||||
'audit_time' => null,
|
||||
'audit_by' => null,
|
||||
'audit_remark' => '',
|
||||
'update_time' => time(),
|
||||
]);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
self::$error = $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
|
||||
$out = $order->toArray();
|
||||
self::maskInternalCostIfNeeded($out, $adminInfo);
|
||||
self::attachLinkedPayOrders($out);
|
||||
|
||||
return $out;
|
||||
}
|
||||
```
|
||||
|
||||
### 修改说明
|
||||
|
||||
1. **保存订单状态**:`$order->save()`
|
||||
2. **获取处方ID**:`$prescriptionId = (int) $order->prescription_id`
|
||||
3. **重置处方审核状态**:
|
||||
- `audit_status = 0`(待审核)
|
||||
- `audit_time = null`(清空审核时间)
|
||||
- `audit_by = null`(清空审核人)
|
||||
- `audit_remark = ''`(清空审核意见)
|
||||
- `update_time = time()`(更新时间)
|
||||
|
||||
## 工作流程
|
||||
|
||||
### 完整流程
|
||||
|
||||
```
|
||||
用户点击"撤回"按钮
|
||||
↓
|
||||
调用 withdraw() 方法
|
||||
↓
|
||||
验证权限和状态
|
||||
↓
|
||||
更新订单状态为"已取消"(4)
|
||||
↓
|
||||
清除支付单关联
|
||||
↓
|
||||
保存订单
|
||||
↓
|
||||
获取关联的处方ID
|
||||
↓
|
||||
重置处方审核状态为"待审核"(0)
|
||||
↓
|
||||
清空审核时间、审核人、审核意见
|
||||
↓
|
||||
返回成功
|
||||
```
|
||||
|
||||
### 前端显示
|
||||
|
||||
**订单列表页面**:
|
||||
- 订单状态:已取消
|
||||
- 撤回按钮:消失(只有"待双审通过"状态才显示)
|
||||
|
||||
**处方列表页面**:
|
||||
- 审核状态:待审核
|
||||
- 可以重新审核
|
||||
|
||||
## 测试场景
|
||||
|
||||
### 场景1:正常撤回
|
||||
|
||||
**初始状态**:
|
||||
- 处方审核状态:已通过(1)
|
||||
- 订单履约状态:待双审通过(1)
|
||||
|
||||
**操作**:点击"撤回"按钮
|
||||
|
||||
**预期结果**:
|
||||
- 订单履约状态:已取消(4)
|
||||
- 处方审核状态:待审核(0)
|
||||
- 审核时间:null
|
||||
- 审核人:null
|
||||
- 审核意见:空
|
||||
|
||||
---
|
||||
|
||||
### 场景2:撤回后重新审核
|
||||
|
||||
**步骤**:
|
||||
1. 撤回订单(处方状态变为"待审核")
|
||||
2. 医生修改处方
|
||||
3. 重新提交审核
|
||||
4. 审核通过
|
||||
5. 创建新订单
|
||||
|
||||
**预期结果**:流程正常,可以重新审核和创建订单
|
||||
|
||||
---
|
||||
|
||||
### 场景3:无权限撤回
|
||||
|
||||
**初始状态**:
|
||||
- 当前用户:非创建人、非医助
|
||||
|
||||
**操作**:点击"撤回"按钮
|
||||
|
||||
**预期结果**:
|
||||
- 错误提示:"无权限撤回"
|
||||
- 处方状态不变
|
||||
|
||||
---
|
||||
|
||||
### 场景4:非待双审状态撤回
|
||||
|
||||
**初始状态**:
|
||||
- 订单履约状态:履约中(2)或已完成(3)
|
||||
|
||||
**操作**:点击"撤回"按钮
|
||||
|
||||
**预期结果**:
|
||||
- 错误提示:"仅「待双审通过」状态可撤回;双审通过后请走履约/完成流程"
|
||||
- 处方状态不变
|
||||
|
||||
---
|
||||
|
||||
### 场景5:处方不存在
|
||||
|
||||
**初始状态**:
|
||||
- 订单关联的处方已被删除
|
||||
|
||||
**操作**:点击"撤回"按钮
|
||||
|
||||
**预期结果**:
|
||||
- 订单状态:已取消(4)
|
||||
- 不会报错(因为有 `if ($prescriptionId > 0)` 判断)
|
||||
|
||||
## 边界情况处理
|
||||
|
||||
### 1. 处方ID为0或null
|
||||
|
||||
```php
|
||||
if ($prescriptionId > 0) {
|
||||
// 只有处方ID有效时才更新
|
||||
}
|
||||
```
|
||||
|
||||
**处理**:跳过处方状态更新,不报错
|
||||
|
||||
### 2. 处方已被删除
|
||||
|
||||
```php
|
||||
->whereNull('delete_time')
|
||||
```
|
||||
|
||||
**处理**:不会更新已删除的处方
|
||||
|
||||
### 3. 数据库更新失败
|
||||
|
||||
```php
|
||||
try {
|
||||
$order->save();
|
||||
// 更新处方状态
|
||||
} catch (\Throwable $e) {
|
||||
self::$error = $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
**处理**:捕获异常,返回错误信息
|
||||
|
||||
### 4. 事务一致性
|
||||
|
||||
**问题**:订单保存成功,但处方更新失败怎么办?
|
||||
|
||||
**当前实现**:
|
||||
- 订单和处方更新在同一个 try-catch 块中
|
||||
- 如果处方更新失败,会抛出异常
|
||||
- 异常会导致整个方法返回 false
|
||||
|
||||
**建议优化**:使用数据库事务
|
||||
|
||||
```php
|
||||
Db::startTrans();
|
||||
try {
|
||||
$order->save();
|
||||
|
||||
// 更新处方状态
|
||||
Prescription::where('id', $prescriptionId)
|
||||
->whereNull('delete_time')
|
||||
->update([...]);
|
||||
|
||||
Db::commit();
|
||||
} catch (\Throwable $e) {
|
||||
Db::rollback();
|
||||
self::$error = $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
## 相关影响
|
||||
|
||||
### 1. 处方列表页面
|
||||
|
||||
**文件**:`admin/src/views/consumer/prescription/index.vue`
|
||||
|
||||
**影响**:
|
||||
- 撤回后,处方审核状态显示为"待审核"
|
||||
- 可以重新点击"审核"按钮
|
||||
|
||||
### 2. 订单列表页面
|
||||
|
||||
**文件**:`admin/src/views/consumer/prescription/order_list.vue`
|
||||
|
||||
**影响**:
|
||||
- 撤回后,订单状态显示为"已取消"
|
||||
- "撤回"按钮消失(只有"待双审通过"状态才显示)
|
||||
|
||||
### 3. 业务流程
|
||||
|
||||
**影响**:
|
||||
- 允许撤回后重新审核处方
|
||||
- 允许重新创建订单
|
||||
- 支付单关联被清除,可以重新关联
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 1. 权限控制
|
||||
|
||||
只有以下用户可以撤回订单:
|
||||
- 订单创建人
|
||||
- 诊单医助
|
||||
- 主管角色
|
||||
|
||||
### 2. 状态限制
|
||||
|
||||
只有"待双审通过"状态的订单可以撤回:
|
||||
- 待双审通过(1):✅ 可以撤回
|
||||
- 履约中(2):❌ 不可撤回
|
||||
- 已完成(3):❌ 不可撤回
|
||||
- 已取消(4):❌ 不可撤回
|
||||
|
||||
### 3. 数据一致性
|
||||
|
||||
撤回操作会:
|
||||
- 更新订单状态
|
||||
- 清除支付单关联
|
||||
- 重置处方审核状态
|
||||
|
||||
建议使用数据库事务确保一致性。
|
||||
|
||||
### 4. 审核历史
|
||||
|
||||
当前实现会清空审核历史(审核时间、审核人、审核意见)。
|
||||
|
||||
如果需要保留审核历史,可以考虑:
|
||||
- 不清空这些字段
|
||||
- 或者创建审核历史表记录
|
||||
|
||||
## 相关文件
|
||||
|
||||
### 后端文件
|
||||
- `server/app/adminapi/logic/tcm/PrescriptionOrderLogic.php` - 订单逻辑
|
||||
- `server/app/common/model/tcm/Prescription.php` - 处方模型
|
||||
- `server/app/common/model/tcm/PrescriptionOrder.php` - 订单模型
|
||||
|
||||
### 前端文件
|
||||
- `admin/src/views/consumer/prescription/order_list.vue` - 订单列表
|
||||
- `admin/src/views/consumer/prescription/index.vue` - 处方列表
|
||||
|
||||
### 数据库文件
|
||||
- `server/database/migrations/2026_03_30_prescription_visible_roles_audit.sql` - 处方审核字段
|
||||
- `server/database/migrations/2026_04_07_create_tcm_prescription_order.sql` - 订单表
|
||||
|
||||
## 总结
|
||||
|
||||
✅ 撤回订单时重置处方审核状态为"待审核"
|
||||
✅ 清空审核时间、审核人、审核意见
|
||||
✅ 更新处方的 update_time
|
||||
✅ 保持订单状态为"已取消"
|
||||
✅ 清除支付单关联
|
||||
✅ 异常处理和错误提示
|
||||
|
||||
**优势**:
|
||||
- 支持撤回后重新审核
|
||||
- 状态一致性
|
||||
- 业务流程完整
|
||||
- 用户体验友好
|
||||
|
||||
**建议优化**:
|
||||
- 使用数据库事务确保一致性
|
||||
- 考虑保留审核历史记录
|
||||
- 添加操作日志
|
||||
|
||||
**下一步**:
|
||||
1. 测试撤回功能
|
||||
2. 验证处方状态变化
|
||||
3. 测试重新审核流程
|
||||
4. 考虑添加事务支持
|
||||
@@ -1,255 +0,0 @@
|
||||
# 处方管理功能测试指南
|
||||
|
||||
## 测试前准备
|
||||
|
||||
### 1. 安装数据库字段
|
||||
运行安装脚本添加新字段:
|
||||
```bash
|
||||
# Windows
|
||||
install_prescription_shared.bat
|
||||
|
||||
# Linux/Mac
|
||||
./install_prescription_shared.sh
|
||||
```
|
||||
|
||||
### 2. 确认路由配置
|
||||
确保后端路由已配置处方管理相关接口。
|
||||
|
||||
## 测试用例
|
||||
|
||||
### 测试用例1:新增处方(不共享)
|
||||
|
||||
**步骤:**
|
||||
1. 登录管理后台
|
||||
2. 进入"消费者管理" -> "处方管理"
|
||||
3. 点击"新增处方"按钮
|
||||
4. 填写表单:
|
||||
- 处方名称:感冒清热方
|
||||
- 患者姓名:张三
|
||||
- 性别:男
|
||||
- 年龄:35
|
||||
- 处方日期:选择今天
|
||||
- 临床诊断:风寒感冒
|
||||
- 点击"添加药材",添加以下药材:
|
||||
- 麻黄 9克
|
||||
- 桂枝 6克
|
||||
- 杏仁 9克
|
||||
- 甘草 3克
|
||||
- 剂数:7
|
||||
- 剂量单位:剂
|
||||
- 服用天数:7
|
||||
- 用法:水煎服,一日二次
|
||||
- 服用说明:饭前30分钟服用,温水送服;忌食辛辣、生冷食物
|
||||
- 医师姓名:李医生
|
||||
- 是否共享:不勾选
|
||||
5. 点击"确定"
|
||||
|
||||
**预期结果:**
|
||||
- 提示"添加成功"
|
||||
- 列表中显示新增的处方
|
||||
- "是否共享"列显示"仅自己可见"
|
||||
|
||||
### 测试用例2:新增处方(共享)
|
||||
|
||||
**步骤:**
|
||||
1. 点击"新增处方"
|
||||
2. 填写表单(同测试用例1)
|
||||
3. 勾选"是否共享"开关
|
||||
4. 点击"确定"
|
||||
|
||||
**预期结果:**
|
||||
- 提示"添加成功"
|
||||
- "是否共享"列显示"所有人可见"
|
||||
|
||||
### 测试用例3:查看处方
|
||||
|
||||
**步骤:**
|
||||
1. 在列表中点击某个处方的"查看"按钮
|
||||
2. 查看处方详情
|
||||
|
||||
**预期结果:**
|
||||
- 弹出详情对话框
|
||||
- 显示所有处方信息
|
||||
- 所有字段为只读状态
|
||||
- 没有"确定"按钮
|
||||
|
||||
### 测试用例4:编辑处方
|
||||
|
||||
**步骤:**
|
||||
1. 在列表中点击某个处方的"编辑"按钮
|
||||
2. 修改处方名称为"感冒清热方(加强版)"
|
||||
3. 添加一味药材:生姜 6克
|
||||
4. 点击"确定"
|
||||
|
||||
**预期结果:**
|
||||
- 提示"编辑成功"
|
||||
- 列表中显示更新后的处方名称
|
||||
- 药材数量增加1
|
||||
|
||||
### 测试用例5:删除处方
|
||||
|
||||
**步骤:**
|
||||
1. 在列表中点击某个处方的"删除"按钮
|
||||
2. 确认删除
|
||||
|
||||
**预期结果:**
|
||||
- 提示"删除成功"
|
||||
- 该处方从列表中消失
|
||||
|
||||
### 测试用例6:搜索功能
|
||||
|
||||
**步骤:**
|
||||
1. 在"处方名称"输入框输入"感冒"
|
||||
2. 点击"查询"
|
||||
|
||||
**预期结果:**
|
||||
- 只显示处方名称包含"感冒"的记录
|
||||
|
||||
**步骤:**
|
||||
1. 在"是否共享"下拉框选择"所有人可见"
|
||||
2. 点击"查询"
|
||||
|
||||
**预期结果:**
|
||||
- 只显示共享的处方
|
||||
|
||||
**步骤:**
|
||||
1. 点击"重置"按钮
|
||||
|
||||
**预期结果:**
|
||||
- 清空所有搜索条件
|
||||
- 显示所有处方
|
||||
|
||||
### 测试用例7:表单验证
|
||||
|
||||
**步骤:**
|
||||
1. 点击"新增处方"
|
||||
2. 不填写任何信息,直接点击"确定"
|
||||
|
||||
**预期结果:**
|
||||
- 显示必填项验证错误提示
|
||||
|
||||
**步骤:**
|
||||
1. 填写基本信息,但不添加药材
|
||||
2. 点击"确定"
|
||||
|
||||
**预期结果:**
|
||||
- 提示"请至少添加一味药材"
|
||||
|
||||
**步骤:**
|
||||
1. 添加药材,但不填写药材名称
|
||||
2. 点击"确定"
|
||||
|
||||
**预期结果:**
|
||||
- 提示"第X味药材名称不能为空"
|
||||
|
||||
**步骤:**
|
||||
1. 填写药材名称,但剂量为0
|
||||
2. 点击"确定"
|
||||
|
||||
**预期结果:**
|
||||
- 提示"第X味药材剂量必须大于0"
|
||||
|
||||
### 测试用例8:权限测试(多用户)
|
||||
|
||||
**步骤:**
|
||||
1. 用户A创建一个不共享的处方
|
||||
2. 用户B登录系统
|
||||
3. 查看处方列表
|
||||
|
||||
**预期结果:**
|
||||
- 用户B看不到用户A创建的不共享处方
|
||||
|
||||
**步骤:**
|
||||
1. 用户A创建一个共享的处方
|
||||
2. 用户B登录系统
|
||||
3. 查看处方列表
|
||||
|
||||
**预期结果:**
|
||||
- 用户B可以看到用户A创建的共享处方
|
||||
- 用户B可以编辑该处方
|
||||
|
||||
### 测试用例9:分页功能
|
||||
|
||||
**步骤:**
|
||||
1. 创建超过10条处方记录
|
||||
2. 查看列表底部的分页器
|
||||
|
||||
**预期结果:**
|
||||
- 显示分页器
|
||||
- 可以切换页码
|
||||
- 每页显示正确数量的记录
|
||||
|
||||
## 常见问题排查
|
||||
|
||||
### 问题1:点击"新增处方"没有反应
|
||||
**排查:**
|
||||
- 检查浏览器控制台是否有错误
|
||||
- 检查网络请求是否正常
|
||||
|
||||
### 问题2:提交表单后提示"处方名称不能为空"
|
||||
**排查:**
|
||||
- 确认数据库字段已添加
|
||||
- 检查后端验证规则
|
||||
|
||||
### 问题3:列表显示为空
|
||||
**排查:**
|
||||
- 检查API接口是否正常
|
||||
- 检查数据库表是否有数据
|
||||
- 检查权限过滤逻辑
|
||||
|
||||
### 问题4:共享功能不生效
|
||||
**排查:**
|
||||
- 确认is_shared字段已添加到数据库
|
||||
- 检查后端列表查询的权限逻辑
|
||||
- 确认用户ID获取正确
|
||||
|
||||
## 性能测试
|
||||
|
||||
### 测试场景1:大量数据加载
|
||||
1. 创建100条处方记录
|
||||
2. 测试列表加载速度
|
||||
3. 测试搜索响应速度
|
||||
|
||||
### 测试场景2:复杂处方
|
||||
1. 创建包含50味药材的处方
|
||||
2. 测试保存和加载速度
|
||||
|
||||
## 兼容性测试
|
||||
|
||||
### 浏览器兼容性
|
||||
- Chrome(推荐)
|
||||
- Firefox
|
||||
- Edge
|
||||
- Safari
|
||||
|
||||
### 分辨率测试
|
||||
- 1920x1080
|
||||
- 1366x768
|
||||
- 1280x720
|
||||
|
||||
## 测试报告模板
|
||||
|
||||
```
|
||||
测试日期:____年__月__日
|
||||
测试人员:________
|
||||
测试环境:________
|
||||
|
||||
| 测试用例 | 测试结果 | 备注 |
|
||||
|---------|---------|------|
|
||||
| 新增处方(不共享) | ☐ 通过 ☐ 失败 | |
|
||||
| 新增处方(共享) | ☐ 通过 ☐ 失败 | |
|
||||
| 查看处方 | ☐ 通过 ☐ 失败 | |
|
||||
| 编辑处方 | ☐ 通过 ☐ 失败 | |
|
||||
| 删除处方 | ☐ 通过 ☐ 失败 | |
|
||||
| 搜索功能 | ☐ 通过 ☐ 失败 | |
|
||||
| 表单验证 | ☐ 通过 ☐ 失败 | |
|
||||
| 权限测试 | ☐ 通过 ☐ 失败 | |
|
||||
| 分页功能 | ☐ 通过 ☐ 失败 | |
|
||||
|
||||
总体评价:☐ 通过 ☐ 需要修复
|
||||
|
||||
问题列表:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
```
|
||||
@@ -1,129 +0,0 @@
|
||||
# 业务订单中处方查看权限修复
|
||||
|
||||
## 问题描述
|
||||
|
||||
药师角色(角色ID=6)在查看业务订单详情时,提示"无权限查看此处方"。
|
||||
|
||||
虽然药师有业务订单管理权限(`order_edit_all_roles` 包含角色6),但在业务订单详情中需要显示关联的处方信息时,`PrescriptionLogic::canViewPrescription()` 方法会拒绝访问。
|
||||
|
||||
## 问题原因
|
||||
|
||||
`canViewPrescription` 方法的权限检查层级为:
|
||||
1. 超级管理员
|
||||
2. 处方创建人
|
||||
3. 医助
|
||||
4. 共享处方
|
||||
5. 可见角色
|
||||
6. 有开方权限的医生(`tcm.diagnosis/kaifang`)
|
||||
|
||||
药师角色通常没有 `tcm.diagnosis/kaifang` 权限,因此无法通过权限检查。
|
||||
|
||||
## 解决方案
|
||||
|
||||
在 `canViewPrescription` 方法中添加新的权限检查层级:
|
||||
|
||||
**有业务订单管理权限的角色(如药师)可以查看处方(只读)**
|
||||
|
||||
这样药师可以在业务订单中查看关联的处方信息,以便进行订单审核和管理。
|
||||
|
||||
## 代码修改
|
||||
|
||||
文件:`server/app/adminapi/logic/tcm/PrescriptionLogic.php`
|
||||
|
||||
```php
|
||||
public static function canViewPrescription($row, int $adminId, array $adminInfo): bool
|
||||
{
|
||||
// 超级管理员
|
||||
if (!empty($adminInfo['root']) && (int) $adminInfo['root'] === 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 创建人
|
||||
$creatorId = is_array($row) ? (int) ($row['creator_id'] ?? 0) : (int) $row->creator_id;
|
||||
if ($creatorId === $adminId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 医助
|
||||
$assistantId = is_array($row) ? (int) ($row['assistant_id'] ?? 0) : (int) ($row->assistant_id ?? 0);
|
||||
if ($assistantId > 0 && $assistantId === $adminId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 共享处方
|
||||
$isShared = is_array($row) ? (int) ($row['is_shared'] ?? 0) : (int) $row->is_shared;
|
||||
if ($isShared === 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 可见角色
|
||||
$vis = is_array($row) ? (string) ($row['visible_role_ids'] ?? '') : (string) ($row->visible_role_ids ?? '');
|
||||
$allowed = array_filter(array_map('intval', explode(',', $vis)));
|
||||
$myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
|
||||
if (count(array_intersect($myRoles, $allowed)) > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 有开方权限的医生可以查看所有处方(只读)
|
||||
$permissions = $adminInfo['permissions'] ?? [];
|
||||
if (in_array('tcm.diagnosis/kaifang', $permissions, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 【新增】有业务订单管理权限的角色(如药师)可以查看处方(只读)
|
||||
$orderEditAllRoles = Config::get('project.order_edit_all_roles', [0, 3]);
|
||||
$myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
|
||||
$allowedRoles = array_map('intval', is_array($orderEditAllRoles) ? $orderEditAllRoles : []);
|
||||
if (count(array_intersect($myRoles, $allowedRoles)) > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
## 权限层级(更新后)
|
||||
|
||||
1. 超级管理员(root=1)
|
||||
2. 处方创建人
|
||||
3. 医助
|
||||
4. 共享处方(is_shared=1)
|
||||
5. 可见角色(visible_role_ids)
|
||||
6. 有开方权限的医生(`tcm.diagnosis/kaifang`)
|
||||
7. **有业务订单管理权限的角色(`order_edit_all_roles`)** ← 新增
|
||||
|
||||
## 配置文件
|
||||
|
||||
文件:`server/config/project.php`
|
||||
|
||||
```php
|
||||
// 医助创建订单权限:拥有以下角色ID的用户可修改任意订单,其他用户只能修改自己创建的订单
|
||||
'order_edit_all_roles' => [0, 3, 6],
|
||||
```
|
||||
|
||||
- 角色 0:超级管理员
|
||||
- 角色 3:管理员
|
||||
- 角色 6:药师
|
||||
|
||||
## 使用场景
|
||||
|
||||
药师角色现在可以:
|
||||
1. 查看业务订单列表
|
||||
2. 打开业务订单详情
|
||||
3. 查看订单中关联的处方信息(只读)
|
||||
4. 进行处方审核
|
||||
5. 进行支付单审核
|
||||
6. 编辑业务订单信息
|
||||
|
||||
## 测试验证
|
||||
|
||||
1. 使用药师账号登录
|
||||
2. 进入"消费者处方订单"列表
|
||||
3. 点击任意订单的"详情"按钮
|
||||
4. 确认可以正常查看处方信息,不再提示"无权限查看此处方"
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 此权限为只读权限,药师不能编辑处方内容
|
||||
2. 药师只能通过业务订单查看处方,不能直接在处方列表中查看其他医生的处方
|
||||
3. 修改代码后需要清除缓存:`php think clear`
|
||||
@@ -1,83 +0,0 @@
|
||||
# 本地录制修复 - 快速参考
|
||||
|
||||
## 🔍 问题
|
||||
本地录制总是失败,blob 为空
|
||||
|
||||
## 🎯 根本原因
|
||||
录制启动太晚(1200ms),在对方挂断后才启动,此时 SDK 已销毁视频轨道
|
||||
|
||||
## ✅ 解决方案
|
||||
|
||||
### 1. 缩短启动延迟
|
||||
```
|
||||
1200ms → 500ms
|
||||
```
|
||||
|
||||
### 2. 快速重试
|
||||
```
|
||||
300ms 间隔,最多 10 次
|
||||
```
|
||||
|
||||
### 3. 立即停止录制
|
||||
```
|
||||
监听 USER_LEAVE 事件,立即调用 beginStopNow()
|
||||
```
|
||||
|
||||
### 4. 缩短停止延迟
|
||||
```
|
||||
300ms → 100ms
|
||||
```
|
||||
|
||||
## 📊 测试检查清单
|
||||
|
||||
### 启动阶段(接通后 1 秒内)
|
||||
- [ ] 看到 "尝试启动本地录制"
|
||||
- [ ] 看到 "视频元素已就绪"
|
||||
- [ ] 看到 "✅ 本地录制已成功启动"
|
||||
|
||||
### 录制阶段(每 250ms)
|
||||
- [ ] 看到 "收到数据块,大小: XXX"
|
||||
- [ ] 数据块数持续增加
|
||||
|
||||
### 停止阶段(挂断时)
|
||||
- [ ] 看到 "检测到通话结束事件"
|
||||
- [ ] 看到 "已收集数据块: X"(X > 0)
|
||||
- [ ] 看到 "生成 Blob: XXX bytes"(不是 null)
|
||||
- [ ] 看到 "本地录制已上传并关联到通话记录"
|
||||
|
||||
## 🚨 常见问题
|
||||
|
||||
### 问题:数据块数为 0
|
||||
**原因**:录制启动太晚或视频流无效
|
||||
**检查**:
|
||||
```javascript
|
||||
// 控制台执行
|
||||
document.querySelectorAll('video').forEach((v, i) => {
|
||||
console.log(`Video ${i}:`, v.readyState, v.srcObject)
|
||||
})
|
||||
```
|
||||
|
||||
### 问题:Blob 为 null
|
||||
**原因**:通话时间太短(< 2 秒)
|
||||
**解决**:保持通话至少 5 秒
|
||||
|
||||
### 问题:录制启动失败
|
||||
**原因**:找不到视频元素
|
||||
**检查**:是否看到 "未找到视频元素" 日志
|
||||
|
||||
## 📝 修改的文件
|
||||
1. `admin/src/components/chat-dialog/index.vue`
|
||||
2. `admin/src/utils/call-local-recorder.ts`
|
||||
|
||||
## 🔗 详细文档
|
||||
- `LOCAL_RECORDING_DEBUG_GUIDE.md` - 完整调试指南
|
||||
- `RECORDING_FIX_SUMMARY.md` - 修复总结
|
||||
- `admin/test-local-recording.html` - 测试工具
|
||||
|
||||
## 💡 测试建议
|
||||
1. 清除浏览器缓存
|
||||
2. 打开控制台(F12)
|
||||
3. 发起视频通话
|
||||
4. 保持通话 5 秒以上
|
||||
5. 正常挂断
|
||||
6. 检查日志和录制结果
|
||||
@@ -1,80 +0,0 @@
|
||||
# 首次登录修改密码 - 快速开始
|
||||
|
||||
## 一分钟快速安装
|
||||
|
||||
### Windows 用户
|
||||
|
||||
1. 双击运行 `install_first_login_password.bat`
|
||||
2. 按提示输入数据库信息
|
||||
3. 完成!
|
||||
|
||||
### Linux/Mac 用户
|
||||
|
||||
```bash
|
||||
chmod +x install_first_login_password.sh
|
||||
./install_first_login_password.sh
|
||||
```
|
||||
|
||||
## 快速测试
|
||||
|
||||
### 1. 设置测试账号
|
||||
|
||||
```sql
|
||||
-- 设置管理员 ID 为 1 的账号需要修改密码
|
||||
UPDATE la_admin SET is_paw = 0 WHERE id = 1;
|
||||
```
|
||||
|
||||
### 2. 登录测试
|
||||
|
||||
1. 使用该账号登录系统
|
||||
2. 登录后会自动跳转到修改密码页面
|
||||
3. 输入新密码(至少 6 位)
|
||||
4. 点击"确认修改"
|
||||
5. 系统提示"密码修改成功,请重新登录"
|
||||
6. 使用新密码重新登录
|
||||
|
||||
### 3. 验证成功
|
||||
|
||||
```sql
|
||||
-- 查看 is_paw 字段,应该已经变成 1
|
||||
SELECT id, account, is_paw FROM la_admin WHERE id = 1;
|
||||
```
|
||||
|
||||
## 常用操作
|
||||
|
||||
### 强制单个管理员修改密码
|
||||
|
||||
```sql
|
||||
UPDATE la_admin SET is_paw = 0 WHERE id = <管理员ID>;
|
||||
```
|
||||
|
||||
### 强制所有管理员修改密码
|
||||
|
||||
```sql
|
||||
UPDATE la_admin SET is_paw = 0 WHERE delete_time = 0;
|
||||
```
|
||||
|
||||
### 恢复正常状态
|
||||
|
||||
```sql
|
||||
UPDATE la_admin SET is_paw = 1 WHERE id = <管理员ID>;
|
||||
```
|
||||
|
||||
## 功能说明
|
||||
|
||||
- `is_paw = 0`:下次登录时强制修改密码
|
||||
- `is_paw = 1`:正常状态,无需修改密码
|
||||
|
||||
## 文件说明
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `add_is_paw_field.sql` | 数据库字段添加 SQL |
|
||||
| `install_first_login_password.sh` | Linux/Mac 安装脚本 |
|
||||
| `install_first_login_password.bat` | Windows 安装脚本 |
|
||||
| `test_first_login.sql` | 测试用 SQL 语句 |
|
||||
| `FIRST_LOGIN_PASSWORD_CHANGE.md` | 完整功能文档 |
|
||||
|
||||
## 需要帮助?
|
||||
|
||||
查看完整文档:`FIRST_LOGIN_PASSWORD_CHANGE.md`
|
||||
@@ -1,135 +0,0 @@
|
||||
# 处方库功能 - 快速开始指南
|
||||
|
||||
## 🚀 5分钟快速上手
|
||||
|
||||
### 第一步:安装数据库表
|
||||
|
||||
#### Windows 用户
|
||||
双击运行 `install_prescription_library.bat`
|
||||
|
||||
#### Linux/Mac 用户
|
||||
```bash
|
||||
chmod +x install_prescription_library.sh
|
||||
./install_prescription_library.sh
|
||||
```
|
||||
|
||||
#### 手动安装
|
||||
使用数据库工具执行:`server/database/migrations/create_prescription_library.sql`
|
||||
|
||||
### 第二步:访问功能
|
||||
|
||||
1. 登录后台管理系统
|
||||
2. 在菜单中找到"处方库"功能
|
||||
3. 开始使用!
|
||||
|
||||
## 📝 快速创建第一个处方
|
||||
|
||||
### 示例:创建"六味地黄丸"处方
|
||||
|
||||
1. 点击"新增处方"按钮
|
||||
|
||||
2. 填写处方名称:
|
||||
```
|
||||
六味地黄丸
|
||||
```
|
||||
|
||||
3. 添加药材(点击6次"添加药材"按钮):
|
||||
```
|
||||
熟地黄 24克
|
||||
山茱萸 12克
|
||||
山药 12克
|
||||
泽泻 9克
|
||||
牡丹皮 9克
|
||||
茯苓 9克
|
||||
```
|
||||
|
||||
4. 选择是否公开:
|
||||
- 仅自己可见:只有你能看到
|
||||
- 所有人可见:所有医生都能看到
|
||||
|
||||
5. 点击"确定"保存
|
||||
|
||||
✅ 完成!你的第一个处方已创建成功!
|
||||
|
||||
## 🎯 常用操作
|
||||
|
||||
### 查看处方
|
||||
- 在列表中点击"查看"按钮
|
||||
- 以只读模式查看处方详情
|
||||
|
||||
### 编辑处方
|
||||
- 在列表中点击"编辑"按钮
|
||||
- 修改处方信息后保存
|
||||
|
||||
### 删除处方
|
||||
- 在列表中点击"删除"按钮
|
||||
- 确认删除(只能删除自己创建的处方)
|
||||
|
||||
### 搜索处方
|
||||
- 在搜索框输入处方名称
|
||||
- 选择是否公开筛选条件
|
||||
- 点击"查询"按钮
|
||||
|
||||
## 💡 使用技巧
|
||||
|
||||
### 技巧1:快速复制处方
|
||||
1. 查看已有处方
|
||||
2. 点击"编辑"
|
||||
3. 修改处方名称
|
||||
4. 保存为新处方
|
||||
|
||||
### 技巧2:分享经典处方
|
||||
创建处方时,将"是否公开"设置为"所有人可见",其他医生就能看到并参考。
|
||||
|
||||
### 技巧3:管理个人处方库
|
||||
使用"是否公开"筛选,快速查看自己的私有处方。
|
||||
|
||||
## 📊 测试数据
|
||||
|
||||
想快速测试功能?执行以下SQL插入测试数据:
|
||||
|
||||
```sql
|
||||
-- 插入3个经典处方
|
||||
INSERT INTO `zyt_prescription_library`
|
||||
(`prescription_name`, `herbs`, `is_public`, `creator_id`, `creator_name`, `create_time`, `update_time`)
|
||||
VALUES
|
||||
('六味地黄丸', '[{"name":"熟地黄","dosage":24},{"name":"山茱萸","dosage":12},{"name":"山药","dosage":12},{"name":"泽泻","dosage":9},{"name":"牡丹皮","dosage":9},{"name":"茯苓","dosage":9}]', 1, 1, '测试医生', UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
|
||||
('补中益气汤', '[{"name":"黄芪","dosage":15},{"name":"党参","dosage":10},{"name":"白术","dosage":10},{"name":"当归","dosage":10},{"name":"陈皮","dosage":6},{"name":"升麻","dosage":6},{"name":"柴胡","dosage":6},{"name":"甘草","dosage":5}]', 1, 1, '测试医生', UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
|
||||
('逍遥散', '[{"name":"柴胡","dosage":10},{"name":"当归","dosage":10},{"name":"白芍","dosage":10},{"name":"白术","dosage":10},{"name":"茯苓","dosage":10},{"name":"薄荷","dosage":6},{"name":"生姜","dosage":6},{"name":"甘草","dosage":5}]', 0, 1, '测试医生', UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
|
||||
```
|
||||
|
||||
## ❓ 常见问题
|
||||
|
||||
### Q1: 安装后看不到菜单?
|
||||
A: 需要在系统菜单管理中添加处方库菜单项,或联系管理员配置权限。
|
||||
|
||||
### Q2: 无法删除处方?
|
||||
A: 只有创建者才能删除处方。如果是其他人创建的,即使是公开的也不能删除。
|
||||
|
||||
### Q3: 其他人看不到我的处方?
|
||||
A: 检查"是否公开"设置,确保设置为"所有人可见"。
|
||||
|
||||
### Q4: 药材剂量可以是小数吗?
|
||||
A: 可以,支持精确到0.1克,如:3.5克。
|
||||
|
||||
### Q5: 可以导入Excel处方吗?
|
||||
A: 当前版本暂不支持,需要手动录入。这是未来的扩展方向。
|
||||
|
||||
## 📚 更多文档
|
||||
|
||||
- **完整说明:** PRESCRIPTION_LIBRARY_README.md
|
||||
- **功能演示:** PRESCRIPTION_LIBRARY_DEMO.md
|
||||
- **开发总结:** PRESCRIPTION_LIBRARY_SUMMARY.md
|
||||
- **测试SQL:** TEST_PRESCRIPTION_LIBRARY.sql
|
||||
|
||||
## 🆘 需要帮助?
|
||||
|
||||
如遇到问题,请:
|
||||
1. 查看完整文档
|
||||
2. 检查数据库连接
|
||||
3. 查看浏览器控制台错误
|
||||
4. 联系技术支持
|
||||
|
||||
---
|
||||
|
||||
**祝使用愉快!** 🎉
|
||||
@@ -1,164 +0,0 @@
|
||||
# 企业微信客户同步问题修复指南
|
||||
|
||||
## 问题描述
|
||||
|
||||
同步企业微信客户时,能成功获取企业成员列表(207人),但无法获取任何客户数据,所有API调用都返回错误码 `48002: "api forbidden"`。
|
||||
|
||||
## 根本原因
|
||||
|
||||
企业微信应用未开启"客户联系"API权限。错误码 `48002` 表示API接口被禁用或未授权。
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 方案一:开启API权限(推荐)
|
||||
|
||||
1. 登录企业微信管理后台
|
||||
- 访问:https://work.weixin.qq.com/
|
||||
- 使用管理员账号登录
|
||||
|
||||
2. 进入应用管理
|
||||
- 点击"应用管理"
|
||||
- 找到您正在使用的应用(使用 `external_pay_secret` 的应用)
|
||||
|
||||
3. 开启客户联系权限
|
||||
- 在应用详情页面,找到"客户联系"权限设置
|
||||
- 开启以下权限:
|
||||
* ✓ 客户联系 - 获取客户列表
|
||||
* ✓ 客户联系 - 获取客户详情
|
||||
* ✓ 客户联系 - 获取客户数据统计(可选)
|
||||
|
||||
4. 配置可信IP
|
||||
- 在应用设置中找到"企业可信IP"
|
||||
- 添加您的服务器IP地址
|
||||
- 保存配置
|
||||
|
||||
5. 等待生效
|
||||
- 权限修改后可能需要几分钟生效
|
||||
- 建议等待5-10分钟后再测试
|
||||
|
||||
### 方案二:升级企业微信版本
|
||||
|
||||
如果您的企业微信版本不支持客户联系API:
|
||||
|
||||
1. 检查当前版本是否支持"客户联系"功能
|
||||
2. 如需要,升级到支持该功能的版本
|
||||
3. 部分功能可能需要付费版本
|
||||
|
||||
### 方案三:使用替代方案
|
||||
|
||||
如果无法开启API权限:
|
||||
|
||||
1. 使用企业微信后台手动导出客户数据
|
||||
2. 通过CSV导入到系统
|
||||
3. 或使用企业微信的webhook回调功能(需要配置)
|
||||
|
||||
## 诊断工具
|
||||
|
||||
### 1. 运行权限检查命令
|
||||
|
||||
```bash
|
||||
php think qywx:check-permissions
|
||||
```
|
||||
|
||||
这个命令会:
|
||||
- 检查应用配置是否正确
|
||||
- 测试获取部门成员权限
|
||||
- 测试获取客户列表权限
|
||||
- 显示详细的错误信息和解决建议
|
||||
|
||||
### 2. 查看日志
|
||||
|
||||
日志文件位置:`runtime/log/`
|
||||
|
||||
关键日志信息:
|
||||
```
|
||||
企业微信-获取成员客户列表响应 userId=XXX: {"errcode":48002,"errmsg":"api forbidden"...}
|
||||
```
|
||||
|
||||
如果看到 `errcode: 48002`,说明是权限问题。
|
||||
|
||||
## 代码改进
|
||||
|
||||
已对代码进行以下改进:
|
||||
|
||||
1. **更好的错误处理**
|
||||
- `getExternalContactList()` 方法现在会检测 `48002` 错误
|
||||
- 权限错误时返回 `false` 而不是空数组
|
||||
- 同步逻辑会捕获权限错误并抛出明确的异常
|
||||
|
||||
2. **新增诊断方法**
|
||||
- `WechatWorkService::checkAppPermissions()` - 检查应用权限
|
||||
- `QywxCheckPermissions` 命令 - 完整的权限诊断工具
|
||||
|
||||
3. **改进的日志记录**
|
||||
- 权限错误会记录更详细的信息
|
||||
- 包含解决方案提示
|
||||
|
||||
## 测试步骤
|
||||
|
||||
1. 确认已开启API权限
|
||||
2. 运行诊断命令:
|
||||
```bash
|
||||
php think qywx:check-permissions
|
||||
```
|
||||
|
||||
3. 如果诊断通过,运行同步:
|
||||
```bash
|
||||
php think qywx:sync-customer
|
||||
```
|
||||
|
||||
4. 检查同步结果:
|
||||
- 查看日志文件
|
||||
- 检查数据库表 `la_qywx_external_contact`
|
||||
- 在管理后台查看客户列表
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 权限已开启,但仍然报错48002
|
||||
A:
|
||||
- 等待5-10分钟让权限生效
|
||||
- 清除企业微信access_token缓存:删除缓存键 `qywx_access_token`
|
||||
- 检查服务器IP是否在可信IP列表中
|
||||
|
||||
### Q2: 找不到"客户联系"权限选项
|
||||
A:
|
||||
- 您的企业微信版本可能不支持此功能
|
||||
- 需要升级到企业版或专业版
|
||||
- 联系企业微信客服确认
|
||||
|
||||
### Q3: 部分成员能获取客户,部分不能
|
||||
A:
|
||||
- 检查成员是否有"客户联系"权限
|
||||
- 在企业微信后台为成员分配权限
|
||||
- 确认成员确实有添加客户
|
||||
|
||||
### Q4: 同步速度很慢
|
||||
A:
|
||||
- 这是正常的,因为需要逐个成员获取客户列表
|
||||
- 207个成员 × 每个成员的客户数 = 大量API调用
|
||||
- 建议设置定时任务,每小时或每天同步一次
|
||||
|
||||
## 相关文件
|
||||
|
||||
- `server/app/adminapi/logic/qywx/CustomerLogic.php` - 同步逻辑
|
||||
- `server/app/common/service/wechat/WechatWorkService.php` - API服务
|
||||
- `server/app/command/QywxSyncCustomer.php` - 同步命令
|
||||
- `server/app/command/QywxCheckPermissions.php` - 诊断命令(新增)
|
||||
- `server/config/project.php` - 配置文件
|
||||
|
||||
## 联系支持
|
||||
|
||||
如果按照以上步骤仍无法解决:
|
||||
|
||||
1. 收集以下信息:
|
||||
- 诊断命令的完整输出
|
||||
- 最近的日志文件
|
||||
- 企业微信应用的权限截图
|
||||
|
||||
2. 联系企业微信技术支持
|
||||
- 官方文档:https://developer.work.weixin.qq.com/
|
||||
- 开发者社区:https://developers.weixin.qq.com/community/business
|
||||
|
||||
---
|
||||
|
||||
最后更新:2024-04-11
|
||||
@@ -1,246 +0,0 @@
|
||||
# 企业微信客户同步功能说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
自动从企业微信同步外部联系人(客户)信息,包括客户基本信息、跟进人等,用于患者信息匹配和自动关联。
|
||||
|
||||
同步流程:
|
||||
1. 获取企业成员列表(从指定部门开始,递归获取所有子部门成员)
|
||||
2. 遍历每个成员,获取其客户列表
|
||||
3. 获取客户详情并保存到数据库
|
||||
4. 自动去重,避免重复处理同一客户
|
||||
|
||||
## 功能特性
|
||||
|
||||
1. **手动同步**:点击"立即同步"按钮手动触发同步
|
||||
2. **自动同步**:配置定时自动同步,支持多种时间间隔
|
||||
3. **客户管理**:查看客户列表、搜索、查看详情
|
||||
4. **统计信息**:显示总客户数、今日新增、最后同步时间等
|
||||
|
||||
## 安装步骤
|
||||
|
||||
### 1. 数据库迁移
|
||||
|
||||
执行SQL文件创建数据表:
|
||||
|
||||
```bash
|
||||
mysql -u用户名 -p密码 数据库名 < server/database/migrations/create_qywx_external_contact.sql
|
||||
```
|
||||
|
||||
### 2. 配置企业微信
|
||||
|
||||
在 `server/.env` 文件中配置企业微信信息:
|
||||
|
||||
```env
|
||||
# 企业微信配置
|
||||
WECHAT_WORK_CORP_ID=你的企业ID
|
||||
WECHAT_WORK_EXTERNAL_PAY_SECRET=你的应用Secret
|
||||
```
|
||||
|
||||
在 `server/config/project.php` 文件中配置同步部门(可选,默认为1即根部门):
|
||||
|
||||
```php
|
||||
// 企业微信客户同步配置
|
||||
// 从哪个部门开始同步(1=根部门,会递归获取所有子部门成员)
|
||||
'qywx_sync_department_id' => 1,
|
||||
```
|
||||
|
||||
获取方式:
|
||||
- 企业ID:企业微信管理后台 → 我的企业 → 企业信息 → 企业ID
|
||||
- Secret:企业微信管理后台 → 应用管理 → 选择应用 → Secret
|
||||
- 部门ID:企业微信管理后台 → 通讯录 → 选择部门 → 查看部门ID(默认根部门ID为1)
|
||||
|
||||
### 3. 配置权限
|
||||
|
||||
需要在企业微信应用中开启以下权限:
|
||||
- 通讯录管理 → 成员信息读取(用于获取企业成员列表)
|
||||
- 客户联系 → 客户基础信息
|
||||
- 客户联系 → 客户标签
|
||||
- 客户联系 → 客户联系人
|
||||
|
||||
注意:Secret必须是对应应用的Secret,且该应用需要有上述权限。
|
||||
|
||||
### 4. 配置IP白名单(重要!)
|
||||
|
||||
企业微信API有IP白名单限制,必须将服务器IP添加到白名单:
|
||||
|
||||
1. 获取服务器公网IP:`curl ifconfig.me`
|
||||
2. 登录企业微信管理后台 → 应用管理 → 选择应用
|
||||
3. 找到"企业可信IP"设置,添加服务器IP
|
||||
4. 保存并等待几分钟生效
|
||||
|
||||
详细说明请查看:[QYWX_IP_WHITELIST_GUIDE.md](./QYWX_IP_WHITELIST_GUIDE.md)
|
||||
|
||||
### 5. 配置定时任务(可选)
|
||||
|
||||
如果需要自动同步,配置Linux crontab:
|
||||
|
||||
```bash
|
||||
# 编辑crontab
|
||||
crontab -e
|
||||
|
||||
# 添加以下行(每小时执行一次)
|
||||
0 * * * * cd /path/to/your/project/server && php think qywx:sync-customer >> /dev/null 2>&1
|
||||
```
|
||||
|
||||
或者手动强制同步(忽略自动同步设置):
|
||||
```bash
|
||||
php think qywx:sync-customer --force
|
||||
```
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 前端页面
|
||||
|
||||
访问路径:`/fans/qywx`
|
||||
|
||||
功能:
|
||||
- 查看客户列表
|
||||
- 搜索客户(按名称、跟进人)
|
||||
- 查看客户详情
|
||||
- 手动同步
|
||||
- 配置自动同步
|
||||
|
||||
### 同步设置
|
||||
|
||||
1. 点击"同步设置"按钮
|
||||
2. 开启"自动同步"开关
|
||||
3. 选择同步间隔(1小时、2小时、4小时、6小时、12小时、24小时)
|
||||
4. 点击"保存"
|
||||
|
||||
### API接口
|
||||
|
||||
#### 1. 获取客户列表
|
||||
```
|
||||
GET /qywx.customer/lists
|
||||
参数:
|
||||
- name: 客户名称(可选)
|
||||
- follow_user: 跟进人(可选)
|
||||
- page: 页码
|
||||
- limit: 每页数量
|
||||
```
|
||||
|
||||
#### 2. 同步客户
|
||||
```
|
||||
POST /qywx.customer/sync
|
||||
返回:
|
||||
- sync_count: 同步数量
|
||||
- new_count: 新增数量
|
||||
- update_count: 更新数量
|
||||
```
|
||||
|
||||
#### 3. 获取统计信息
|
||||
```
|
||||
GET /qywx.customer/stats
|
||||
返回:
|
||||
- total: 总客户数
|
||||
- today: 今日新增
|
||||
- lastSync: 最后同步时间
|
||||
- syncStatus: 同步状态
|
||||
```
|
||||
|
||||
#### 4. 获取同步设置
|
||||
```
|
||||
GET /qywx.customer/getSyncSettings
|
||||
返回:
|
||||
- auto_sync: 是否自动同步
|
||||
- interval: 同步间隔(秒)
|
||||
- last_sync_time: 最后同步时间
|
||||
```
|
||||
|
||||
#### 5. 保存同步设置
|
||||
```
|
||||
POST /qywx.customer/saveSyncSettings
|
||||
参数:
|
||||
- auto_sync: 是否自动同步(boolean)
|
||||
- interval: 同步间隔(秒,3600-86400)
|
||||
```
|
||||
|
||||
## 数据表结构
|
||||
|
||||
### zyt_qywx_external_contact(外部联系人表)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | int | 主键ID |
|
||||
| external_userid | varchar(64) | 外部联系人ID(唯一) |
|
||||
| name | varchar(64) | 客户名称 |
|
||||
| avatar | varchar(255) | 头像URL |
|
||||
| type | tinyint | 类型:1=微信用户,2=企业微信用户 |
|
||||
| gender | tinyint | 性别:0=未知,1=男,2=女 |
|
||||
| unionid | varchar(64) | 微信unionid |
|
||||
| position | varchar(64) | 职位 |
|
||||
| corp_name | varchar(128) | 企业名称 |
|
||||
| corp_full_name | varchar(255) | 企业全称 |
|
||||
| external_profile | text | 扩展信息JSON |
|
||||
| follow_users | text | 跟进人列表JSON |
|
||||
| create_time | int | 添加时间 |
|
||||
| update_time | int | 更新时间 |
|
||||
| delete_time | int | 删除时间 |
|
||||
|
||||
### zyt_qywx_sync_settings(同步设置表)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | int | 主键ID |
|
||||
| setting_key | varchar(64) | 设置键(唯一) |
|
||||
| setting_value | text | 设置值JSON |
|
||||
| create_time | int | 创建时间 |
|
||||
| update_time | int | 更新时间 |
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **API调用限制**:企业微信API有调用频率限制,建议同步间隔不要太短
|
||||
2. **Secret安全**:请妥善保管企业微信Secret,不要泄露
|
||||
3. **权限配置**:确保应用有足够的权限访问客户信息
|
||||
4. **错误处理**:同步失败会记录日志,可在系统日志中查看详细错误信息
|
||||
5. **数据更新**:同步会更新已存在的客户信息,不会删除已有数据
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 1. 错误码 60020 - IP白名单限制
|
||||
|
||||
错误信息:`not allow to access from your ip`
|
||||
|
||||
解决方法:
|
||||
- 将服务器IP添加到企业微信应用的"企业可信IP"白名单
|
||||
- 详细说明:[QYWX_IP_WHITELIST_GUIDE.md](./QYWX_IP_WHITELIST_GUIDE.md)
|
||||
|
||||
### 2. 同步失败
|
||||
|
||||
检查:
|
||||
- 企业微信配置是否正确(corp_id、secret)
|
||||
- 应用权限是否开启
|
||||
- IP是否在白名单中(最常见问题)
|
||||
- 网络是否正常
|
||||
- 查看系统日志:`server/runtime/log/`
|
||||
|
||||
### 3. 获取不到客户
|
||||
|
||||
检查:
|
||||
- 应用是否有客户联系权限和通讯录读取权限
|
||||
- 是否有外部联系人
|
||||
- Secret是否正确(需要使用对应应用的Secret)
|
||||
- 企业成员是否有添加客户
|
||||
- 部门ID配置是否正确(默认1为根部门)
|
||||
|
||||
### 4. missing field `userid` 错误
|
||||
|
||||
这个错误已修复。新版本会先获取企业成员列表,然后遍历每个成员获取其客户。如果仍然出现此错误,请检查:
|
||||
- 应用是否有"通讯录管理-成员信息读取"权限
|
||||
- 配置的Secret是否正确
|
||||
|
||||
### 5. 定时任务不执行
|
||||
|
||||
检查:
|
||||
- crontab是否配置正确
|
||||
- PHP路径是否正确
|
||||
- 项目路径是否正确
|
||||
- 是否开启了自动同步
|
||||
|
||||
## 参考文档
|
||||
|
||||
- [企业微信API文档](https://developer.work.weixin.qq.com/document/)
|
||||
- [获取部门成员](https://developer.work.weixin.qq.com/document/path/90200)
|
||||
- [获取成员客户列表](https://developer.work.weixin.qq.com/document/path/92113)
|
||||
- [获取客户详情](https://developer.work.weixin.qq.com/document/path/92114)
|
||||
@@ -1,88 +0,0 @@
|
||||
# 企业微信IP白名单配置指南
|
||||
|
||||
## 问题现象
|
||||
|
||||
同步企业微信客户时报错:
|
||||
```
|
||||
errcode: 60020
|
||||
errmsg: not allow to access from your ip
|
||||
```
|
||||
|
||||
## 问题原因
|
||||
|
||||
企业微信API有IP白名单限制,只有在白名单中的IP才能调用API。
|
||||
|
||||
## 解决方法
|
||||
|
||||
### 1. 获取服务器IP地址
|
||||
|
||||
在服务器上运行以下命令获取公网IP:
|
||||
|
||||
```bash
|
||||
curl ifconfig.me
|
||||
```
|
||||
|
||||
或者查看日志中的IP地址,例如:
|
||||
```
|
||||
from ip: 8.219.70.152
|
||||
```
|
||||
|
||||
### 2. 配置企业微信IP白名单
|
||||
|
||||
1. 登录企业微信管理后台:https://work.weixin.qq.com/
|
||||
2. 进入"应用管理"
|
||||
3. 选择你配置的应用(使用该应用的Secret)
|
||||
4. 找到"企业可信IP"设置
|
||||
5. 点击"配置"或"修改"
|
||||
6. 添加服务器的公网IP地址
|
||||
7. 保存配置
|
||||
|
||||
### 3. 验证配置
|
||||
|
||||
配置完成后,等待几分钟让配置生效,然后重新运行同步命令:
|
||||
|
||||
```bash
|
||||
php think qywx:sync-customer --force
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **IP地址格式**:
|
||||
- 单个IP:`8.219.70.152`
|
||||
- IP段:`8.219.70.0/24`
|
||||
|
||||
2. **多个IP**:如果有多个服务器,需要添加所有服务器的IP
|
||||
|
||||
3. **动态IP**:如果服务器IP会变化,建议:
|
||||
- 使用固定IP的服务器
|
||||
- 或配置IP段
|
||||
- 或使用代理服务器
|
||||
|
||||
4. **测试环境**:开发环境和生产环境可能有不同的IP,都需要添加
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 添加IP后还是报错?
|
||||
A: 等待5-10分钟让配置生效,清除缓存后重试:
|
||||
```bash
|
||||
php think clear
|
||||
php think qywx:sync-customer --force
|
||||
```
|
||||
|
||||
### Q: 不知道服务器IP?
|
||||
A: 在服务器上运行:
|
||||
```bash
|
||||
curl ifconfig.me
|
||||
# 或
|
||||
curl ip.sb
|
||||
# 或
|
||||
curl ipinfo.io/ip
|
||||
```
|
||||
|
||||
### Q: 本地开发如何测试?
|
||||
A: 将本地公网IP也添加到白名单,或使用内网穿透工具(如ngrok)
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [企业微信API - IP白名单说明](https://developer.work.weixin.qq.com/document/path/90238)
|
||||
- [企业微信错误码查询](https://open.work.weixin.qq.com/devtool/query)
|
||||
@@ -1,108 +0,0 @@
|
||||
# 企业微信客户同步功能修复
|
||||
|
||||
## 问题描述
|
||||
|
||||
调用企业微信API获取客户列表时报错:
|
||||
```
|
||||
missing field `userid`. invalid Request Parameter
|
||||
```
|
||||
|
||||
## 问题原因
|
||||
|
||||
根据企业微信API文档,获取客户列表接口 `/cgi-bin/externalcontact/list` 需要传入 `userid` 参数(企业成员ID)。之前的实现直接调用该接口,没有传入必需的 `userid` 参数。
|
||||
|
||||
## 解决方案
|
||||
|
||||
修改同步逻辑,采用正确的流程:
|
||||
|
||||
1. 先调用 `/cgi-bin/user/list` 获取企业成员列表
|
||||
2. 遍历每个成员,调用 `/cgi-bin/externalcontact/list?userid=xxx` 获取该成员的客户列表
|
||||
3. 对每个客户调用 `/cgi-bin/externalcontact/get` 获取详情
|
||||
4. 保存到数据库,自动去重
|
||||
|
||||
## 修改文件
|
||||
|
||||
### 1. server/app/common/service/wechat/WechatWorkService.php
|
||||
|
||||
新增方法:
|
||||
- `getDepartmentUserList()` - 获取部门成员列表
|
||||
|
||||
修改方法:
|
||||
- `getExternalContactList($userId)` - 添加 `$userId` 参数,获取指定成员的客户列表
|
||||
|
||||
### 2. server/app/adminapi/logic/qywx/CustomerLogic.php
|
||||
|
||||
修改 `syncCustomers()` 方法:
|
||||
- 先获取企业成员列表
|
||||
- 遍历成员获取其客户
|
||||
- 使用 `$processedCustomers` 数组避免重复处理同一客户
|
||||
|
||||
### 3. server/config/project.php
|
||||
|
||||
新增配置项:
|
||||
```php
|
||||
// 企业微信客户同步配置
|
||||
// 从哪个部门开始同步(1=根部门,会递归获取所有子部门成员)
|
||||
'qywx_sync_department_id' => 1,
|
||||
```
|
||||
|
||||
### 4. QYWX_CUSTOMER_SYNC_GUIDE.md
|
||||
|
||||
更新文档:
|
||||
- 添加同步流程说明
|
||||
- 添加部门ID配置说明
|
||||
- 添加通讯录权限要求
|
||||
- 添加 `missing field userid` 错误排查说明
|
||||
|
||||
## 权限要求
|
||||
|
||||
企业微信应用需要开启以下权限:
|
||||
- 通讯录管理 → 成员信息读取(新增)
|
||||
- 客户联系 → 客户基础信息
|
||||
- 客户联系 → 客户标签
|
||||
- 客户联系 → 客户联系人
|
||||
|
||||
## 配置说明
|
||||
|
||||
### 必需配置(server/.env)
|
||||
```env
|
||||
WECHAT_WORK_CORP_ID=你的企业ID
|
||||
WECHAT_WORK_EXTERNAL_PAY_SECRET=你的应用Secret
|
||||
```
|
||||
|
||||
### 可选配置(server/config/project.php)
|
||||
```php
|
||||
'qywx_sync_department_id' => 1, // 默认1(根部门),会递归获取所有子部门成员
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
1. 确保配置正确
|
||||
2. 清除缓存:`php think clear`
|
||||
3. 在前端页面点击"立即同步"按钮
|
||||
4. 或运行命令:`php think qywx:sync-customer`
|
||||
|
||||
## 测试验证
|
||||
|
||||
修复后,同步功能应该能够:
|
||||
- 成功获取企业成员列表
|
||||
- 遍历每个成员获取其客户
|
||||
- 正确保存客户信息到数据库
|
||||
- 显示同步统计(总数、新增、更新)
|
||||
|
||||
注意:首次使用需要配置IP白名单,否则会报错 60020。
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 错误码 60020 - IP白名单限制
|
||||
|
||||
如果遇到 `not allow to access from your ip` 错误,需要:
|
||||
1. 获取服务器公网IP:`curl ifconfig.me`
|
||||
2. 在企业微信管理后台添加IP到白名单
|
||||
3. 详细说明:[QYWX_IP_WHITELIST_GUIDE.md](./QYWX_IP_WHITELIST_GUIDE.md)
|
||||
|
||||
## 参考文档
|
||||
|
||||
- [获取部门成员](https://developer.work.weixin.qq.com/document/path/90200)
|
||||
- [获取成员客户列表](https://developer.work.weixin.qq.com/document/path/92113)
|
||||
- [获取客户详情](https://developer.work.weixin.qq.com/document/path/92114)
|
||||
@@ -1,377 +0,0 @@
|
||||
# 处方库功能 - 文档索引
|
||||
|
||||
## 📚 欢迎使用处方库管理系统
|
||||
|
||||
这是一个独立的中药处方模板管理系统,用于创建、管理和分享常用处方配方。
|
||||
|
||||
**版本:** v1.0.0
|
||||
**状态:** ✅ 已完成并测试通过
|
||||
**日期:** 2026-03-22
|
||||
|
||||
---
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 第一次使用?从这里开始!
|
||||
|
||||
1. **[快速开始指南](QUICK_START_PRESCRIPTION_LIBRARY.md)** ⭐ 推荐
|
||||
- 5分钟快速上手
|
||||
- 创建第一个处方
|
||||
- 常用操作说明
|
||||
|
||||
2. **[安装检查清单](INSTALLATION_CHECKLIST.md)**
|
||||
- 详细的安装步骤
|
||||
- 环境检查
|
||||
- 问题排查
|
||||
|
||||
---
|
||||
|
||||
## 📖 完整文档
|
||||
|
||||
### 使用文档
|
||||
|
||||
| 文档名称 | 说明 | 适合人群 |
|
||||
|---------|------|---------|
|
||||
| [完整使用说明](PRESCRIPTION_LIBRARY_README.md) | 详细的功能说明和使用指南 | 所有用户 |
|
||||
| [功能演示](PRESCRIPTION_LIBRARY_DEMO.md) | 使用场景和示例 | 新用户 |
|
||||
| [快速开始](QUICK_START_PRESCRIPTION_LIBRARY.md) | 5分钟快速上手 | 新用户 ⭐ |
|
||||
|
||||
### 技术文档
|
||||
|
||||
| 文档名称 | 说明 | 适合人群 |
|
||||
|---------|------|---------|
|
||||
| [开发总结](PRESCRIPTION_LIBRARY_SUMMARY.md) | 技术实现和开发总结 | 开发人员 |
|
||||
| [文件清单](PRESCRIPTION_LIBRARY_FILES.md) | 所有文件列表和说明 | 开发人员 |
|
||||
| [交付文档](DELIVERY_PACKAGE.md) | 项目交付信息 | 项目经理 |
|
||||
|
||||
### 安装文档
|
||||
|
||||
| 文档名称 | 说明 | 适合人群 |
|
||||
|---------|------|---------|
|
||||
| [安装检查清单](INSTALLATION_CHECKLIST.md) | 详细的安装步骤和验证 | 运维人员 ⭐ |
|
||||
| [测试SQL](TEST_PRESCRIPTION_LIBRARY.sql) | 测试数据和查询语句 | 测试人员 |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 安装文件
|
||||
|
||||
### 自动安装脚本
|
||||
|
||||
| 文件名 | 平台 | 使用方法 |
|
||||
|--------|------|---------|
|
||||
| `install_prescription_library.bat` | Windows | 双击运行 |
|
||||
| `install_prescription_library.sh` | Linux/Mac | `chmod +x install_prescription_library.sh && ./install_prescription_library.sh` |
|
||||
|
||||
### 手动安装
|
||||
|
||||
如果自动安装脚本无法运行,请执行:
|
||||
```sql
|
||||
server/database/migrations/create_prescription_library.sql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📂 文件结构
|
||||
|
||||
### 后端文件(PHP)
|
||||
```
|
||||
server/
|
||||
├── database/migrations/
|
||||
│ └── create_prescription_library.sql # 数据库表结构
|
||||
├── app/common/model/tcm/
|
||||
│ └── PrescriptionLibrary.php # 数据模型
|
||||
└── app/adminapi/
|
||||
├── controller/tcm/
|
||||
│ └── PrescriptionLibraryController.php # 控制器
|
||||
├── logic/tcm/
|
||||
│ └── PrescriptionLibraryLogic.php # 业务逻辑
|
||||
├── lists/tcm/
|
||||
│ └── PrescriptionLibraryLists.php # 列表逻辑
|
||||
└── validate/tcm/
|
||||
└── PrescriptionLibraryValidate.php # 数据验证
|
||||
```
|
||||
|
||||
### 前端文件(Vue)
|
||||
```
|
||||
admin/
|
||||
└── src/
|
||||
├── views/consumer/prescription/
|
||||
│ └── list.vue # 处方库页面
|
||||
└── api/
|
||||
└── tcm.ts # API接口(已更新)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 核心功能
|
||||
|
||||
### ✅ 已实现功能
|
||||
|
||||
1. **处方管理**
|
||||
- 创建处方(处方名称 + 药材配方)
|
||||
- 编辑处方
|
||||
- 删除处方
|
||||
- 查看处方详情
|
||||
|
||||
2. **药材管理**
|
||||
- 动态添加药材
|
||||
- 设置药材名称和剂量(克)
|
||||
- 支持小数剂量(精确到0.1克)
|
||||
|
||||
3. **权限控制**
|
||||
- 是否公开设置(仅自己可见/所有人可见)
|
||||
- 查看权限控制
|
||||
- 编辑权限控制
|
||||
- 删除权限控制(仅创建者)
|
||||
|
||||
4. **搜索筛选**
|
||||
- 按处方名称搜索(模糊匹配)
|
||||
- 按是否公开筛选
|
||||
- 分页显示
|
||||
|
||||
### 🔮 未来扩展
|
||||
|
||||
1. 处方分类功能
|
||||
2. 处方标签功能
|
||||
3. 使用统计功能
|
||||
4. 导入导出功能
|
||||
5. 处方评论功能
|
||||
6. 版本管理功能
|
||||
|
||||
---
|
||||
|
||||
## 🔌 API接口
|
||||
|
||||
### 接口列表
|
||||
|
||||
| 接口路径 | 方法 | 说明 |
|
||||
|---------|------|------|
|
||||
| `/tcm.prescriptionLibrary/lists` | GET | 获取处方列表 |
|
||||
| `/tcm.prescriptionLibrary/add` | POST | 添加处方 |
|
||||
| `/tcm.prescriptionLibrary/edit` | POST | 编辑处方 |
|
||||
| `/tcm.prescriptionLibrary/delete` | POST | 删除处方 |
|
||||
| `/tcm.prescriptionLibrary/detail` | GET | 获取处方详情 |
|
||||
|
||||
详细说明请参阅:[开发总结](PRESCRIPTION_LIBRARY_SUMMARY.md)
|
||||
|
||||
---
|
||||
|
||||
## 💡 使用示例
|
||||
|
||||
### 示例1:六味地黄丸
|
||||
|
||||
```json
|
||||
{
|
||||
"prescription_name": "六味地黄丸",
|
||||
"herbs": [
|
||||
{"name": "熟地黄", "dosage": 24},
|
||||
{"name": "山茱萸", "dosage": 12},
|
||||
{"name": "山药", "dosage": 12},
|
||||
{"name": "泽泻", "dosage": 9},
|
||||
{"name": "牡丹皮", "dosage": 9},
|
||||
{"name": "茯苓", "dosage": 9}
|
||||
],
|
||||
"is_public": 1
|
||||
}
|
||||
```
|
||||
|
||||
### 示例2:补中益气汤
|
||||
|
||||
```json
|
||||
{
|
||||
"prescription_name": "补中益气汤",
|
||||
"herbs": [
|
||||
{"name": "黄芪", "dosage": 15},
|
||||
{"name": "党参", "dosage": 10},
|
||||
{"name": "白术", "dosage": 10},
|
||||
{"name": "当归", "dosage": 10},
|
||||
{"name": "陈皮", "dosage": 6},
|
||||
{"name": "升麻", "dosage": 6},
|
||||
{"name": "柴胡", "dosage": 6},
|
||||
{"name": "甘草", "dosage": 5}
|
||||
],
|
||||
"is_public": 1
|
||||
}
|
||||
```
|
||||
|
||||
更多示例请参阅:[功能演示](PRESCRIPTION_LIBRARY_DEMO.md)
|
||||
|
||||
---
|
||||
|
||||
## ❓ 常见问题
|
||||
|
||||
### Q1: 如何安装?
|
||||
A: 运行安装脚本或手动执行SQL文件。详见:[安装检查清单](INSTALLATION_CHECKLIST.md)
|
||||
|
||||
### Q2: 如何创建处方?
|
||||
A: 点击"新增处方"按钮,填写信息后保存。详见:[快速开始](QUICK_START_PRESCRIPTION_LIBRARY.md)
|
||||
|
||||
### Q3: 如何分享处方?
|
||||
A: 创建处方时,将"是否公开"设置为"所有人可见"。
|
||||
|
||||
### Q4: 如何删除处方?
|
||||
A: 只有创建者才能删除处方,点击"删除"按钮即可。
|
||||
|
||||
### Q5: 药材剂量可以是小数吗?
|
||||
A: 可以,支持精确到0.1克,如:3.5克。
|
||||
|
||||
更多问题请参阅:[完整使用说明](PRESCRIPTION_LIBRARY_README.md)
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试
|
||||
|
||||
### 快速测试
|
||||
|
||||
1. 执行测试SQL插入测试数据:
|
||||
```bash
|
||||
mysql -u用户名 -p密码 数据库名 < TEST_PRESCRIPTION_LIBRARY.sql
|
||||
```
|
||||
|
||||
2. 访问处方库页面,查看测试数据
|
||||
|
||||
3. 测试各项功能
|
||||
|
||||
详细测试步骤请参阅:[安装检查清单](INSTALLATION_CHECKLIST.md)
|
||||
|
||||
---
|
||||
|
||||
## 📊 技术栈
|
||||
|
||||
### 后端
|
||||
- **框架:** ThinkPHP 6.x
|
||||
- **语言:** PHP 7.4+
|
||||
- **数据库:** MySQL 5.7+
|
||||
|
||||
### 前端
|
||||
- **框架:** Vue 3
|
||||
- **UI库:** Element Plus
|
||||
- **语言:** TypeScript
|
||||
|
||||
---
|
||||
|
||||
## 🔐 权限说明
|
||||
|
||||
### 查看权限
|
||||
- ✅ 可以查看自己创建的所有处方
|
||||
- ✅ 可以查看其他人创建的公开处方
|
||||
- ❌ 不能查看其他人创建的私有处方
|
||||
|
||||
### 编辑权限
|
||||
- ✅ 可以编辑自己创建的处方
|
||||
- ✅ 可以编辑其他人创建的公开处方
|
||||
- ❌ 不能编辑其他人创建的私有处方
|
||||
|
||||
### 删除权限
|
||||
- ✅ 只能删除自己创建的处方
|
||||
- ❌ 不能删除其他人创建的处方
|
||||
|
||||
---
|
||||
|
||||
## 📝 更新日志
|
||||
|
||||
### v1.0.0 (2026-03-22)
|
||||
- ✅ 初始版本发布
|
||||
- ✅ 实现核心功能
|
||||
- ✅ 完成文档编写
|
||||
- ✅ 通过测试验收
|
||||
|
||||
---
|
||||
|
||||
## 🆘 技术支持
|
||||
|
||||
### 获取帮助
|
||||
|
||||
1. **查看文档**
|
||||
- 先查看相关文档
|
||||
- 查找常见问题
|
||||
|
||||
2. **问题反馈**
|
||||
- 提供错误信息截图
|
||||
- 提供操作步骤描述
|
||||
- 提供浏览器控制台日志
|
||||
|
||||
3. **联系支持**
|
||||
- 联系技术支持团队
|
||||
|
||||
---
|
||||
|
||||
## 📄 文档导航
|
||||
|
||||
### 按角色查看
|
||||
|
||||
#### 👨💼 管理员/使用者
|
||||
1. [快速开始指南](QUICK_START_PRESCRIPTION_LIBRARY.md) ⭐
|
||||
2. [完整使用说明](PRESCRIPTION_LIBRARY_README.md)
|
||||
3. [功能演示](PRESCRIPTION_LIBRARY_DEMO.md)
|
||||
|
||||
#### 👨💻 开发人员
|
||||
1. [开发总结](PRESCRIPTION_LIBRARY_SUMMARY.md) ⭐
|
||||
2. [文件清单](PRESCRIPTION_LIBRARY_FILES.md)
|
||||
3. [交付文档](DELIVERY_PACKAGE.md)
|
||||
|
||||
#### 🔧 运维人员
|
||||
1. [安装检查清单](INSTALLATION_CHECKLIST.md) ⭐
|
||||
2. [测试SQL](TEST_PRESCRIPTION_LIBRARY.sql)
|
||||
|
||||
#### 📋 项目经理
|
||||
1. [交付文档](DELIVERY_PACKAGE.md) ⭐
|
||||
2. [开发总结](PRESCRIPTION_LIBRARY_SUMMARY.md)
|
||||
|
||||
### 按任务查看
|
||||
|
||||
#### 🚀 安装部署
|
||||
1. [安装检查清单](INSTALLATION_CHECKLIST.md)
|
||||
2. [快速开始指南](QUICK_START_PRESCRIPTION_LIBRARY.md)
|
||||
|
||||
#### 📖 学习使用
|
||||
1. [快速开始指南](QUICK_START_PRESCRIPTION_LIBRARY.md)
|
||||
2. [功能演示](PRESCRIPTION_LIBRARY_DEMO.md)
|
||||
3. [完整使用说明](PRESCRIPTION_LIBRARY_README.md)
|
||||
|
||||
#### 🔧 开发维护
|
||||
1. [开发总结](PRESCRIPTION_LIBRARY_SUMMARY.md)
|
||||
2. [文件清单](PRESCRIPTION_LIBRARY_FILES.md)
|
||||
|
||||
#### 📦 项目交付
|
||||
1. [交付文档](DELIVERY_PACKAGE.md)
|
||||
2. [文件清单](PRESCRIPTION_LIBRARY_FILES.md)
|
||||
|
||||
---
|
||||
|
||||
## ✅ 检查清单
|
||||
|
||||
### 安装前
|
||||
- [ ] 已阅读快速开始指南
|
||||
- [ ] 已备份数据库
|
||||
- [ ] 已检查环境要求
|
||||
|
||||
### 安装中
|
||||
- [ ] 已执行安装脚本
|
||||
- [ ] 已配置菜单权限
|
||||
- [ ] 已重启服务
|
||||
|
||||
### 安装后
|
||||
- [ ] 已测试基本功能
|
||||
- [ ] 已创建测试处方
|
||||
- [ ] 已验证权限控制
|
||||
|
||||
---
|
||||
|
||||
## 🎉 开始使用
|
||||
|
||||
准备好了吗?让我们开始吧!
|
||||
|
||||
**推荐路径:**
|
||||
1. 阅读 [快速开始指南](QUICK_START_PRESCRIPTION_LIBRARY.md)
|
||||
2. 运行安装脚本
|
||||
3. 创建第一个处方
|
||||
4. 探索更多功能
|
||||
|
||||
**祝使用愉快!** 🚀
|
||||
|
||||
---
|
||||
|
||||
**版本:** v1.0.0
|
||||
**最后更新:** 2026-03-22
|
||||
**状态:** ✅ 已完成
|
||||
@@ -1,144 +0,0 @@
|
||||
# 本地录制修复总结
|
||||
|
||||
## 问题根源
|
||||
|
||||
通过分析实际日志发现,本地录制失败的根本原因是:
|
||||
|
||||
1. **录制启动太晚**:1200ms 延迟导致录制在对方挂断后才启动
|
||||
2. **SDK 提前销毁轨道**:`stopLocalVideo/stopLocalAudio` 在录制完成前就销毁了视频轨道
|
||||
3. **时间窗口极短**:从对方离开到轨道销毁只有 200-300ms
|
||||
|
||||
## 关键时间线(问题场景)
|
||||
|
||||
```
|
||||
15:30:51.841 - 对方离开(USER_LEAVE 事件)
|
||||
15:30:51.xxx - beginStopNow 被调用(但录制还未启动!)
|
||||
15:30:52.072 - stopLocalVideo/stopLocalAudio(轨道被销毁)
|
||||
15:30:52.102 - exitRoom
|
||||
15:30:52.374 - MediaRecorder 已启动(太晚了!)
|
||||
15:30:52.xxx - 数据块数: 0(因为轨道已销毁)
|
||||
```
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 1. 大幅缩短录制启动延迟
|
||||
- **从 1200ms 缩短到 500ms**
|
||||
- 使用 300ms 间隔快速重试(最多 10 次)
|
||||
- 目标:在通话接通后 1 秒内启动录制
|
||||
|
||||
### 2. 在通话结束事件时立即停止录制
|
||||
- 监听 `TUICallEvent.USER_LEAVE`、`CALL_END`、`ON_CALL_NOT_CONNECTED`
|
||||
- 检测到事件时立即调用 `beginStopNow()`
|
||||
- 抢在 SDK 销毁轨道前完成录制
|
||||
|
||||
### 3. 缩短停止延迟
|
||||
- **从 300ms 缩短到 100ms**
|
||||
- 减少等待时间,避免 SDK 提前销毁轨道
|
||||
|
||||
### 4. 智能视频元素检测
|
||||
- 首先检查容器内的视频
|
||||
- 如果没有,检查 body(TUICallKit Teleport)
|
||||
- 过滤出有活跃视频流的元素
|
||||
- 验证 readyState >= HAVE_CURRENT_DATA
|
||||
|
||||
## 修改的文件
|
||||
|
||||
1. **admin/src/components/chat-dialog/index.vue**
|
||||
- 缩短录制启动延迟(1200ms → 500ms)
|
||||
- 添加快速重试机制(300ms 间隔,最多 10 次)
|
||||
- 在 `bindEngineRoomEvents` 中添加立即停止逻辑
|
||||
- 增强日志输出
|
||||
|
||||
2. **admin/src/utils/call-local-recorder.ts**
|
||||
- 缩短停止延迟(300ms → 100ms)
|
||||
- 增强日志输出
|
||||
- 添加详细的状态跟踪
|
||||
|
||||
3. **LOCAL_RECORDING_DEBUG_GUIDE.md**
|
||||
- 完整的调试指南
|
||||
- 常见问题诊断
|
||||
- 测试建议
|
||||
|
||||
4. **admin/test-local-recording.html**
|
||||
- 独立的测试工具
|
||||
- 浏览器兼容性检测
|
||||
- MediaRecorder 功能测试
|
||||
|
||||
## 测试步骤
|
||||
|
||||
### 1. 清除浏览器缓存
|
||||
确保使用最新的代码。
|
||||
|
||||
### 2. 打开控制台
|
||||
F12 打开开发者工具,切换到 Console 标签。
|
||||
|
||||
### 3. 发起视频通话
|
||||
点击视频通话按钮。
|
||||
|
||||
### 4. 观察关键日志
|
||||
|
||||
#### 接通后应该看到(1 秒内):
|
||||
```
|
||||
[chat-dialog] 尝试启动本地录制(第 1 次)
|
||||
[chat-dialog] 视频元素已就绪,立即启动录制
|
||||
[call-local-recorder] MediaRecorder.start() 调用成功
|
||||
[call-local-recorder] MediaRecorder 已启动
|
||||
[chat-dialog] ✅ 本地录制已成功启动
|
||||
```
|
||||
|
||||
#### 通话过程中应该看到(每 250ms):
|
||||
```
|
||||
[call-local-recorder] 收到数据块,大小: 12345 (总计: 1 块)
|
||||
[call-local-recorder] 收到数据块,大小: 15678 (总计: 2 块)
|
||||
```
|
||||
|
||||
#### 挂断时应该看到:
|
||||
```
|
||||
[chat-dialog] 检测到通话结束事件,立即停止录制
|
||||
[call-local-recorder] beginStopNow 被调用
|
||||
[call-local-recorder] 准备停止 MediaRecorder,当前状态: recording 已收集数据块: 15
|
||||
[call-local-recorder] 生成 Blob: 1234567 bytes
|
||||
[chat-dialog] 开始上传录制文件
|
||||
本地录制已上传并关联到通话记录
|
||||
```
|
||||
|
||||
### 5. 验证结果
|
||||
- 检查诊单是否有录制文件
|
||||
- 播放录制文件验证内容
|
||||
|
||||
## 预期效果
|
||||
|
||||
### 修复前
|
||||
- 录制成功率:0%
|
||||
- 数据块数:0
|
||||
- Blob 大小:null
|
||||
|
||||
### 修复后
|
||||
- 录制成功率:>95%(通话时长 > 2 秒)
|
||||
- 数据块数:>= 通话秒数 * 4
|
||||
- Blob 大小:>= 通话秒数 * 50KB
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **最短通话时长**:建议至少 2 秒,否则可能来不及启动录制
|
||||
2. **浏览器兼容性**:Chrome/Edge 85+、Firefox 78+、Safari 14.1+
|
||||
3. **网络状况**:网络差时视频流可能不稳定,影响录制质量
|
||||
4. **权限问题**:确保浏览器已授予摄像头和麦克风权限
|
||||
|
||||
## 如果问题仍然存在
|
||||
|
||||
1. 查看完整的控制台日志
|
||||
2. 确认录制是否在接通后 1 秒内启动
|
||||
3. 确认是否有 "收到数据块" 日志
|
||||
4. 检查通话时长是否 > 2 秒
|
||||
5. 参考 `LOCAL_RECORDING_DEBUG_GUIDE.md` 进行详细诊断
|
||||
|
||||
## 进一步优化方向
|
||||
|
||||
如果修复后仍有问题,可以考虑:
|
||||
|
||||
1. **进一步缩短启动延迟**:300ms 或更短
|
||||
2. **使用 MutationObserver**:监听视频元素添加,立即启动
|
||||
3. **添加录制状态监控**:定期检查 MediaRecorder 状态
|
||||
4. **添加降级方案**:本地录制失败时提示用户
|
||||
5. **添加手动控制**:让用户手动开始/停止录制
|
||||
@@ -1,278 +0,0 @@
|
||||
# 安全修复总结 - 防止绕过修改密码页面
|
||||
|
||||
## 问题描述
|
||||
|
||||
原始实现中存在两个安全漏洞:
|
||||
|
||||
1. **URL 绕过**:用户登录后虽然会跳转到修改密码页面,但可以通过直接在浏览器地址栏输入其他页面 URL 来绕过修改密码页面
|
||||
2. **刷新绕过**:页面刷新后,`isPaw` 状态丢失(只存在内存中),导致用户可以直接进入系统
|
||||
|
||||
## 安全风险
|
||||
|
||||
- 用户可以使用初始密码继续使用系统
|
||||
- 无法强制用户修改弱密码
|
||||
- 违反安全策略要求
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 1. 后端返回 is_paw 字段
|
||||
|
||||
在 `AdminLogic::detail()` 方法中添加 `is_paw` 字段:
|
||||
|
||||
```php
|
||||
$admin = Admin::field([
|
||||
'id', 'account', 'name', 'disable', 'root',
|
||||
'multipoint_login', 'avatar', 'is_paw', // 添加此字段
|
||||
// ... 其他字段
|
||||
])->findOrEmpty($params['id'])->toArray();
|
||||
```
|
||||
|
||||
### 2. 状态管理
|
||||
|
||||
在 Vuex store 中添加 `isPaw` 状态:
|
||||
|
||||
```typescript
|
||||
export interface UserState {
|
||||
token: string
|
||||
userInfo: Record<string, any>
|
||||
routes: RouteRecordRaw[]
|
||||
perms: string[]
|
||||
isPaw: number // 0=需要修改密码,1=正常
|
||||
}
|
||||
```
|
||||
|
||||
登录时保存 `isPaw` 状态:
|
||||
|
||||
```typescript
|
||||
login(playload: any) {
|
||||
return new Promise((resolve, reject) => {
|
||||
login({ account: account.trim(), password: password })
|
||||
.then((data) => {
|
||||
this.token = data.token
|
||||
this.isPaw = data.is_paw ?? 1 // 保存状态
|
||||
cache.set(TOKEN_KEY, data.token)
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 状态管理
|
||||
|
||||
在 Vuex store 中添加 `isPaw` 状态:
|
||||
|
||||
```typescript
|
||||
export interface UserState {
|
||||
token: string
|
||||
userInfo: Record<string, any>
|
||||
routes: RouteRecordRaw[]
|
||||
perms: string[]
|
||||
isPaw: number // 0=需要修改密码,1=正常
|
||||
}
|
||||
```
|
||||
|
||||
登录时保存 `isPaw` 状态:
|
||||
|
||||
```typescript
|
||||
login(playload: any) {
|
||||
return new Promise((resolve, reject) => {
|
||||
login({ account: account.trim(), password: password })
|
||||
.then((data) => {
|
||||
this.token = data.token
|
||||
this.isPaw = data.is_paw ?? 1 // 保存状态
|
||||
cache.set(TOKEN_KEY, data.token)
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
获取用户信息时更新 `isPaw` 状态(解决刷新问题):
|
||||
|
||||
```typescript
|
||||
getUserInfo() {
|
||||
return new Promise((resolve, reject) => {
|
||||
getUserInfo()
|
||||
.then((data) => {
|
||||
this.userInfo = data.user
|
||||
this.perms = data.permissions
|
||||
this.routes = filterAsyncRoutes(data.menu)
|
||||
this.isPaw = data.user.is_paw ?? 1 // 从后端获取最新状态
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 路由守卫
|
||||
|
||||
在 `permission.ts` 中添加路由守卫逻辑:
|
||||
|
||||
```typescript
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 特殊处理:修改密码页面
|
||||
if (to.path === changePasswordPath) {
|
||||
if (!userStore.token) {
|
||||
// 未登录,跳转到登录页
|
||||
next({ path: loginPath })
|
||||
return
|
||||
}
|
||||
if (userStore.isPaw === 1) {
|
||||
// 已经修改过密码,跳转到首页
|
||||
next({ path: defaultPath })
|
||||
return
|
||||
}
|
||||
// 需要修改密码,允许访问
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
// 其他页面:检查是否需要修改密码
|
||||
if (userStore.token && userStore.isPaw === 0) {
|
||||
// 需要修改密码,强制跳转到修改密码页面
|
||||
next({ path: changePasswordPath })
|
||||
return
|
||||
}
|
||||
|
||||
// ... 其他路由逻辑
|
||||
})
|
||||
```
|
||||
|
||||
### 3. 路由守卫
|
||||
|
||||
在 `permission.ts` 中添加路由守卫逻辑,关键是在获取用户信息后再检查 `isPaw`:
|
||||
|
||||
```typescript
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const userStore = useUserStore()
|
||||
|
||||
if (userStore.token) {
|
||||
const hasGetUserInfo = Object.keys(userStore.userInfo).length !== 0
|
||||
|
||||
if (hasGetUserInfo) {
|
||||
// 已经获取过用户信息,检查是否需要修改密码
|
||||
if (userStore.isPaw === 0) {
|
||||
next({ path: changePasswordPath })
|
||||
return
|
||||
}
|
||||
// ... 其他逻辑
|
||||
} else {
|
||||
// 首次获取用户信息
|
||||
await userStore.getUserInfo()
|
||||
|
||||
// 获取用户信息后,检查是否需要修改密码
|
||||
if (userStore.isPaw === 0) {
|
||||
next({ path: changePasswordPath })
|
||||
return
|
||||
}
|
||||
// ... 其他逻辑
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### 4. 防护机制
|
||||
|
||||
### 4. 防护机制
|
||||
|
||||
#### 4.1 强制跳转
|
||||
- 当 `isPaw = 0` 时,访问任何页面都会被拦截
|
||||
- 自动跳转到修改密码页面
|
||||
|
||||
#### 4.2 URL 防护
|
||||
- 即使直接输入 URL,路由守卫也会拦截
|
||||
- 无法绕过修改密码页面
|
||||
|
||||
#### 4.3 状态验证
|
||||
- 修改密码页面需要登录才能访问
|
||||
- 已修改密码的用户无法再次访问该页面
|
||||
|
||||
#### 4.4 刷新防护
|
||||
- 页面刷新时重新从后端获取 `is_paw` 状态
|
||||
- 确保状态始终与数据库一致
|
||||
|
||||
#### 4.5 多标签页防护
|
||||
- 所有标签页共享同一个 store 状态
|
||||
- 新打开的标签页也会被拦截
|
||||
|
||||
## 修复后的安全流程
|
||||
|
||||
```
|
||||
用户登录 (is_paw = 0)
|
||||
↓
|
||||
路由守卫检查 isPaw
|
||||
↓
|
||||
强制跳转到修改密码页面
|
||||
↓
|
||||
用户尝试访问其他页面(输入 URL)
|
||||
↓
|
||||
路由守卫再次检查 isPaw
|
||||
↓
|
||||
再次强制跳转到修改密码页面
|
||||
↓
|
||||
用户修改密码成功
|
||||
↓
|
||||
isPaw 更新为 1
|
||||
↓
|
||||
退出登录
|
||||
↓
|
||||
使用新密码重新登录
|
||||
↓
|
||||
路由守卫检查 isPaw = 1
|
||||
↓
|
||||
允许正常访问系统
|
||||
```
|
||||
|
||||
## 测试验证
|
||||
|
||||
### 测试场景 1:直接输入 URL
|
||||
1. 使用 `is_paw = 0` 的账号登录
|
||||
2. 在地址栏输入 `/workbench/index`
|
||||
3. 结果:自动跳转回 `/change-password`
|
||||
|
||||
### 测试场景 2:多标签页
|
||||
1. 使用 `is_paw = 0` 的账号登录
|
||||
2. 打开新标签页访问其他页面
|
||||
3. 结果:新标签页也被跳转到修改密码页面
|
||||
|
||||
### 测试场景 3:页面刷新
|
||||
1. 在修改密码页面刷新浏览器
|
||||
2. 结果:仍然停留在修改密码页面
|
||||
|
||||
### 测试场景 4:已修改密码的用户
|
||||
1. 使用 `is_paw = 1` 的账号登录
|
||||
2. 尝试访问 `/change-password`
|
||||
3. 结果:自动跳转到首页
|
||||
|
||||
## 相关文件
|
||||
|
||||
### 修改的文件
|
||||
- `admin/src/stores/modules/user.ts` - 添加 isPaw 状态管理
|
||||
- `admin/src/permission.ts` - 添加路由守卫逻辑
|
||||
- `admin/src/views/account/change-password.vue` - 更新 isPaw 状态
|
||||
|
||||
### 新增的文件
|
||||
- `TEST_CHECKLIST.md` - 测试清单
|
||||
|
||||
## 安全等级
|
||||
|
||||
修复前:⚠️ 低(可绕过)
|
||||
修复后:✅ 高(无法绕过)
|
||||
|
||||
## 建议
|
||||
|
||||
1. 定期审计管理员账号的 `is_paw` 状态
|
||||
2. 为新创建的管理员默认设置 `is_paw = 0`
|
||||
3. 定期要求管理员修改密码(批量设置 `is_paw = 0`)
|
||||
4. 考虑添加密码强度验证(大小写、数字、特殊字符)
|
||||
5. 考虑添加密码历史记录,防止重复使用旧密码
|
||||
|
||||
## 完成时间
|
||||
|
||||
2024-XX-XX
|
||||
|
||||
## 测试状态
|
||||
|
||||
✅ 已通过所有测试场景
|
||||
@@ -1,192 +0,0 @@
|
||||
# 首次登录修改密码功能 - 测试清单
|
||||
|
||||
## 测试前准备
|
||||
|
||||
1. ✅ 执行安装脚本或 SQL,添加 `is_paw` 字段
|
||||
2. ✅ 设置测试账号:`UPDATE la_admin SET is_paw = 0 WHERE id = 1;`
|
||||
3. ✅ 确认前后端代码已更新
|
||||
|
||||
## 测试场景
|
||||
|
||||
### 场景 1:正常登录流程
|
||||
|
||||
**步骤:**
|
||||
1. 使用 `is_paw = 0` 的账号登录
|
||||
2. 观察是否自动跳转到修改密码页面
|
||||
3. 输入新密码(至少 6 位)
|
||||
4. 点击"确认修改"
|
||||
5. 观察是否提示"密码修改成功,请重新登录"
|
||||
6. 使用新密码重新登录
|
||||
7. 观察是否能正常进入系统
|
||||
|
||||
**预期结果:**
|
||||
- ✅ 登录后自动跳转到修改密码页面
|
||||
- ✅ 修改密码成功
|
||||
- ✅ 使用新密码能正常登录
|
||||
- ✅ 数据库中 `is_paw` 已更新为 1
|
||||
|
||||
### 场景 2:URL 防护测试(重要!)
|
||||
|
||||
**步骤:**
|
||||
1. 使用 `is_paw = 0` 的账号登录
|
||||
2. 登录后会跳转到修改密码页面
|
||||
3. 在浏览器地址栏直接输入其他页面 URL(如:`/workbench/index`)
|
||||
4. 按回车访问
|
||||
|
||||
**预期结果:**
|
||||
- ✅ 无论输入什么 URL,都会被强制跳转回修改密码页面
|
||||
- ✅ 无法绕过修改密码页面访问其他页面
|
||||
|
||||
### 场景 3:密码验证测试
|
||||
|
||||
**步骤:**
|
||||
1. 在修改密码页面输入少于 6 位的密码
|
||||
2. 点击"确认修改"
|
||||
|
||||
**预期结果:**
|
||||
- ✅ 提示"密码长度不能少于6位"
|
||||
|
||||
**步骤:**
|
||||
1. 两次输入不同的密码
|
||||
2. 点击"确认修改"
|
||||
|
||||
**预期结果:**
|
||||
- ✅ 提示"两次输入的密码不一致"
|
||||
|
||||
### 场景 4:已修改密码的用户
|
||||
|
||||
**步骤:**
|
||||
1. 使用 `is_paw = 1` 的账号登录
|
||||
2. 观察是否直接进入系统
|
||||
|
||||
**预期结果:**
|
||||
- ✅ 直接进入系统,不跳转到修改密码页面
|
||||
|
||||
**步骤:**
|
||||
1. 在浏览器地址栏输入 `/change-password`
|
||||
2. 按回车访问
|
||||
|
||||
**预期结果:**
|
||||
- ✅ 自动跳转到首页,无法访问修改密码页面
|
||||
|
||||
### 场景 5:未登录用户
|
||||
|
||||
**步骤:**
|
||||
1. 退出登录
|
||||
2. 在浏览器地址栏输入 `/change-password`
|
||||
3. 按回车访问
|
||||
|
||||
**预期结果:**
|
||||
- ✅ 自动跳转到登录页面
|
||||
|
||||
### 场景 6:企业微信登录
|
||||
|
||||
**步骤:**
|
||||
1. 设置企业微信绑定的管理员 `is_paw = 0`
|
||||
2. 使用企业微信登录
|
||||
3. 观察是否跳转到修改密码页面
|
||||
|
||||
**预期结果:**
|
||||
- ✅ 登录后自动跳转到修改密码页面
|
||||
- ✅ 修改密码流程与账号密码登录一致
|
||||
|
||||
### 场景 7:页面刷新测试(重要!)
|
||||
|
||||
**步骤:**
|
||||
1. 使用 `is_paw = 0` 的账号登录
|
||||
2. 跳转到修改密码页面后,刷新浏览器(F5 或 Ctrl+R)
|
||||
3. 观察页面状态
|
||||
|
||||
**预期结果:**
|
||||
- ✅ 刷新后仍然停留在修改密码页面
|
||||
- ✅ 不会跳转到其他页面
|
||||
|
||||
**步骤(进阶测试):**
|
||||
1. 使用 `is_paw = 0` 的账号登录
|
||||
2. 跳转到修改密码页面
|
||||
3. 在地址栏输入其他页面 URL(如 `/workbench/index`)
|
||||
4. 按回车
|
||||
5. 观察是否跳转回修改密码页面
|
||||
6. 刷新浏览器
|
||||
7. 观察是否仍然在修改密码页面
|
||||
|
||||
**预期结果:**
|
||||
- ✅ 输入其他 URL 后被拦截,跳转回修改密码页面
|
||||
- ✅ 刷新后仍然在修改密码页面
|
||||
- ✅ 无法通过任何方式绕过
|
||||
|
||||
### 场景 8:多标签页测试
|
||||
|
||||
**步骤:**
|
||||
1. 使用 `is_paw = 0` 的账号登录
|
||||
2. 跳转到修改密码页面
|
||||
3. 打开新标签页,访问系统其他页面
|
||||
|
||||
**预期结果:**
|
||||
- ✅ 新标签页也会被强制跳转到修改密码页面
|
||||
|
||||
## 数据库验证
|
||||
|
||||
### 修改前
|
||||
```sql
|
||||
SELECT id, account, is_paw FROM la_admin WHERE id = 1;
|
||||
-- 应该显示 is_paw = 0
|
||||
```
|
||||
|
||||
### 修改后
|
||||
```sql
|
||||
SELECT id, account, is_paw FROM la_admin WHERE id = 1;
|
||||
-- 应该显示 is_paw = 1
|
||||
```
|
||||
|
||||
## 常见问题排查
|
||||
|
||||
### 问题 1:登录后没有跳转到修改密码页面
|
||||
|
||||
**检查:**
|
||||
- 数据库中 `is_paw` 字段是否为 0
|
||||
- 前端代码是否正确判断 `is_paw` 字段
|
||||
- 浏览器控制台是否有错误
|
||||
|
||||
### 问题 2:刷新后可以进入系统
|
||||
|
||||
**检查:**
|
||||
- 后端 `AdminLogic::detail()` 方法是否返回 `is_paw` 字段
|
||||
- 前端 `getUserInfo()` 方法是否更新 `isPaw` 状态
|
||||
- 路由守卫是否在获取用户信息后检查 `isPaw`
|
||||
- 浏览器控制台查看 API 返回的数据是否包含 `is_paw`
|
||||
|
||||
### 问题 3:可以通过 URL 绕过修改密码页面
|
||||
|
||||
**检查:**
|
||||
- `permission.ts` 路由守卫是否正确配置
|
||||
- `userStore.isPaw` 状态是否正确存储
|
||||
- 浏览器控制台查看路由守卫是否执行
|
||||
|
||||
### 问题 4:修改密码后 `is_paw` 没有更新
|
||||
|
||||
**检查:**
|
||||
- 后端 `changeFirstPassword` 方法是否正确更新数据库
|
||||
- 查看数据库确认字段值
|
||||
- 检查后端日志是否有错误
|
||||
|
||||
### 问题 5:修改密码后无法登录
|
||||
|
||||
**检查:**
|
||||
- 密码加密方式是否正确(应使用 `create_password` 函数)
|
||||
- 检查后端日志
|
||||
- 尝试重置密码
|
||||
|
||||
## 测试完成标准
|
||||
|
||||
- ✅ 所有 8 个测试场景都通过
|
||||
- ✅ 数据库字段正确更新
|
||||
- ✅ 无法通过任何方式绕过修改密码页面
|
||||
- ✅ 密码验证规则正常工作
|
||||
- ✅ 修改密码后能正常登录使用系统
|
||||
|
||||
## 性能测试
|
||||
|
||||
- ✅ 路由守卫不影响正常用户的访问速度
|
||||
- ✅ 登录流程响应时间正常(< 2 秒)
|
||||
- ✅ 修改密码操作响应时间正常(< 1 秒)
|
||||
@@ -10,6 +10,7 @@ lerna-debug.log*
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
unpackage/dist
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
|
||||
+13
-2
@@ -15,11 +15,22 @@ const globalData = {
|
||||
let avatarUrl = ref(""); // 声明为响应式变量
|
||||
uni.CallManager = new CallManager();
|
||||
onLaunch(() => {
|
||||
// iOS 静音键模式下 InnerAudioContext 默认不发声,
|
||||
// 必须在 onLaunch 全局调用此 API,单实例 obeyMuteSwitch 在 iOS 不可靠
|
||||
// #ifdef MP-WEIXIN
|
||||
try {
|
||||
uni.setInnerAudioOption({
|
||||
obeyMuteSwitch: false,
|
||||
mixWithOther: true,
|
||||
})
|
||||
} catch (e) {
|
||||
console.warn('setInnerAudioOption failed:', e)
|
||||
}
|
||||
// #endif
|
||||
|
||||
uni.login({
|
||||
provider: 'weixin',
|
||||
success: function (loginRes) {
|
||||
console.log('xx');
|
||||
console.log(loginRes.code);
|
||||
if( uni.getStorageSync('token')){
|
||||
userinfo(loginRes.code);
|
||||
}else{
|
||||
|
||||
@@ -1,6 +1,30 @@
|
||||
## 简介
|
||||
本 demo 演示了如何在 uni-app 项目中集成 [TUICallKit](https://www.npmjs.com/package/@trtc/calls-uikit-wx-uniapp) 音视频通话组件。
|
||||
|
||||
## 两种开发方式
|
||||
|
||||
本工程同时支持 **HBuilderX** 和 **命令行 (vite)** 两套编译链路,二选一即可。
|
||||
|
||||
### 方式 A:命令行 (vite)
|
||||
|
||||
```bash
|
||||
npm install # 安装依赖
|
||||
npm run dev:mp-weixin # watch 模式,产物 dist/dev/mp-weixin
|
||||
npm run build:mp-weixin # 生产构建,产物 dist/build/mp-weixin
|
||||
```
|
||||
|
||||
构建完成后用微信开发者工具打开对应 `dist/.../mp-weixin` 目录即可。
|
||||
|
||||
支持平台:`mp-weixin` / `h5` / `app`(脚本里有 dev/build 两组)。
|
||||
|
||||
### 方式 B:HBuilderX
|
||||
|
||||
HBuilderX 3.4+ 会自动识别 `vite.config.ts` 并切换到 vite 编译模式。
|
||||
|
||||
1. 用 HBuilderX 打开工程根目录
|
||||
2. 菜单「运行」→「运行到小程序模拟器」→「微信开发者工具」
|
||||
3. 产物会输出到 `unpackage/dist/dev/mp-weixin`
|
||||
|
||||
|
||||
## 环境准备
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user