Bläddra i källkod

feat(cfc-web): admin/teacher 页面全局搜索 + 统一 admin-page 样式

- Layout.vue: header-right 显示 pageTitle, auto-refresh 机制
- ~50 admin/9 teacher 页面统一添加 admin-page 样式类
- 所有列表/管理页添加 <=el-input> 关键字搜索
- 有分页的: keyword 传 API 参数
- 无分页的: computed filteredData 客户端过滤
- teacher 页面同时补齐 admin-page 类与面包屑样式
Xiaogang Liao 4 månader sedan
förälder
incheckning
fdecbe97ec
54 ändrade filer med 607 tillägg och 131 borttagningar
  1. 56 1
      cfc-web/src/views/Layout.vue
  2. 10 1
      cfc-web/src/views/admin/Activities.vue
  3. 1 1
      cfc-web/src/views/admin/ActivityEdit.vue
  4. 15 3
      cfc-web/src/views/admin/ActivityRegistrationReview.vue
  5. 10 6
      cfc-web/src/views/admin/ActivityReview.vue
  6. 19 3
      cfc-web/src/views/admin/ArticleCategory.vue
  7. 1 1
      cfc-web/src/views/admin/ArticleEdit.vue
  8. 1 1
      cfc-web/src/views/admin/ArticleManage.vue
  9. 16 4
      cfc-web/src/views/admin/Assessment.vue
  10. 15 2
      cfc-web/src/views/admin/BaziConfigs.vue
  11. 15 2
      cfc-web/src/views/admin/BloodTypeConfigs.vue
  12. 25 3
      cfc-web/src/views/admin/CategoryManage.vue
  13. 9 3
      cfc-web/src/views/admin/CouponManagement.vue
  14. 13 3
      cfc-web/src/views/admin/EducationSystems.vue
  15. 10 1
      cfc-web/src/views/admin/EmotionAlertManage.vue
  16. 8 1
      cfc-web/src/views/admin/EnergyRuleManagement.vue
  17. 12 2
      cfc-web/src/views/admin/EnergySandbox.vue
  18. 8 2
      cfc-web/src/views/admin/Foods.vue
  19. 10 1
      cfc-web/src/views/admin/GrowthTaskManagement.vue
  20. 1 1
      cfc-web/src/views/admin/HealthEnergyConfig.vue
  21. 14 3
      cfc-web/src/views/admin/KnowledgeTag.vue
  22. 1 1
      cfc-web/src/views/admin/OrderDetail.vue
  23. 10 1
      cfc-web/src/views/admin/OrderManage.vue
  24. 15 3
      cfc-web/src/views/admin/PackageTemplates.vue
  25. 1 1
      cfc-web/src/views/admin/ProductEdit.vue
  26. 1 1
      cfc-web/src/views/admin/ProductManage.vue
  27. 2 2
      cfc-web/src/views/admin/ProductProfitRate.vue
  28. 16 2
      cfc-web/src/views/admin/PromotionManagement.vue
  29. 8 2
      cfc-web/src/views/admin/Recipes.vue
  30. 16 4
      cfc-web/src/views/admin/RelationshipTypes.vue
  31. 7 2
      cfc-web/src/views/admin/SeasonalFoods.vue
  32. 14 2
      cfc-web/src/views/admin/ServiceContents.vue
  33. 16 4
      cfc-web/src/views/admin/ServiceTypes.vue
  34. 13 3
      cfc-web/src/views/admin/SupplySystem.vue
  35. 11 3
      cfc-web/src/views/admin/SysConfig.vue
  36. 10 2
      cfc-web/src/views/admin/VendorReview.vue
  37. 1 1
      cfc-web/src/views/admin/VirtualTeamDetail.vue
  38. 4 1
      cfc-web/src/views/admin/VirtualTeamList.vue
  39. 16 8
      cfc-web/src/views/admin/WithdrawAudit.vue
  40. 15 2
      cfc-web/src/views/admin/ZodiacConfigs.vue
  41. 10 4
      cfc-web/src/views/admin/data-source-config.vue
  42. 18 3
      cfc-web/src/views/admin/dimension-config.vue
  43. 15 5
      cfc-web/src/views/admin/dimension/index.vue
  44. 2 2
      cfc-web/src/views/admin/indicators/index.vue
  45. 2 2
      cfc-web/src/views/admin/knowledge/index.vue
  46. 17 4
      cfc-web/src/views/teacher/FamilyList.vue
  47. 18 4
      cfc-web/src/views/teacher/GrowthPlans.vue
  48. 17 4
      cfc-web/src/views/teacher/GrowthRecords.vue
  49. 17 3
      cfc-web/src/views/teacher/TeacherAssessment.vue
  50. 1 1
      cfc-web/src/views/teacher/TeacherFamilies.vue
  51. 14 3
      cfc-web/src/views/teacher/TeacherMessages.vue
  52. 1 1
      cfc-web/src/views/teacher/TeacherOrders.vue
  53. 12 2
      cfc-web/src/views/teacher/TeacherPackages.vue
  54. 17 3
      cfc-web/src/views/teacher/TeacherTeam.vue

+ 56 - 1
cfc-web/src/views/Layout.vue

@@ -134,7 +134,9 @@
         </span>
       </el-dialog>
       <el-main>
-        <router-view />
+        <div class="main-wrapper">
+          <router-view />
+        </div>
       </el-main>
     </el-container>
   </el-container>
@@ -634,5 +636,58 @@ export default {
   padding: 20px;
   position: relative;
   z-index: 1;
+  display: flex;
+  flex-direction: column;
+  min-height: 0; /* flexbox overflow fix */
+}
+
+.main-wrapper {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  min-height: 0;
+}
+
+/* ========== 全局 .admin-page 样式 ========== */
+/* 所有后台维护页面根元素使用此 class,使页面内容充满可视区 */
+.admin-page {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  min-height: 0;
+}
+/* 当 admin-page 的直接子元素是 el-card 时让卡片自动填充 */
+.admin-page > .el-card {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  min-height: 0;
+}
+.admin-page > .el-card .el-card__body {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  min-height: 0;
+  padding: 20px;
+}
+/* 当 admin-page > div > el-card(如 ArticleManage 的嵌套 div) */
+.admin-page > div:only-child {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  min-height: 0;
+}
+.admin-page > div:only-child > .el-card {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  min-height: 0;
+}
+.admin-page > div:only-child > .el-card .el-card__body {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  min-height: 0;
+  padding: 20px;
 }
 </style>

+ 10 - 1
cfc-web/src/views/admin/Activities.vue

@@ -1,9 +1,10 @@
 <template>
-  <div class="activities">
+  <div class="activities admin-page">
     <el-card>
       <div slot="header">
         <span>活动管理</span>
         <div style="float:right">
+          <el-input v-model="keyword" placeholder="搜索..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
           <el-select
             v-model="filter.status"
             placeholder="状态筛选"
@@ -151,6 +152,7 @@ export default {
       loading: false,
       activities: [],
       total: 0,
+      keyword: '',
       filter: {
         status: '',
         page: 1,
@@ -189,6 +191,9 @@ export default {
         if (this.filter.status) {
           params.status = this.filter.status
         }
+        if (this.keyword) {
+          params.keyword = this.keyword
+        }
         const res = await getActivityList(params)
         this.activities = (res.data && res.data.records) || []
         this.total = (res.data && res.data.total) || 0
@@ -207,6 +212,10 @@ export default {
       this.filter.page = page
       this.loadActivities()
     },
+    handleSearch() {
+      this.filter.page = 1
+      this.loadActivities()
+    },
     async handlePublish(row) {
       try {
         await publishActivity(row.id)

+ 1 - 1
cfc-web/src/views/admin/ActivityEdit.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="activity-edit">
+  <div class="activity-edit admin-page">
     <el-card>
       <div slot="header">
         <span>{{ readonly && activityStatus !== 'rejected' ? '活动详情' : (isEdit ? '编辑活动' : '创建活动') }}</span>

+ 15 - 3
cfc-web/src/views/admin/ActivityRegistrationReview.vue

@@ -1,12 +1,13 @@
 <template>
-  <div class="registration-review">
+  <div class="registration-review admin-page">
     <div class="header">
       <h2>活动报名审核</h2>
       <div class="filters">
         <el-select v-model="activityId" placeholder="选择活动" @change="loadList" style="width: 200px;" clearable>
           <el-option v-for="a in activityList" :key="a.id" :label="a.title" :value="a.id" />
         </el-select>
-        <el-select v-model="statusFilter" placeholder="状态筛选" @change="loadList" style="width: 130px;" clearable>
+         <el-input v-model="keyword" placeholder="搜索用户/孩子ID..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="loadList" @clear="loadList" />
+         <el-select v-model="statusFilter" placeholder="状态筛选" @change="loadList" style="width: 130px;" clearable>
           <el-option label="全部" value="" />
           <el-option label="待审核" value="pending" />
           <el-option label="已通过" value="approved" />
@@ -15,7 +16,7 @@
       </div>
     </div>
 
-    <el-table :data="list" v-loading="loading" border stripe>
+    <el-table :data="filteredList" v-loading="loading" border stripe>
       <el-table-column prop="id" label="ID" width="60" />
       <el-table-column prop="userId" label="用户ID" width="70" />
       <el-table-column prop="childId" label="孩子ID" width="70" />
@@ -61,6 +62,7 @@ export default {
       activityList: [],
       list: [],
       loading: false,
+      keyword: '',
       activityId: null,
       statusFilter: '',
       rejectDialogVisible: false,
@@ -68,6 +70,16 @@ export default {
       currentRow: null
     }
   },
+  computed: {
+    filteredList() {
+      if (!this.keyword) return this.list
+      var kw = this.keyword.toLowerCase()
+      return this.list.filter(function(item) {
+        return (item.userId && String(item.userId).includes(kw)) ||
+               (item.childId && String(item.childId).includes(kw))
+      })
+    }
+  },
   mounted() {
     this.loadActivities()
   },

+ 10 - 6
cfc-web/src/views/admin/ActivityReview.vue

@@ -1,8 +1,9 @@
 <template>
-  <div class="activity-review">
+  <div class="activity-review admin-page">
     <el-card>
       <div slot="header">
         <span>活动审核</span>
+        <el-input v-model="keyword" placeholder="搜索活动..." prefix-icon="el-icon-search" style="width:200px;float:right" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
       </div>
       <el-table :data="pendingActivities" v-loading="loading" border stripe>
         <el-table-column prop="id" label="ID" width="80" />
@@ -57,6 +58,7 @@ export default {
     return {
       loading: false,
       pendingActivities: [],
+      keyword: '',
       page: 1,
       size: 20,
       total: 0
@@ -69,11 +71,9 @@ export default {
     async loadPending() {
       this.loading = true
       try {
-        const res = await getActivityList({
-          status: 'draft',
-          page: this.page,
-          size: this.size
-        })
+        const params = { status: 'draft', page: this.page, size: this.size }
+        if (this.keyword) params.keyword = this.keyword
+        const res = await getActivityList(params)
         this.pendingActivities = (res.data && res.data.records) || []
         this.total = (res.data && res.data.total) || 0
       } catch (e) {
@@ -86,6 +86,10 @@ export default {
       this.page = val
       this.loadPending()
     },
+    handleSearch() {
+      this.page = 1
+      this.loadPending()
+    },
     async approve(row) {
       try {
         await reviewActivity({ id: row.id, action: 'publish' })

+ 19 - 3
cfc-web/src/views/admin/ArticleCategory.vue

@@ -1,11 +1,14 @@
 <template>
-  <div class="article-category">
+  <div class="article-category admin-page">
     <el-card>
       <div slot="header">
         <span>文章分类管理</span>
-        <el-button style="float: right" type="primary" size="small" @click="handleCreate">+ 新增分类</el-button>
+        <div style="float: right">
+          <el-input v-model="keyword" placeholder="搜索分类..." prefix-icon="el-icon-search" style="width:200px;margin-right:10px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
+          <el-button type="primary" size="small" @click="handleCreate">+ 新增分类</el-button>
+        </div>
       </div>
-      <el-table :data="categories" v-loading="loading" border stripe>
+      <el-table :data="filteredData" v-loading="loading" border stripe>
         <el-table-column prop="id" label="ID" width="70" />
         <el-table-column label="图标" width="80">
           <template slot-scope="{ row }">
@@ -79,6 +82,7 @@ export default {
       categories: [],
       dialogVisible: false,
       isEdit: false,
+      keyword: '',
       form: {
         id: null,
         name: '',
@@ -92,6 +96,15 @@ export default {
   created() {
     this.loadData()
   },
+  computed: {
+    filteredData() {
+      if (!this.keyword) return this.categories
+      const kw = this.keyword.toLowerCase()
+      return this.categories.filter(item =>
+        [item.name, item.id+''].some(v => (v||'').toLowerCase().includes(kw))
+      )
+    }
+  },
   methods: {
     async loadData() {
       this.loading = true
@@ -106,6 +119,9 @@ export default {
         this.loading = false
       }
     },
+    handleSearch() {
+      this.keyword = this.keyword
+    },
     handleCreate() {
       this.isEdit = false
       this.form = { id: null, name: '', icon: '', color: '', sortOrder: 0, statusBool: true }

+ 1 - 1
cfc-web/src/views/admin/ArticleEdit.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="article-edit">
+  <div class="article-edit admin-page">
     <el-card>
       <div slot="header">
         <span>{{ readonly && articleStatus !== 'rejected' ? '文章详情' : (isEdit ? '编辑文章' : '新建文章') }}</span>

+ 1 - 1
cfc-web/src/views/admin/ArticleManage.vue

@@ -1,5 +1,5 @@
 <template>
-  <div>
+  <div class="admin-page">
     <div class="article-manage">
     <el-card>
       <div slot="header">

+ 16 - 4
cfc-web/src/views/admin/Assessment.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="assessment-admin">
+  <div class="assessment-admin admin-page">
     <el-card class="config-card">
       <div slot="header">
         <span class="card-title">💰 积分配置</span>
@@ -22,10 +22,11 @@
     <el-card class="materials-card">
       <div slot="header">
         <span class="card-title">📚 测评资料</span>
-        <el-button type="primary" size="small" style="float: right" @click="showCreateDialog">添加资料</el-button>
+        <el-input v-model="keyword" placeholder="搜索测评资料..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
+        <el-button type="primary" size="small" style="float: right; margin-left: 10px" @click="showCreateDialog">添加资料</el-button>
       </div>
 
-      <el-table :data="materials" border stripe>
+      <el-table :data="filteredData" border stripe>
         <el-table-column prop="id" label="ID" width="60" />
         <el-table-column prop="title" label="标题" />
         <el-table-column prop="content" label="内容" show-overflow-tooltip />
@@ -87,7 +88,17 @@ export default {
         title: '',
         content: '',
         coverImage: ''
-      }
+      },
+      keyword: ''
+    }
+  },
+  computed: {
+    filteredData() {
+      if (!this.keyword) return this.materials
+      const kw = this.keyword.toLowerCase()
+      return this.materials.filter(item =>
+        [item.title, item.content].some(v => (v||'').toLowerCase().includes(kw))
+      )
     }
   },
   mounted() {
@@ -95,6 +106,7 @@ export default {
     this.loadMaterials()
   },
   methods: {
+    handleSearch() {},
     async loadConfig() {
       try {
         const res = await getAssessmentConfig()

+ 15 - 2
cfc-web/src/views/admin/BaziConfigs.vue

@@ -1,11 +1,12 @@
 <template>
-  <div class="bazi-configs">
+  <div class="bazi-configs admin-page">
     <el-card>
       <div slot="header">
         <span>八字配置管理</span>
+        <el-input v-model="keyword" placeholder="搜索八字..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
         <el-button style="float: right" type="primary" size="small" @click="handleCreate">+ 添加配置</el-button>
       </div>
-      <el-table :data="configs" stripe>
+      <el-table :data="filteredData" stripe>
         <el-table-column prop="id" label="ID" width="80"></el-table-column>
         <el-table-column prop="stemOrBranch" label="天干/地支" width="100"></el-table-column>
         <el-table-column prop="type" label="类型" width="80">
@@ -86,6 +87,7 @@ export default {
   data() {
     return {
       configs: [],
+      keyword: '',
       dialogVisible: false,
       isEdit: false,
       form: this.getEmptyForm()
@@ -94,6 +96,15 @@ export default {
   created() {
     this.loadData()
   },
+  computed: {
+    filteredData() {
+      if (!this.keyword) return this.configs
+      const kw = this.keyword.toLowerCase()
+      return this.configs.filter(item =>
+        [item.stemOrBranch, item.element, item.yinYang].some(v => (v||'').toLowerCase().includes(kw))
+      )
+    }
+  },
   methods: {
     getEmptyForm() {
       return {
@@ -117,6 +128,8 @@ export default {
         console.error(e)
       }
     },
+    handleSearch() {
+    },
     handleCreate() {
       this.isEdit = false
       this.form = this.getEmptyForm()

+ 15 - 2
cfc-web/src/views/admin/BloodTypeConfigs.vue

@@ -1,11 +1,12 @@
 <template>
-  <div class="blood-type-configs">
+  <div class="blood-type-configs admin-page">
     <el-card>
       <div slot="header">
         <span>血型配置管理</span>
+        <el-input v-model="keyword" placeholder="搜索血型..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
         <el-button style="float: right" type="primary" size="small" @click="handleCreate">+ 添加配置</el-button>
       </div>
-      <el-table :data="configs" stripe>
+      <el-table :data="filteredData" stripe>
         <el-table-column prop="id" label="ID" width="80"></el-table-column>
         <el-table-column prop="bloodType" label="血型" width="80"></el-table-column>
         <el-table-column prop="description" label="性格描述" min-width="200"></el-table-column>
@@ -65,6 +66,7 @@ export default {
   data() {
     return {
       configs: [],
+      keyword: '',
       dialogVisible: false,
       isEdit: false,
       form: this.getEmptyForm()
@@ -73,6 +75,15 @@ export default {
   created() {
     this.loadData()
   },
+  computed: {
+    filteredData() {
+      if (!this.keyword) return this.configs
+      const kw = this.keyword.toLowerCase()
+      return this.configs.filter(item =>
+        [item.bloodType, item.element, item.description].some(v => (v||'').toLowerCase().includes(kw))
+      )
+    }
+  },
   methods: {
     getEmptyForm() {
       return {
@@ -94,6 +105,8 @@ export default {
         console.error(e)
       }
     },
+    handleSearch() {
+    },
     handleCreate() {
       this.isEdit = false
       this.form = this.getEmptyForm()

+ 25 - 3
cfc-web/src/views/admin/CategoryManage.vue

@@ -1,15 +1,18 @@
 <template>
-  <div class="category-manage">
+  <div class="category-manage admin-page">
     <el-card>
       <div slot="header">
         <span>商品分类管理</span>
-        <el-button style="float: right;" type="primary" size="mini" icon="el-icon-plus" @click="handleCreate">新增分类</el-button>
+        <div style="float: right; display: flex; gap: 10px;">
+          <el-input v-model="keyword" placeholder="搜索分类名称..." prefix-icon="el-icon-search" size="small" clearable style="width: 200px;" />
+          <el-button type="primary" size="mini" icon="el-icon-plus" @click="handleCreate">新增分类</el-button>
+        </div>
       </div>
 
       <div class="category-layout">
         <div class="category-tree-panel">
           <el-tree
-            :data="treeData"
+            :data="filteredTreeData"
             :props="{ label: 'name', children: 'children' }"
             node-key="id"
             default-expand-all
@@ -99,6 +102,7 @@ export default {
     return {
       treeData: [],
       flatCategories: [],
+      keyword: '',
       form: {
         id: null,
         name: '',
@@ -117,6 +121,24 @@ export default {
       deleting: false
     }
   },
+  computed: {
+    filteredTreeData() {
+      if (!this.keyword) return this.treeData
+      const kw = this.keyword.toLowerCase()
+      const filterNodes = (nodes) => {
+        const result = []
+        for (const node of nodes) {
+          const nameMatch = node.name && node.name.toLowerCase().includes(kw)
+          const filteredChildren = node.children ? filterNodes(node.children) : []
+          if (nameMatch || filteredChildren.length > 0) {
+            result.push({ ...node, children: filteredChildren.length > 0 ? filteredChildren : (node.children || []) })
+          }
+        }
+        return result
+      }
+      return filterNodes(this.treeData)
+    }
+  },
   mounted() {
     this.loadTree()
   },

+ 9 - 3
cfc-web/src/views/admin/CouponManagement.vue

@@ -1,9 +1,10 @@
 <template>
-  <div class="coupon-management">
+  <div class="coupon-management admin-page">
     <el-card>
       <div slot="header">
         <span>优惠券管理</span>
         <div style="float: right">
+          <el-input v-model="keyword" placeholder="搜索优惠券..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
           <el-button type="primary" size="small" @click="handleCreate">新建优惠券</el-button>
           <el-button type="success" size="small" @click="handleBatchIssue">批量发放</el-button>
         </div>
@@ -136,7 +137,8 @@ export default {
       batchCouponId: '',
       batchUserIds: '',
       batchSubmitting: false,
-      couponOptions: []
+      couponOptions: [],
+      keyword: ''
     }
   },
   created() {
@@ -163,7 +165,7 @@ export default {
     async loadData() {
       this.loading = true
       try {
-        const res = await getCouponList({ page: this.page, size: this.size })
+        const res = await getCouponList({ page: this.page, size: this.size, keyword: this.keyword || undefined })
         if (res.data) {
           this.list = res.data.records || res.data.list || res.data
           this.total = res.data.total || this.list.length
@@ -174,6 +176,10 @@ export default {
         this.loading = false
       }
     },
+    handleSearch() {
+      this.page = 1
+      this.loadData()
+    },
     onPageChange(p) {
       if (p) this.page = p
       this.loadData()

+ 13 - 3
cfc-web/src/views/admin/EducationSystems.vue

@@ -1,11 +1,12 @@
 <template>
-  <div class="education-systems">
+  <div class="education-systems admin-page">
     <el-card>
       <div slot="header">
         <span>教育体系管理</span>
+        <el-input v-model="keyword" placeholder="搜索体系..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
         <el-button style="float: right" type="primary" size="small" @click="handleCreate">+ 添加体系</el-button>
       </div>
-      <el-table :data="systems" stripe>
+      <el-table :data="filteredData" stripe>
         <el-table-column prop="id" label="ID" width="80"></el-table-column>
         <el-table-column prop="name" label="编码" width="120"></el-table-column>
         <el-table-column prop="label" label="显示名称"></el-table-column>
@@ -60,6 +61,7 @@ export default {
   data() {
     return {
       systems: [],
+      keyword: '',
       dialogVisible: false,
       isEdit: false,
       form: {
@@ -72,6 +74,13 @@ export default {
       }
     }
   },
+  computed: {
+    filteredData() {
+      if (!this.keyword) return this.systems
+      const kw = this.keyword.toLowerCase()
+      return this.systems.filter(item => item.name?.toLowerCase().includes(kw) || item.label?.toLowerCase().includes(kw))
+    }
+  },
   created() {
     this.loadData()
   },
@@ -119,7 +128,8 @@ export default {
       } catch (e) {
         this.$message.error('保存失败')
       }
-    }
+    },
+    handleSearch() {}
   }
 }
 </script>

+ 10 - 1
cfc-web/src/views/admin/EmotionAlertManage.vue

@@ -1,7 +1,8 @@
 <template>
-  <div class="emotion-alert-manage">
+  <div class="emotion-alert-manage admin-page">
     <div class="header">
       <h2>情绪告警管理</h2>
+      <el-input v-model="keyword" placeholder="搜索..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
       <el-radio-group v-model="resolvedFilter" @change="onFilterChange">
         <el-radio-button label="">全部</el-radio-button>
         <el-radio-button label="false">未处理</el-radio-button>
@@ -77,6 +78,7 @@ export default {
       page: 1,
       size: 20,
       total: 0,
+      keyword: '',
       resolvedFilter: ''
     }
   },
@@ -91,6 +93,9 @@ export default {
         if (this.resolvedFilter !== '') {
           params.resolved = this.resolvedFilter
         }
+        if (this.keyword) {
+          params.keyword = this.keyword
+        }
         var res = await getEmotionAlertList(params)
         this.list = res.data && res.data.list ? res.data.list : []
         this.total = res.data && res.data.total ? res.data.total : 0
@@ -104,6 +109,10 @@ export default {
       this.page = 1
       this.loadList()
     },
+    handleSearch() {
+      this.page = 1
+      this.loadList()
+    },
     onPageChange(val) {
       this.page = val
       this.loadList()

+ 8 - 1
cfc-web/src/views/admin/EnergyRuleManagement.vue

@@ -1,8 +1,9 @@
 <template>
-  <div class="energy-rule-manage">
+  <div class="energy-rule-manage admin-page">
     <div class="header">
       <h2>能量规则管理</h2>
       <div class="filters">
+        <el-input v-model="keyword" placeholder="搜索..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
         <el-select v-model="dimensionFilter" placeholder="维度筛选" @change="loadList" style="width: 140px;" clearable>
           <el-option label="全部维度" value="" />
           <el-option v-for="d in dimensions" :key="d.code" :label="d.name" :value="d.code" />
@@ -103,6 +104,7 @@ export default {
       page: 1,
       size: 15,
       total: 0,
+      keyword: '',
       dimensionFilter: '',
       dialogVisible: false,
       saving: false,
@@ -128,6 +130,7 @@ export default {
       try {
         const params = { page: this.page, size: this.size }
         if (this.dimensionFilter) params.dimensionCode = this.dimensionFilter
+        if (this.keyword) params.keyword = this.keyword
         const res = await getEnergyRuleList(params)
         if (res.code === 200 && res.data) {
           this.list = res.data.records || []
@@ -140,6 +143,10 @@ export default {
       this.page = typeof p === 'number' ? p : 1
       this.loadList()
     },
+    handleSearch() {
+      this.page = 1
+      this.loadList()
+    },
     showDialog(row) {
       if (row) {
         this.form = { ...row }

+ 12 - 2
cfc-web/src/views/admin/EnergySandbox.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="page-container">
+  <div class="page-container admin-page">
     <el-card>
       <div slot="header">
         <span>五维能量沙盘</span>
@@ -39,8 +39,9 @@
     <el-card class="section-card" v-loading="loading">
       <div slot="header">
         <span>家庭成员数据</span>
+        <el-input v-model="memberKeyword" placeholder="搜索成员姓名..." prefix-icon="el-icon-search" size="small" clearable style="float: right; width: 200px;" />
       </div>
-      <el-table :data="sandboxData.members || []" border style="width: 100%">
+      <el-table :data="filteredMembers" border style="width: 100%">
         <el-table-column prop="memberType" label="成员类型" width="100"></el-table-column>
         <el-table-column prop="name" label="姓名" width="120"></el-table-column>
         <el-table-column prop="bodyScore" label="身" width="80" align="center">
@@ -89,6 +90,7 @@ export default {
   data() {
     return {
       loading: false,
+      memberKeyword: '',
       sandboxData: {
         bodyScore: null,
         mindScore: null,
@@ -108,6 +110,14 @@ export default {
       ]
     }
   },
+  computed: {
+    filteredMembers() {
+      const members = this.sandboxData.members || []
+      if (!this.memberKeyword) return members
+      const kw = this.memberKeyword.toLowerCase()
+      return members.filter(m => m.name && m.name.toLowerCase().includes(kw))
+    }
+  },
   mounted() {
     this.loadData()
   },

+ 8 - 2
cfc-web/src/views/admin/Foods.vue

@@ -1,9 +1,10 @@
 <template>
-  <div class="foods">
+  <div class="foods admin-page">
     <el-card>
       <div slot="header">
         <span>食材管理</span>
         <el-button type="primary" size="mini" style="float:right" @click="openCreate">新建食材</el-button>
+        <el-input v-model="keyword" placeholder="搜索..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
         <el-select
           v-model="categoryFilter"
           placeholder="分类筛选"
@@ -130,6 +131,7 @@ export default {
       list: [],
       dialogVisible: false,
       categoryFilter: '',
+      keyword: '',
       editId: null,
       form: {
         name: '',
@@ -158,7 +160,7 @@ export default {
         const baseURL = process.env.VUE_APP_BASE_API || ''
         const token = localStorage.getItem('token')
         const axios = this.$axios || (await import('axios')).default
-        const res = await axios.post(baseURL + '/api/admin/foods/list', { category: this.categoryFilter || undefined }, {
+        const res = await axios.post(baseURL + '/api/admin/foods/list', { category: this.categoryFilter || undefined, keyword: this.keyword || undefined }, {
           headers: { Authorization: 'Bearer ' + token }
         })
         this.list = res.data && res.data.data || []
@@ -168,6 +170,10 @@ export default {
         this.loading = false
       }
     },
+    handleSearch() {
+      this.page = 1
+      this.loadList()
+    },
     openCreate() {
       this.editId = null
       this.form = { name: '', category: '', calories: 0, protein: 0, carbs: 0, fat: 0, fiber: 0, energyKj: 0, starch: 0, cholesterol: 0, unit: '克', status: 'active', months: [] }

+ 10 - 1
cfc-web/src/views/admin/GrowthTaskManagement.vue

@@ -1,9 +1,10 @@
 <template>
-  <div class="growth-task">
+  <div class="growth-task admin-page">
     <el-card>
       <div slot="header">
         <span>成长任务管理</span>
         <div style="float:right">
+          <el-input v-model="keyword" placeholder="搜索..." prefix-icon="el-icon-search" style="width:200px;margin-right:12px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
           <el-select
             v-model="filter.status"
             placeholder="状态筛选"
@@ -173,6 +174,7 @@ export default {
       total: 0,
       editId: null,
       dialogVisible: false,
+      keyword: '',
       filter: {
         status: '',
         page: 1,
@@ -214,6 +216,9 @@ export default {
         if (this.filter.status) {
           params.status = this.filter.status
         }
+        if (this.keyword) {
+          params.keyword = this.keyword
+        }
         const res = await getTaskList(params)
         this.tasks = (res.data && res.data.records) || []
         this.total = (res.data && res.data.total) || 0
@@ -223,6 +228,10 @@ export default {
         this.loading = false
       }
     },
+    handleSearch() {
+      this.filter.page = 1
+      this.loadTasks()
+    },
     handleSizeChange(size) {
       this.filter.size = size
       this.filter.page = 1

+ 1 - 1
cfc-web/src/views/admin/HealthEnergyConfig.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="health-energy-config">
+  <div class="health-energy-config admin-page">
     <el-card>
       <div slot="header">
         <span>七维健康能量配置</span>

+ 14 - 3
cfc-web/src/views/admin/KnowledgeTag.vue

@@ -1,11 +1,14 @@
 <template>
-  <div class="knowledge-tag">
+  <div class="knowledge-tag admin-page">
     <el-card>
       <div slot="header">
         <span>知识标签</span>
-        <el-button style="float: right" type="primary" size="small" @click="handleCreate">+ 新增标签</el-button>
+        <div style="float: right; display: flex; gap: 10px;">
+          <el-input v-model="keyword" placeholder="搜索标签名称..." prefix-icon="el-icon-search" size="small" clearable style="width: 200px;" />
+          <el-button type="primary" size="small" @click="handleCreate">+ 新增标签</el-button>
+        </div>
       </div>
-      <el-table :data="tags" v-loading="loading" border stripe>
+      <el-table :data="filteredTags" v-loading="loading" border stripe>
         <el-table-column prop="id" label="ID" width="70" />
         <el-table-column prop="tagName" label="标签名称" min-width="200" />
         <el-table-column label="类型" width="140">
@@ -55,6 +58,7 @@ export default {
       loading: false,
       submitting: false,
       tags: [],
+      keyword: '',
       dialogVisible: false,
       isEdit: false,
       form: {
@@ -64,6 +68,13 @@ export default {
       }
     }
   },
+  computed: {
+    filteredTags() {
+      if (!this.keyword) return this.tags
+      const kw = this.keyword.toLowerCase()
+      return this.tags.filter(t => t.tagName && t.tagName.toLowerCase().includes(kw))
+    }
+  },
   created() {
     this.loadData()
   },

+ 1 - 1
cfc-web/src/views/admin/OrderDetail.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="order-detail" v-loading="loading">
+  <div class="order-detail admin-page" v-loading="loading">
     <div class="page-header">
       <el-button type="text" @click="$router.push('/order-manage')">&lt; 返回订单管理</el-button>
       <h2>订单详情</h2>

+ 10 - 1
cfc-web/src/views/admin/OrderManage.vue

@@ -1,8 +1,9 @@
 <template>
-  <div class="order-manage">
+  <div class="order-manage admin-page">
     <div class="header">
       <h2>订单管理</h2>
       <div class="filters">
+        <el-input v-model="keyword" placeholder="搜索..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
         <el-select v-model="statusFilter" placeholder="订单状态" @change="loadList" style="width: 180px;">
           <el-option label="全部状态" value="" />
           <el-option label="待支付" value="pending" />
@@ -147,6 +148,7 @@ export default {
       size: 20,
       total: 0,
       statusFilter: '',
+      keyword: '',
       detailDialogVisible: false,
       detail: null,
       shipDialogVisible: false,
@@ -163,6 +165,10 @@ export default {
     this.loadList()
   },
   methods: {
+    handleSearch() {
+      this.page = 1
+      this.loadList()
+    },
     async loadList() {
       this.loading = true
       try {
@@ -170,6 +176,9 @@ export default {
         if (this.statusFilter) {
           params.status = this.statusFilter
         }
+        if (this.keyword) {
+          params.keyword = this.keyword
+        }
         var res = await getOrderList(params)
         this.list = res.data.records || []
         this.total = res.data.total || 0

+ 15 - 3
cfc-web/src/views/admin/PackageTemplates.vue

@@ -1,11 +1,12 @@
 <template>
-  <div class="package-templates">
+  <div class="package-templates admin-page">
     <el-card>
       <div slot="header">
         <span>套餐模板管理</span>
+        <el-input v-model="keyword" placeholder="搜索套餐..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
         <el-button style="float: right" type="primary" size="small" @click="handleCreate">+ 添加模板</el-button>
       </div>
-      <el-table :data="templates" stripe>
+      <el-table :data="filteredData" stripe>
         <el-table-column prop="id" label="ID" width="80"></el-table-column>
         <el-table-column prop="name" label="模板名称"></el-table-column>
         <el-table-column prop="validityMonths" label="有效期(月)" width="100"></el-table-column>
@@ -86,6 +87,7 @@ export default {
   data() {
     return {
       templates: [],
+      keyword: '',
       dialogVisible: false,
       servicesVisible: false,
       currentServices: [],
@@ -101,6 +103,15 @@ export default {
       }
     }
   },
+  computed: {
+    filteredData() {
+      if (!this.keyword) return this.templates
+      const kw = this.keyword.toLowerCase()
+      return this.templates.filter(item =>
+        [item.name, item.description].some(v => (v||'').toLowerCase().includes(kw))
+      )
+    }
+  },
   created() {
     this.loadData()
   },
@@ -181,7 +192,8 @@ export default {
       } catch (e) {
         this.$message.error('保存失败')
       }
-    }
+    },
+    handleSearch() {}
   }
 }
 </script>

+ 1 - 1
cfc-web/src/views/admin/ProductEdit.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="product-edit">
+  <div class="product-edit admin-page">
     <el-card v-loading="pageLoading">
       <div slot="header">
         <span>{{ isEdit ? '编辑商品' : '新增商品' }}</span>

+ 1 - 1
cfc-web/src/views/admin/ProductManage.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="product-manage">
+  <div class="product-manage admin-page">
     <div class="header">
       <h2>商品管理</h2>
       <div class="filters">

+ 2 - 2
cfc-web/src/views/admin/ProductProfitRate.vue

@@ -1,9 +1,9 @@
 <template>
-  <div class="product-profit-rate">
+  <div class="product-profit-rate admin-page">
     <div class="header">
       <h2>产品利润率设置</h2>
       <div class="filters">
-        <el-input v-model="searchText" placeholder="商品名称搜索" style="width: 200px;" clearable @keyup.enter.native="loadList" />
+        <el-input v-model="searchText" placeholder="商品名称搜索" prefix-icon="el-icon-search" style="width: 200px;" clearable @keyup.enter.native="loadList" />
         <el-select v-model="typeFilter" placeholder="商品类型" @change="loadList" style="width: 140px;">
           <el-option label="全部类型" value="" />
           <el-option label="实物商品" value="physical" />

+ 16 - 2
cfc-web/src/views/admin/PromotionManagement.vue

@@ -1,9 +1,12 @@
 <template>
-  <div class="promotion-management">
+  <div class="promotion-management admin-page">
     <el-card>
       <el-tabs v-model="activeTab" @tab-click="handleTabChange">
         <el-tab-pane label="邀请里程碑" name="milestone">
-          <el-table :data="milestoneList" v-loading="milestoneLoading" border stripe>
+          <div style="margin-bottom: 16px">
+            <el-input v-model="milestoneKeyword" placeholder="搜索里程碑..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleMilestoneSearch" @clear="handleMilestoneSearch" />
+          </div>
+          <el-table :data="filteredMilestones" v-loading="milestoneLoading" border stripe>
             <el-table-column prop="milestone" label="里程碑" min-width="180" show-overflow-tooltip />
             <el-table-column prop="rewardPoints" label="奖励积分" width="120" />
             <el-table-column prop="rewardEnergy" label="奖励能量" width="120" />
@@ -88,6 +91,7 @@ export default {
     return {
       activeTab: 'milestone',
       milestoneList: [],
+      milestoneKeyword: '',
       milestoneLoading: false,
       milestoneDialogVisible: false,
       milestoneSubmitting: false,
@@ -109,6 +113,15 @@ export default {
   created() {
     this.loadMilestones()
   },
+  computed: {
+    filteredMilestones() {
+      if (!this.milestoneKeyword) return this.milestoneList
+      var kw = this.milestoneKeyword.toLowerCase()
+      return this.milestoneList.filter(function(item) {
+        return item.milestone && item.milestone.toLowerCase().indexOf(kw) !== -1
+      })
+    }
+  },
   methods: {
     getCurrentWeekStart() {
       const now = new Date()
@@ -120,6 +133,7 @@ export default {
     leaderboardIndex(index) {
       return (this.leaderboardPage - 1) * this.leaderboardSize + index + 1
     },
+    handleMilestoneSearch() {},
     handleTabChange() {
       if (this.activeTab === 'leaderboard') {
         this.loadLeaderboard()

+ 8 - 2
cfc-web/src/views/admin/Recipes.vue

@@ -1,9 +1,10 @@
 <template>
-  <div class="recipes">
+  <div class="recipes admin-page">
     <el-card>
       <div slot="header">
         <span>食谱管理</span>
         <el-button type="primary" size="mini" style="float:right" @click="openCreate">新建食谱</el-button>
+        <el-input v-model="keyword" placeholder="搜索..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
         <el-select
           v-model="mealTypeFilter"
           placeholder="餐次筛选"
@@ -95,6 +96,7 @@ export default {
       list: [],
       dialogVisible: false,
       mealTypeFilter: '',
+      keyword: '',
       editId: null,
       form: {
         name: '',
@@ -121,7 +123,7 @@ export default {
         const baseURL = process.env.VUE_APP_BASE_API || ''
         const token = localStorage.getItem('token')
         const axios = this.$axios || (await import('axios')).default
-        const res = await axios.post(baseURL + '/api/admin/recipes/list', { mealType: this.mealTypeFilter || undefined }, {
+        const res = await axios.post(baseURL + '/api/admin/recipes/list', { mealType: this.mealTypeFilter || undefined, keyword: this.keyword || undefined }, {
           headers: { Authorization: 'Bearer ' + token }
         })
         this.list = res.data && res.data.data || []
@@ -131,6 +133,10 @@ export default {
         this.loading = false
       }
     },
+    handleSearch() {
+      this.page = 1
+      this.loadList()
+    },
     openCreate() {
       this.editId = null
       this.form = { name: '', mealType: '', description: '', ingredients: '', instructions: '', calories: 0, protein: 0, carbs: 0, fat: 0, imageUrl: '', status: 'active' }

+ 16 - 4
cfc-web/src/views/admin/RelationshipTypes.vue

@@ -1,11 +1,12 @@
 <template>
-  <div class="relationship-types">
+  <div class="relationship-types admin-page">
     <el-card>
       <div slot="header">
         <span>关系类型配置</span>
-        <el-button style="float: right" type="primary" size="small" @click="handleCreate">+ 添加关系类型</el-button>
+        <el-input v-model="keyword" placeholder="搜索关系类型..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
+        <el-button style="float: right; margin-left: 10px" type="primary" size="small" @click="handleCreate">+ 添加关系类型</el-button>
       </div>
-      <el-table :data="types" stripe>
+      <el-table :data="filteredData" stripe>
         <el-table-column prop="id" label="ID" width="80"></el-table-column>
         <el-table-column prop="typeKey" label="编码" width="120"></el-table-column>
         <el-table-column prop="typeName" label="显示名称" width="120"></el-table-column>
@@ -72,13 +73,24 @@ export default {
       types: [],
       dialogVisible: false,
       isEdit: false,
-      form: this.getEmptyForm()
+      form: this.getEmptyForm(),
+      keyword: ''
+    }
+  },
+  computed: {
+    filteredData() {
+      if (!this.keyword) return this.types
+      const kw = this.keyword.toLowerCase()
+      return this.types.filter(item =>
+        [item.typeKey, item.typeName].some(v => (v||'').toLowerCase().includes(kw))
+      )
     }
   },
   created() {
     this.loadData()
   },
   methods: {
+    handleSearch() {},
     getEmptyForm() {
       return {
         id: null,

+ 7 - 2
cfc-web/src/views/admin/SeasonalFoods.vue

@@ -1,9 +1,10 @@
 <template>
-  <div class="seasonal-foods">
+  <div class="seasonal-foods admin-page">
     <el-card>
       <div slot="header">
         <span>应季食材配置</span>
         <el-button type="primary" size="mini" style="float:right" @click="openCreate">新增应季映射</el-button>
+        <el-input v-model="keyword" placeholder="搜索..." prefix-icon="el-icon-search" style="width:200px;float:right;margin-right:12px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
         <el-select
           v-model="monthFilter"
           placeholder="选择月份"
@@ -70,6 +71,7 @@ export default {
       loading: false,
       list: [],
       dialogVisible: false,
+      keyword: '',
       monthFilter: now.getMonth() + 1,
       form: {
         foodId: null,
@@ -88,7 +90,7 @@ export default {
         const baseURL = process.env.VUE_APP_BASE_API || ''
         const token = localStorage.getItem('token')
         const axios = this.$axios || (await import('axios')).default
-        const res = await axios.post(baseURL + '/api/admin/seasonal/list', { month: this.monthFilter }, {
+        const res = await axios.post(baseURL + '/api/admin/seasonal/list', { month: this.monthFilter, keyword: this.keyword }, {
           headers: { Authorization: 'Bearer ' + token }
         })
         this.list = res.data && res.data.data || []
@@ -98,6 +100,9 @@ export default {
         this.loading = false
       }
     },
+    handleSearch() {
+      this.loadList()
+    },
     openCreate() {
       this.form = { foodId: null, month: new Date().getMonth() + 1, peak: 1 }
       this.dialogVisible = true

+ 14 - 2
cfc-web/src/views/admin/ServiceContents.vue

@@ -1,8 +1,9 @@
 <template>
-  <div class="service-contents">
+  <div class="service-contents admin-page">
     <el-card>
       <div slot="header">
         <span>服务内容管理</span>
+        <el-input v-model="keyword" placeholder="搜索内容..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
         <el-button style="float: right" type="primary" size="small" @click="handleCreate">+ 添加内容</el-button>
       </div>
       <el-form :inline="true">
@@ -21,7 +22,7 @@
           <el-button @click="handleQuery">查询</el-button>
         </el-form-item>
       </el-form>
-      <el-table :data="contents" stripe>
+      <el-table :data="filteredData" stripe>
         <el-table-column prop="id" label="ID" width="80"></el-table-column>
         <el-table-column prop="title" label="标题"></el-table-column>
         <el-table-column prop="typeId" label="类型" width="120">
@@ -112,6 +113,7 @@ export default {
   data() {
     return {
       contents: [],
+      keyword: '',
       serviceTypes: [],
       query: {
         typeId: null,
@@ -137,6 +139,15 @@ export default {
     this.loadTypes()
     this.loadData()
   },
+  computed: {
+    filteredData() {
+      if (!this.keyword) return this.contents
+      var kw = this.keyword.toLowerCase()
+      return this.contents.filter(function(item) {
+        return item.title && item.title.toLowerCase().indexOf(kw) !== -1
+      })
+    }
+  },
   methods: {
     async loadTypes() {
       try {
@@ -171,6 +182,7 @@ export default {
       const type = this.serviceTypes.find(t => t.id === typeId)
       return type ? type.name : '-'
     },
+    handleSearch() {},
     handleQuery() {
       this.loadData()
     },

+ 16 - 4
cfc-web/src/views/admin/ServiceTypes.vue

@@ -1,11 +1,12 @@
 <template>
-  <div class="service-types">
+  <div class="service-types admin-page">
     <el-card>
       <div slot="header">
         <span>服务类型管理</span>
-        <el-button style="float: right" type="primary" size="small" @click="handleCreate">+ 添加类型</el-button>
+        <el-input v-model="keyword" placeholder="搜索服务类型..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
+        <el-button style="float: right; margin-left: 10px" type="primary" size="small" @click="handleCreate">+ 添加类型</el-button>
       </div>
-      <el-table :data="types" stripe>
+      <el-table :data="filteredData" stripe>
         <el-table-column prop="id" label="ID" width="80"></el-table-column>
         <el-table-column prop="name" label="类型名称"></el-table-column>
         <el-table-column prop="icon" label="图标" width="100">
@@ -71,13 +72,24 @@ export default {
         description: '',
         sortOrder: 0,
         status: 'ACTIVE'
-      }
+      },
+      keyword: ''
+    }
+  },
+  computed: {
+    filteredData() {
+      if (!this.keyword) return this.types
+      const kw = this.keyword.toLowerCase()
+      return this.types.filter(item =>
+        [item.name, item.description].some(v => (v||'').toLowerCase().includes(kw))
+      )
     }
   },
   created() {
     this.loadData()
   },
   methods: {
+    handleSearch() {},
     async loadData() {
       try {
         const res = await getAllServiceTypes()

+ 13 - 3
cfc-web/src/views/admin/SupplySystem.vue

@@ -1,12 +1,13 @@
 <template>
-  <div class="supply-system">
+  <div class="supply-system admin-page">
     <!-- System List -->
     <el-card>
       <div slot="header">
         <span>供应商体系管理</span>
+        <el-input v-model="keyword" placeholder="搜索供应商体系..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
         <el-button style="float: right" type="primary" size="small" @click="handleCreateSystem">+ 添加体系</el-button>
       </div>
-      <el-table :data="systems" stripe>
+      <el-table :data="filteredData" stripe>
         <el-table-column prop="id" label="ID" width="80"></el-table-column>
         <el-table-column prop="name" label="名称" width="150"></el-table-column>
         <el-table-column prop="description" label="描述"></el-table-column>
@@ -124,6 +125,7 @@ export default {
   data() {
     return {
       systems: [],
+      keyword: '',
       systemDialogVisible: false,
       isEditSystem: false,
       systemForm: {
@@ -149,6 +151,13 @@ export default {
       }
     }
   },
+  computed: {
+    filteredData() {
+      if (!this.keyword) return this.systems
+      const kw = this.keyword.toLowerCase()
+      return this.systems.filter(item => item.name?.toLowerCase().includes(kw))
+    }
+  },
   created() {
     this.loadData()
   },
@@ -255,7 +264,8 @@ export default {
       } catch (e) {
         this.$message.error('保存失败')
       }
-    }
+    },
+    handleSearch() {}
   }
 }
 </script>

+ 11 - 3
cfc-web/src/views/admin/SysConfig.vue

@@ -1,8 +1,11 @@
 <template>
-  <div class="sys-config">
+  <div class="sys-config admin-page">
     <div class="header">
       <h2>系统配置</h2>
-      <el-button type="primary" size="small" @click="addConfig" icon="el-icon-plus">新增配置</el-button>
+      <div>
+        <el-input v-model="keyword" placeholder="搜索配置..." prefix-icon="el-icon-search" style="width:200px;margin-right:10px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
+        <el-button type="primary" size="small" @click="addConfig" icon="el-icon-plus">新增配置</el-button>
+      </div>
     </div>
 
     <el-table :data="list" v-loading="loading" border stripe>
@@ -83,6 +86,7 @@ export default {
         description: '',
         valueType: 'string'
       },
+      keyword: '',
       rules: {
         configKey: [{ required: true, message: '请输入配置键', trigger: 'blur' }],
         configValue: [{ required: true, message: '请输入配置值', trigger: 'blur' }]
@@ -96,7 +100,7 @@ export default {
     async loadList() {
       this.loading = true
       try {
-        const res = await getSysConfigList({})
+        const res = await getSysConfigList({ keyword: this.keyword || undefined })
         this.list = res.data || []
         this.total = (res.data || []).length
       } catch (e) {
@@ -105,6 +109,10 @@ export default {
         this.loading = false
       }
     },
+    handleSearch() {
+      this.page = 1
+      this.loadList()
+    },
     onPageChange(val) {
       this.page = val
       this.loadList()

+ 10 - 2
cfc-web/src/views/admin/VendorReview.vue

@@ -1,7 +1,8 @@
 <template>
-  <div class="vendor-review">
+  <div class="vendor-review admin-page">
     <div class="header">
       <h2>服务商入驻审核</h2>
+      <el-input v-model="keyword" placeholder="搜索..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
       <el-select v-model="statusFilter" placeholder="审核状态" @change="loadList" style="width: 150px;">
         <el-option label="全部" value="" />
         <el-option label="待审核" value="pending" />
@@ -68,6 +69,7 @@ export default {
       page: 1,
       size: 20,
       total: 0,
+      keyword: '',
       statusFilter: 'pending',
       rejectDialogVisible: false,
       rejectTarget: null,
@@ -93,7 +95,9 @@ export default {
     async loadList() {
       this.loading = true
       try {
-        const res = await getVendorList({ page: this.page, size: this.size, status: this.statusFilter })
+        const params = { page: this.page, size: this.size, status: this.statusFilter }
+        if (this.keyword) params.keyword = this.keyword
+        const res = await getVendorList(params)
         this.list = res.data.list || []
         this.total = res.data.total || 0
       } catch (e) {
@@ -106,6 +110,10 @@ export default {
       this.page = val
       this.loadList()
     },
+    handleSearch() {
+      this.page = 1
+      this.loadList()
+    },
     async handleReview(row, action) {
       try {
         await reviewVendor({ userId: row.id, action })

+ 1 - 1
cfc-web/src/views/admin/VirtualTeamDetail.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="virtual-team-detail">
+  <div class="virtual-team-detail admin-page">
     <el-card>
       <div slot="header">
         <el-button size="small" icon="el-icon-back" @click="goBack">返回</el-button>

+ 4 - 1
cfc-web/src/views/admin/VirtualTeamList.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="virtual-team-list">
+  <div class="virtual-team-list admin-page">
     <el-card>
       <div slot="header">
         <span>虚拟服务商团队</span>
@@ -137,6 +137,9 @@ export default {
       if (!val) return '-'
       return val.replace('T', ' ').substring(0, 19)
     },
+    handleSearch() {
+      this.loadData()
+    },
     async loadData() {
       this.loading = true
       try {

+ 16 - 8
cfc-web/src/views/admin/WithdrawAudit.vue

@@ -1,13 +1,16 @@
 <template>
-  <div class="withdraw-audit">
+  <div class="withdraw-audit admin-page">
     <div class="header">
       <h2>提现审核</h2>
-      <el-select v-model="statusFilter" placeholder="审核状态" @change="loadList" style="width: 150px;">
-        <el-option label="全部" value="" />
-        <el-option label="待审核" value="pending" />
-        <el-option label="已通过" value="approved" />
-        <el-option label="已拒绝" value="rejected" />
-      </el-select>
+      <div style="display:flex;align-items:center;gap:12px">
+        <el-input v-model="keyword" placeholder="搜索..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
+        <el-select v-model="statusFilter" placeholder="审核状态" @change="loadList" style="width: 150px;">
+          <el-option label="全部" value="" />
+          <el-option label="待审核" value="pending" />
+          <el-option label="已通过" value="approved" />
+          <el-option label="已拒绝" value="rejected" />
+        </el-select>
+      </div>
     </div>
 
     <el-table :data="list" v-loading="loading" border stripe>
@@ -111,6 +114,7 @@ export default {
     return {
       list: [],
       loading: false,
+      keyword: '',
       page: 1,
       size: 20,
       total: 0,
@@ -129,7 +133,7 @@ export default {
     async loadList() {
       this.loading = true
       try {
-        const res = await getWithdrawalList({ status: this.statusFilter })
+        const res = await getWithdrawalList({ status: this.statusFilter, keyword: this.keyword })
         this.list = res.data || []
         this.total = (res.data || []).length
       } catch (e) {
@@ -138,6 +142,10 @@ export default {
         this.loading = false
       }
     },
+    handleSearch() {
+      this.page = 1
+      this.loadList()
+    },
     onPageChange(val) {
       this.page = val
       this.loadList()

+ 15 - 2
cfc-web/src/views/admin/ZodiacConfigs.vue

@@ -1,11 +1,12 @@
 <template>
-  <div class="zodiac-configs">
+  <div class="zodiac-configs admin-page">
     <el-card>
       <div slot="header">
         <span>星座配置管理</span>
+        <el-input v-model="keyword" placeholder="搜索星座..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
         <el-button style="float: right" type="primary" size="small" @click="handleCreate">+ 添加配置</el-button>
       </div>
-      <el-table :data="configs" stripe>
+      <el-table :data="filteredData" stripe>
         <el-table-column prop="id" label="ID" width="80"></el-table-column>
         <el-table-column prop="zodiacCode" label="代码" width="120"></el-table-column>
         <el-table-column prop="zodiacName" label="名称"></el-table-column>
@@ -83,6 +84,7 @@ export default {
   data() {
     return {
       configs: [],
+      keyword: '',
       dialogVisible: false,
       isEdit: false,
       form: this.getEmptyForm()
@@ -91,6 +93,15 @@ export default {
   created() {
     this.loadData()
   },
+  computed: {
+    filteredData() {
+      if (!this.keyword) return this.configs
+      const kw = this.keyword.toLowerCase()
+      return this.configs.filter(item =>
+        [item.zodiacCode, item.zodiacName, item.element].some(v => (v||'').toLowerCase().includes(kw))
+      )
+    }
+  },
   methods: {
     getEmptyForm() {
       return {
@@ -117,6 +128,8 @@ export default {
         console.error(e)
       }
     },
+    handleSearch() {
+    },
     handleCreate() {
       this.isEdit = false
       this.form = this.getEmptyForm()

+ 10 - 4
cfc-web/src/views/admin/data-source-config.vue

@@ -1,9 +1,10 @@
 <template>
-  <div class="data-source-config">
+  <div class="data-source-config admin-page">
     <el-card>
       <div slot="header">
         <span>健康数据源配置</span>
-        <el-button style="float: right" type="primary" size="small" @click="handleCreate">+ 添加数据源</el-button>
+        <el-input v-model="keyword" placeholder="搜索数据源..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
+        <el-button style="float: right; margin-left: 10px" type="primary" size="small" @click="handleCreate">+ 添加数据源</el-button>
       </div>
       <el-table :data="records" stripe v-loading="loading">
         <el-table-column prop="id" label="ID" width="60"></el-table-column>
@@ -91,13 +92,18 @@ export default {
       size: 20,
       dialogVisible: false,
       isEdit: false,
-      form: this.emptyForm()
+      form: this.emptyForm(),
+      keyword: ''
     }
   },
   mounted() {
     this.fetchRecords()
   },
   methods: {
+    handleSearch() {
+      this.page = 1
+      this.fetchRecords()
+    },
     emptyForm() {
       return {
         id: null,
@@ -115,7 +121,7 @@ export default {
     },
     fetchRecords() {
       this.loading = true
-      this.$axios.post('/api/admin/dimension/data-source/list', null, { params: { page: this.page, size: this.size } })
+      this.$axios.post('/api/admin/dimension/data-source/list', null, { params: { page: this.page, size: this.size, keyword: this.keyword || undefined } })
         .then(res => {
           if (res.data.code === 200) {
             this.records = res.data.data.records || []

+ 18 - 3
cfc-web/src/views/admin/dimension-config.vue

@@ -1,11 +1,12 @@
 <template>
-  <div class="norm-config">
+  <div class="norm-config admin-page">
     <el-card>
       <div slot="header">
         <span>健康维度常模配置</span>
-        <el-button style="float: right" type="primary" size="small" @click="handleCreate">+ 添加常模</el-button>
+        <el-input v-model="keyword" placeholder="搜索维度/来源..." prefix-icon="el-icon-search" style="width:200px;float:right;margin-right:8px" clearable @keyup.enter.native="loadData" @clear="loadData" />
+        <el-button style="float: right;margin-right:8px" type="primary" size="small" @click="handleCreate">+ 添加常模</el-button>
       </div>
-      <el-table :data="norms" stripe v-loading="loading">
+      <el-table :data="filteredNorms" stripe v-loading="loading">
         <el-table-column prop="id" label="ID" width="60"></el-table-column>
         <el-table-column prop="dimension" label="维度" width="100">
           <template slot-scope="scope">
@@ -117,11 +118,22 @@ export default {
       loading: false,
       submitting: false,
       norms: [],
+      keyword: '',
       dialogVisible: false,
       isEdit: false,
       form: this.emptyForm()
     }
   },
+  computed: {
+    filteredNorms() {
+      if (!this.keyword) return this.norms
+      var kw = this.keyword.toLowerCase()
+      return this.norms.filter(function(item) {
+        return (item.dimension && item.dimension.toLowerCase().includes(kw)) ||
+               (item.source && item.source.toLowerCase().includes(kw))
+      })
+    }
+  },
   mounted() {
     this.fetchNorms()
   },
@@ -143,6 +155,9 @@ export default {
         source: ''
       }
     },
+    loadData() {
+      this.fetchNorms()
+    },
     genderLabel(g) {
       const map = { all: '全部', male: '男', female: '女' }
       return map[g] || g

+ 15 - 5
cfc-web/src/views/admin/dimension/index.vue

@@ -1,12 +1,13 @@
 <template>
-  <div class="dimension-manage">
+  <div class="dimension-manage admin-page">
     <el-card>
       <div slot="header">
         <span>维度配置管理</span>
-        <el-button style="float: right" type="primary" size="small" @click="handleCreate">+ 新增维度</el-button>
+        <el-input v-model="keyword" placeholder="搜索维度..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
+        <el-button style="float: right; margin-left: 10px" type="primary" size="small" @click="handleCreate">+ 新增维度</el-button>
       </div>
 
-      <el-table :data="dimensions" stripe v-loading="loading">
+      <el-table :data="filteredData" stripe v-loading="loading">
         <el-table-column prop="id" label="ID" width="80" />
         <el-table-column prop="dimensionCode" label="代码" width="120" />
         <el-table-column prop="dimensionName" label="名称" width="160" />
@@ -92,16 +93,25 @@ export default {
       submitting: false,
       isEdit: false,
       form: this.initForm(),
-      dimensions: []
+      dimensions: [],
+      keyword: ''
     }
   },
   computed: {
-    ...mapState('dimension', ['dimensionTree'])
+    ...mapState('dimension', ['dimensionTree']),
+    filteredData() {
+      if (!this.keyword) return this.dimensions
+      const kw = this.keyword.toLowerCase()
+      return this.dimensions.filter(item =>
+        [item.dimensionCode, item.dimensionName].some(v => (v||'').toLowerCase().includes(kw))
+      )
+    }
   },
   created() {
     this.fetchList()
   },
   methods: {
+    handleSearch() {},
     initForm() {
       return {
         id: null,

+ 2 - 2
cfc-web/src/views/admin/indicators/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="indicators-page">
+  <div class="indicators-page admin-page">
     <div class="page-header">
       <h2>指标管理</h2>
       <el-button type="primary" @click="showDialog(null)">新增指标</el-button>
@@ -12,7 +12,7 @@
         <el-option label="心理健康" value="mental" />
         <el-option label="营养" value="nutrition" />
       </el-select>
-      <el-input v-model="filterKeyword" placeholder="搜索名称/编码" clearable style="width:200px;margin-left:12px" @keyup.enter.native="loadData" />
+      <el-input v-model="filterKeyword" placeholder="搜索名称/编码" prefix-icon="el-icon-search" clearable style="width:200px;margin-left:12px" @keyup.enter.native="loadData" />
     </div>
 
     <!-- 表格 -->

+ 2 - 2
cfc-web/src/views/admin/knowledge/index.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="knowledge-manage">
+  <div class="knowledge-manage admin-page">
     <el-row :gutter="20">
       <!-- 左侧筛选面板 -->
       <el-col :span="5">
@@ -41,7 +41,7 @@
 
           <!-- 搜索栏 -->
           <div style="margin-bottom: 16px; display: flex; gap: 12px">
-            <el-input v-model="searchKeyword" placeholder="搜索标题/内容" style="width: 300px" clearable @keyup.enter.native="handleSearch" />
+            <el-input v-model="searchKeyword" placeholder="搜索标题/内容" prefix-icon="el-icon-search" style="width: 300px" clearable @keyup.enter.native="handleSearch" />
             <el-button type="primary" @click="handleSearch">搜索</el-button>
             <el-button @click="resetSearch">重置</el-button>
           </div>

+ 17 - 4
cfc-web/src/views/teacher/FamilyList.vue

@@ -1,10 +1,11 @@
 <template>
-  <div class="family-list">
+  <div class="family-list admin-page">
     <el-card>
-      <div slot="header">
+      <div slot="header" style="display:flex;justify-content:space-between;align-items:center">
         <span>我的家庭</span>
+        <el-input v-model="keyword" placeholder="搜索家庭..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
       </div>
-      <el-table :data="families" v-loading="loading" border stripe>
+      <el-table :data="filteredFamilies" v-loading="loading" border stripe>
         <el-table-column prop="id" label="ID" width="80" />
         <el-table-column prop="familyName" label="家庭名称" />
         <el-table-column prop="memberCount" label="成员数" width="100" />
@@ -23,10 +24,22 @@
 export default {
   name: 'FamilyList',
   data() {
-    return { loading: false, families: [] }
+    return { loading: false, families: [], keyword: '' }
+  },
+  computed: {
+    filteredFamilies() {
+      if (!this.keyword) return this.families
+      const kw = this.keyword.toLowerCase()
+      return this.families.filter(f =>
+        (f.familyName || '').toLowerCase().includes(kw) ||
+        String(f.id).includes(kw)
+      )
+    }
   },
   created() { this.loadFamilies() },
   methods: {
+    handleSearch() {
+    },
     async loadFamilies() {
       this.loading = true
       try {

+ 18 - 4
cfc-web/src/views/teacher/GrowthPlans.vue

@@ -1,8 +1,11 @@
 <template>
-  <div class="growth-plans">
+  <div class="growth-plans admin-page">
     <el-card>
-      <div slot="header"><span>成长计划</span></div>
-      <el-table :data="plans" v-loading="loading" border stripe>
+      <div slot="header">
+        <span>成长计划</span>
+        <el-input v-model="keyword" placeholder="搜索成长计划..." prefix-icon="el-icon-search" style="width:200px; float: right;" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
+      </div>
+      <el-table :data="filteredPlans" v-loading="loading" border stripe>
         <el-table-column prop="id" label="ID" width="80" />
         <el-table-column prop="childName" label="孩子" width="120" />
         <el-table-column prop="title" label="计划标题" />
@@ -16,9 +19,20 @@
 <script>
 export default {
   name: 'GrowthPlans',
-  data() { return { loading: false, plans: [] } },
+  data() { return { loading: false, plans: [], keyword: '' } },
+  computed: {
+    filteredPlans() {
+      if (!this.keyword) return this.plans
+      var kw = this.keyword.toLowerCase()
+      return this.plans.filter(function(p) {
+        return (p.title && p.title.toLowerCase().indexOf(kw) !== -1) ||
+               (p.childName && p.childName.toLowerCase().indexOf(kw) !== -1)
+      })
+    }
+  },
   created() { this.loadPlans() },
   methods: {
+    handleSearch() {},
     async loadPlans() {
       this.loading = true
       try {

+ 17 - 4
cfc-web/src/views/teacher/GrowthRecords.vue

@@ -1,9 +1,10 @@
 <template>
-  <div class="growth-records">
+  <div class="growth-records admin-page">
     <el-card>
       <div slot="header">
         <span>成长记录</span>
-        <el-select v-model="sourceFilter" placeholder="来源筛选" clearable size="small" style="float: right; width: 140px" @change="loadRecords">
+        <el-input v-model="keyword" placeholder="搜索成长记录..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
+        <el-select v-model="sourceFilter" placeholder="来源筛选" clearable size="small" style="float: right; width: 140px; margin-left: 10px;" @change="loadRecords">
           <el-option label="全部" value="" />
           <el-option label="手动创建" value="manual" />
           <el-option label="测评录入" value="assessment" />
@@ -11,7 +12,7 @@
           <el-option label="补充材料" value="supplement" />
         </el-select>
       </div>
-      <el-table :data="records" v-loading="loading" border stripe>
+      <el-table :data="filteredRecords" v-loading="loading" border stripe>
         <el-table-column prop="id" label="ID" width="70" />
         <el-table-column prop="childName" label="孩子" width="100" />
         <el-table-column prop="sourceType" label="来源" width="100">
@@ -47,7 +48,18 @@ export default {
     return {
       loading: false,
       records: [],
-      sourceFilter: ''
+      sourceFilter: '',
+      keyword: ''
+    }
+  },
+  computed: {
+    filteredRecords() {
+      if (!this.keyword) return this.records
+      var kw = this.keyword.toLowerCase()
+      return this.records.filter(function(r) {
+        return (r.recordTitle && r.recordTitle.toLowerCase().indexOf(kw) !== -1) ||
+               (r.childName && r.childName.toLowerCase().indexOf(kw) !== -1)
+      })
     }
   },
   created() {
@@ -62,6 +74,7 @@ export default {
       var map = { manual: 'info', assessment: 'success', upload: 'warning', supplement: '' }
       return map[type] || 'info'
     },
+    handleSearch() {},
     async loadRecords() {
       this.loading = true
       try {

+ 17 - 3
cfc-web/src/views/teacher/TeacherAssessment.vue

@@ -1,11 +1,12 @@
 <template>
-  <div class="family-assessment">
+  <div class="family-assessment admin-page">
     <el-card>
-      <div slot="header">
+      <div slot="header" style="display:flex;justify-content:space-between;align-items:center">
         <span class="card-title">🏠 家庭DAN测评管理</span>
+        <el-input v-model="keyword" placeholder="搜索家庭ID..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
       </div>
 
-      <el-table :data="familyConfigs" border stripe v-loading="loading">
+      <el-table :data="filteredConfigs" border stripe v-loading="loading">
         <el-table-column prop="familyId" label="家庭ID" width="80" />
         <el-table-column label="家庭名称" width="120">
           <template slot-scope="scope">
@@ -93,6 +94,7 @@ export default {
       loading: false,
       familyConfigs: [],
       materials: [],
+      keyword: '',
       availableFamilies: [],
       enableDialogVisible: false,
       editDialogVisible: false,
@@ -107,10 +109,22 @@ export default {
       }
     }
   },
+  computed: {
+    filteredConfigs() {
+      if (!this.keyword) return this.familyConfigs
+      const kw = this.keyword.toLowerCase()
+      return this.familyConfigs.filter(c =>
+        String(c.familyId).includes(kw) ||
+        (c.materialTitle || '').toLowerCase().includes(kw)
+      )
+    }
+  },
   mounted() {
     this.loadData()
   },
   methods: {
+    handleSearch() {
+    },
     async loadData() {
       this.loading = true
       try {

+ 1 - 1
cfc-web/src/views/teacher/TeacherFamilies.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="teacher-families">
+  <div class="teacher-families admin-page">
     <!-- 页面标题区 -->
     <div class="page-header">
       <div class="header-left">

+ 14 - 3
cfc-web/src/views/teacher/TeacherMessages.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="teacher-messages">
+  <div class="teacher-messages admin-page">
     <!-- 页面标题区 -->
     <div class="page-header">
       <div class="header-left">
@@ -7,6 +7,7 @@
         <span class="page-desc">与家长的沟通渠道</span>
       </div>
       <div class="header-actions">
+        <el-input v-model="keyword" placeholder="搜索消息..." prefix-icon="el-icon-search" style="width:200px; margin-right:12px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
         <el-button class="mark-read-btn" @click="markAllRead">
           <i class="el-icon-check"></i> 全部已读
         </el-button>
@@ -26,7 +27,7 @@
             <div
               class="conversation-item"
               :class="{ active: activeConversation === msg.conversationId, unread: msg.unreadCount > 0 }"
-              v-for="msg in messages"
+              v-for="msg in filteredMessages"
               :key="msg.id"
               @click="selectConversation(msg)"
             >
@@ -42,7 +43,7 @@
                 <div class="conversation-preview">{{ msg.preview }}</div>
               </div>
             </div>
-            <div v-if="messages.length === 0" class="empty-conversations">
+            <div v-if="filteredMessages.length === 0" class="empty-conversations">
               <i class="el-icon-chat-dot-round"></i>
               <p>暂无消息</p>
             </div>
@@ -209,6 +210,7 @@ export default {
       currentChatName: '',
       chatMessages: [],
       newMessage: '',
+      keyword: '',
       // 家庭信息弹窗
       familyInfoVisible: false,
       familyInfo: null,
@@ -220,12 +222,21 @@ export default {
   computed: {
     totalUnread() {
       return this.messages.reduce((sum, msg) => sum + (msg.unreadCount || 0), 0)
+    },
+    filteredMessages() {
+      if (!this.keyword) return this.messages
+      var kw = this.keyword.toLowerCase()
+      return this.messages.filter(function(msg) {
+        return (msg.senderName && msg.senderName.toLowerCase().indexOf(kw) !== -1) ||
+               (msg.preview && msg.preview.toLowerCase().indexOf(kw) !== -1)
+      })
     }
   },
   created() {
     this.loadMessages()
   },
   methods: {
+    handleSearch() {},
     async loadMessages() {
       try {
         const res = await getGuideMessages({})

+ 1 - 1
cfc-web/src/views/teacher/TeacherOrders.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="teacher-orders">
+  <div class="teacher-orders admin-page">
     <!-- 页面标题区 -->
     <div class="page-header">
       <div class="header-left">

+ 12 - 2
cfc-web/src/views/teacher/TeacherPackages.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="teacher-packages">
+  <div class="teacher-packages admin-page">
     <!-- 页面标题区 -->
     <div class="page-header">
       <div class="header-left">
@@ -7,6 +7,7 @@
         <span class="page-desc">管理您的服务套餐</span>
       </div>
       <div class="header-actions">
+        <el-input v-model="keyword" placeholder="搜索套餐..." prefix-icon="el-icon-search" style="width:200px; margin-right:12px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
         <el-button type="primary" class="primary-btn" @click="handleCreate">
           <i class="el-icon-plus"></i> 创建套餐
         </el-button>
@@ -63,7 +64,7 @@
 
     <!-- 套餐列表 -->
     <el-card class="table-card">
-      <el-table :data="packages" stripe style="width: 100%" v-loading="loading">
+      <el-table :data="filteredPackages" stripe style="width: 100%" v-loading="loading">
         <el-table-column label="套餐信息" min-width="200">
           <template slot-scope="scope">
             <div class="package-cell">
@@ -180,6 +181,7 @@ export default {
       dialogVisible: false,
       isEdit: false,
       loading: false,
+      keyword: '',
       form: {
         id: null,
         packageId: null,
@@ -194,6 +196,13 @@ export default {
     }
   },
   computed: {
+    filteredPackages() {
+      if (!this.keyword) return this.packages
+      var kw = this.keyword.toLowerCase()
+      return this.packages.filter(function(p) {
+        return p.packageName && p.packageName.toLowerCase().indexOf(kw) !== -1
+      })
+    },
     stats() {
       const activePackages = this.packages.filter(p => p.status === 'active')
       return {
@@ -211,6 +220,7 @@ export default {
     this.loadTemplates()
   },
   methods: {
+    handleSearch() {},
     async loadPackages() {
       this.loading = true
       try {

+ 17 - 3
cfc-web/src/views/teacher/TeacherTeam.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="teacher-team">
+  <div class="teacher-team admin-page">
     <!-- 页面标题区 -->
     <div class="page-header">
       <div class="header-left">
@@ -80,9 +80,10 @@
     <el-card class="table-card">
       <div slot="header" class="card-header">
         <span class="card-title">团队成员</span>
-        <span class="card-count">共 {{ teamMembers.length }} 人</span>
+        <el-input v-model="keyword" placeholder="搜索成员..." prefix-icon="el-icon-search" style="width:200px" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
+        <span class="card-count">共 {{ filteredMembers.length }} 人</span>
       </div>
-      <el-table :data="teamMembers" stripe style="width: 100%" v-loading="loading">
+      <el-table :data="filteredMembers" stripe style="width: 100%" v-loading="loading">
         <el-table-column label="成员信息" min-width="200">
           <template slot-scope="scope">
             <div class="member-cell">
@@ -207,6 +208,7 @@ export default {
   data() {
     return {
       teamMembers: [],
+      keyword: '',
       teamStats: {
         totalSales: 0,
         myCommission: 0,
@@ -219,6 +221,16 @@ export default {
       loading: false
     }
   },
+  computed: {
+    filteredMembers() {
+      if (!this.keyword) return this.teamMembers
+      const kw = this.keyword.toLowerCase()
+      return this.teamMembers.filter(m =>
+        (m.nickname || '').toLowerCase().includes(kw) ||
+        String(m.id).includes(kw)
+      )
+    }
+  },
   created() {
     this.loadData()
   },
@@ -246,6 +258,8 @@ export default {
       this.currentMember = row
       this.drawerVisible = true
     },
+    handleSearch() {
+    },
     handleGenerateCode() {
       this.$prompt('请选择邀请码有效期', '生成邀请码', {
         confirmButtonText: '7天',