后台管理系统

This commit is contained in:
Hoshinasuzu 2024-12-22 14:43:02 +08:00
parent 5aa0643b94
commit 25319d4122
23 changed files with 2311 additions and 580 deletions

View File

@ -12,6 +12,7 @@
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@vueup/vue-quill": "^1.2.0",
"axios": "^1.7.2",
"element-plus": "^2.7.6",
"pinia": "^2.1.7",

View File

@ -11,6 +11,9 @@ importers:
'@element-plus/icons-vue':
specifier: ^2.3.1
version: 2.3.1(vue@3.5.10)
'@vueup/vue-quill':
specifier: ^1.2.0
version: 1.2.0(vue@3.5.10)
axios:
specifier: ^1.7.2
version: 1.7.2
@ -459,6 +462,11 @@ packages:
'@vue/shared@3.5.10':
resolution: {integrity: sha512-VkkBhU97Ki+XJ0xvl4C9YJsIZ2uIlQ7HqPpZOS3m9VCvmROPaChZU6DexdMJqvz9tbgG+4EtFVrSuailUq5KGQ==}
'@vueup/vue-quill@1.2.0':
resolution: {integrity: sha512-kd5QPSHMDpycklojPXno2Kw2JSiKMYduKYQckTm1RJoVDA557MnyUXgcuuDpry4HY/Rny9nGNcK+m3AHk94wag==}
peerDependencies:
vue: ^3.2.41
'@vueuse/core@9.13.0':
resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==}
@ -525,6 +533,18 @@ packages:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
call-bind-apply-helpers@1.0.1:
resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==}
engines: {node: '>= 0.4'}
call-bind@1.0.8:
resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==}
engines: {node: '>= 0.4'}
call-bound@1.0.3:
resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==}
engines: {node: '>= 0.4'}
callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
@ -537,6 +557,10 @@ packages:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
clone@2.1.2:
resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==}
engines: {node: '>=0.8'}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@ -578,9 +602,21 @@ packages:
supports-color:
optional: true
deep-equal@1.1.2:
resolution: {integrity: sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==}
engines: {node: '>= 0.4'}
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
define-data-property@1.1.4:
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
engines: {node: '>= 0.4'}
define-properties@1.2.1:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'}
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
@ -589,6 +625,10 @@ packages:
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
engines: {node: '>=6.0.0'}
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
element-plus@2.7.6:
resolution: {integrity: sha512-36sw1K23hYjgeooR10U6CiCaCp2wvOqwoFurADZVlekeQ9v5U1FhJCFGEXO6i/kZBBMwsE1c9fxjLs9LENw2Rg==}
peerDependencies:
@ -598,6 +638,18 @@ packages:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
es-define-property@1.0.1:
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
engines: {node: '>= 0.4'}
es-errors@1.3.0:
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
engines: {node: '>= 0.4'}
es-object-atoms@1.0.0:
resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==}
engines: {node: '>= 0.4'}
esbuild@0.21.5:
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
engines: {node: '>=12'}
@ -679,9 +731,21 @@ packages:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
eventemitter3@2.0.3:
resolution: {integrity: sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==}
extend@3.0.2:
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
fast-diff@1.1.2:
resolution: {integrity: sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==}
fast-diff@1.2.0:
resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==}
fast-diff@1.3.0:
resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
@ -738,6 +802,16 @@ packages:
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
functions-have-names@1.2.3:
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
get-intrinsic@1.2.6:
resolution: {integrity: sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==}
engines: {node: '>= 0.4'}
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
@ -754,6 +828,10 @@ packages:
resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
engines: {node: '>=8'}
gopd@1.2.0:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
graphemer@1.4.0:
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
@ -761,6 +839,21 @@ packages:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
has-property-descriptors@1.0.2:
resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
has-symbols@1.1.0:
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
engines: {node: '>= 0.4'}
has-tostringtag@1.0.2:
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
engines: {node: '>= 0.4'}
hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
ignore@5.3.1:
resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==}
engines: {node: '>= 4'}
@ -783,10 +876,18 @@ packages:
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
is-arguments@1.2.0:
resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==}
engines: {node: '>= 0.4'}
is-binary-path@2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
is-date-object@1.1.0:
resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
engines: {node: '>= 0.4'}
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
@ -803,6 +904,10 @@ packages:
resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
engines: {node: '>=8'}
is-regex@1.2.1:
resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
engines: {node: '>= 0.4'}
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
@ -847,6 +952,12 @@ packages:
lodash: '*'
lodash-es: '*'
lodash.clonedeep@4.5.0:
resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==}
lodash.isequal@4.5.0:
resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
@ -859,6 +970,10 @@ packages:
magic-string@0.30.11:
resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==}
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
memoize-one@6.0.0:
resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
@ -909,6 +1024,14 @@ packages:
nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
object-is@1.1.6:
resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==}
engines: {node: '>= 0.4'}
object-keys@1.1.1:
resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
engines: {node: '>= 0.4'}
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
@ -924,6 +1047,9 @@ packages:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'}
parchment@1.1.4:
resolution: {integrity: sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==}
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@ -1013,10 +1139,24 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
quill-delta@3.6.3:
resolution: {integrity: sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==}
engines: {node: '>=0.10'}
quill-delta@4.2.2:
resolution: {integrity: sha512-qjbn82b/yJzOjstBgkhtBjN2TNK+ZHP/BgUQO+j6bRhWQQdmj2lH6hXG7+nwwLF41Xgn//7/83lxs9n2BkTtTg==}
quill@1.3.7:
resolution: {integrity: sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==}
readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
regexp.prototype.flags@1.5.3:
resolution: {integrity: sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==}
engines: {node: '>= 0.4'}
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
@ -1051,6 +1191,14 @@ packages:
engines: {node: '>=10'}
hasBin: true
set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'}
set-function-name@2.0.2:
resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
engines: {node: '>= 0.4'}
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
@ -1533,6 +1681,12 @@ snapshots:
'@vue/shared@3.5.10': {}
'@vueup/vue-quill@1.2.0(vue@3.5.10)':
dependencies:
quill: 1.3.7
quill-delta: 4.2.2
vue: 3.5.10
'@vueuse/core@9.13.0(vue@3.5.10)':
dependencies:
'@types/web-bluetooth': 0.0.16
@ -1609,6 +1763,23 @@ snapshots:
dependencies:
fill-range: 7.1.1
call-bind-apply-helpers@1.0.1:
dependencies:
es-errors: 1.3.0
function-bind: 1.1.2
call-bind@1.0.8:
dependencies:
call-bind-apply-helpers: 1.0.1
es-define-property: 1.0.1
get-intrinsic: 1.2.6
set-function-length: 1.2.2
call-bound@1.0.3:
dependencies:
call-bind-apply-helpers: 1.0.1
get-intrinsic: 1.2.6
callsites@3.1.0: {}
chalk@4.1.2:
@ -1628,6 +1799,8 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
clone@2.1.2: {}
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
@ -1658,14 +1831,41 @@ snapshots:
dependencies:
ms: 2.1.2
deep-equal@1.1.2:
dependencies:
is-arguments: 1.2.0
is-date-object: 1.1.0
is-regex: 1.2.1
object-is: 1.1.6
object-keys: 1.1.1
regexp.prototype.flags: 1.5.3
deep-is@0.1.4: {}
define-data-property@1.1.4:
dependencies:
es-define-property: 1.0.1
es-errors: 1.3.0
gopd: 1.2.0
define-properties@1.2.1:
dependencies:
define-data-property: 1.1.4
has-property-descriptors: 1.0.2
object-keys: 1.1.1
delayed-stream@1.0.0: {}
doctrine@3.0.0:
dependencies:
esutils: 2.0.3
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.1
es-errors: 1.3.0
gopd: 1.2.0
element-plus@2.7.6(vue@3.5.10):
dependencies:
'@ctrl/tinycolor': 3.6.1
@ -1689,6 +1889,14 @@ snapshots:
entities@4.5.0: {}
es-define-property@1.0.1: {}
es-errors@1.3.0: {}
es-object-atoms@1.0.0:
dependencies:
es-errors: 1.3.0
esbuild@0.21.5:
optionalDependencies:
'@esbuild/aix-ppc64': 0.21.5
@ -1822,8 +2030,16 @@ snapshots:
esutils@2.0.3: {}
eventemitter3@2.0.3: {}
extend@3.0.2: {}
fast-deep-equal@3.1.3: {}
fast-diff@1.1.2: {}
fast-diff@1.2.0: {}
fast-diff@1.3.0: {}
fast-glob@3.3.2:
@ -1876,6 +2092,23 @@ snapshots:
fsevents@2.3.3:
optional: true
function-bind@1.1.2: {}
functions-have-names@1.2.3: {}
get-intrinsic@1.2.6:
dependencies:
call-bind-apply-helpers: 1.0.1
dunder-proto: 1.0.1
es-define-property: 1.0.1
es-errors: 1.3.0
es-object-atoms: 1.0.0
function-bind: 1.1.2
gopd: 1.2.0
has-symbols: 1.1.0
hasown: 2.0.2
math-intrinsics: 1.1.0
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
@ -1897,10 +2130,26 @@ snapshots:
dependencies:
type-fest: 0.20.2
gopd@1.2.0: {}
graphemer@1.4.0: {}
has-flag@4.0.0: {}
has-property-descriptors@1.0.2:
dependencies:
es-define-property: 1.0.1
has-symbols@1.1.0: {}
has-tostringtag@1.0.2:
dependencies:
has-symbols: 1.1.0
hasown@2.0.2:
dependencies:
function-bind: 1.1.2
ignore@5.3.1: {}
immutable@4.3.6: {}
@ -1919,10 +2168,20 @@ snapshots:
inherits@2.0.4: {}
is-arguments@1.2.0:
dependencies:
call-bound: 1.0.3
has-tostringtag: 1.0.2
is-binary-path@2.1.0:
dependencies:
binary-extensions: 2.3.0
is-date-object@1.1.0:
dependencies:
call-bound: 1.0.3
has-tostringtag: 1.0.2
is-extglob@2.1.1: {}
is-glob@4.0.3:
@ -1933,6 +2192,13 @@ snapshots:
is-path-inside@3.0.3: {}
is-regex@1.2.1:
dependencies:
call-bound: 1.0.3
gopd: 1.2.0
has-tostringtag: 1.0.2
hasown: 2.0.2
isexe@2.0.0: {}
js-tokens@9.0.0: {}
@ -1973,6 +2239,10 @@ snapshots:
lodash: 4.17.21
lodash-es: 4.17.21
lodash.clonedeep@4.5.0: {}
lodash.isequal@4.5.0: {}
lodash.merge@4.6.2: {}
lodash@4.17.21: {}
@ -1985,6 +2255,8 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
math-intrinsics@1.1.0: {}
memoize-one@6.0.0: {}
merge2@1.4.1: {}
@ -2029,6 +2301,13 @@ snapshots:
dependencies:
boolbase: 1.0.0
object-is@1.1.6:
dependencies:
call-bind: 1.0.8
define-properties: 1.2.1
object-keys@1.1.1: {}
once@1.4.0:
dependencies:
wrappy: 1.0.2
@ -2050,6 +2329,8 @@ snapshots:
dependencies:
p-limit: 3.1.0
parchment@1.1.4: {}
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
@ -2117,10 +2398,38 @@ snapshots:
queue-microtask@1.2.3: {}
quill-delta@3.6.3:
dependencies:
deep-equal: 1.1.2
extend: 3.0.2
fast-diff: 1.1.2
quill-delta@4.2.2:
dependencies:
fast-diff: 1.2.0
lodash.clonedeep: 4.5.0
lodash.isequal: 4.5.0
quill@1.3.7:
dependencies:
clone: 2.1.2
deep-equal: 1.1.2
eventemitter3: 2.0.3
extend: 3.0.2
parchment: 1.1.4
quill-delta: 3.6.3
readdirp@3.6.0:
dependencies:
picomatch: 2.3.1
regexp.prototype.flags@1.5.3:
dependencies:
call-bind: 1.0.8
define-properties: 1.2.1
es-errors: 1.3.0
set-function-name: 2.0.2
resolve-from@4.0.0: {}
reusify@1.0.4: {}
@ -2165,6 +2474,22 @@ snapshots:
semver@7.6.2: {}
set-function-length@1.2.2:
dependencies:
define-data-property: 1.1.4
es-errors: 1.3.0
function-bind: 1.1.2
get-intrinsic: 1.2.6
gopd: 1.2.0
has-property-descriptors: 1.0.2
set-function-name@2.0.2:
dependencies:
define-data-property: 1.1.4
es-errors: 1.3.0
functions-have-names: 1.2.3
has-property-descriptors: 1.0.2
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0

View File

@ -1,7 +1,3 @@
<script setup></script>
<template>
<router-view />
</template>
<style lang="scss" scoped></style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,31 +1,30 @@
<script setup>
defineProps({
title: {
Required: true,
type: String,
}
})
</script>
<template>
<el-card style="height: 100%">
<el-card style="height: 100%; width: 100%;">
<template #header>
<div class="header">
<span>{{ title }}</span>
<div>
<slot name="extra"></slot>
</div>
<div>
<slot name="search"></slot>
</div>
</div>
</template>
<slot></slot>
<div style="height: calc(100% - 48px);">
<slot></slot>
</div>
</el-card>
</template>
<style lang='scss' scoped>
<style lang="scss" scoped>
.header {
height: 30px;
height: auto;
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
:deep().el-card__body {
height: calc(100% - 68px);
padding-bottom: 0;
}
</style>

View File

@ -0,0 +1,54 @@
<script setup>
import { ref } from 'vue'
const imageUrl = ref('')
const onSelectFile = (file) => {
//
imageUrl.value = URL.createObjectURL(file.raw)
articleForm.value.url = file.raw
}
</script>
<template>
<el-upload
:show-file-list="false"
:auto-upload="false"
:on-change="onSelectFile"
class="avatar-uploader"
>
<img v-if="imageUrl" :src="imageUrl" class="avatar" />
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
</template>
<style lang="scss" scoped>
//
.avatar-uploader {
:deep() {
.avatar {
width: 160px;
height: 160px;
display: block;
object-fit: contain;
}
.el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 160px;
height: 160px;
text-align: center;
}
}
}
</style>

View File

@ -11,12 +11,8 @@ const router = createRouter({
{
path: '/',
component: () => import('../views/2_layout/Layout.vue'),
redirect: '/homePage',
redirect: '/hospital',
children: [
{
path: '/homePage',
component: () => import('../views/3_homePage/HomePage.vue')
},
{
path: '/hospital',
component: () => import('../views/4_hospital/Hospital.vue')
@ -32,6 +28,10 @@ const router = createRouter({
{
path: '/userInfo',
component: () => import('../views/7_userInfo/UserInfo.vue')
},
{
path: '/appoint',
component: () => import('../views/8_appoint/Appoint.vue')
}
]
}

View File

@ -14,8 +14,8 @@ export const useUserStore = defineStore(
}
const user = ref({})
const getUser = async() => {
const res = await userGetInfoService()
user.value = res.data.data
// const res = await userGetInfoService()
// user.value = res.data.data
}
const setUser = (newUser) => {
user.value = newUser

View File

@ -131,7 +131,7 @@ const getVerification = async () => {
border-radius: 50%;
margin-top: 16px;
opacity: 0.8;
background: url('@/assets/logo.png') center / cover;
background: url('/logo.png') center / cover;
}
.button {
width: 100%;

View File

@ -1,71 +1,101 @@
<script setup>
import { ref } from 'vue'
import {
Management,
Promotion,
UserFilled,
Avatar,
CirclePlusFilled
CirclePlusFilled,
List,
Expand,
Fold
} from '@element-plus/icons-vue'
import avatar from '@/assets/default.png'
import { useUserStore } from '@/stores'
import { onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElConfigProvider } from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
const userStore = useUserStore()
const isCollapse = ref(false)
//
const handleResize = () => {
if (document.documentElement.clientWidth <= 768) {
isCollapse.value = true
} else {
isCollapse.value = false
}
}
window.addEventListener('resize', handleResize)
//
onMounted(() => {
userStore.getUser()
handleResize()
})
</script>
<template>
<el-container class="layout-container">
<el-aside width="200px">
<div class="el-aside__logo"></div>
<el-menu
class="el-menu"
active-text-color="#409EFF"
background-color="#444"
:default-active="$route.path"
text-color="#fff"
router
>
<el-menu-item index="/homePage">
<el-icon><Promotion /></el-icon>
<span>首页</span>
</el-menu-item>
<el-menu-item index="/hospital">
<el-icon><CirclePlusFilled /></el-icon>
<span>医院管理</span>
</el-menu-item>
<el-menu-item index="/doctor">
<el-icon><Avatar /></el-icon>
<span>医生管理</span>
</el-menu-item>
<el-menu-item index="/article">
<el-icon><Management /></el-icon>
<span>医说管理</span>
</el-menu-item>
<el-menu-item index="/userInfo">
<el-icon><UserFilled /></el-icon>
<span>用户信息管理</span>
</el-menu-item>
</el-menu>
</el-aside>
<el-menu
class="el-menu el-menu-vertical-demo"
:collapse="isCollapse"
:default-active="$route.path"
active-text-color="#409EFF"
background-color="#444"
text-color="#fff"
router
>
<div class="logo"></div>
<el-menu-item index="/hospital">
<el-icon><CirclePlusFilled /></el-icon>
<span>医院管理</span>
</el-menu-item>
<el-menu-item index="/doctor">
<el-icon><Avatar /></el-icon>
<span>医生管理</span>
</el-menu-item>
<el-menu-item index="/article">
<el-icon><Management /></el-icon>
<span>文章管理</span>
</el-menu-item>
<el-menu-item index="/appoint">
<el-icon><List /></el-icon>
<span>预约管理</span>
</el-menu-item>
<el-menu-item index="/userInfo">
<el-icon><UserFilled /></el-icon>
<span>用户信息管理</span>
</el-menu-item>
</el-menu>
<el-container>
<el-header>
<div>
后台管理员<strong>{{ userStore.user.name }}</strong>
<div style="display: flex; font-size: 20px">
<span
@click="isCollapse = !isCollapse"
style="
display: flex;
align-items: center;
margin-right: 16px;
cursor: pointer;
"
>
<el-icon v-if="!isCollapse" size="24"><Fold /></el-icon>
<el-icon v-else size="24"><Expand /></el-icon>
</span>
<strong>后台管理员{{ userStore.user.name || 'admin' }}</strong>
</div>
<el-avatar :src="userStore.user.avatarUrl || avatar" />
</el-header>
<el-main>
<router-view v-slot="{ Component }">
<transition name="scale" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
<el-config-provider :locale="zhCn">
<router-view v-slot="{ Component }">
<transition name="scale" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</el-config-provider>
</el-main>
</el-container>
</el-container>
@ -74,15 +104,17 @@ onMounted(() => {
<style lang="scss" scoped>
.layout-container {
height: 100vh;
.el-aside {
background-color: #444;
&__logo {
height: 120px;
width: 100%;
background: url('@/assets/logo.png') no-repeat center / 80px auto;
}
:deep().el-menu {
border: none;
.logo {
height: 100px;
margin-top: 16px;
margin-bottom: 4px;
background: url('/logo.png') no-repeat center / 50%;
}
:deep() .el-menu {
width: auto;
.el-menu-item {
padding-left: 30px !important;
padding-right: 30px !important;
}
}
.el-header {
@ -111,3 +143,26 @@ onMounted(() => {
}
}
</style>
<!--
<el-menu-item index="/hospital">
<el-icon><CirclePlusFilled /></el-icon>
<template #title>医院管理</template>
</el-menu-item>
<el-menu-item index="/doctor">
<el-icon><Avatar /></el-icon>
<template #title>医生管理</template>
</el-menu-item>
<el-menu-item index="/article">
<el-icon><Management /></el-icon>
<template #title>文章管理</template>
</el-menu-item>
<el-menu-item index="/appoint">
<el-icon><FirstAidKit /></el-icon>
<template #title>预约管理</template>
</el-menu-item>
<el-menu-item index="/userInfo">
<el-icon><UserFilled /></el-icon>
<template #title>用户信息管理</template>
</el-menu-item>
-->

View File

@ -1,13 +0,0 @@
<script setup>
import { ref } from 'vue'
</script>
<template>
<page-container title="首页">
<template #extra>
<el-button type="primary">添加分类</el-button>
</template>
</page-container>
</template>
<style lang="scss" scoped></style>

View File

@ -1,30 +1,557 @@
<script setup>
import { ref } from 'vue'
const hospital = ref([
import { ref, nextTick, useTemplateRef, onMounted } from 'vue'
import { Edit, Delete, Plus } from '@element-plus/icons-vue'
import '@vueup/vue-quill/dist/vue-quill.snow.css'
//
const hospital = ref({
total: 10,
records: [
{
id: 1,
hospitalName: '中南大学湘雅医院',
districtId: 430105,
districtName: '开福区',
cityName: '长沙市',
provinceId: 43,
provinceName: '湖南省',
address: '湖南省长沙市开福区湘雅路87号',
introduction:
'中南大学湘雅医院Xiangya city Central South University创建于1906年坐落在人文荟萃的楚汉名城长沙是中国最早的西医医院之一是国家卫生健康委员会直管的三级甲等综合医院、教育部直属高校中南大学的附属医院。\n建院伊始即将欧美医学最高标准融入办医理念从长沙西牌楼一幢旧房起步筚路蓝缕、精勤进取书写了我国西医发展史的重要篇章。\n新中国成立后在中国共产党的领导下经过几代湘雅人的不懈努力各项事业取得长足发展现已发展成为我国重要的临床诊疗、医学教育与科技创新中心。\n现有编制床位3500张总建筑面积51万平方米。开设临床医疗医技科室和亚专科111个病区76个护理单元101个。拥有国家重点学科7个国家临床重点专科25个。神经内科、神经外科、皮肤科、骨科、呼吸内科、老年病学等专科的诊疗水平和科技影响力位居全国前列是国家老年疾病临床医学研究中心。具备医学本科生、研究生、进修生、住院医师规范化培训等完整的学位教育和继续教育教学体系。2020年6月入选湖南省开展新冠病毒核酸检测的医疗卫生机构名单。2021年8月中南大学湘雅医院骨科被评选为第20届全国青年文明号。',
phone: '0746-4356788',
level: '三甲',
url: '',
departmentList: [
{ id: 1, name: '呼吸内科', belong: '内科' },
{ id: 2, name: '内分泌科', belong: '内科' }
]
}
]
})
onMounted(async () => {
// const res = await getdepatmentList()
})
//
const search = ref('')
const searchhospital = async () => {
console.log(search.value)
}
//
const pageSize = ref(5)
const currentPage = ref(1)
const handleCurrentChange = (val) => {
currentPage.value = val
}
const handleSizeChange = (val) => {
pageSize.value = val
}
//
const deletehospital = async (row) => {
console.log(row.id)
ElMessage.success('删除成功')
}
//
const hospitalIds = ref([])
const select = (val) => {
hospitalIds.value = val.map((item) => item.id)
}
const deletehospitals = async () => {
ElMessage.success('批量删除成功')
}
//
const drawerVisible = ref(false)
const add = ref(false)
const hospitalForm = ref({})
const formRef = useTemplateRef('hospitalFormRef')
const show = ref(true)
const saveOrUpdate = async (row) => {
drawerVisible.value = true
hospitalForm.value = { ...row }
if (row) {
hospitalForm.value.cityInfo =
row.provinceName + '/' + row.cityName + '/' + row.districtName
} else {
add.value = true
imageUrl.value = ''
}
}
const confirm = async () => {
await formRef.value.validate()
drawerVisible.value = false
if (add.value) {
add.value = false
ElMessage.success('添加成功')
} else {
ElMessage.success('修改成功')
}
}
//
const levelList = ref([
{ text: '内科', value: '内科' },
{ text: '外科', value: '外科' },
{ text: '儿科', value: '儿科' },
{ text: '妇产科', value: '妇产科' },
{ text: '眼科', value: '眼科' }
])
const levelFilter = (value, row) => {
return row.level === value
}
//
const imageUrl = ref('')
const onSelectFile = (file) => {
//
imageUrl.value = URL.createObjectURL(file.raw)
hospitalForm.value.url = file.raw
}
//
const cityChoose = ref([
{
id:1,
name:'12',
alias:'123',
value: '43',
label: '湖南省',
children: [
{
value: '4301',
label: '长沙市',
children: [
{
value: '430101-岳麓区',
label: '岳麓区'
},
{
value: '430102-天心区',
label: '天心区'
},
{
value: '430103-开福区',
label: '开福区'
},
{
value: '430104-芙蓉区',
label: '芙蓉区'
}
]
}
]
},
{
value: '42',
label: '湖北省',
children: [
{
value: '4201',
label: '武汉市',
children: [
{
value: '420101-武昌区',
label: '武昌区'
},
{
value: '420102-洪山区',
label: '洪山区'
}
]
}
]
}
])
const cityChange = (val) => {
const cityId = val[2].split('-')[0]
const cityName = val[2].split('-')[1]
console.log(cityId, cityName)
}
const levelChange = (val) => {
hospitalForm.value.type = val
console.log(val)
}
//
const dialogVisible = ref(false)
const departmentAdd = ref(false)
const options = ref([
{
value: '内科',
label: '内科',
children: [
{
value: '呼吸内科',
label: '呼吸内科'
},
{
value: '消化内科',
label: '消化内科'
}
]
},
{
value: '外科',
label: '外科',
children: [
{
value: '神经外科',
label: '神经外科'
},
{
value: '肝胆外科',
label: '肝胆外科'
}
]
}
])
///
const deletedoctorDepartment = (row) => {
console.log(row.id)
ElMessage.success('删除成功')
}
///
const selectDepartment = ref([])
const departmentChange = (val) => {
selectDepartment.value = val
console.log(val)
}
const saveDepartment = async (row) => {
dialogVisible.value = true
if (!row) {
departmentAdd.value = true
}
}
const department = async () => {
dialogVisible.value = false
if (departmentAdd.value) {
ElMessage.success('添加成功')
departmentAdd.value = false
} else {
ElMessage.success('修改成功')
}
}
//
const inputValue = ref('')
const dynamicTags = ref(['综合医院', '专科医院', '社区医院'])
const inputVisible = ref(false)
const InputRef = useTemplateRef('InputRef')
const handleClose = (tag) => {
dynamicTags.value.splice(dynamicTags.value.indexOf(tag), 1)
}
const handleInputConfirm = () => {
if (inputValue.value) {
dynamicTags.value.push(inputValue.value)
}
inputVisible.value = false
inputValue.value = ''
}
const showInput = () => {
inputVisible.value = true
nextTick(() => {
InputRef.value.input.focus()
})
}
</script>
<template>
<page-container title="医院管理">
<page-container>
<template #extra>
<el-button type="primary">添加分类</el-button>
<el-button @click="saveOrUpdate(null)" type="primary">
添加医院
</el-button>
<el-button @click="deletehospitals" type="danger"> 批量删除 </el-button>
</template>
<el-table :data="hospital" stripe>
<el-table-column prop="id" label="序号"></el-table-column>
<el-table-column prop="name" label="分类名称"></el-table-column>
<el-table-column prop="alias" label="分类别名"></el-table-column>
<el-table-column prop="operate" label="操作">
<template #default="{row}">
<el-button type="primary" size="small">
编辑
<template #search>
<el-cascader
@change="cityChange"
placeholder="请选择辖区"
filterable
clearable
style="width: fit-content; margin-right: 16px"
:options="cityChoose"
/>
<el-input
v-model="search"
placeholder="搜索医院名称"
clearable
@keyup.enter="searchhospital"
style="width: 220px"
>
</el-input>
</template>
<el-drawer
v-model="drawerVisible"
title="编辑或增加"
size="50%"
@closed="show = true"
>
<el-form
ref="hospitalFormRef"
:model="hospitalForm"
label-width="auto"
inline
label-position="left"
style="padding: 0 24px"
>
<el-form-item
label="医院名称"
:rules="[
{ required: true, message: '请输入医院名称', trigger: 'blur' }
]"
prop="hospitalName"
>
<el-input
placeholder="请输入医院名称"
v-model="hospitalForm.hospitalName"
/>
</el-form-item>
<el-form-item
label="所在辖区"
:rules="[
{ required: true, message: '请选择所在辖区', trigger: 'blur' }
]"
prop="cityInfo"
>
<el-select
v-if="hospitalForm.cityInfo && show"
v-model="hospitalForm.cityInfo"
@click="show = false"
/>
<el-cascader
v-else
@change="cityChange"
placeholder="请选择辖区"
filterable
style="width: fit-content"
:options="cityChoose"
/>
</el-form-item>
<el-form-item
label="医院地址"
:rules="[
{ required: true, message: '请输入医院地址', trigger: 'blur' }
]"
prop="address"
>
<el-input
placeholder="请输入医院地址"
v-model="hospitalForm.address"
/>
</el-form-item>
<el-form-item
label="等&emsp;&emsp;级"
:rules="[
{ required: true, message: '请选择医院等级', trigger: 'blur' }
]"
prop="level"
>
<el-select
v-model="hospitalForm.level"
placeholder="请选择医院等级"
@change="levelChange"
>
<el-option v-for="item in levelList" :key="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item
label="医院电话"
prop="phone"
:rules="[
{ required: true, message: '请输入医院电话', trigger: 'blur' }
]"
>
<el-input placeholder="请输入医院电话" v-model="hospitalForm.phone" />
</el-form-item>
<el-form-item
label="医院照片"
:rules="[{ required: true, message: '请上传图片', trigger: 'blur' }]"
prop="url"
style="width: 100%"
>
<el-upload
:show-file-list="false"
:auto-upload="false"
:on-change="onSelectFile"
class="avatar-uploader"
>
<img
v-if="imageUrl || hospitalForm.url"
:src="imageUrl || hospitalForm.url"
class="avatar"
/>
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
</el-form-item>
<el-form-item
label="医院简介"
style="width: 100%"
:rules="[
{ required: true, message: '请输入医院简介', trigger: 'blur' }
]"
>
<el-input
placeholder="请输入医院简介"
type="textarea"
:rows="10"
v-model="hospitalForm.introduction"
/>
</el-form-item>
</el-form>
<template #footer>
<div style="width: 100%; display: flex; justify-content: space-evenly">
<el-button @click="drawerVisible = false"> 取消 </el-button>
<el-button type="primary" @click="confirm()"> 确定 </el-button>
</div>
</template>
</el-drawer>
<el-dialog v-model="dialogVisible" title="增加" width="400">
<el-cascader-panel
@change="departmentChange"
style="width: fit-content"
:options="options"
/>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false"> 取消 </el-button>
<el-button type="primary" @click="department()">确定 </el-button>
</div>
</template>
</el-dialog>
<el-table
stripe
:data="hospital.records"
height="100%"
@selection-change="select"
>
<el-table-column type="selection" width="50" />
<el-table-column type="expand">
<template #default="{ row }">
<div class="expand-content">
<div>
<h3 style="margin-bottom: 8px">医院简介</h3>
<p
v-html="
row.introduction
.replace(/\n/g, '<br/>')
.replace(/\t/g, '&emsp;')
"
></p>
</div>
<div style="display: flex">
<h3 style="margin-bottom: 20px">标签</h3>
<el-tag
v-for="tag in dynamicTags"
:key="tag"
closable
style="margin-right: 8px"
:disable-transitions="false"
@close="handleClose(tag)"
>
{{ tag }}
</el-tag>
<el-input
v-if="inputVisible"
ref="InputRef"
v-model="inputValue"
size="small"
style="height: 100%; width: 82px"
@keyup.enter="handleInputConfirm"
@blur="handleInputConfirm"
/>
<el-button v-else size="small" @click="showInput">
+ 添加科室
</el-button>
</div>
<div style="display: flex">
<h3 style="margin-bottom: 8px">科室列表</h3>
<el-button
size="small"
@click="saveDepartment(null)"
type="primary"
>
添加科室
</el-button>
</div>
<el-table :data="row.departmentList" max-height="300">
<el-table-column prop="id" label="id" show-overflow-tooltip />
<el-table-column
prop="belong"
label="科室"
show-overflow-tooltip
/>
<el-table-column
prop="name"
label="具体科室"
show-overflow-tooltip
/>
<el-table-column label="删除" width="108">
<template #default="{ row }">
<el-button
plain
type="danger"
size="small"
@click="deletedoctorDepartment(row)"
>
<el-icon><Delete /></el-icon>
</el-button>
</template>
</el-table-column>
<template #empty>
<el-empty description="没有数据" />
</template>
</el-table>
</div>
</template>
</el-table-column>
<el-table-column
prop="id"
label="id"
show-overflow-tooltip
width="60px"
/>
<el-table-column width="100px" label="图片">
<template #default>
<el-image :src="hospital.url || '/logo.png'" />
</template>
</el-table-column>
<el-table-column
prop="hospitalName"
label="医院名称"
show-overflow-tooltip
/>
<el-table-column prop="cityName" label="所在市" show-overflow-tooltip>
</el-table-column>
<el-table-column prop="address" label="地址" show-overflow-tooltip />
<el-table-column
prop="level"
label="等级"
:filters="levelList"
:filter-method="levelFilter"
show-overflow-tooltip
>
<template #default="scope">
<span>{{ scope.row.level }}</span>
</template>
</el-table-column>
<el-table-column prop="phone" label="电话" show-overflow-tooltip />
<el-table-column label="编辑/删除" width="108" fixed="right">
<template #default="{ row }">
<el-button
plain
type="primary"
size="small"
@click="saveOrUpdate(row)"
>
<el-icon><Edit /></el-icon>
</el-button>
<el-button type="danger" size="small">
删除
<el-button
plain
type="danger"
size="small"
@click="deletehospital(row)"
>
<el-icon><Delete /></el-icon>
</el-button>
</template>
</el-table-column>
@ -32,7 +559,67 @@ const hospital = ref([
<el-empty description="没有数据" />
</template>
</el-table>
<el-pagination
v-model:page-size="pageSize"
layout="total, prev, pager, next"
:total="+hospital.total"
style="height: 48px; justify-content: center"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</page-container>
</template>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.el-form-item {
width: 290px;
}
.expand-content {
display: flex;
width: 100%;
flex-direction: column;
padding: 16px;
p {
border-radius: 12px;
line-height: 1.5;
background: #f5f5f5;
padding: 24px;
margin-bottom: 24px;
}
.el-table {
width: 50%;
}
}
//
.avatar-uploader {
:deep() {
.avatar {
width: 160px;
height: 160px;
display: block;
object-fit: contain;
}
.el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 160px;
height: 160px;
text-align: center;
}
}
}
</style>

View File

@ -1,23 +1,524 @@
<script setup>
import { ref } from 'vue'
const cate_data = ref([])
import { ref, useTemplateRef, onMounted } from 'vue'
import { Edit, Delete, Plus } from '@element-plus/icons-vue'
import '@vueup/vue-quill/dist/vue-quill.snow.css'
//
const doctor = ref({
total: 10,
records: [
{
id: 1,
doctorName: '王强',
level: '主任医师',
expertise: '内分泌科专家',
source: '中南大学湘雅医院博士生导师',
department: '内分泌科',
briefly:
'内分泌专家,甲状腺综合手术治疗专家,省级内分泌科委员会委员,独创中医疗法,内分泌科疑难杂症综合治疗',
url: '',
hospitalInfo: {
id: 1,
hospitalName: '中南大学湘雅医院'
},
appointmentTimeList: [
{
id: 1,
fee: 100,
date: '2024-12-23'
}
]
}
]
})
onMounted(async () => {
// const res = await getdepatmentList()
hospitalList.value = doctor.value.records.map(
(item) => item.hospitalInfo.id + '. ' + item.hospitalInfo.hospitalName
)
})
//
const search = ref('')
const searchdoctor = async () => {
console.log(search.value)
}
//
const pageSize = ref(5)
const currentPage = ref(1)
const handleCurrentChange = (val) => {
currentPage.value = val
}
const handleSizeChange = (val) => {
pageSize.value = val
}
//
const deletedoctor = async (row) => {
console.log(row.id)
ElMessage.success('删除成功')
}
//
const doctorIds = ref([])
const select = (val) => {
doctorIds.value = val.map((item) => item.id)
}
const deletedoctors = async () => {
ElMessage.success('批量删除成功')
}
//
const drawerVisible = ref(false)
const add = ref(false)
const doctorForm = ref({})
const saveOrUpdate = async (row) => {
drawerVisible.value = true
doctorForm.value = { ...row }
if (row) {
doctorForm.value.hospital =
row.hospitalInfo.id + '. ' + row.hospitalInfo.hospitalName
} else {
add.value = true
imageUrl.value = ''
}
}
const formRef = useTemplateRef('doctorFormRef')
const confirm = async () => {
await formRef.value.validate()
drawerVisible.value = false
if (add.value) {
add.value = false
ElMessage.success('添加成功')
} else {
ElMessage.success('修改成功')
}
}
//
const departmentList = ref([
{ text: '内科', value: '内科' },
{ text: '外科', value: '外科' },
{ text: '儿科', value: '儿科' },
{ text: '妇产科', value: '妇产科' },
{ text: '眼科', value: '眼科' }
])
const departmentFilter = (value, row) => {
return row.department === value
}
const hospitalCategoryList = ref([
{ text: '北京协和医院', value: '北京协和医院' },
{ text: '北京儿童医院', value: '北京儿童医院' }
])
const hospitalFilter = (value, row) => {
return row.hospitalInfo.hospitalName === value
}
//
const imageUrl = ref('')
const onSelectFile = (file) => {
//
imageUrl.value = URL.createObjectURL(file.raw)
doctorForm.value.url = file.raw
}
//
const hospitalList = ref([])
const hospitalChange = (val) => {
// -id
const id = val.split('. ')[0]
doctorForm.value.hospitalInfo = { id, hospitalName: val.split('. ')[1] }
console.log(id)
}
const departmentChange = (val) => {
doctorForm.value.type = val
console.log(val)
}
//
const dialogVisible = ref(false)
const appointForm = ref({})
const appointAdd = ref(false)
///
const deletedoctorAppoint = (row) => {
console.log(row.id)
ElMessage.success('删除成功')
}
///
const saveOrUpdateAppoint = async (row) => {
dialogVisible.value = true
appointForm.value = { ...row }
console.log(row)
if (!row) {
appointAdd.value = true
}
}
const appointRef = useTemplateRef('appointRef')
const appoint = async () => {
await appointRef.value.validate()
dialogVisible.value = false
if (appointAdd.value) {
ElMessage.success('添加成功')
appointAdd.value = false
} else {
ElMessage.success('修改成功')
}
}
</script>
<template>
<page-container title="医生管理">
<page-container>
<template #extra>
<el-button type="primary">添加分类</el-button>
<el-button @click="saveOrUpdate(null)" type="primary">
添加医院
</el-button>
<el-button @click="deletedoctors" type="danger"> 批量删除 </el-button>
</template>
<el-table :data="cate_data">
<el-table-column prop="id" label="序号"></el-table-column>
<el-table-column prop="cate_name" label="分类名称"></el-table-column>
<el-table-column prop="cate_alias" label="分类别名"></el-table-column>
<el-table-column prop="operate" label="操作"></el-table-column>
<template #search>
<el-input
v-model="search"
placeholder="请输入医生名称"
clearable
@keyup.enter="searchdoctor"
style="width: 250px"
>
</el-input>
</template>
<!-- 医生弹出层 -->
<el-drawer v-model="drawerVisible" title="编辑或增加" size="50%">
<el-form
ref="doctorFormRef"
:model="doctorForm"
label-width="auto"
inline
label-position="left"
style="padding: 0 24px"
>
<el-form-item
label="医生姓名"
:rules="[
{ required: true, message: '请输入医生姓名', trigger: 'blur' }
]"
prop="doctorName"
>
<el-input
placeholder="请输入医生姓名"
v-model="doctorForm.doctorName"
/>
</el-form-item>
<el-form-item
label="职&emsp;&emsp;称"
:rules="[
{ required: true, message: '请输入医生职称', trigger: 'blur' }
]"
prop="level"
>
<el-input placeholder="请输入医生职称" v-model="doctorForm.level" />
</el-form-item>
<el-form-item
label="头&emsp;&emsp;衔"
:rules="[
{ required: true, message: '请输入医生学历', trigger: 'blur' }
]"
prop="source"
>
<el-input placeholder="请输入医生学历" v-model="doctorForm.source" />
</el-form-item>
<el-form-item label="&nbsp;&nbsp;擅长领域" prop="expertise">
<el-input
placeholder="请输入医生头衔"
v-model="doctorForm.expertise"
/>
</el-form-item>
<el-form-item
label="所属科室"
:rules="[{ required: true, message: '请选择医生', trigger: 'blur' }]"
prop="department"
>
<el-select
v-model="doctorForm.department"
placeholder="请选择科室"
@change="departmentChange"
>
<el-option
v-for="item in departmentList"
:key="item.value"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item
label="所属医院"
:rules="[{ required: true, message: '请选择医院', trigger: 'blur' }]"
prop="hospital"
>
<el-select
v-model="doctorForm.hospital"
placeholder="请选择医院"
@change="hospitalChange"
>
<el-option v-for="item in hospitalList" :key="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item
label="医生照片"
:rules="[{ required: true, message: '请上传图片', trigger: 'blur' }]"
prop="url"
>
<el-upload
:show-file-list="false"
:auto-upload="false"
:on-change="onSelectFile"
class="avatar-uploader"
>
<img
v-if="imageUrl || doctorForm.url"
:src="imageUrl || doctorForm.url"
class="avatar"
/>
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
</el-form-item>
<el-form-item
label="医生简介"
style="width: 100%"
:rules="[
{ required: true, message: '请输入医生简介', trigger: 'blur' }
]"
>
<el-input
placeholder="请输入医生简介"
type="textarea"
:rows="3"
v-model="doctorForm.introduction"
/>
</el-form-item>
</el-form>
<template #footer>
<div style="width: 100%; display: flex; justify-content: space-evenly">
<el-button @click="drawerVisible = false"> 取消 </el-button>
<el-button type="primary" @click="confirm()"> 确定 </el-button>
</div>
</template>
</el-drawer>
<!-- 预约弹出框 -->
<el-dialog v-model="dialogVisible" title="编辑或增加" width="400">
<el-form
ref="appointRef"
:model="appointForm"
label-width="100px"
style="width: 350px"
>
<el-form-item
label="日期"
prop="date"
:rules="{ required: true, message: '请输入日期', trigger: 'blur' }"
>
<el-input placeholder="请输入日期" v-model="appointForm.date" />
</el-form-item>
<el-form-item
label="费用"
prop="fee"
:rules="{ required: true, message: '请输入费用', trigger: 'blur' }"
>
<el-input placeholder="请输入费用" v-model="appointForm.fee" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogVisible = false"> 取消 </el-button>
<el-button type="primary" @click="appoint()">确定 </el-button>
</div>
</template>
</el-dialog>
<el-table
stripe
:data="doctor.records"
height="100%"
@selection-change="select"
>
<el-table-column type="selection" width="50" />
<el-table-column type="expand">
<template #default="{ row }">
<div class="expand-content">
<h3 style="margin-bottom: 8px">医生简介</h3>
<p
v-html="
row.briefly.replace(/\n/g, '<br/>').replace(/\t/g, '&emsp;')
"
></p>
<!-- 预约列表 -->
<div style="display: flex">
<h3 style="margin-bottom: 8px">预约列表</h3>
<el-button
size="small"
@click="saveOrUpdateAppoint(null)"
type="primary"
>
添加预约
</el-button>
</div>
<el-table :data="row.appointmentTimeList" max-height="300">
<el-table-column prop="id" label="id" show-overflow-tooltip />
<el-table-column prop="date" label="日期" show-overflow-tooltip />
<el-table-column prop="fee" label="费用" show-overflow-tooltip />
<el-table-column label="编辑/删除" width="108">
<template #default="{ row }">
<el-button
plain
type="primary"
size="small"
@click="saveOrUpdateAppoint(row)"
>
<el-icon><Edit /></el-icon>
</el-button>
<el-button
plain
type="danger"
size="small"
@click="deletedoctorAppoint(row)"
>
<el-icon><Delete /></el-icon>
</el-button>
</template>
</el-table-column>
<template #empty>
<el-empty description="没有数据" />
</template>
</el-table>
</div>
</template>
</el-table-column>
<el-table-column
prop="id"
label="id"
show-overflow-tooltip
width="60px"
/>
<el-table-column width="100px" label="图片">
<template #default>
<el-image :src="doctor.url || '/logo.png'" />
</template>
</el-table-column>
<el-table-column
prop="doctorName"
label="医生姓名"
show-overflow-tooltip
/>
<el-table-column prop="level" label="职称" show-overflow-tooltip />
<el-table-column prop="source" label="头衔" show-overflow-tooltip />
<el-table-column
prop="expertise"
label="擅长领域"
show-overflow-tooltip
/>
<el-table-column
prop="department"
label="所属科室"
:filters="departmentList"
:filter-method="departmentFilter"
show-overflow-tooltip
>
<template #default="scope">
<span>{{ scope.row.department }}</span>
</template>
</el-table-column>
<el-table-column
prop="hospitalInfo.hospitalName"
label="所属医院"
:filters="hospitalCategoryList"
:filter-method="hospitalFilter"
show-overflow-tooltip
>
<template #default="scope">
<span>{{ scope.row.hospitalInfo.hospitalName }}</span>
</template>
</el-table-column>
<el-table-column label="编辑/删除" width="108" fixed="right">
<template #default="{ row }">
<el-button
plain
type="primary"
size="small"
@click="saveOrUpdate(row)"
>
<el-icon><Edit /></el-icon>
</el-button>
<el-button
plain
type="danger"
size="small"
@click="deletedoctor(row)"
>
<el-icon><Delete /></el-icon>
</el-button>
</template>
</el-table-column>
<template #empty>
<el-empty description="没有数据" />
</template>
</el-table>
<el-pagination
v-model:page-size="pageSize"
layout="total, prev, pager, next"
:total="+doctor.total"
style="height: 48px; justify-content: center"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</page-container>
</template>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.el-form-item {
width: 290px;
}
.expand-content {
display: flex;
width: 100%;
flex-direction: column;
padding: 16px;
p {
border-radius: 12px;
line-height: 1.5;
background: #f5f5f5;
padding: 24px;
margin-bottom: 24px;
}
.el-table {
width: 50%;
}
}
//
.avatar-uploader {
:deep() {
.avatar {
width: 160px;
height: 160px;
display: block;
object-fit: contain;
}
.el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 160px;
height: 160px;
text-align: center;
}
}
}
</style>

View File

@ -1,23 +1,402 @@
<script setup>
import { ref } from 'vue'
const cate_data = ref([])
import { ref, useTemplateRef, onMounted } from 'vue'
import { Edit, Delete, Plus } from '@element-plus/icons-vue'
import { QuillEditor } from '@vueup/vue-quill'
import '@vueup/vue-quill/dist/vue-quill.snow.css'
//
const article = ref({
total: 10,
records: [
{
id: 1,
titleBig: '空腹能不能吃汤圆?无糖汤圆不“胖人”吗?',
titleSmall: '元宵吃汤圆,有什么禁忌吗?',
content:
'\t空腹不建议吃汤圆因为汤圆主要成分是糯米糯米较难消化空腹食用可能会刺激胃酸分泌导致胃部不适。另外汤圆通常含有较多的糖分和油脂空腹食用可能会引起血糖快速上升对糖尿病患者或血糖控制不佳的人尤其不利。\r\n\r\n\t至于无糖汤圆虽然去掉了糖分但糯米本身和其他配料如豆沙、芝麻等仍含有较高的热量和脂肪。因此无糖汤圆并不能完全做到“不胖人”。如果担心体重问题即使是无糖汤圆也应适量食用不宜过量。同时要注意汤圆的整体热量摄入合理安排饮食配合适当的运动才能更好地控制体重。\r\n\r\n\t元宵节吃汤圆是中国传统习俗但在享受这一美食时也需要注意以下禁忌\r\n\r\n\t糖尿病患者和血糖控制不佳者汤圆含有较多糖分和淀粉即使是无糖汤圆也含有较高的碳水化合物可能会引起血糖快速升高应尽量少吃或不吃。\r\n消化系统疾病患者汤圆的外皮主要由糯米制成糯米较难消化患有胃病、胃溃疡、胃炎、消化不良等疾病的人应谨慎食用。\r\n\r\n\t1、老年人老年人的消化功能相对较弱应适量食用避免过量导致消化不良。\r\n\t2、婴幼儿婴幼儿的消化系统尚未完全成熟汤圆不易咀嚼和消化且含有较多的糖和油脂不适合婴幼儿食用。\r\n\t3、肥胖和正在减肥的人汤圆热量较高应适量食用以免影响减肥效果。\r\n\t4、食用时不宜过快汤圆应慢慢咀嚼以免噎到。\r\n\t5、不宜空腹食用空腹吃汤圆可能会刺激胃酸分泌导致胃部不适。\r\n\t6、注意汤圆的食用温度汤圆应煮熟后食用不宜过热以免烫伤口腔和食道。\r\n\t7、不宜与油腻食物同食汤圆本身较油腻不宜与其他油腻食物同食以免加重消化负担。\r\n\t8、注意食品卫生自制汤圆时应确保食材新鲜煮熟煮透购买现成的汤圆时应选择信誉良好的品牌和商家。\r\n\r\n\t遵循以上禁忌可以更健康地享受汤圆带来的节日氛围。',
type: '健康养生',
publishTime: '2023-02-01',
doctorInfo: {
id: 1,
doctorName: '王强'
},
url: '/logo.png'
},
{
id: 2,
titleBig: '空腹能不能吃汤圆?无糖汤圆不“胖人”吗?',
titleSmall: '元宵吃汤圆,有什么禁忌吗?',
content:
'\t空腹不建议吃汤圆因为汤圆主要成分是糯米糯米较难消化空腹食用可能会刺激胃酸分泌导致胃部不适。另外汤圆通常含有较多的糖分和油脂空腹食用可能会引起血糖快速上升对糖尿病患者或血糖控制不佳的人尤其不利。\r\n\r\n\t至于无糖汤圆虽然去掉了糖分但糯米本身和其他配料如豆沙、芝麻等仍含有较高的热量和脂肪。因此无糖汤圆并不能完全做到“不胖人”。如果担心体重问题即使是无糖汤圆也应适量食用不宜过量。同时要注意汤圆的整体热量摄入合理安排饮食配合适当的运动才能更好地控制体重。\r\n\r\n\t元宵节吃汤圆是中国传统习俗但在享受这一美食时也需要注意以下禁忌\r\n\r\n\t糖尿病患者和血糖控制不佳者汤圆含有较多糖分和淀粉即使是无糖汤圆也含有较高的碳水化合物可能会引起血糖快速升高应尽量少吃或不吃。\r\n消化系统疾病患者汤圆的外皮主要由糯米制成糯米较难消化患有胃病、胃溃疡、胃炎、消化不良等疾病的人应谨慎食用。\r\n\r\n\t1、老年人老年人的消化功能相对较弱应适量食用避免过量导致消化不良。\r\n\t2、婴幼儿婴幼儿的消化系统尚未完全成熟汤圆不易咀嚼和消化且含有较多的糖和油脂不适合婴幼儿食用。\r\n\t3、肥胖和正在减肥的人汤圆热量较高应适量食用以免影响减肥效果。\r\n\t4、食用时不宜过快汤圆应慢慢咀嚼以免噎到。\r\n\t5、不宜空腹食用空腹吃汤圆可能会刺激胃酸分泌导致胃部不适。\r\n\t6、注意汤圆的食用温度汤圆应煮熟后食用不宜过热以免烫伤口腔和食道。\r\n\t7、不宜与油腻食物同食汤圆本身较油腻不宜与其他油腻食物同食以免加重消化负担。\r\n\t8、注意食品卫生自制汤圆时应确保食材新鲜煮熟煮透购买现成的汤圆时应选择信誉良好的品牌和商家。\r\n\r\n\t遵循以上禁忌可以更健康地享受汤圆带来的节日氛围。',
type: '健康饮食',
publishTime: '2023-02-01',
doctorInfo: {
id: 2,
doctorName: '李康'
},
url: ''
}
]
})
const doctorList = ref()
onMounted(async () => {
// const res = await getDoctorList()
doctorList.value = article.value.records.map(
(item) => item.doctorInfo.id + '. ' + item.doctorInfo.doctorName
)
})
//
const search = ref('')
const searchArticle = async () => {
console.log(search.value)
}
//
const pageSize = ref(5)
const currentPage = ref(1)
const handleCurrentChange = (val) => {
currentPage.value = val
}
const handleSizeChange = (val) => {
pageSize.value = val
}
//
const deleteArticle = async (row) => {
console.log(row.id)
ElMessage.success('删除成功')
}
//
const articleIds = ref([])
const select = (val) => {
articleIds.value = val.map((item) => item.id)
}
const deleteArticles = async () => {
ElMessage.success('批量删除成功')
}
//
const drawerVisible = ref(false)
const add = ref(false)
const articleForm = ref({})
const saveOrUpdate = async (row) => {
drawerVisible.value = true
articleForm.value = { ...row }
if (row) {
articleForm.value.doctor =
row.doctorInfo.id + '. ' + row.doctorInfo.doctorName
} else {
add.value = true
imageUrl.value = ''
articleForm.value.content = '\n'
}
}
const formRef = useTemplateRef('articleFormRef')
const confirm = async () => {
await formRef.value.validate()
drawerVisible.value = false
if (add.value) {
add.value = false
ElMessage.success('添加成功')
} else {
ElMessage.success('修改成功')
}
}
//
const categoryList = ref([
{ text: '抗疫专区', value: '1' },
{ text: '健康养生', value: '2' },
{ text: '医疗动态', value: '3' },
{ text: '育儿专区', value: '4' }
])
const filter = (value, row) => {
return row.type === value
}
//
const imageUrl = ref('')
const onSelectFile = (file) => {
//
imageUrl.value = URL.createObjectURL(file.raw)
articleForm.value.url = file.raw
}
//
const doctorChange = (val) => {
// -id
const id = val.split('. ')[0]
articleForm.value.doctorInfo = { id, doctorName: val.split('. ')[1] }
console.log(id)
}
const typeChange = (val) => {
articleForm.value.type = val
console.log(val)
}
</script>
<template>
<page-container title="医说管理">
<page-container>
<template #extra>
<el-button type="primary">添加分类</el-button>
<el-button @click="saveOrUpdate(null)" type="primary">
添加医院
</el-button>
<el-button @click="deleteArticles" type="danger"> 批量删除 </el-button>
</template>
<el-table :data="cate_data">
<el-table-column prop="id" label="序号"></el-table-column>
<el-table-column prop="cate_name" label="分类名称"></el-table-column>
<el-table-column prop="cate_alias" label="分类别名"></el-table-column>
<el-table-column prop="operate" label="操作"></el-table-column>
<template #search>
<el-input
v-model="search"
placeholder="请输入文章名称"
clearable
@keyup.enter="searchArticle"
style="width: 250px"
>
</el-input>
</template>
<el-drawer v-model="drawerVisible" title="编辑或增加" size="50%">
<el-form
ref="articleFormRef"
:model="articleForm"
label-width="auto"
inline
label-position="left"
style="padding: 0 24px"
>
<el-form-item
label="标&emsp;&emsp;题"
:rules="[{ required: true, message: '请输入标题', trigger: 'blur' }]"
prop="titleBig"
>
<el-input placeholder="请输入标题" v-model="articleForm.titleBig" />
</el-form-item>
<el-form-item
label="副&ensp;标&ensp;题"
:rules="[
{ required: true, message: '请输入副标题', trigger: 'blur' }
]"
prop="titleSmall"
>
<el-input
placeholder="请输入副标题"
v-model="articleForm.titleSmall"
/>
</el-form-item>
<el-form-item
label="所属医生"
:rules="[{ required: true, message: '请选择医生', trigger: 'blur' }]"
prop="doctor"
>
<el-select
v-model="articleForm.doctor"
placeholder="请选择医生"
@change="doctorChange"
>
<el-option v-for="item in doctorList" :key="item" :value="item" />
</el-select>
</el-form-item>
<el-form-item
label="文章分类"
:rules="[{ required: true, message: '请选择分类', trigger: 'blur' }]"
prop="type"
>
<el-select
v-model="articleForm.type"
placeholder="请选择分类"
@change="typeChange"
>
<el-option
v-for="item in categoryList"
:key="item.value"
:label="item.text"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item
label="文章图片"
:rules="[{ required: true, message: '请上传图片', trigger: 'blur' }]"
prop="url"
>
<el-upload
:show-file-list="false"
:auto-upload="false"
:on-change="onSelectFile"
class="avatar-uploader"
>
<img
v-if="imageUrl || articleForm.url"
:src="imageUrl || articleForm.url"
class="avatar"
/>
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
<!-- <UploadImage @onSelectFile="onSelectFile" /> -->
</el-form-item>
<el-form-item
label="文章内容"
:rules="[{ required: true, message: '请输入内容', trigger: 'blur' }]"
prop="content"
style="width: 100%"
>
<div class="editor">
<quillEditor
theme="snow"
v-model:content="articleForm.content"
contentType="html"
/>
</div>
</el-form-item>
</el-form>
<template #footer>
<div style="width: 100%; display: flex; justify-content: space-evenly">
<el-button @click="drawerVisible = false"> 取消 </el-button>
<el-button type="primary" @click="confirm()"> 确定 </el-button>
</div>
</template>
</el-drawer>
<el-table
stripe
:data="article.records"
height="100%"
@selection-change="select"
>
<el-table-column type="selection" width="50" />
<el-table-column type="expand">
<template #default="{ row }">
<div class="expand-content">
<h3 style="margin-bottom: 8px">文章内容</h3>
<p
v-html="
row.content.replace(/\n/g, '<br/>').replace(/\t/g, '&emsp;')
"
></p>
</div>
</template>
</el-table-column>
<el-table-column
prop="id"
label="id"
show-overflow-tooltip
width="60px"
/>
<el-table-column width="100px" label="图片">
<template #default>
<el-image :src="'/logo.png'" />
</template>
</el-table-column>
<el-table-column prop="titleBig" label="标题" show-overflow-tooltip />
<el-table-column prop="titleSmall" label="副标题" show-overflow-tooltip />
<el-table-column
prop="type"
label="分类"
:filters="categoryList"
:filter-method="filter"
show-overflow-tooltip
>
<template #default="scope">
<span>{{ scope.row.type }}</span>
</template>
</el-table-column>
<el-table-column
prop="doctorInfo.doctorName"
label="作者"
show-overflow-tooltip
/>
<el-table-column
prop="publishTime"
label="发布时间"
show-overflow-tooltip
/>
<el-table-column label="编辑/删除" width="108" fixed="right">
<template #default="{ row }">
<el-button
plain
type="primary"
size="small"
@click="saveOrUpdate(row)"
>
<el-icon><Edit /></el-icon>
</el-button>
<el-button
plain
type="danger"
size="small"
@click="deleteArticle(row)"
>
<el-icon><Delete /></el-icon>
</el-button>
</template>
</el-table-column>
<template #empty>
<el-empty description="没有数据" />
</template>
</el-table>
<el-pagination
v-model:page-size="pageSize"
layout="total, prev, pager, next"
:total="+article.total"
style="height: 48px; justify-content: center"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</page-container>
</template>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
.el-form-item {
width: 290px;
}
.expand-content {
display: flex;
flex-direction: column;
margin: 16px;
p {
border-radius: 12px;
line-height: 1.5;
background: #f5f5f5;
padding: 24px;
}
}
//
.avatar-uploader {
:deep() {
.avatar {
width: 160px;
height: 160px;
display: block;
object-fit: contain;
}
.el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 160px;
height: 160px;
text-align: center;
}
}
}
//
.editor {
width: 100%;
:deep() {
.ql-editor {
width: 100%;
min-height: 200px;
}
}
}
</style>

View File

@ -1,22 +1,114 @@
<script setup>
import { ref } from 'vue'
const cate_data = ref([])
import { Close, Delete } from '@element-plus/icons-vue'
const userInfo = ref({
total: 10,
records: [
{
id: 1,
nickname: '张三-5667',
appointNum: '14',
phone: '13345678901',
url: '',
status: '1'
}
]
})
const search = ref('')
const searchUser = () => {
console.log(search.value)
}
//
const pageSize = ref(5)
const currentPage = ref(1)
const handleCurrentChange = (val) => {
currentPage.value = val
}
const handleSizeChange = (val) => {
pageSize.value = val
}
const forbiddenUser = (row) => {
if (row.status === '1') {
ElMessage(row.nickname + '已禁用')
row.status = '0'
} else {
ElMessage.success(row.nickname + '已启用')
row.status = '1'
}
}
const deleteUser = (row) => {
ElMessageBox.confirm('确定要删除该用户吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
ElMessage.success('删除成功')
})
.catch(() => {
ElMessage({
type: 'info',
message: '已取消删除'
})
})
}
</script>
<template>
<page-container title="用户信息管理">
<page-container>
<template #extra>
<el-button type="primary">添加分类</el-button>
<el-input
v-model="search"
placeholder="请输入用户昵称"
clearable
@keyup.enter="searchUser"
style="width: 250px"
>
</el-input>
</template>
<el-table :data="cate_data">
<el-table-column prop="id" label="序号"></el-table-column>
<el-table-column prop="cate_name" label="分类名称"></el-table-column>
<el-table-column prop="cate_alias" label="分类别名"></el-table-column>
<el-table-column prop="operate" label="操作"></el-table-column>
<el-table stripe :data="userInfo.records" height="100%">
<el-table-column prop="id" label="id" width="60"></el-table-column>
<el-table-column prop="url" label="头像">
<template #default="{ row }">
<el-avatar :size="40" :src="row.url || './src/assets/default.png'" />
</template>
</el-table-column>
<el-table-column prop="nickname" label="昵称" show-overflow-tooltip></el-table-column>
<el-table-column prop="phone" label="手机号" show-overflow-tooltip></el-table-column>
<el-table-column prop="appointNum" label="预约次数"></el-table-column>
<el-table-column label="禁用/删除" width="108" fixed="right">
<template #default="{ row }">
<el-button
plain
type="warning"
size="small"
@click="forbiddenUser(row)"
>
<el-icon><Close /></el-icon>
</el-button>
<el-button plain type="danger" size="small" @click="deleteUser(row)">
<el-icon><Delete /></el-icon>
</el-button>
</template>
</el-table-column>
<template #empty>
<el-empty description="没有数据" />
</template>
</el-table>
<el-pagination
v-model:page-size="pageSize"
layout="total, prev, pager, next"
:total="+userInfo.total"
style="height: 48px; justify-content: center"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</page-container>
</template>

View File

@ -0,0 +1,198 @@
<script setup>
import { ref } from 'vue'
import { Delete } from '@element-plus/icons-vue'
const appoint = ref({
total: 10,
records: [
{
id: 1,
name: '张三',
appointHospital: '北京医院',
appointDoctor: '李四',
appointTime: '2023-01-01',
phone: '13800138000',
fee: 100,
appointUser: '王五-5667',
appointStatus: 1,
meno: '备注'
}
]
})
const search = ref('')
const searchUser = () => {
console.log(search.value)
}
//
const pageSize = ref(5)
const currentPage = ref(1)
const handleCurrentChange = (val) => {
currentPage.value = val
}
const handleSizeChange = (val) => {
pageSize.value = val
}
const deleteUser = () => {
ElMessageBox.confirm('确定要删除该用户吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(() => {
ElMessage.success('删除成功')
})
.catch(() => {
ElMessage({
type: 'info',
message: '已取消删除'
})
})
}
const userFilters = ref([
{ text: '王五-5667', value: '王五-5667' },
{ text: '王五-5667', value: '王五-5667' }
])
const userFilterTag = (value, row) => {
return row.appointStatus === value
}
const filters = ref([
{ text: '未就诊', value: 1 },
{ text: '已就诊', value: 2 }
])
const filterTag = (value, row) => {
return row.appointStatus === value
}
const handleUpdate = (row) => {
if (row.appointStatus === 1) {
row.appointStatus = 2
} else {
row.appointStatus = 1
}
}
</script>
<template>
<page-container>
<template #extra>
<el-input
v-model="search"
placeholder="请输入就诊人姓名"
clearable
@keyup.enter="searchUser"
style="width: 250px"
>
</el-input>
</template>
<el-table stripe :data="appoint.records" height="100%">
<el-table-column type="expand">
<template #default="{ row }">
<div class="expand-content">
<h3 style="margin-bottom: 8px">备注</h3>
<p
v-html="
row.meno.replace(/\n/g, '<br/>').replace(/\t/g, '&emsp;')
"
></p>
</div>
</template>
</el-table-column>
<el-table-column prop="id" label="id" width="60"></el-table-column>
<el-table-column
prop="name"
label="就诊人"
show-overflow-tooltip
></el-table-column>
<el-table-column
prop="appointHospital"
label="预约医院"
show-overflow-tooltip
></el-table-column>
<el-table-column
prop="appointDoctor"
label="预约医生"
show-overflow-tooltip
></el-table-column>
<el-table-column
prop="appointTime"
label="预约时间"
show-overflow-tooltip
></el-table-column>
<el-table-column
prop="phone"
label="手机号"
show-overflow-tooltip
></el-table-column>
<el-table-column
prop="fee"
label="费用"
show-overflow-tooltip
></el-table-column>
<el-table-column
prop="appointUser"
label="预约用户"
show-overflow-tooltip
:filters="userFilters"
:filter-method="userFilterTag"
>
<template #default="{ row }">
{{ row.appointUser }}
</template>
</el-table-column>
<el-table-column
prop="appointStatus"
label="状态"
fixed="right"
width="66"
:filters="filters"
:filter-method="filterTag"
>
<template #default="{ row }">
<el-text
size="small"
style="cursor: pointer;"
@click="handleUpdate(row)"
:type="row.appointStatus === 1 ? 'danger' : 'success'"
>{{ row.appointStatus === 1 ? '未就诊' : '已就诊' }}
</el-text>
</template>
</el-table-column>
<el-table-column label="删除" width="60" fixed="right">
<template #default="{ row }">
<el-button plain type="danger" size="small" @click="deleteUser(row)">
<el-icon><Delete /></el-icon>
</el-button>
</template>
</el-table-column>
<template #empty>
<el-empty description="没有数据" />
</template>
</el-table>
<el-pagination
v-model:page-size="pageSize"
layout="total, prev, pager, next"
:total="+appoint.total"
style="height: 48px; justify-content: center"
@current-change="handleCurrentChange"
@size-change="handleSizeChange"
/>
</page-container>
</template>
<style lang="scss" scoped>
.expand-content {
display: flex;
flex-direction: column;
margin: 16px;
p {
border-radius: 12px;
line-height: 1.5;
background: #f5f5f5;
padding: 24px;
}
}
</style>

View File

@ -1,23 +0,0 @@
<script setup>
import { ref } from 'vue'
const cate_data = ref([])
</script>
<template>
<page-container title="文章分类">
<template #extra>
<el-button type="primary">添加分类</el-button>
</template>
<el-table :data="cate_data">
<el-table-column prop="id" label="序号"></el-table-column>
<el-table-column prop="cate_name" label="分类名称"></el-table-column>
<el-table-column prop="cate_alias" label="分类别名"></el-table-column>
<el-table-column prop="operate" label="操作"></el-table-column>
<template #empty>
<el-empty description="没有数据" />
</template>
</el-table>
</page-container>
</template>
<style lang="scss" scoped></style>

View File

@ -1,24 +0,0 @@
<script setup>
import { ref } from 'vue'
const article = ref([])
</script>
<template>
<page-container title="文章管理">
<template #extra>
<el-button type="primary">发布文章</el-button>
</template>
<el-table :data="article">
<el-table-column prop="title" label="文章标题"></el-table-column>
<el-table-column prop="cate" label="分类"></el-table-column>
<el-table-column prop="time" label="发布时间"></el-table-column>
<el-table-column prop="status" label="状态"></el-table-column>
<el-table-column prop="operate" label="操作"></el-table-column>
<template #empty>
<el-empty description="没有数据" />
</template>
</el-table>
</page-container>
</template>
<style lang="scss" scoped></style>

View File

@ -1,183 +0,0 @@
<script setup>
import {
Management,
Promotion,
UserFilled,
User,
Crop,
EditPen,
SwitchButton,
CaretBottom
} from '@element-plus/icons-vue'
import avatar from '@/assets/default.png'
import { useUserStore } from '@/stores'
import { onMounted } from 'vue'
import { useRouter } from 'vue-router'
const userStore = useUserStore()
//
onMounted(() => {
userStore.getUser()
})
//
const router = useRouter()
const handleCommand = (key) => {
if (key === 'logout') {
// 退
ElMessageBox.confirm('确定要退出吗?', '温馨提示', {
type: 'warning',
confirmButtonText: '确定',
cancelButtonText: '取消'
}).then(() => {
//
userStore.removeToken()
userStore.setUser({})
router.push('/login')
})
} else {
router.push(`/user/${key}`)
}
}
</script>
<template>
<!-- 1el-container 布局容器
2el-menu 菜单组件
:default-active="$route.path" 配置默认激活高亮的菜单
router选项开启, el-menu-item index 就是跳转的路径
el-menu-item 菜单项
index="..." 配置的是访问的跳转路径, 配合 default-active 实现高亮
-->
<el-container class="layout-container">
<el-aside width="200px">
<div class="el-aside__logo"></div>
<!-- 菜单组件 -->
<el-menu
active-text-color="#ffd04b"
background-color="#232323"
:default-active="$route.path"
text-color="#fff"
router
>
<!-- 一级菜单 -->
<el-menu-item index="/article/channel">
<el-icon><Management /></el-icon>
<span>文章分类</span>
</el-menu-item>
<el-menu-item index="/article/manage">
<el-icon><Promotion /></el-icon>
<span>文章管理</span>
</el-menu-item>
<!-- 多级菜单 -->
<el-sub-menu index="/user">
<template #title>
<el-icon><UserFilled /></el-icon>
<span>个人中心</span>
</template>
<el-menu-item index="/user/profile">
<el-icon><User /></el-icon>
<span>基本资料</span>
</el-menu-item>
<el-menu-item index="/user/avatar">
<el-icon><Crop /></el-icon>
<span>更换头像</span>
</el-menu-item>
<el-menu-item index="/user/password">
<el-icon><EditPen /></el-icon>
<span>重置密码</span>
</el-menu-item>
</el-sub-menu>
</el-menu>
</el-aside>
<el-container>
<el-header>
<div>
前端程序员<strong>{{ userStore.user.name }}</strong>
</div>
<!-- 下拉菜单 -->
<el-dropdown placement="bottom-end" @command="handleCommand">
<span class="el-dropdown__box">
<el-avatar :src="userStore.user.avatarUrl || avatar" />
<el-icon><CaretBottom /></el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item command="profile" :icon="User"
>基本资料</el-dropdown-item
>
<el-dropdown-item command="avatar" :icon="Crop"
>更换头像</el-dropdown-item
>
<el-dropdown-item command="password" :icon="EditPen"
>重置密码</el-dropdown-item
>
<el-dropdown-item command="logout" :icon="SwitchButton"
>退出登录</el-dropdown-item
>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-header>
<el-main>
<router-view v-slot="{ Component }">
<transition name="scale" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
</el-main>
</el-container>
</el-container>
</template>
<style lang="scss" scoped>
.layout-container {
height: 100vh;
.el-aside {
background-color: #232323;
&__logo {
height: 120px;
background: url('@/assets/logo.png') no-repeat center / 80px auto;
}
.el-menu {
border-right: none;
}
}
.el-header {
background-color: #fff;
display: flex;
align-items: center;
justify-content: space-between;
.el-dropdown__box {
display: flex;
align-items: center;
.el-icon {
color: #999;
margin-left: 10px;
}
&:active,
&:focus {
outline: none;
}
}
}
.el-footer {
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: #666;
}
.scale-enter-active,
.scale-leave-active {
transition: all 0.5s ease;
}
.scale-enter-from,
.scale-leave-to {
opacity: 0;
transform: scale(0.9);
}
}
</style>

View File

@ -1,186 +0,0 @@
<script setup>
import { userLoginService, userGetVerificationService } from '@/api/user'
import { User, Lock } from '@element-plus/icons-vue'
import { ref, onMounted, useTemplateRef } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores'
const form = useTemplateRef('form')
// form
const formModle = ref({
username: '',
password: '',
captchaKey: '',
captchaCode: ''
})
let src = ref('')
onMounted(async () => {
const init = await userGetVerificationService()
formModle.value.captchaKey = init.data.data.key
src.value = init.data.data.image
})
//
/// 1 required:truemessagetrigger blur change
/// 2 minmax
/// 3 pattern
/// 4 validator
//// validator: (rule, value, callback)
///// 1. rule
///// 2. value
///// 3. callbackcallback
///// - callback()
///// - callback(new Error(''))
const rules = ref({
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 1, max: 10, message: '用户名必须是 5-10 位字符', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{
pattern: /^\S{6,15}$/,
message: '密码必须是 6-15 位的非空字符',
trigger: 'blur'
}
],
captchaCode: [{ required: true, message: '请输入验证码', trigger: 'blur' }]
})
//
const userStore = useUserStore()
const router = useRouter()
const login = async () => {
await form.value.validate()
const res = await userLoginService(formModle.value)
// token
userStore.setToken(res.data.data)
ElMessage.success('登录成功')
router.push('/')
}
const getVerification = async () => {
const update = await userGetVerificationService()
formModle.value.captchaKey = update.data.data.key
src.value = update.data.data.image
}
</script>
<template>
<!-- 结构
el-form 整个表单组件
1ref="form" 给组件起一个名字方便后续操作
2size="large" 设置表单大小
3autocomplete="off" 关闭浏览器自动填充功能
el-form-item 表单的一行
el-input 表单元素
:prefix-icon="User" 设置表单前面的元素
-->
<!-- 校验
1el-form =>
:model="formModle" 绑定整个form的数据对象
:rules="rules" 绑定整个rules规则的数据对象
3el-form-item =>
prop="username"配置生效的是哪个校验规则
v-model="formModle.username" 绑定表单元素的数据
-->
<div class="login-page">
<div class="form">
<div class="logo"></div>
<el-form :model="formModle" :rules ref="form" size="large" class="forms">
<el-form-item>
<h1 style="margin-bottom: 8px">欢迎来到后台管理</h1>
</el-form-item>
<el-form-item prop="username">
<el-input
v-model="formModle.username"
:prefix-icon="User"
placeholder="请输入用户名"
></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model="formModle.password"
:prefix-icon="Lock"
type="password"
placeholder="请输入密码"
></el-input>
</el-form-item>
<el-form-item prop="captchaCode" class="verification">
<el-input
v-model="formModle.captchaCode"
:prefix-icon="Lock"
type="captchaCode"
placeholder="请输入验证码"
>
</el-input>
<img @click="getVerification" :src="src" alt="" />
</el-form-item>
<el-form-item>
<el-button
@click="login"
class="button"
type="primary"
auto-insert-space
>
登录
</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<style lang="scss" scoped>
.login-page {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #fff;
background: url('@/assets/login_bg.png') no-repeat center / cover;
border-radius: 0 20px 20px 0;
.form {
width: 420px;
height: 500px;
display: flex;
background-color: #fff;
flex-direction: column;
align-items: center;
justify-content: space-evenly;
border-radius: 8px;
box-shadow: 0px 0px 20px 8px #ccc;
user-select: none; //
.logo {
width: 60px;
height: 60px;
border-radius: 50%;
margin-top: 16px;
opacity: 0.8;
background: url('@/assets/logo.png') center / cover;
}
.button {
width: 100%;
margin-top: 20px;
}
.forms {
width: 75%;
box-sizing: border-box;
:deep(.el-form-item__content) {
flex-wrap: nowrap;
align-items: center;
img {
cursor: pointer;
border: #ccc 1px solid;
border-radius: 4px;
margin-left: 6px;
height: 38px;
width: 68px;
}
}
}
}
}
</style>

View File

@ -1,9 +0,0 @@
<script setup></script>
<template>
<page-container title="更换头像">
其他内容
</page-container>
</template>
<style lang="scss" scoped></style>

View File

@ -1,9 +0,0 @@
<script setup></script>
<template>
<page-container title="重置密码">
其他内容
</page-container>
</template>
<style lang="scss" scoped></style>

View File

@ -1,9 +0,0 @@
<script setup></script>
<template>
<page-container title="个人详情">
其他内容
</page-container>
</template>
<style lang="scss" scoped></style>