Bläddra i källkod

feat(medicine): 优化药品管理页面,新增搜索功能和加载状态,调整样式

mcbaiyun 4 veckor sedan
förälder
incheckning
4bf7af71c6
1 ändrade filer med 127 tillägg och 25 borttagningar
  1. 127 25
      src/pages/doctor/manage/medicine.vue

+ 127 - 25
src/pages/doctor/manage/medicine.vue

@@ -1,18 +1,40 @@
 <template>
   <CustomNav title="药品信息管理" leftType="back" />
   <view class="page-container">
-    <view class="search-container">
-      <input 
-        type="text" 
-        v-model="searchKeyword" 
-        class="search-input" 
-        placeholder="搜索药品名称或拼音首字母"
-        @input="handleSearch"
-      />
-      <button class="add-btn" @click="showAddModal">添加药品</button>
+      <view class="action-bar">
+        <view class="search-wrap">
+          <input
+            class="search-input"
+            placeholder="搜索药品名称或拼音首字母"
+            v-model="searchKeyword"
+            @confirm="handleSearchImmediate"
+            @input="handleSearch"
+          />
+          <view class="search-btn" @click="handleSearchImmediate">
+            <uni-icons type="search" size="22" color="#fff" />
+          </view>
+        </view>
+
+      </view>
+
+    <view v-if="isLoading" class="skeleton-list">
+      <view v-for="i in 3" :key="i" class="skeleton-card">
+        <view class="skeleton-meta">
+          <view class="skeleton-line title" />
+          <view class="skeleton-line summary" />
+        </view>
+        <view class="skeleton-actions">
+          <view class="skeleton-btn" />
+          <view class="skeleton-btn small" />
+        </view>
+      </view>
+    </view>
+
+    <view v-else-if="filteredMedicines.length === 0" class="empty-state">
+      <text class="empty-text">暂无药品</text>
     </view>
 
-    <view class="medicine-list">
+    <view class="medicine-list" v-else>
       <view 
         class="medicine-item" 
         v-for="medicine in filteredMedicines" 
@@ -84,6 +106,9 @@
       </view>
     </view>
   </view>
+  <view class="fab" @click="showAddModal" role="button" aria-label="新建药品">
+    <view class="fab-inner">+</view>
+  </view>
 </template>
 
 <script setup lang="ts">
@@ -110,6 +135,8 @@ const medicines = ref<Medicine[]>([])
 
 // 搜索关键词
 const searchKeyword = ref('')
+// 加载态
+const isLoading = ref(false)
 
 // 模态框控制
 const showModal = ref(false)
@@ -142,11 +169,28 @@ const filteredMedicines = computed(() => {
   )
 })
 
-// 处理搜索
-const handleSearch = () => {
+// 处理搜索(使用防抖以减少请求频率)
+// 简单防抖实现
+function debounce<T extends (...args: any[]) => void> (fn: T, wait = 400) {
+  let timer: ReturnType<typeof setTimeout> | null = null
+  return function(this: any, ...args: Parameters<T>) {
+    if (timer) clearTimeout(timer)
+    timer = setTimeout(() => fn.apply(this, args), wait)
+  }
+}
+
+// 当用户按下确认时,立即搜索
+const handleSearchImmediate = () => {
+  pagination.value.pageNum = 1
   loadMedicineList()
 }
 
+// 防抖版搜索(绑定到 @input)
+const handleSearch = debounce(() => {
+  pagination.value.pageNum = 1
+  loadMedicineList()
+}, 420)
+
 // 显示添加模态框
 const showAddModal = () => {
   isEditing.value = false
@@ -285,6 +329,7 @@ const scanBarcode = () => {
 
 // 加载药品列表
 const loadMedicineList = async () => {
+  isLoading.value = true
   try {
     const params = {
       pageNum: pagination.value.pageNum,
@@ -308,6 +353,9 @@ const loadMedicineList = async () => {
       icon: 'none'
     })
   }
+  finally {
+    isLoading.value = false
+  }
 }
 
 // 页面加载时
@@ -324,35 +372,89 @@ onMounted(() => {
   padding-bottom: 40rpx;
 }
 
-.search-container {
+.action-bar {
   display: flex;
-  padding: 20rpx;
-  gap: 20rpx;
+  justify-content: space-between;
+  align-items: center;
+  gap: 18rpx;
+  padding: 28rpx 20rpx;
   background-color: #fff;
   margin-bottom: 20rpx;
 }
 
+.search-wrap {
+  display: flex;
+  align-items: center;
+  gap: 12rpx;
+  flex: 1; /* 自动撑满剩余空间 */
+}
+
 .search-input {
   flex: 1;
-  padding: 16rpx 20rpx;
-  border-radius: 10rpx;
-  border: 1rpx solid #eee;
+  height: 64rpx;
+  border-radius: 12rpx;
+  padding: 0 20rpx;
+  border: 1rpx solid #e6e6e6;
   font-size: 28rpx;
+  background: #fff;
 }
 
-.add-btn {
-  padding: 16rpx 24rpx;
-  background-color: #3742fa;
-  color: #fff;
-  border-radius: 10rpx;
-  font-size: 28rpx;
-  white-space: nowrap;
+.search-btn {
+  width: 64rpx;
+  height: 64rpx;
+  border-radius: 12rpx;
+  background-color: #4a90e2;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+/* 顶部创建按钮已移除,保留悬浮按钮作为主要新建入口 */
+
+/* 悬浮按钮 */
+.fab {
+  position: fixed;
+  right: 28rpx;
+  bottom: 160rpx;
+  width: 110rpx;
+  height: 110rpx;
+  border-radius: 999px;
+  background: linear-gradient(180deg, #4a90e2, #2d8cf0);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 6rpx 18rpx rgba(0, 0, 0, 0.2);
+  z-index: 1200;
+  border: none;
 }
+.fab:active { transform: translateY(2rpx); }
+.fab:focus { outline: none; }
+.fab-inner { color: #fff; font-size: 56rpx; line-height: 56rpx }
 
 .medicine-list {
   padding: 0 20rpx;
 }
 
+.empty-state {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 100rpx 40rpx;
+}
+.empty-text { font-size: 36rpx; margin-top: 20rpx; color: #999; }
+.create-cta { margin-top: 18rpx; background-color: #2d8cf0; color: #fff; padding: 16rpx 28rpx; border-radius: 12rpx; }
+
+.skeleton-list { padding: 18rpx 18rpx; }
+.skeleton-card { background: #fff; display: flex; gap: 18rpx; padding: 16rpx; border-radius: 12rpx; margin-bottom: 16rpx; align-items: center; justify-content: space-between; }
+.skeleton-meta { flex: 1; display: flex; flex-direction: column; gap: 10rpx; }
+.skeleton-line { background: linear-gradient(90deg, #eee, #f6f6f6); border-radius: 6rpx; }
+.skeleton-line.title { width: 40%; height: 28rpx; }
+.skeleton-line.summary { width: 60%; height: 18rpx; }
+.skeleton-actions { display:flex; gap: 12rpx; align-items: center; }
+.skeleton-btn { width: 100rpx; height: 40rpx; background: linear-gradient(90deg, #eee, #f6f6f6); border-radius: 20rpx; }
+.skeleton-btn.small { width: 60rpx; }
+
 .medicine-item {
   display: flex;
   justify-content: space-between;