Ver código fonte

feat(web): 根级/规划师/营养师/健康页全列排序 - 所有数据列补齐 sort-header(27 页)

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
E2E Test Bot 3 dias atrás
pai
commit
b0c5fdafb0

+ 19 - 4
cfc-web/src/views/Children.vue

@@ -45,8 +45,16 @@
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
           </template>
         </el-table-column>
-        <el-table-column prop="nickname" label="昵称" width="150"></el-table-column>
-        <el-table-column prop="age" label="年龄" width="80"></el-table-column>
+        <el-table-column prop="nickname" label="昵称" width="150">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('nickname', $event)">昵称<span v-if="sortIndex('nickname') !== null" class="sort-index" :class="sortClass('nickname') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('nickname') }}</span>{{ sortIcon('nickname') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="age" label="年龄" width="80">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('age', $event)">年龄<span v-if="sortIndex('age') !== null" class="sort-index" :class="sortClass('age') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('age') }}</span>{{ sortIcon('age') }}</span>
+          </template>
+        </el-table-column>
         <el-table-column prop="totalPoints" label="当前积分" width="120">
           <template slot-scope="scope">
             <el-tag type="success">{{ scope.row.totalPoints || 0 }}</el-tag>
@@ -57,13 +65,20 @@
             <span>{{ scope.row.streakDays || 0 }}天</span>
           </template>
         </el-table-column>
-        <el-table-column prop="familyId" label="家庭ID" width="100"></el-table-column>
+        <el-table-column prop="familyId" label="家庭ID" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('familyId', $event)">家庭ID<span v-if="sortIndex('familyId') !== null" class="sort-index" :class="sortClass('familyId') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('familyId') }}</span>{{ sortIcon('familyId') }}</span>
+          </template>
+        </el-table-column>
         <el-table-column prop="penaltyEnabled" label="扣分" width="80">
           <template slot-scope="scope">
             <el-tag :type="scope.row.penaltyEnabled ? 'danger' : 'info'" size="small">
               {{ scope.row.penaltyEnabled ? '开启' : '关闭' }}
             </el-tag>
           </template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('penaltyEnabled', $event)">扣分<span v-if="sortIndex('penaltyEnabled') !== null" class="sort-index" :class="sortClass('penaltyEnabled') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('penaltyEnabled') }}</span>{{ sortIcon('penaltyEnabled') }}</span>
+          </template>
         </el-table-column>
         <el-table-column prop="createdAt" label="创建时间" width="180">
           <template slot-scope="scope">
@@ -168,7 +183,7 @@ export default {
       loading: false,
       submitting: false,
       tableData: [],
-      sortableColumns: { id: 'ID', createdAt: '创建时间' },
+      sortableColumns: { id: 'ID', nickname: '昵称', age: '年龄', familyId: '家庭ID', penaltyEnabled: '扣分', createdAt: '创建时间' },
       searchForm: {
         nickname: '',
         familyId: ''

+ 16 - 4
cfc-web/src/views/Families.vue

@@ -38,9 +38,21 @@
           style="width: 100%"
           :max-height="tableHeight"
         >
-        <el-table-column prop="id" label="ID" width="80"></el-table-column>
-        <el-table-column prop="name" label="家庭名称" width="200"></el-table-column>
-        <el-table-column prop="inviteCode" label="邀请码" width="150"></el-table-column>
+        <el-table-column prop="id" label="ID" width="80">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="name" label="家庭名称" width="200">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('name', $event)">家庭名称<span v-if="sortIndex('name') !== null" class="sort-index" :class="sortClass('name') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('name') }}</span>{{ sortIcon('name') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="inviteCode" label="邀请码" width="150">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('inviteCode', $event)">邀请码<span v-if="sortIndex('inviteCode') !== null" class="sort-index" :class="sortClass('inviteCode') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('inviteCode') }}</span>{{ sortIcon('inviteCode') }}</span>
+          </template>
+        </el-table-column>
         <el-table-column prop="createdAt" label="创建时间" width="180">
           <template slot-scope="scope">
             {{ formatDate(scope.row.createdAt) }}
@@ -302,7 +314,7 @@ export default {
       loading: false,
       submitting: false,
       tableData: [],
-      sortableColumns: { createdAt: '创建时间' },
+      sortableColumns: { id: 'ID', name: '家庭名称', inviteCode: '邀请码', createdAt: '创建时间' },
       searchForm: {
         name: ''
       },

+ 23 - 3
cfc-web/src/views/GuideFamilyTask.vue

@@ -103,23 +103,43 @@
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
           </template>
         </el-table-column>
-        <el-table-column prop="title" label="任务标题" min-width="150"></el-table-column>
-        <el-table-column prop="points" label="积分" width="80"></el-table-column>
+        <el-table-column prop="title" label="任务标题" min-width="150">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('title', $event)">任务标题<span v-if="sortIndex('title') !== null" class="sort-index" :class="sortClass('title') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('title') }}</span>{{ sortIcon('title') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="points" label="积分" width="80">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('points', $event)">积分<span v-if="sortIndex('points') !== null" class="sort-index" :class="sortClass('points') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('points') }}</span>{{ sortIcon('points') }}</span>
+          </template>
+        </el-table-column>
         <el-table-column prop="deadline" label="截止时间" width="160">
           <template slot-scope="{ row }">{{ formatDate(row.deadline) }}</template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('deadline', $event)">截止时间<span v-if="sortIndex('deadline') !== null" class="sort-index" :class="sortClass('deadline') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('deadline') }}</span>{{ sortIcon('deadline') }}</span>
+          </template>
         </el-table-column>
         <el-table-column prop="status" label="状态" width="100">
           <template slot-scope="{ row }">
             <el-tag :type="getStatusType(row.status)" size="small">{{ getStatusText(row.status) }}</el-tag>
           </template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('status', $event)">状态<span v-if="sortIndex('status') !== null" class="sort-index" :class="sortClass('status') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('status') }}</span>{{ sortIcon('status') }}</span>
+          </template>
         </el-table-column>
         <el-table-column prop="needReview" label="需审核" width="80">
           <template slot-scope="{ row }">
             <el-tag :type="row.needReview === 1 ? 'warning' : 'info'" size="small">{{ row.needReview === 1 ? '是' : '否' }}</el-tag>
           </template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('needReview', $event)">需审核<span v-if="sortIndex('needReview') !== null" class="sort-index" :class="sortClass('needReview') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('needReview') }}</span>{{ sortIcon('needReview') }}</span>
+          </template>
         </el-table-column>
         <el-table-column prop="completedAt" label="完成时间" width="160">
           <template slot-scope="{ row }">{{ formatDate(row.completedAt) }}</template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('completedAt', $event)">完成时间<span v-if="sortIndex('completedAt') !== null" class="sort-index" :class="sortClass('completedAt') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('completedAt') }}</span>{{ sortIcon('completedAt') }}</span>
+          </template>
         </el-table-column>
         <el-table-column label="操作" width="220" fixed="right">
           <template slot-scope="{ row }">
@@ -219,7 +239,7 @@ taskLoading: false,
     families: [],
       children: [],
       tasks: [],
-      sortableColumns: { id: 'ID' },
+      sortableColumns: { id: 'ID', title: '任务标题', points: '积分', deadline: '截止时间', status: '状态', needReview: '需审核', completedAt: '完成时间' },
       familyOverview: null,
       total: 0,
       searchForm: {

+ 28 - 4
cfc-web/src/views/GuidePackages.vue

@@ -27,25 +27,49 @@
 
       <!-- 任务模板列表 -->
       <el-table :max-height="tableHeight" :data="tableData" border v-loading="loading">
-        <el-table-column prop="id" label="ID" width="60"></el-table-column>
-        <el-table-column prop="name" label="任务模板名称" min-width="150"></el-table-column>
+        <el-table-column prop="id" label="ID" width="60">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="name" label="任务模板名称" min-width="150">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('name', $event)">任务模板名称<span v-if="sortIndex('name') !== null" class="sort-index" :class="sortClass('name') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('name') }}</span>{{ sortIcon('name') }}</span>
+          </template>
+        </el-table-column>
         <el-table-column prop="cycleDays" label="周期" width="80">
           <template slot-scope="{ row }">{{ row.cycleDays }}天</template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('cycleDays', $event)">周期<span v-if="sortIndex('cycleDays') !== null" class="sort-index" :class="sortClass('cycleDays') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('cycleDays') }}</span>{{ sortIcon('cycleDays') }}</span>
+          </template>
         </el-table-column>
         <el-table-column prop="price" label="价格" width="80">
           <template slot-scope="{ row }">{{ formatPriceWithSymbol(row.price) }}</template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('price', $event)">价格<span v-if="sortIndex('price') !== null" class="sort-index" :class="sortClass('price') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('price') }}</span>{{ sortIcon('price') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="category" label="分类" width="80">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('category', $event)">分类<span v-if="sortIndex('category') !== null" class="sort-index" :class="sortClass('category') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('category') }}</span>{{ sortIcon('category') }}</span>
+          </template>
         </el-table-column>
-        <el-table-column prop="category" label="分类" width="80"></el-table-column>
         <el-table-column prop="status" label="状态" width="100">
           <template slot-scope="{ row }">
             <el-tag :type="getStatusType(row.status)" size="small">{{ getStatusText(row.status) }}</el-tag>
           </template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('status', $event)">状态<span v-if="sortIndex('status') !== null" class="sort-index" :class="sortClass('status') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('status') }}</span>{{ sortIcon('status') }}</span>
+          </template>
         </el-table-column>
         <el-table-column prop="shareCode" label="分享码" width="100">
           <template slot-scope="{ row }">
             <span v-if="row.shareCode" class="code-text">{{ row.shareCode }}</span>
             <span v-else>-</span>
           </template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('shareCode', $event)">分享码<span v-if="sortIndex('shareCode') !== null" class="sort-index" :class="sortClass('shareCode') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('shareCode') }}</span>{{ sortIcon('shareCode') }}</span>
+          </template>
         </el-table-column>
         <el-table-column prop="createdAt" label="创建时间" width="160">
           <template slot-scope="{ row }">{{ formatDate(row.createdAt) }}</template>
@@ -208,7 +232,7 @@ export default {
       loading: false,
       saving: false,
       tableData: [],
-      sortableColumns: { createdAt: '创建时间' },
+      sortableColumns: { id: 'ID', name: '任务模板名称', cycleDays: '周期', price: '价格', category: '分类', status: '状态', shareCode: '分享码', createdAt: '创建时间' },
       searchForm: { status: '' },
       dialogVisible: false,
         dialogTitle: '创建任务模板',

+ 37 - 7
cfc-web/src/views/OperationLogs.vue

@@ -36,26 +36,56 @@
     <!-- 日志列表 -->
     <el-card>
       <el-table :max-height="tableHeight" :data="logs" v-loading="loading" stripe>
-        <el-table-column prop="id" label="ID" width="80"></el-table-column>
-        <el-table-column prop="nickname" label="用户名" width="120"></el-table-column>
+        <el-table-column prop="id" label="ID" width="80">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="nickname" label="用户名" width="120">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('nickname', $event)">用户名<span v-if="sortIndex('nickname') !== null" class="sort-index" :class="sortClass('nickname') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('nickname') }}</span>{{ sortIcon('nickname') }}</span>
+          </template>
+        </el-table-column>
         <el-table-column prop="operationType" label="操作类型" width="100">
           <template slot-scope="scope">
             <el-tag :type="getOperationTypeTag(scope.row.operationType)" size="small">
               {{ scope.row.operationType }}
             </el-tag>
           </template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('operationType', $event)">操作类型<span v-if="sortIndex('operationType') !== null" class="sort-index" :class="sortClass('operationType') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('operationType') }}</span>{{ sortIcon('operationType') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="description" label="操作描述" min-width="200">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('description', $event)">操作描述<span v-if="sortIndex('description') !== null" class="sort-index" :class="sortClass('description') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('description') }}</span>{{ sortIcon('description') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="requestPath" label="请求路径" min-width="180">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('requestPath', $event)">请求路径<span v-if="sortIndex('requestPath') !== null" class="sort-index" :class="sortClass('requestPath') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('requestPath') }}</span>{{ sortIcon('requestPath') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="requestMethod" label="方法" width="80">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('requestMethod', $event)">方法<span v-if="sortIndex('requestMethod') !== null" class="sort-index" :class="sortClass('requestMethod') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('requestMethod') }}</span>{{ sortIcon('requestMethod') }}</span>
+          </template>
         </el-table-column>
-        <el-table-column prop="description" label="操作描述" min-width="200"></el-table-column>
-        <el-table-column prop="requestPath" label="请求路径" min-width="180"></el-table-column>
-        <el-table-column prop="requestMethod" label="方法" width="80"></el-table-column>
         <el-table-column prop="responseStatus" label="状态" width="80">
           <template slot-scope="scope">
             <el-tag :type="scope.row.responseStatus === 200 ? 'success' : 'danger'" size="small">
               {{ scope.row.responseStatus }}
             </el-tag>
           </template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('responseStatus', $event)">状态<span v-if="sortIndex('responseStatus') !== null" class="sort-index" :class="sortClass('responseStatus') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('responseStatus') }}</span>{{ sortIcon('responseStatus') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="clientIp" label="IP地址" width="130">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('clientIp', $event)">IP地址<span v-if="sortIndex('clientIp') !== null" class="sort-index" :class="sortClass('clientIp') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('clientIp') }}</span>{{ sortIcon('clientIp') }}</span>
+          </template>
         </el-table-column>
-        <el-table-column prop="clientIp" label="IP地址" width="130"></el-table-column>
         <el-table-column prop="createdAt" label="操作时间" width="160">
           <template slot-scope="scope">
             {{ formatDate(scope.row.createdAt) }}
@@ -118,7 +148,7 @@ export default {
     return {
       loading: false,
       logs: [],
-      sortableColumns: { createdAt: '操作时间' },
+      sortableColumns: { id: 'ID', nickname: '用户名', operationType: '操作类型', description: '操作描述', requestPath: '请求路径', requestMethod: '方法', responseStatus: '状态', clientIp: 'IP地址', createdAt: '操作时间' },
       filterForm: {
         userId: null,
         operationType: null

+ 25 - 4
cfc-web/src/views/Points.vue

@@ -42,14 +42,25 @@
 
       <!-- 积分流水列表 -->
       <el-table :data="tableData" border v-loading="loading" style="width: 100%" :max-height="tableHeight">
-        <el-table-column prop="id" label="ID" width="80"></el-table-column>
-        <el-table-column prop="childId" label="孩子ID" width="100"></el-table-column>
+        <el-table-column prop="id" label="ID" width="80">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="childId" label="孩子ID" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('childId', $event)">孩子ID<span v-if="sortIndex('childId') !== null" class="sort-index" :class="sortClass('childId') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('childId') }}</span>{{ sortIcon('childId') }}</span>
+          </template>
+        </el-table-column>
         <el-table-column prop="amount" label="积分变动" width="120">
           <template slot-scope="scope">
             <span :style="{ color: scope.row.amount > 0 ? '#67C23A' : '#F56C6C', fontWeight: 'bold' }">
               {{ scope.row.amount > 0 ? '+' : '' }}{{ scope.row.amount }}
             </span>
           </template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('amount', $event)">积分变动<span v-if="sortIndex('amount') !== null" class="sort-index" :class="sortClass('amount') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('amount') }}</span>{{ sortIcon('amount') }}</span>
+          </template>
         </el-table-column>
         <el-table-column prop="type" label="类型" width="100">
           <template slot-scope="scope">
@@ -57,12 +68,22 @@
               {{ getTypeText(scope.row.type) }}
             </el-tag>
           </template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('type', $event)">类型<span v-if="sortIndex('type') !== null" class="sort-index" :class="sortClass('type') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('type') }}</span>{{ sortIcon('type') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="description" label="描述" min-width="200">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('description', $event)">描述<span v-if="sortIndex('description') !== null" class="sort-index" :class="sortClass('description') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('description') }}</span>{{ sortIcon('description') }}</span>
+          </template>
         </el-table-column>
-        <el-table-column prop="description" label="描述" min-width="200"></el-table-column>
         <el-table-column prop="taskId" label="任务ID" width="100">
           <template slot-scope="scope">
             <span>{{ scope.row.taskId || '-' }}</span>
           </template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('taskId', $event)">任务ID<span v-if="sortIndex('taskId') !== null" class="sort-index" :class="sortClass('taskId') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('taskId') }}</span>{{ sortIcon('taskId') }}</span>
+          </template>
         </el-table-column>
         <el-table-column prop="createdAt" label="时间" width="180">
           <template slot-scope="scope">
@@ -105,7 +126,7 @@ export default {
     return {
       loading: false,
       tableData: [],
-      sortableColumns: { createdAt: '时间' },
+      sortableColumns: { id: 'ID', childId: '孩子ID', amount: '积分变动', type: '类型', description: '描述', taskId: '任务ID', createdAt: '时间' },
       searchForm: {
         memberId: '',
         type: ''

+ 48 - 26
cfc-web/src/views/Rewards.vue

@@ -72,31 +72,53 @@
             ></el-pagination>
           </el-dialog>
 
-<el-table :data="tableData" border v-loading="loading" style="width: 100%" :max-height="tableHeight">
-             <el-table-column prop="id" label="ID" width="80"></el-table-column>
-             <el-table-column prop="title" label="奖励名称" min-width="200"></el-table-column>
-            <el-table-column prop="pointsRequired" label="所需积分" width="120">
-              <template slot-scope="scope">
-                <el-tag type="warning">{{ scope.row.pointsRequired }}</el-tag>
-              </template>
-            </el-table-column>
-            <el-table-column prop="category" label="分类" width="100"></el-table-column>
-            <el-table-column prop="familyId" label="家庭ID" width="80"></el-table-column>
-            <el-table-column prop="status" label="状态" width="100">
-              <template slot-scope="scope">
-                <el-tag :type="getStatusType(scope.row.status)" size="small">
-                  {{ getStatusText(scope.row.status) }}
-                </el-tag>
-              </template>
-            </el-table-column>
-            <el-table-column prop="createdAt" label="创建时间" width="180">
-              <template slot-scope="scope">
-                {{ formatDate(scope.row.createdAt) }}
-              </template>
-              <template slot="header">
-                <span class="sort-header" @click.stop="onSortClick('createdAt', $event)">创建时间<span v-if="sortIndex('createdAt') !== null" class="sort-index" :class="sortClass('createdAt') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('createdAt') }}</span>{{ sortIcon('createdAt') }}</span>
-              </template>
-            </el-table-column>
+          <el-table :data="tableData" border v-loading="loading" style="width: 100%" :max-height="tableHeight">
+             <el-table-column prop="id" label="ID" width="80">
+               <template slot="header">
+                 <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
+               </template>
+             </el-table-column>
+             <el-table-column prop="title" label="奖励名称" min-width="200">
+               <template slot="header">
+                 <span class="sort-header" @click.stop="onSortClick('title', $event)">奖励名称<span v-if="sortIndex('title') !== null" class="sort-index" :class="sortClass('title') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('title') }}</span>{{ sortIcon('title') }}</span>
+               </template>
+             </el-table-column>
+             <el-table-column prop="pointsRequired" label="所需积分" width="120">
+               <template slot-scope="scope">
+                 <el-tag type="warning">{{ scope.row.pointsRequired }}</el-tag>
+               </template>
+               <template slot="header">
+                 <span class="sort-header" @click.stop="onSortClick('pointsRequired', $event)">所需积分<span v-if="sortIndex('pointsRequired') !== null" class="sort-index" :class="sortClass('pointsRequired') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('pointsRequired') }}</span>{{ sortIcon('pointsRequired') }}</span>
+               </template>
+             </el-table-column>
+             <el-table-column prop="category" label="分类" width="100">
+               <template slot="header">
+                 <span class="sort-header" @click.stop="onSortClick('category', $event)">分类<span v-if="sortIndex('category') !== null" class="sort-index" :class="sortClass('category') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('category') }}</span>{{ sortIcon('category') }}</span>
+               </template>
+             </el-table-column>
+             <el-table-column prop="familyId" label="家庭ID" width="80">
+               <template slot="header">
+                 <span class="sort-header" @click.stop="onSortClick('familyId', $event)">家庭ID<span v-if="sortIndex('familyId') !== null" class="sort-index" :class="sortClass('familyId') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('familyId') }}</span>{{ sortIcon('familyId') }}</span>
+               </template>
+             </el-table-column>
+             <el-table-column prop="status" label="状态" width="100">
+               <template slot-scope="scope">
+                 <el-tag :type="getStatusType(scope.row.status)" size="small">
+                   {{ getStatusText(scope.row.status) }}
+                 </el-tag>
+               </template>
+               <template slot="header">
+                 <span class="sort-header" @click.stop="onSortClick('status', $event)">状态<span v-if="sortIndex('status') !== null" class="sort-index" :class="sortClass('status') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('status') }}</span>{{ sortIcon('status') }}</span>
+               </template>
+             </el-table-column>
+             <el-table-column prop="createdAt" label="创建时间" width="180">
+               <template slot-scope="scope">
+                 {{ formatDate(scope.row.createdAt) }}
+               </template>
+               <template slot="header">
+                 <span class="sort-header" @click.stop="onSortClick('createdAt', $event)">创建时间<span v-if="sortIndex('createdAt') !== null" class="sort-index" :class="sortClass('createdAt') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('createdAt') }}</span>{{ sortIcon('createdAt') }}</span>
+               </template>
+             </el-table-column>
             <el-table-column label="操作" width="200" fixed="right">
               <template slot-scope="{ row }">
                 <el-button
@@ -206,7 +228,7 @@ export default {
       loadingTemplates: false,
       tableData: [],
       templates: [],
-      sortableColumns: { createdAt: '创建时间' },
+      sortableColumns: { id: 'ID', title: '奖励名称', pointsRequired: '所需积分', category: '分类', familyId: '家庭ID', status: '状态', createdAt: '创建时间' },
       searchForm: {
         status: '',
         familyId: '',

+ 54 - 22
cfc-web/src/views/TaskTemplates.vue

@@ -61,19 +61,33 @@
         style="width: 100%"
         :max-height="tableHeight"
       >
-        <el-table-column prop="id" label="ID" width="80"></el-table-column>
-        <el-table-column prop="title" label="模板名称" min-width="200"></el-table-column>
+        <el-table-column prop="id" label="ID" width="80">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="title" label="模板名称" min-width="200">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('title', $event)">模板名称<span v-if="sortIndex('title') !== null" class="sort-index" :class="sortClass('title') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('title') }}</span>{{ sortIcon('title') }}</span>
+          </template>
+        </el-table-column>
         <el-table-column prop="category" label="分类" width="100">
           <template slot-scope="scope">
             <el-tag :type="getCategoryType(scope.row.category)" size="small">
               {{ scope.row.category }}
             </el-tag>
           </template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('category', $event)">分类<span v-if="sortIndex('category') !== null" class="sort-index" :class="sortClass('category') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('category') }}</span>{{ sortIcon('category') }}</span>
+          </template>
         </el-table-column>
         <el-table-column prop="points" label="积分" width="80">
           <template slot-scope="scope">
             <el-tag type="success">{{ scope.row.points }}</el-tag>
           </template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('points', $event)">积分<span v-if="sortIndex('points') !== null" class="sort-index" :class="sortClass('points') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('points') }}</span>{{ sortIcon('points') }}</span>
+          </template>
         </el-table-column>
         <el-table-column prop="difficulty" label="难度" width="100">
           <template slot-scope="scope">
@@ -81,30 +95,45 @@
               {{ getDifficultyText(scope.row.difficulty) }}
             </el-tag>
           </template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('difficulty', $event)">难度<span v-if="sortIndex('difficulty') !== null" class="sort-index" :class="sortClass('difficulty') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('difficulty') }}</span>{{ sortIcon('difficulty') }}</span>
+          </template>
         </el-table-column>
-<el-table-column prop="recommendedAge" label="推荐年龄" width="100">
-      <template slot-scope="scope">
-        <span v-if="scope.row.recommendedAge">{{ scope.row.recommendedAge }}岁+</span>
-        <span v-else>-</span>
-      </template>
-    </el-table-column>
-    <el-table-column prop="duration" label="时长" width="80">
-      <template slot-scope="scope">
-        <span v-if="scope.row.duration">{{ scope.row.duration }}分钟</span>
-        <span v-else>-</span>
-      </template>
-    </el-table-column>
-    <el-table-column prop="taskType" label="性质" width="80">
-      <template slot-scope="scope">
-        <el-tag v-if="scope.row.taskType === 'recurring'" type="warning" size="small">循环</el-tag>
-        <el-tag v-else type="info" size="small">一次性</el-tag>
-      </template>
-    </el-table-column>
-    <el-table-column prop="needReview" label="需审核" width="80">
+        <el-table-column prop="recommendedAge" label="推荐年龄" width="100">
+          <template slot-scope="scope">
+            <span v-if="scope.row.recommendedAge">{{ scope.row.recommendedAge }}岁+</span>
+            <span v-else>-</span>
+          </template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('recommendedAge', $event)">推荐年龄<span v-if="sortIndex('recommendedAge') !== null" class="sort-index" :class="sortClass('recommendedAge') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('recommendedAge') }}</span>{{ sortIcon('recommendedAge') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="duration" label="时长" width="80">
+          <template slot-scope="scope">
+            <span v-if="scope.row.duration">{{ scope.row.duration }}分钟</span>
+            <span v-else>-</span>
+          </template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('duration', $event)">时长<span v-if="sortIndex('duration') !== null" class="sort-index" :class="sortClass('duration') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('duration') }}</span>{{ sortIcon('duration') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="taskType" label="性质" width="80">
+          <template slot-scope="scope">
+            <el-tag v-if="scope.row.taskType === 'recurring'" type="warning" size="small">循环</el-tag>
+            <el-tag v-else type="info" size="small">一次性</el-tag>
+          </template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('taskType', $event)">性质<span v-if="sortIndex('taskType') !== null" class="sort-index" :class="sortClass('taskType') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('taskType') }}</span>{{ sortIcon('taskType') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="needReview" label="需审核" width="80">
           <template slot-scope="scope">
             <el-tag v-if="scope.row.needReview === 1" type="warning" size="small">是</el-tag>
             <el-tag v-else type="info" size="small">否</el-tag>
           </template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('needReview', $event)">需审核<span v-if="sortIndex('needReview') !== null" class="sort-index" :class="sortClass('needReview') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('needReview') }}</span>{{ sortIcon('needReview') }}</span>
+          </template>
         </el-table-column>
         <el-table-column prop="isActive" label="状态" width="80">
           <template slot-scope="scope">
@@ -115,6 +144,9 @@
               @change="handleStatusChange(scope.row)"
             ></el-switch>
           </template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('isActive', $event)">状态<span v-if="sortIndex('isActive') !== null" class="sort-index" :class="sortClass('isActive') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('isActive') }}</span>{{ sortIcon('isActive') }}</span>
+          </template>
         </el-table-column>
         <el-table-column prop="createdAt" label="创建时间" width="180">
           <template slot-scope="scope">
@@ -276,7 +308,7 @@ export default {
       loading: false,
       submitting: false,
       tableData: [],
-      sortableColumns: { createdAt: '创建时间' },
+      sortableColumns: { id: 'ID', title: '模板名称', category: '分类', points: '积分', difficulty: '难度', recommendedAge: '推荐年龄', duration: '时长', taskType: '性质', needReview: '需审核', isActive: '状态', createdAt: '创建时间' },
       searchForm: {
         category: '',
         difficulty: '',

+ 47 - 5
cfc-web/src/views/Tasks.vue

@@ -96,18 +96,36 @@
 
       <!-- 任务列表 -->
       <el-table :data="tableData" border v-loading="loading" style="width: 100%" :max-height="tableHeight">
-        <el-table-column prop="id" label="ID" width="80"></el-table-column>
-        <el-table-column prop="title" label="任务名称" min-width="200" :class-name="sortClass('title')" @click.native="onSortClick('title')"></el-table-column>
+        <el-table-column prop="id" label="ID" width="80">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="title" label="任务名称" min-width="200">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('title', $event)">任务名称<span v-if="sortIndex('title') !== null" class="sort-index" :class="sortClass('title') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('title') }}</span>{{ sortIcon('title') }}</span>
+          </template>
+        </el-table-column>
         <el-table-column prop="points" label="积分" width="80">
           <template slot-scope="scope">
             <el-tag type="success">{{ scope.row.points }}</el-tag>
           </template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('points', $event)">积分<span v-if="sortIndex('points') !== null" class="sort-index" :class="sortClass('points') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('points') }}</span>{{ sortIcon('points') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="category" label="分类" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('category', $event)">分类<span v-if="sortIndex('category') !== null" class="sort-index" :class="sortClass('category') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('category') }}</span>{{ sortIcon('category') }}</span>
+          </template>
         </el-table-column>
-        <el-table-column prop="category" label="分类" width="100"></el-table-column>
         <el-table-column prop="deadline" label="截止时间" width="180">
           <template slot-scope="scope">
             {{ formatDate(scope.row.deadline) }}
           </template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('deadline', $event)">截止时间<span v-if="sortIndex('deadline') !== null" class="sort-index" :class="sortClass('deadline') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('deadline') }}</span>{{ sortIcon('deadline') }}</span>
+          </template>
         </el-table-column>
         <el-table-column prop="status" label="状态" width="100">
           <template slot-scope="scope">
@@ -115,6 +133,9 @@
               {{ getStatusText(scope.row.status) }}
             </el-tag>
           </template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('status', $event)">状态<span v-if="sortIndex('status') !== null" class="sort-index" :class="sortClass('status') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('status') }}</span>{{ sortIcon('status') }}</span>
+          </template>
         </el-table-column>
         <el-table-column prop="needReview" label="需审核" width="80">
           <template slot-scope="scope">
@@ -122,12 +143,22 @@
               {{ scope.row.needReview ? '是' : '否' }}
             </el-tag>
           </template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('needReview', $event)">需审核<span v-if="sortIndex('needReview') !== null" class="sort-index" :class="sortClass('needReview') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('needReview') }}</span>{{ sortIcon('needReview') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="executorName" label="用户" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('executorName', $event)">用户<span v-if="sortIndex('executorName') !== null" class="sort-index" :class="sortClass('executorName') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('executorName') }}</span>{{ sortIcon('executorName') }}</span>
+          </template>
         </el-table-column>
-        <el-table-column prop="executorName" label="用户" width="100"></el-table-column>
         <el-table-column prop="createdAt" label="创建时间" width="180">
           <template slot-scope="scope">
             {{ formatDate(scope.row.createdAt) }}
           </template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('createdAt', $event)">创建时间<span v-if="sortIndex('createdAt') !== null" class="sort-index" :class="sortClass('createdAt') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('createdAt') }}</span>{{ sortIcon('createdAt') }}</span>
+          </template>
         </el-table-column>
         <el-table-column label="操作" width="180" fixed="right">
           <template slot-scope="scope">
@@ -177,7 +208,6 @@ import SortMixin from '@/mixins/SortMixin'
 export default {
   mixins: [SortMixin],
   name: 'Tasks',
-  sortableColumns: { title: '任务名称' },
   computed: {
     tableHeight() {
       return window.innerHeight - 340
@@ -188,6 +218,7 @@ export default {
       loading: false,
       sortState: [],
       tableData: [],
+      sortableColumns: { title: '任务名称', id: 'ID', points: '积分', category: '分类', deadline: '截止时间', status: '状态', needReview: '需审核', executorName: '用户', createdAt: '创建时间' },
       stats: {
         total: 0,
         today: 0
@@ -375,4 +406,15 @@ export default {
   font-weight: bold;
   color: #409EFF;
 }
+
+.sort-header {
+  cursor: pointer;
+  user-select: none;
+  font-weight: 500;
+  font-size: 12px;
+  white-space: nowrap;
+}
+.sort-header:hover {
+  color: #409eff;
+}
 </style>

+ 17 - 7
cfc-web/src/views/TeacherConsult.vue

@@ -21,20 +21,30 @@
 
       <!-- 咨询列表 -->
       <el-table :max-height="tableHeight" :data="messages" border v-loading="loading">
-        <el-table-column prop="id" label="ID" width="60"></el-table-column>
-        <el-table-column prop="familyId" label="家庭ID" width="80"></el-table-column>
-        <el-table-column label="家长" width="100">
-          <template slot-scope="{ row }">
-            {{ row.parentNickname || '用户' + row.parentId }}
+        <el-table-column prop="id" label="ID" width="60">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="familyId" label="家庭ID" width="80">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('familyId', $event)">家庭ID<span v-if="sortIndex('familyId') !== null" class="sort-index" :class="sortClass('familyId') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('familyId') }}</span>{{ sortIcon('familyId') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="content" label="咨询内容" min-width="200">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('content', $event)">咨询内容<span v-if="sortIndex('content') !== null" class="sort-index" :class="sortClass('content') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('content') }}</span>{{ sortIcon('content') }}</span>
           </template>
         </el-table-column>
-        <el-table-column prop="content" label="咨询内容" min-width="200"></el-table-column>
         <el-table-column prop="status" label="状态" width="80">
           <template slot-scope="{ row }">
             <el-tag :type="row.status === 'pending' ? 'warning' : 'success'" size="small">
               {{ row.status === 'pending' ? '待回复' : '已回复' }}
             </el-tag>
           </template>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('status', $event)">状态<span v-if="sortIndex('status') !== null" class="sort-index" :class="sortClass('status') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('status') }}</span>{{ sortIcon('status') }}</span>
+          </template>
         </el-table-column>
         <el-table-column prop="createdAt" label="咨询时间" width="160">
           <template slot-scope="{ row }">{{ formatDate(row.createdAt) }}</template>
@@ -102,7 +112,7 @@ export default {
       replyLoading: false,
       messages: [],
       total: 0,
-      sortableColumns: { createdAt: '咨询时间' },
+      sortableColumns: { id: 'ID', familyId: '家庭ID', content: '咨询内容', status: '状态', createdAt: '咨询时间' },
       searchForm: {
         status: ''
       },

+ 21 - 5
cfc-web/src/views/Users.vue

@@ -71,14 +71,26 @@
         style="width: 100%"
         :max-height="tableHeight"
       >
-        <el-table-column prop="id" label="ID" width="80"></el-table-column>
+        <el-table-column prop="id" label="ID" width="80">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
+          </template>
+        </el-table-column>
         <el-table-column prop="nickname" label="昵称" width="150">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('nickname', $event)" @dblclick.stop="onSortToggle('nickname')">昵称<span v-if="sortIndex('nickname') !== null" class="sort-index" :class="sortClass('nickname') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('nickname') }}</span>{{ sortIcon('nickname') }}</span>
 
           </template></el-table-column>
-        <el-table-column prop="realName" label="真实姓名" width="120"></el-table-column>
-        <el-table-column prop="phone" label="手机号" width="120"></el-table-column>
+        <el-table-column prop="realName" label="真实姓名" width="120">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('realName', $event)">真实姓名<span v-if="sortIndex('realName') !== null" class="sort-index" :class="sortClass('realName') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('realName') }}</span>{{ sortIcon('realName') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="phone" label="手机号" width="120">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('phone', $event)">手机号<span v-if="sortIndex('phone') !== null" class="sort-index" :class="sortClass('phone') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('phone') }}</span>{{ sortIcon('phone') }}</span>
+          </template>
+        </el-table-column>
         <el-table-column label="角色" width="200">
           <template slot-scope="scope">
             <el-tag
@@ -92,7 +104,11 @@
             </el-tag>
           </template>
         </el-table-column>
-        <el-table-column prop="familyId" label="家庭ID" width="100"></el-table-column>
+        <el-table-column prop="familyId" label="家庭ID" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('familyId', $event)">家庭ID<span v-if="sortIndex('familyId') !== null" class="sort-index" :class="sortClass('familyId') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('familyId') }}</span>{{ sortIcon('familyId') }}</span>
+          </template>
+        </el-table-column>
         <el-table-column label="会员级别" width="100">
           <template slot-scope="{ row }">
             <el-tag :type="memberLevelType(row.memberLevel)" size="small">{{ memberLevelLabel(row.memberLevel) }}</el-tag>
@@ -255,7 +271,7 @@ export default {
       loading: false,
       submitting: false,
       tableData: [],
-      sortableColumns: { nickname: '昵称', createdAt: '创建时间' },
+      sortableColumns: { id: 'ID', nickname: '昵称', realName: '真实姓名', phone: '手机号', familyId: '家庭ID', createdAt: '创建时间' },
 
       searchForm: {
         role: '',

+ 30 - 5
cfc-web/src/views/Wishes.vue

@@ -213,22 +213,44 @@
           </el-dialog>
 
           <el-table :data="tableData" border v-loading="loading" style="width: 100%" :max-height="tableHeight">
-            <el-table-column prop="id" label="ID" width="80"></el-table-column>
-            <el-table-column prop="childName" label="孩子" width="100"></el-table-column>
-            <el-table-column prop="title" label="心愿名称" min-width="200"></el-table-column>
+            <el-table-column prop="id" label="ID" width="80">
+              <template slot="header">
+                <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column prop="childName" label="孩子" width="100">
+              <template slot="header">
+                <span class="sort-header" @click.stop="onSortClick('childName', $event)">孩子<span v-if="sortIndex('childName') !== null" class="sort-index" :class="sortClass('childName') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('childName') }}</span>{{ sortIcon('childName') }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column prop="title" label="心愿名称" min-width="200">
+              <template slot="header">
+                <span class="sort-header" @click.stop="onSortClick('title', $event)">心愿名称<span v-if="sortIndex('title') !== null" class="sort-index" :class="sortClass('title') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('title') }}</span>{{ sortIcon('title') }}</span>
+              </template>
+            </el-table-column>
             <el-table-column prop="pointsRequired" label="所需积分" width="120">
               <template slot-scope="scope">
                 <el-tag v-if="scope.row.pointsRequired" type="warning">{{ scope.row.pointsRequired }}</el-tag>
                 <span v-else class="no-price">未定价</span>
               </template>
+              <template slot="header">
+                <span class="sort-header" @click.stop="onSortClick('pointsRequired', $event)">所需积分<span v-if="sortIndex('pointsRequired') !== null" class="sort-index" :class="sortClass('pointsRequired') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('pointsRequired') }}</span>{{ sortIcon('pointsRequired') }}</span>
+              </template>
+            </el-table-column>
+            <el-table-column prop="category" label="分类" width="100">
+              <template slot="header">
+                <span class="sort-header" @click.stop="onSortClick('category', $event)">分类<span v-if="sortIndex('category') !== null" class="sort-index" :class="sortClass('category') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('category') }}</span>{{ sortIcon('category') }}</span>
+              </template>
             </el-table-column>
-            <el-table-column prop="category" label="分类" width="100"></el-table-column>
             <el-table-column prop="status" label="状态" width="100">
               <template slot-scope="scope">
                 <el-tag :type="getStatusType(scope.row.status)" size="small">
                   {{ scope.row.statusText }}
                 </el-tag>
               </template>
+              <template slot="header">
+                <span class="sort-header" @click.stop="onSortClick('status', $event)">状态<span v-if="sortIndex('status') !== null" class="sort-index" :class="sortClass('status') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('status') }}</span>{{ sortIcon('status') }}</span>
+              </template>
             </el-table-column>
             <el-table-column prop="creatorType" label="创建者" width="80">
               <template slot-scope="scope">
@@ -236,6 +258,9 @@
                   {{ scope.row.creatorType === 'parent' ? '家长' : '孩子' }}
                 </el-tag>
               </template>
+              <template slot="header">
+                <span class="sort-header" @click.stop="onSortClick('creatorType', $event)">创建者<span v-if="sortIndex('creatorType') !== null" class="sort-index" :class="sortClass('creatorType') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('creatorType') }}</span>{{ sortIcon('creatorType') }}</span>
+              </template>
             </el-table-column>
             <el-table-column prop="createdAt" label="创建时间" width="180">
               <template slot-scope="scope">
@@ -300,7 +325,7 @@ export default {
       loading: false,
       submitting: false,
       tableData: [],
-      sortableColumns: { createdAt: '创建时间' },
+      sortableColumns: { id: 'ID', childName: '孩子', title: '心愿名称', pointsRequired: '所需积分', category: '分类', status: '状态', creatorType: '创建者', createdAt: '创建时间' },
       pendingWishes: [],
       searchForm: {
         status: '',

+ 15 - 6
cfc-web/src/views/health/HealthReportAudit.vue

@@ -5,12 +5,21 @@
     </div>
 
     <el-table :data="list" v-loading="loading" border stripe :max-height="tableHeight">
-      <el-table-column prop="id" label="草稿ID" width="80" />
-      <el-table-column label="上传用户" width="140">
-        <template slot-scope="{ row }">{{ row.userName || '用户' + row.userId }}</template>
+      <el-table-column prop="id" label="草稿ID" width="80">
+        <template slot="header">
+          <span class="sort-header" @click.stop="onSortClick('id', $event)">草稿ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="reportType" label="报告类型" width="120">
+        <template slot="header">
+          <span class="sort-header" @click.stop="onSortClick('reportType', $event)">报告类型<span v-if="sortIndex('reportType') !== null" class="sort-index" :class="sortClass('reportType') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('reportType') }}</span>{{ sortIcon('reportType') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="originalFilename" label="文件名" min-width="200" show-overflow-tooltip>
+        <template slot="header">
+          <span class="sort-header" @click.stop="onSortClick('originalFilename', $event)">文件名<span v-if="sortIndex('originalFilename') !== null" class="sort-index" :class="sortClass('originalFilename') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('originalFilename') }}</span>{{ sortIcon('originalFilename') }}</span>
+        </template>
       </el-table-column>
-      <el-table-column prop="reportType" label="报告类型" width="120" />
-      <el-table-column prop="originalFilename" label="文件名" min-width="200" show-overflow-tooltip />
       <el-table-column label="状态" width="100">
         <template slot-scope="{ row }">
           <el-tag type="warning">待审核</el-tag>
@@ -129,7 +138,7 @@ export default {
       detailVisible: false,
       detail: null,
       activeTab: 'indicators',
-      sortableColumns: { createdAt: '上传时间' }
+      sortableColumns: { createdAt: '上传时间', id: '草稿ID', reportType: '报告类型', originalFilename: '文件名' },
     }
   },
   computed: {

+ 16 - 4
cfc-web/src/views/nutritionist/HealthCheckins.vue

@@ -8,9 +8,21 @@
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
           </template>
         </el-table-column>
-        <el-table-column prop="childName" label="孩子" width="120" />
-        <el-table-column prop="checkinType" label="打卡类型" width="120" />
-        <el-table-column prop="note" label="备注" />
+        <el-table-column prop="childName" label="孩子" width="120">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('childName', $event)">孩子<span v-if="sortIndex('childName') !== null" class="sort-index" :class="sortClass('childName') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('childName') }}</span>{{ sortIcon('childName') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="checkinType" label="打卡类型" width="120">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('checkinType', $event)">打卡类型<span v-if="sortIndex('checkinType') !== null" class="sort-index" :class="sortClass('checkinType') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('checkinType') }}</span>{{ sortIcon('checkinType') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="note" label="备注">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('note', $event)">备注<span v-if="sortIndex('note') !== null" class="sort-index" :class="sortClass('note') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('note') }}</span>{{ sortIcon('note') }}</span>
+          </template>
+        </el-table-column>
         <el-table-column prop="createdAt" label="打卡时间" width="180">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('createdAt', $event)">打卡时间<span v-if="sortIndex('createdAt') !== null" class="sort-index" :class="sortClass('createdAt') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('createdAt') }}</span>{{ sortIcon('createdAt') }}</span>
@@ -31,7 +43,7 @@ export default {
       return window.innerHeight - 280
     }
   },
-  data() { return { loading: false, checkins: [], sortableColumns: { id: 'ID', createdAt: '打卡时间' } } },
+  data() { return { loading: false, checkins: [], sortableColumns: { id: 'ID', createdAt: '打卡时间', childName: '孩子', checkinType: '打卡类型', note: '备注' } } },
   created() { this.loadCheckins() },
   methods: {
     loadList() { this.loadCheckins() },

+ 17 - 3
cfc-web/src/views/nutritionist/HealthIndicators.vue

@@ -8,16 +8,30 @@
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
           </template>
         </el-table-column>
-        <el-table-column prop="childName" label="孩子" width="120" />
+        <el-table-column prop="childName" label="孩子" width="120">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('childName', $event)">孩子<span v-if="sortIndex('childName') !== null" class="sort-index" :class="sortClass('childName') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('childName') }}</span>{{ sortIcon('childName') }}</span>
+          </template>
+        </el-table-column>
         <el-table-column prop="indicatorName" label="指标名称" width="120">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('indicatorName', $event)">指标名称<span v-if="sortIndex('indicatorName') !== null" class="sort-index" :class="sortClass('indicatorName') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('indicatorName') }}</span>{{ sortIcon('indicatorName') }}</span>
+          </template>
           <template slot-scope="{ row }">
             <span :style="row.status && row.status !== '正常' ? 'color: #DC2626; font-weight: bold' : ''">
               {{ row.indicatorName }}
             </span>
           </template>
         </el-table-column>
-        <el-table-column prop="value" label="数值" width="100" />
+        <el-table-column prop="value" label="数值" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('value', $event)">数值<span v-if="sortIndex('value') !== null" class="sort-index" :class="sortClass('value') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('value') }}</span>{{ sortIcon('value') }}</span>
+          </template>
+        </el-table-column>
         <el-table-column prop="status" label="状态" width="80">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('status', $event)">状态<span v-if="sortIndex('status') !== null" class="sort-index" :class="sortClass('status') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('status') }}</span>{{ sortIcon('status') }}</span>
+          </template>
           <template slot-scope="{ row }">
             <el-tag v-if="row.status && row.status !== '正常'" :type="statusTagType(row.status)" size="small">
               {{ row.status }}
@@ -55,7 +69,7 @@ export default {
       return window.innerHeight - 280
     }
   },
-  data() { return { loading: false, indicators: [], sortableColumns: { id: 'ID', recordedAt: '记录时间' } } },
+  data() { return { loading: false, indicators: [], sortableColumns: { id: 'ID', recordedAt: '记录时间', childName: '孩子', indicatorName: '指标名称', value: '数值', status: '状态' } } },
   created() { this.loadIndicators() },
   methods: {
     loadList() { this.loadIndicators() },

+ 39 - 8
cfc-web/src/views/nutritionist/HealthReports.vue

@@ -10,20 +10,51 @@
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
           </template>
         </el-table-column>
-        <el-table-column prop="childName" label="孩子" width="100" />
-        <el-table-column prop="reportType" label="报告类型" width="100" />
+        <el-table-column prop="childName" label="孩子" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('childName', $event)">孩子<span v-if="sortIndex('childName') !== null" class="sort-index" :class="sortClass('childName') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('childName') }}</span>{{ sortIcon('childName') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="reportType" label="报告类型" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('reportType', $event)">报告类型<span v-if="sortIndex('reportType') !== null" class="sort-index" :class="sortClass('reportType') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('reportType') }}</span>{{ sortIcon('reportType') }}</span>
+          </template>
+        </el-table-column>
         <el-table-column prop="overallScore" label="综合评分" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('overallScore', $event)">综合评分<span v-if="sortIndex('overallScore') !== null" class="sort-index" :class="sortClass('overallScore') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('overallScore') }}</span>{{ sortIcon('overallScore') }}</span>
+          </template>
           <template slot-scope="{ row }">
             <el-tag :type="scoreType(row.overallScore)" size="small">
               {{ row.overallScore ?? '-' }}
             </el-tag>
           </template>
         </el-table-column>
-        <el-table-column prop="gutHealthScore" label="肠道" width="80" />
-        <el-table-column prop="nutritionScore" label="营养" width="80" />
-        <el-table-column prop="chronicDiseaseScore" label="慢病" width="80" />
-        <el-table-column prop="gutAge" label="肠道年龄" width="80" />
-        <el-table-column prop="gutType" label="肠道类型" width="100" />
+        <el-table-column prop="gutHealthScore" label="肠道" width="80">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('gutHealthScore', $event)">肠道<span v-if="sortIndex('gutHealthScore') !== null" class="sort-index" :class="sortClass('gutHealthScore') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('gutHealthScore') }}</span>{{ sortIcon('gutHealthScore') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="nutritionScore" label="营养" width="80">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('nutritionScore', $event)">营养<span v-if="sortIndex('nutritionScore') !== null" class="sort-index" :class="sortClass('nutritionScore') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('nutritionScore') }}</span>{{ sortIcon('nutritionScore') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="chronicDiseaseScore" label="慢病" width="80">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('chronicDiseaseScore', $event)">慢病<span v-if="sortIndex('chronicDiseaseScore') !== null" class="sort-index" :class="sortClass('chronicDiseaseScore') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('chronicDiseaseScore') }}</span>{{ sortIcon('chronicDiseaseScore') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="gutAge" label="肠道年龄" width="80">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('gutAge', $event)">肠道年龄<span v-if="sortIndex('gutAge') !== null" class="sort-index" :class="sortClass('gutAge') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('gutAge') }}</span>{{ sortIcon('gutAge') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="gutType" label="肠道类型" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('gutType', $event)">肠道类型<span v-if="sortIndex('gutType') !== null" class="sort-index" :class="sortClass('gutType') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('gutType') }}</span>{{ sortIcon('gutType') }}</span>
+          </template>
+        </el-table-column>
         <el-table-column prop="reportDate" label="报告日期" width="160">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('reportDate', $event)">报告日期<span v-if="sortIndex('reportDate') !== null" class="sort-index" :class="sortClass('reportDate') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('reportDate') }}</span>{{ sortIcon('reportDate') }}</span>
@@ -65,7 +96,7 @@ export default {
     }
   },
   data() {
-    return { loading: false, reports: [], page: 1, size: 20, total: 0, sortableColumns: { id: 'ID', reportDate: '报告日期' } }
+    return { loading: false, reports: [], page: 1, size: 20, total: 0, sortableColumns: { id: 'ID', reportDate: '报告日期', childName: '孩子', reportType: '报告类型', overallScore: '综合评分', gutHealthScore: '肠道', nutritionScore: '营养', chronicDiseaseScore: '慢病', gutAge: '肠道年龄', gutType: '肠道类型' } }
   },
   created() { this.loadReports() },
   methods: {

+ 26 - 6
cfc-web/src/views/nutritionist/NutritionMappings.vue

@@ -11,11 +11,31 @@
             <span class="sort-header" @click.stop="onSortClick('id', $event)">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
           </template>
         </el-table-column>
-        <el-table-column prop="indicatorNamePattern" label="指标名模式" width="180" />
-        <el-table-column prop="nutrient" label="营养素" width="100" />
-        <el-table-column prop="severityWeight" label="权重" width="80" />
-        <el-table-column prop="deficiencySymptoms" label="缺乏症状" min-width="150" show-overflow-tooltip />
-        <el-table-column prop="foodSuggestions" label="推荐食物" min-width="180" show-overflow-tooltip />
+        <el-table-column prop="indicatorNamePattern" label="指标名模式" width="180">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('indicatorNamePattern', $event)">指标名模式<span v-if="sortIndex('indicatorNamePattern') !== null" class="sort-index" :class="sortClass('indicatorNamePattern') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('indicatorNamePattern') }}</span>{{ sortIcon('indicatorNamePattern') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="nutrient" label="营养素" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('nutrient', $event)">营养素<span v-if="sortIndex('nutrient') !== null" class="sort-index" :class="sortClass('nutrient') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('nutrient') }}</span>{{ sortIcon('nutrient') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="severityWeight" label="权重" width="80">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('severityWeight', $event)">权重<span v-if="sortIndex('severityWeight') !== null" class="sort-index" :class="sortClass('severityWeight') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('severityWeight') }}</span>{{ sortIcon('severityWeight') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="deficiencySymptoms" label="缺乏症状" min-width="150" show-overflow-tooltip>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('deficiencySymptoms', $event)">缺乏症状<span v-if="sortIndex('deficiencySymptoms') !== null" class="sort-index" :class="sortClass('deficiencySymptoms') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('deficiencySymptoms') }}</span>{{ sortIcon('deficiencySymptoms') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="foodSuggestions" label="推荐食物" min-width="180" show-overflow-tooltip>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('foodSuggestions', $event)">推荐食物<span v-if="sortIndex('foodSuggestions') !== null" class="sort-index" :class="sortClass('foodSuggestions') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('foodSuggestions') }}</span>{{ sortIcon('foodSuggestions') }}</span>
+          </template>
+        </el-table-column>
         <el-table-column prop="sortOrder" label="排序" width="80">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('sortOrder', $event)">排序<span v-if="sortIndex('sortOrder') !== null" class="sort-index" :class="sortClass('sortOrder') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('sortOrder') }}</span>{{ sortIcon('sortOrder') }}</span>
@@ -85,7 +105,7 @@ export default {
       mappings: [],
       dialogVisible: false,
       dialogTitle: '新增映射',
-      sortableColumns: { id: 'ID', sortOrder: '排序' },
+      sortableColumns: { id: 'ID', sortOrder: '排序', indicatorNamePattern: '指标名模式', nutrient: '营养素', severityWeight: '权重', deficiencySymptoms: '缺乏症状', foodSuggestions: '推荐食物' },
       form: {
         id: null,
         indicatorNamePattern: '',

+ 11 - 3
cfc-web/src/views/teacher/FamilyList.vue

@@ -11,8 +11,16 @@
           <span class="sort-header" @click.stop="onSortClick('id', $event)" @dblclick.stop="onSortToggle('id')">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
         </template>
       </el-table-column>
-      <el-table-column prop="familyName" label="家庭名称" />
-      <el-table-column prop="memberCount" label="成员数" width="100" />
+      <el-table-column prop="familyName" label="家庭名称">
+        <template slot="header">
+          <span class="sort-header" @click.stop="onSortClick('familyName', $event)">家庭名称<span v-if="sortIndex('familyName') !== null" class="sort-index" :class="sortClass('familyName') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('familyName') }}</span>{{ sortIcon('familyName') }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="memberCount" label="成员数" width="100">
+        <template slot="header">
+          <span class="sort-header" @click.stop="onSortClick('memberCount', $event)">成员数<span v-if="sortIndex('memberCount') !== null" class="sort-index" :class="sortClass('memberCount') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('memberCount') }}</span>{{ sortIcon('memberCount') }}</span>
+        </template>
+      </el-table-column>
       <el-table-column prop="createdAt" label="绑定时间" width="180">
         <template slot="header">
           <span class="sort-header" @click.stop="onSortClick('createdAt', $event)" @dblclick.stop="onSortToggle('createdAt')">绑定时间<span v-if="sortIndex('createdAt') !== null" class="sort-index" :class="sortClass('createdAt') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('createdAt') }}</span>{{ sortIcon('createdAt') }}</span>
@@ -43,7 +51,7 @@ export default {
   mixins: [SortMixin],
   name: 'FamilyList',
   data() {
-    return { loading: false, families: [], keyword: '', sortableColumns: { id: 'ID', createdAt: '绑定时间' } }
+    return { loading: false, families: [], keyword: '', sortableColumns: { id: 'ID', createdAt: '绑定时间', familyName: '家庭名称', memberCount: '成员数' } }
   },
   computed: {
     tableHeight() {

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

@@ -11,9 +11,21 @@
             <span class="sort-header" @click.stop="onSortClick('id', $event)" @dblclick.stop="onSortToggle('id')">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
           </template>
         </el-table-column>
-        <el-table-column prop="childName" label="孩子" width="120" />
-        <el-table-column prop="title" label="计划标题" />
-        <el-table-column prop="status" label="状态" width="100" />
+        <el-table-column prop="childName" label="孩子" width="120">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('childName', $event)">孩子<span v-if="sortIndex('childName') !== null" class="sort-index" :class="sortClass('childName') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('childName') }}</span>{{ sortIcon('childName') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="title" label="计划标题">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('title', $event)">计划标题<span v-if="sortIndex('title') !== null" class="sort-index" :class="sortClass('title') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('title') }}</span>{{ sortIcon('title') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="status" label="状态" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('status', $event)">状态<span v-if="sortIndex('status') !== null" class="sort-index" :class="sortClass('status') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('status') }}</span>{{ sortIcon('status') }}</span>
+          </template>
+        </el-table-column>
         <el-table-column prop="createdAt" label="创建时间" width="180">
           <template slot="header">
             <span class="sort-header" @click.stop="onSortClick('createdAt', $event)" @dblclick.stop="onSortToggle('createdAt')">创建时间<span v-if="sortIndex('createdAt') !== null" class="sort-index" :class="sortClass('createdAt') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('createdAt') }}</span>{{ sortIcon('createdAt') }}</span>
@@ -29,7 +41,7 @@ import SortMixin from '@/mixins/SortMixin'
 export default {
   mixins: [SortMixin],
   name: 'GrowthPlans',
-  data() { return { loading: false, plans: [], keyword: '', sortableColumns: { id: 'ID', createdAt: '创建时间' } } },
+  data() { return { loading: false, plans: [], keyword: '', sortableColumns: { id: 'ID', createdAt: '创建时间', childName: '孩子', title: '计划标题', status: '状态' } } },
   computed: {
     tableHeight() {
       return window.innerHeight - 300

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

@@ -18,15 +18,30 @@
             <span class="sort-header" @click.stop="onSortClick('id', $event)" @dblclick.stop="onSortToggle('id')">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
           </template>
         </el-table-column>
-        <el-table-column prop="childName" label="孩子" width="100" />
+        <el-table-column prop="childName" label="孩子" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('childName', $event)">孩子<span v-if="sortIndex('childName') !== null" class="sort-index" :class="sortClass('childName') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('childName') }}</span>{{ sortIcon('childName') }}</span>
+          </template>
+        </el-table-column>
         <el-table-column prop="sourceType" label="来源" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('sourceType', $event)">来源<span v-if="sortIndex('sourceType') !== null" class="sort-index" :class="sortClass('sourceType') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('sourceType') }}</span>{{ sortIcon('sourceType') }}</span>
+          </template>
           <template slot-scope="scope">
             <el-tag size="mini" :type="sourceTagType(scope.row.sourceType)">{{ sourceLabel(scope.row.sourceType) }}</el-tag>
           </template>
         </el-table-column>
         <el-table-column prop="recordTitle" label="标题" min-width="140" show-overflow-tooltip />
-        <el-table-column prop="reportDate" label="报告日期" width="110" />
-        <el-table-column prop="overallScore" label="综合得分" width="90" />
+        <el-table-column prop="reportDate" label="报告日期" width="110">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('reportDate', $event)">报告日期<span v-if="sortIndex('reportDate') !== null" class="sort-index" :class="sortClass('reportDate') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('reportDate') }}</span>{{ sortIcon('reportDate') }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="overallScore" label="综合得分" width="90">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('overallScore', $event)">综合得分<span v-if="sortIndex('overallScore') !== null" class="sort-index" :class="sortClass('overallScore') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('overallScore') }}</span>{{ sortIcon('overallScore') }}</span>
+          </template>
+        </el-table-column>
         <el-table-column prop="parentRecordId" label="主记录ID" width="90">
           <template slot-scope="scope">
             <span v-if="scope.row.parentRecordId">{{ scope.row.parentRecordId }}</span>
@@ -34,6 +49,9 @@
           </template>
         </el-table-column>
         <el-table-column prop="isLatest" label="最新" width="60">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('isLatest', $event)">最新<span v-if="sortIndex('isLatest') !== null" class="sort-index" :class="sortClass('isLatest') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('isLatest') }}</span>{{ sortIcon('isLatest') }}</span>
+          </template>
           <template slot-scope="scope">
             <span v-if="scope.row.isLatest === 1">&#10003;</span>
             <span v-else>-</span>
@@ -60,7 +78,7 @@ export default {
       records: [],
       sourceFilter: '',
       keyword: '',
-      sortableColumns: { id: 'ID', createdAt: '创建时间' }
+      sortableColumns: { id: 'ID', createdAt: '创建时间', childName: '孩子', sourceType: '来源', reportDate: '报告日期', overallScore: '综合得分', isLatest: '最新' }
     }
   },
   computed: {

+ 16 - 4
cfc-web/src/views/teacher/HealthPlanReview.vue

@@ -21,10 +21,16 @@
           <span class="sort-header" @click.stop="onSortClick('id', $event)" @dblclick.stop="onSortToggle('id')">方案ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="家庭成员" width="150">
+      <el-table-column prop="memberName" label="家庭成员" width="150">
+        <template slot="header">
+          <span class="sort-header" @click.stop="onSortClick('memberName', $event)">家庭成员<span v-if="sortIndex('memberName') !== null" class="sort-index" :class="sortClass('memberName') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('memberName') }}</span>{{ sortIcon('memberName') }}</span>
+        </template>
         <template slot-scope="{ row }">{{ row.memberName || row.memberIds }}</template>
       </el-table-column>
-      <el-table-column label="目标" min-width="180">
+      <el-table-column prop="goal" label="目标" min-width="180">
+        <template slot="header">
+          <span class="sort-header" @click.stop="onSortClick('goal', $event)">目标<span v-if="sortIndex('goal') !== null" class="sort-index" :class="sortClass('goal') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('goal') }}</span>{{ sortIcon('goal') }}</span>
+        </template>
         <template slot-scope="{ row }">{{ row.goal ? row.goal.substring(0,60) + (row.goal.length>60?'...':'') : '-' }}</template>
       </el-table-column>
       <el-table-column label="维度" width="120">
@@ -32,12 +38,18 @@
           <el-tag v-for="(d,i) in (row.dimensions||'').split(',')" :key="i" size="mini" style="margin-right:4px">{{ d }}</el-tag>
         </template>
       </el-table-column>
-      <el-table-column label="状态" width="100">
+      <el-table-column prop="status" label="状态" width="100">
+        <template slot="header">
+          <span class="sort-header" @click.stop="onSortClick('status', $event)">状态<span v-if="sortIndex('status') !== null" class="sort-index" :class="sortClass('status') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('status') }}</span>{{ sortIcon('status') }}</span>
+        </template>
         <template slot-scope="{ row }">
           <el-tag :type="statusType(row.status)">{{ statusLabel(row.status) }}</el-tag>
         </template>
       </el-table-column>
       <el-table-column prop="reviewComment" label="审核意见" width="150">
+        <template slot="header">
+          <span class="sort-header" @click.stop="onSortClick('reviewComment', $event)">审核意见<span v-if="sortIndex('reviewComment') !== null" class="sort-index" :class="sortClass('reviewComment') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('reviewComment') }}</span>{{ sortIcon('reviewComment') }}</span>
+        </template>
         <template slot-scope="{ row }">{{ row.reviewComment || '-' }}</template>
       </el-table-column>
       <el-table-column prop="createdAt" label="创建时间" width="160">
@@ -127,7 +139,7 @@ export default {
       currentPlan: null,
       editForm: { planContent: '', reviewComment: '' },
       rejectForm: { planId: null, comment: '' },
-      sortableColumns: { id: '方案ID', createdAt: '创建时间' },
+      sortableColumns: { id: '方案ID', createdAt: '创建时间', memberName: '家庭成员', goal: '目标', status: '状态', reviewComment: '审核意见' },
       editTasks: { sections: [] },
       actionTypes: [
         { value: 'buy', label: '购买' },

+ 20 - 4
cfc-web/src/views/teacher/ProxyReport.vue

@@ -88,19 +88,35 @@
             <span class="sort-header" @click.stop="onSortClick('id', $event)" @dblclick.stop="onSortToggle('id')">ID<span v-if="sortIndex('id') !== null" class="sort-index" :class="sortClass('id') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('id') }}</span>{{ sortIcon('id') }}</span>
           </template>
         </el-table-column>
-        <el-table-column label="报告对象" width="120">
+        <el-table-column prop="subjectName" label="报告对象" width="120">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('subjectName', $event)">报告对象<span v-if="sortIndex('subjectName') !== null" class="sort-index" :class="sortClass('subjectName') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('subjectName') }}</span>{{ sortIcon('subjectName') }}</span>
+          </template>
           <template slot-scope="{ row }">{{ row.subjectName || row.userName || '-' }}</template>
         </el-table-column>
         <el-table-column prop="reportType" label="类型" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('reportType', $event)">类型<span v-if="sortIndex('reportType') !== null" class="sort-index" :class="sortClass('reportType') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('reportType') }}</span>{{ sortIcon('reportType') }}</span>
+          </template>
           <template slot-scope="{ row }">{{ row.reportType === 'gut_flora' ? '菌群检测' : row.reportType === 'physical_exam' ? '体检报告' : row.reportType }}</template>
         </el-table-column>
         <el-table-column prop="overallScore" label="健康分" width="80">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('overallScore', $event)">健康分<span v-if="sortIndex('overallScore') !== null" class="sort-index" :class="sortClass('overallScore') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('overallScore') }}</span>{{ sortIcon('overallScore') }}</span>
+          </template>
           <template slot-scope="{ row }">{{ row.overallScore || '-' }}</template>
         </el-table-column>
-        <el-table-column label="代上传人" width="100">
+        <el-table-column prop="proxyUploaderName" label="代上传人" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('proxyUploaderName', $event)">代上传人<span v-if="sortIndex('proxyUploaderName') !== null" class="sort-index" :class="sortClass('proxyUploaderName') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('proxyUploaderName') }}</span>{{ sortIcon('proxyUploaderName') }}</span>
+          </template>
           <template slot-scope="{ row }">{{ row.proxyUploaderName || '-' }}</template>
         </el-table-column>
-        <el-table-column prop="remark" label="备注" show-overflow-tooltip />
+        <el-table-column prop="remark" label="备注" show-overflow-tooltip>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('remark', $event)">备注<span v-if="sortIndex('remark') !== null" class="sort-index" :class="sortClass('remark') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('remark') }}</span>{{ sortIcon('remark') }}</span>
+          </template>
+        </el-table-column>
         <el-table-column prop="createdAt" label="上传时间" width="160">
           <template slot-scope="{ row }">{{ formatTime(row.createdAt) }}</template>
           <template slot="header">
@@ -173,7 +189,7 @@ export default {
       page: 1,
       size: 20,
       total: 0,
-      sortableColumns: { id: 'ID', createdAt: '上传时间' },
+      sortableColumns: { id: 'ID', createdAt: '上传时间', subjectName: '报告对象', reportType: '类型', overallScore: '健康分', proxyUploaderName: '代上传人', remark: '备注' },
       uploadForm: {
         familyId: null,
         subjectId: null,

+ 15 - 2
cfc-web/src/views/teacher/TeacherAssessment.vue

@@ -17,18 +17,31 @@
           家庭{{ scope.row.familyId }}
         </template>
       </el-table-column>
-      <el-table-column prop="materialTitle" label="测评资料" />
+      <el-table-column prop="materialTitle" label="测评资料">
+        <template slot="header">
+          <span class="sort-header" @click.stop="onSortClick('materialTitle', $event)">测评资料<span v-if="sortIndex('materialTitle') !== null" class="sort-index" :class="sortClass('materialTitle') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('materialTitle') }}</span>{{ sortIcon('materialTitle') }}</span>
+        </template>
+      </el-table-column>
       <el-table-column prop="remindMonths" label="提醒周期" width="100">
+        <template slot="header">
+          <span class="sort-header" @click.stop="onSortClick('remindMonths', $event)">提醒周期<span v-if="sortIndex('remindMonths') !== null" class="sort-index" :class="sortClass('remindMonths') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('remindMonths') }}</span>{{ sortIcon('remindMonths') }}</span>
+        </template>
         <template slot-scope="scope">
           {{ scope.row.remindMonths }}个月
         </template>
       </el-table-column>
       <el-table-column prop="lastRemindAt" label="上次提醒" width="140">
+        <template slot="header">
+          <span class="sort-header" @click.stop="onSortClick('lastRemindAt', $event)">上次提醒<span v-if="sortIndex('lastRemindAt') !== null" class="sort-index" :class="sortClass('lastRemindAt') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('lastRemindAt') }}</span>{{ sortIcon('lastRemindAt') }}</span>
+        </template>
         <template slot-scope="scope">
           {{ formatDate(scope.row.lastRemindAt) }}
         </template>
       </el-table-column>
       <el-table-column prop="nextRemindAt" label="下次提醒" width="140">
+        <template slot="header">
+          <span class="sort-header" @click.stop="onSortClick('nextRemindAt', $event)">下次提醒<span v-if="sortIndex('nextRemindAt') !== null" class="sort-index" :class="sortClass('nextRemindAt') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('nextRemindAt') }}</span>{{ sortIcon('nextRemindAt') }}</span>
+        </template>
         <template slot-scope="scope">
           {{ formatDate(scope.row.nextRemindAt) }}
         </template>
@@ -111,7 +124,7 @@ export default {
       availableFamilies: [],
       enableDialogVisible: false,
       editDialogVisible: false,
-      sortableColumns: { familyId: '家庭ID' },
+      sortableColumns: { familyId: '家庭ID', materialTitle: '测评资料', remindMonths: '提醒周期', lastRemindAt: '上次提醒', nextRemindAt: '下次提醒' },
       enableForm: {
         familyId: null,
         materialId: null,

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

@@ -100,6 +100,9 @@
           </template>
         </el-table-column>
         <el-table-column prop="serviceType" label="服务类型" width="120">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('serviceType', $event)">服务类型<span v-if="sortIndex('serviceType') !== null" class="sort-index" :class="sortClass('serviceType') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('serviceType') }}</span>{{ sortIcon('serviceType') }}</span>
+          </template>
           <template slot-scope="scope">
             <el-tag size="small" :type="scope.row.serviceType === 'premium' ? 'warning' : ''" effect="plain">
               {{ scope.row.serviceType === 'premium' ? '高级服务' : '基础服务' }}
@@ -107,16 +110,25 @@
           </template>
         </el-table-column>
         <el-table-column prop="servicePrice" label="服务价格" width="120">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('servicePrice', $event)">服务价格<span v-if="sortIndex('servicePrice') !== null" class="sort-index" :class="sortClass('servicePrice') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('servicePrice') }}</span>{{ sortIcon('servicePrice') }}</span>
+          </template>
           <template slot-scope="scope">
             <span class="price">{{ formatPriceWithSymbol(scope.row.servicePrice) }}</span>
           </template>
         </el-table-column>
         <el-table-column prop="childrenCount" label="孩子数" width="80">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('childrenCount', $event)">孩子数<span v-if="sortIndex('childrenCount') !== null" class="sort-index" :class="sortClass('childrenCount') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('childrenCount') }}</span>{{ sortIcon('childrenCount') }}</span>
+          </template>
           <template slot-scope="scope">
             <span class="children-count">{{ scope.row.childrenCount || 0 }}</span>
           </template>
         </el-table-column>
         <el-table-column prop="taskCompletionRate" label="任务完成率" width="150">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('taskCompletionRate', $event)">任务完成率<span v-if="sortIndex('taskCompletionRate') !== null" class="sort-index" :class="sortClass('taskCompletionRate') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('taskCompletionRate') }}</span>{{ sortIcon('taskCompletionRate') }}</span>
+          </template>
           <template slot-scope="scope">
             <div class="completion-cell">
               <el-progress :percentage="scope.row.taskCompletionRate || 0" :color="getCompletionColor(scope.row.taskCompletionRate || 0)" :show-text="false" style="width: 80px"></el-progress>
@@ -125,6 +137,9 @@
           </template>
         </el-table-column>
         <el-table-column prop="status" label="状态" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('status', $event)">状态<span v-if="sortIndex('status') !== null" class="sort-index" :class="sortClass('status') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('status') }}</span>{{ sortIcon('status') }}</span>
+          </template>
           <template slot-scope="scope">
             <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'" effect="dark" size="small">
               {{ scope.row.status === 'active' ? '服务中' : '已暂停' }}
@@ -242,7 +257,7 @@ export default {
       drawerVisible: false,
       currentFamily: null,
       loading: false,
-      sortableColumns: { familyId: '家庭ID', boundAt: '绑定时间' }
+      sortableColumns: { familyId: '家庭ID', boundAt: '绑定时间', serviceType: '服务类型', servicePrice: '服务价格', childrenCount: '孩子数', taskCompletionRate: '任务完成率', status: '状态' }
     }
   },
   computed: {

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

@@ -109,6 +109,9 @@
           </template>
         </el-table-column>
         <el-table-column prop="familyName" label="家庭" min-width="150">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('familyName', $event)">家庭<span v-if="sortIndex('familyName') !== null" class="sort-index" :class="sortClass('familyName') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('familyName') }}</span>{{ sortIcon('familyName') }}</span>
+          </template>
           <template slot-scope="scope">
             <div class="family-cell">
               <div class="family-icon">
@@ -119,26 +122,41 @@
           </template>
         </el-table-column>
         <el-table-column prop="packageName" label="任务模板" width="150">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('packageName', $event)">任务模板<span v-if="sortIndex('packageName') !== null" class="sort-index" :class="sortClass('packageName') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('packageName') }}</span>{{ sortIcon('packageName') }}</span>
+          </template>
           <template slot-scope="scope">
             <el-tag size="small" effect="plain" type="warning">{{ scope.row.packageName }}</el-tag>
           </template>
         </el-table-column>
         <el-table-column prop="guideName" label="规划师" width="120">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('guideName', $event)">规划师<span v-if="sortIndex('guideName') !== null" class="sort-index" :class="sortClass('guideName') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('guideName') }}</span>{{ sortIcon('guideName') }}</span>
+          </template>
           <template slot-scope="scope">
             <span class="guide-name">{{ scope.row.guideName || '-' }}</span>
           </template>
         </el-table-column>
         <el-table-column prop="amount" label="订单金额" width="120">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('amount', $event)">订单金额<span v-if="sortIndex('amount') !== null" class="sort-index" :class="sortClass('amount') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('amount') }}</span>{{ sortIcon('amount') }}</span>
+          </template>
           <template slot-scope="scope">
             <span class="amount">{{ formatPriceWithSymbol(scope.row.amount) }}</span>
           </template>
         </el-table-column>
         <el-table-column prop="commission" label="佣金" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('commission', $event)">佣金<span v-if="sortIndex('commission') !== null" class="sort-index" :class="sortClass('commission') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('commission') }}</span>{{ sortIcon('commission') }}</span>
+          </template>
           <template slot-scope="scope">
             <span class="commission">+{{ formatPriceWithSymbol(scope.row.commission || Math.round(scope.row.amount * 0.1)) }}</span>
           </template>
         </el-table-column>
         <el-table-column prop="status" label="状态" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('status', $event)">状态<span v-if="sortIndex('status') !== null" class="sort-index" :class="sortClass('status') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('status') }}</span>{{ sortIcon('status') }}</span>
+          </template>
           <template slot-scope="scope">
             <el-tag :type="getStatusType(scope.row.status)" effect="dark" size="small">
               {{ scope.row.status }}
@@ -236,7 +254,7 @@ export default {
       currentOrder: null,
       loading: false,
       commissionRate: 10,
-      sortableColumns: { orderNo: '订单号', createdAt: '创建时间' },
+      sortableColumns: { orderNo: '订单号', createdAt: '创建时间', familyName: '家庭', packageName: '任务模板', guideName: '规划师', amount: '订单金额', commission: '佣金', status: '状态' },
       pagination: {
         current: 1,
         size: 10,

+ 16 - 1
cfc-web/src/views/teacher/TeacherPackages.vue

@@ -82,6 +82,9 @@
           </template>
         </el-table-column>
         <el-table-column prop="level" label="等级" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('level', $event)">等级<span v-if="sortIndex('level') !== null" class="sort-index" :class="sortClass('level') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('level') }}</span>{{ sortIcon('level') }}</span>
+          </template>
           <template slot-scope="scope">
             <el-tag :type="getLevelType(scope.row.level)" effect="dark" size="small">
               {{ scope.row.level || '初级' }}
@@ -89,21 +92,33 @@
           </template>
         </el-table-column>
         <el-table-column prop="price" label="价格" width="120">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('price', $event)">价格<span v-if="sortIndex('price') !== null" class="sort-index" :class="sortClass('price') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('price') }}</span>{{ sortIcon('price') }}</span>
+          </template>
           <template slot-scope="scope">
             <span class="price">{{ formatPriceWithSymbol(scope.row.price) }}</span>
           </template>
         </el-table-column>
         <el-table-column prop="minPrice" label="最低价" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('minPrice', $event)">最低价<span v-if="sortIndex('minPrice') !== null" class="sort-index" :class="sortClass('minPrice') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('minPrice') }}</span>{{ sortIcon('minPrice') }}</span>
+          </template>
           <template slot-scope="scope">
             <span class="min-price">{{ formatPriceWithSymbol(scope.row.minPrice || 0) }}</span>
           </template>
         </el-table-column>
         <el-table-column prop="salesCount" label="销量" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('salesCount', $event)">销量<span v-if="sortIndex('salesCount') !== null" class="sort-index" :class="sortClass('salesCount') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('salesCount') }}</span>{{ sortIcon('salesCount') }}</span>
+          </template>
           <template slot-scope="scope">
             <span class="sales-count">{{ scope.row.salesCount || 0 }}</span>
           </template>
         </el-table-column>
         <el-table-column prop="status" label="状态" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('status', $event)">状态<span v-if="sortIndex('status') !== null" class="sort-index" :class="sortClass('status') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('status') }}</span>{{ sortIcon('status') }}</span>
+          </template>
           <template slot-scope="scope">
             <el-switch v-model="scope.row.status" active-value="active" inactive-value="inactive"
               active-text="在售" inactive-text="已暂停" @change="toggleStatus(scope.row)">
@@ -199,7 +214,7 @@ export default {
       isEdit: false,
       loading: false,
       keyword: '',
-      sortableColumns: { id: '任务模板ID', createdAt: '创建时间' },
+      sortableColumns: { id: '任务模板ID', createdAt: '创建时间', level: '等级', price: '价格', minPrice: '最低价', salesCount: '销量', status: '状态' },
       form: {
         id: null,
         packageId: null,

+ 19 - 1
cfc-web/src/views/teacher/TeacherTeam.vue

@@ -101,6 +101,9 @@
           </template>
         </el-table-column>
         <el-table-column prop="level" label="级别" width="120">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('level', $event)">级别<span v-if="sortIndex('level') !== null" class="sort-index" :class="sortClass('level') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('level') }}</span>{{ sortIcon('level') }}</span>
+          </template>
           <template slot-scope="scope">
             <el-tag :type="getLevelType(scope.row.level)" effect="dark" size="small">
               {{ scope.row.level || '初级' }}
@@ -108,26 +111,41 @@
           </template>
         </el-table-column>
         <el-table-column prop="familyCount" label="服务家庭" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('familyCount', $event)">服务家庭<span v-if="sortIndex('familyCount') !== null" class="sort-index" :class="sortClass('familyCount') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('familyCount') }}</span>{{ sortIcon('familyCount') }}</span>
+          </template>
           <template slot-scope="scope">
             <span class="family-count">{{ scope.row.familyCount || 0 }}</span>
           </template>
         </el-table-column>
         <el-table-column prop="childrenCount" label="服务孩子" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('childrenCount', $event)">服务孩子<span v-if="sortIndex('childrenCount') !== null" class="sort-index" :class="sortClass('childrenCount') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('childrenCount') }}</span>{{ sortIcon('childrenCount') }}</span>
+          </template>
           <template slot-scope="scope">
             <span class="children-count">{{ scope.row.childrenCount || 0 }}</span>
           </template>
         </el-table-column>
         <el-table-column prop="monthlySales" label="本月业绩" width="120">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('monthlySales', $event)">本月业绩<span v-if="sortIndex('monthlySales') !== null" class="sort-index" :class="sortClass('monthlySales') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('monthlySales') }}</span>{{ sortIcon('monthlySales') }}</span>
+          </template>
           <template slot-scope="scope">
             <span class="sales-value">{{ formatPriceWithSymbol(scope.row.monthlySales || 0) }}</span>
           </template>
         </el-table-column>
         <el-table-column prop="totalSales" label="累计业绩" width="120">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('totalSales', $event)">累计业绩<span v-if="sortIndex('totalSales') !== null" class="sort-index" :class="sortClass('totalSales') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('totalSales') }}</span>{{ sortIcon('totalSales') }}</span>
+          </template>
           <template slot-scope="scope">
             <span class="total-value">{{ formatPriceWithSymbol(scope.row.totalSales || 0) }}</span>
           </template>
         </el-table-column>
         <el-table-column prop="commission" label="贡献佣金" width="100">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick('commission', $event)">贡献佣金<span v-if="sortIndex('commission') !== null" class="sort-index" :class="sortClass('commission') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('commission') }}</span>{{ sortIcon('commission') }}</span>
+          </template>
           <template slot-scope="scope">
             <span class="commission-value">+{{ formatPriceWithSymbol(Math.round((scope.row.monthlySales || 0) * 0.1)) }}</span>
           </template>
@@ -233,7 +251,7 @@ export default {
       drawerVisible: false,
       currentMember: null,
       loading: false,
-      sortableColumns: { id: '成员ID', createdAt: '注册时间' }
+      sortableColumns: { id: '成员ID', createdAt: '注册时间', level: '级别', familyCount: '服务家庭', childrenCount: '服务孩子', monthlySales: '本月业绩', totalSales: '累计业绩', commission: '贡献佣金' }
     }
   },
   computed: {